diff --git a/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.dart b/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.dart
new file mode 100644
index 00000000..0bc2c977
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.dart
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:drift/drift.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/seed_state.drift.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
+
+@DriftAccessor()
+class TopSiteSeedStateDao extends DatabaseAccessor
+ with $TopSiteSeedStateDaoMixin {
+ TopSiteSeedStateDao(super.db);
+
+ Future hasSeed(String seedId) async {
+ final row =
+ await (db.topSiteSeedState.select()
+ ..where((t) => t.seedId.equals(seedId)))
+ .getSingleOrNull();
+ return row != null;
+ }
+
+ Future markSeedApplied(String seedId) {
+ return db.topSiteSeedState.insertOne(
+ TopSiteSeedStateCompanion.insert(
+ seedId: seedId,
+ appliedAt: DateTime.now(),
+ ),
+ );
+ }
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.drift.dart b/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.drift.dart
new file mode 100644
index 00000000..facb8658
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/daos/seed_state.drift.dart
@@ -0,0 +1,14 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart'
+ as i1;
+
+mixin $TopSiteSeedStateDaoMixin on i0.DatabaseAccessor {
+ TopSiteSeedStateDaoManager get managers => TopSiteSeedStateDaoManager(this);
+}
+
+class TopSiteSeedStateDaoManager {
+ final $TopSiteSeedStateDaoMixin _db;
+ TopSiteSeedStateDaoManager(this._db);
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.dart b/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.dart
new file mode 100644
index 00000000..f61310a0
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.dart
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:drift/drift.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart';
+
+@DriftAccessor()
+class TopSiteDao extends DatabaseAccessor
+ with $TopSiteDaoMixin {
+ TopSiteDao(super.db);
+
+ Selectable selectPersistedTopSites() {
+ return db.topSite.select()..orderBy([(t) => OrderingTerm.asc(t.orderKey)]);
+ }
+
+ Future> getPersistedTopSites() {
+ return selectPersistedTopSites().get();
+ }
+
+ Future getPersistedTopSiteById(String id) {
+ return (db.topSite.select()..where((t) => t.id.equals(id)))
+ .getSingleOrNull();
+ }
+
+ Future getPersistedTopSiteByUrl(Uri url) {
+ return (db.topSite.select()..where((t) => t.url.equalsValue(url)))
+ .getSingleOrNull();
+ }
+
+ Future countPersistedSites() async {
+ final count = db.topSite.id.count();
+ final query = db.selectOnly(db.topSite)..addColumns([count]);
+ final result = await query.getSingle();
+ return result.read(count)!;
+ }
+
+ Future insertPinnedSite({
+ required String id,
+ required String title,
+ required Uri url,
+ required String orderKey,
+ }) {
+ return db.topSite.insertOne(
+ TopSiteCompanion.insert(
+ id: id,
+ title: title,
+ url: url,
+ source: StoredTopSiteSource.pinned,
+ orderKey: orderKey,
+ createdAt: DateTime.now(),
+ ),
+ );
+ }
+
+ Future insertSeededSites(List rows) {
+ return db.batch((batch) {
+ batch.insertAll(db.topSite, rows);
+ });
+ }
+
+ Future updatePersistedSite(
+ String id, {
+ required String title,
+ required Uri url,
+ }) {
+ return (db.topSite.update()..where((t) => t.id.equals(id))).write(
+ TopSiteCompanion(title: Value(title), url: Value(url)),
+ );
+ }
+
+ Future deletePersistedSite(String id) {
+ return (db.topSite.delete()..where((t) => t.id.equals(id))).go();
+ }
+
+ Future assignOrderKey(String id, {required String orderKey}) {
+ return (db.topSite.update()..where((t) => t.id.equals(id))).write(
+ TopSiteCompanion(orderKey: Value(orderKey)),
+ );
+ }
+
+ Future promoteToSource(
+ String id, {
+ required StoredTopSiteSource source,
+ required String title,
+ required String orderKey,
+ }) {
+ return (db.topSite.update()..where((t) => t.id.equals(id))).write(
+ TopSiteCompanion(
+ source: Value(source),
+ title: Value(title),
+ orderKey: Value(orderKey),
+ ),
+ );
+ }
+
+ SingleSelectable generateLeadingOrderKey({int bucket = 0}) {
+ return db.definitionsDrift.leadingOrderKey(bucket: bucket);
+ }
+
+ SingleSelectable generateTrailingOrderKey({int bucket = 0}) {
+ return db.definitionsDrift.trailingOrderKey(bucket: bucket);
+ }
+
+ SingleOrNullSelectable generateOrderKeyAfterSiteId(String id) {
+ return db.definitionsDrift.orderKeyAfterSite(siteId: id);
+ }
+
+ SingleSelectable generateOrderKeyBeforeSiteId(String id) {
+ return db.definitionsDrift.orderKeyBeforeSite(siteId: id);
+ }
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart b/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart
new file mode 100644
index 00000000..1be4a0c3
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart
@@ -0,0 +1,14 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart'
+ as i1;
+
+mixin $TopSiteDaoMixin on i0.DatabaseAccessor {
+ TopSiteDaoManager get managers => TopSiteDaoManager(this);
+}
+
+class TopSiteDaoManager {
+ final $TopSiteDaoMixin _db;
+ TopSiteDaoManager(this._db);
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/database.dart b/app/lib/features/geckoview/features/top_sites/data/database/database.dart
new file mode 100644
index 00000000..b661d9ae
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/database.dart
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:drift/drift.dart';
+import 'package:drift_dev/api/migrations_native.dart';
+import 'package:flutter/foundation.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/seed_state.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/top_site.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.drift.dart';
+
+@DriftDatabase(
+ include: {'definitions.drift'},
+ daos: [TopSiteDao, TopSiteSeedStateDao],
+)
+class TopSiteDatabase extends $TopSiteDatabase {
+ @override
+ final int schemaVersion = 1;
+
+ @override
+ MigrationStrategy get migration => MigrationStrategy(
+ beforeOpen: (details) async {
+ if (kDebugMode) {
+ await validateDatabaseSchema();
+ }
+
+ await customStatement('PRAGMA foreign_keys = ON;');
+ },
+ );
+
+ TopSiteDatabase(super.e);
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/database.drift.dart b/app/lib/features/geckoview/features/top_sites/data/database/database.drift.dart
new file mode 100644
index 00000000..32635089
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/database.drift.dart
@@ -0,0 +1,92 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart'
+ as i1;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/top_site.dart'
+ as i2;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart'
+ as i3;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/seed_state.dart'
+ as i4;
+import 'package:drift/internal/modular.dart' as i5;
+import 'package:sqlite3/common.dart' as i6;
+
+abstract class $TopSiteDatabase extends i0.GeneratedDatabase {
+ $TopSiteDatabase(i0.QueryExecutor e) : super(e);
+ $TopSiteDatabaseManager get managers => $TopSiteDatabaseManager(this);
+ late final i1.TopSite topSite = i1.TopSite(this);
+ late final i1.TopSiteSeedState topSiteSeedState = i1.TopSiteSeedState(this);
+ late final i2.TopSiteDao topSiteDao = i2.TopSiteDao(
+ this as i3.TopSiteDatabase,
+ );
+ late final i4.TopSiteSeedStateDao topSiteSeedStateDao =
+ i4.TopSiteSeedStateDao(this as i3.TopSiteDatabase);
+ i1.DefinitionsDrift get definitionsDrift => i5.ReadDatabaseContainer(
+ this,
+ ).accessor(i1.DefinitionsDrift.new);
+ @override
+ Iterable> get allTables =>
+ allSchemaEntities.whereType>();
+ @override
+ List get allSchemaEntities => [
+ topSite,
+ i1.idxTopSiteOrderKey,
+ topSiteSeedState,
+ ];
+}
+
+class $TopSiteDatabaseManager {
+ final $TopSiteDatabase _db;
+ $TopSiteDatabaseManager(this._db);
+ i1.$TopSiteTableManager get topSite =>
+ i1.$TopSiteTableManager(_db, _db.topSite);
+ i1.$TopSiteSeedStateTableManager get topSiteSeedState =>
+ i1.$TopSiteSeedStateTableManager(_db, _db.topSiteSeedState);
+}
+
+extension DefineFunctions on i6.CommonDatabase {
+ void defineFunctions({
+ required String Function(int, String?) lexoRankNext,
+ required String Function(int, String?) lexoRankPrevious,
+ required String Function(String?, String?) lexoRankReorderAfter,
+ required String Function(String?, String?) lexoRankReorderBefore,
+ }) {
+ createFunction(
+ functionName: 'lexo_rank_next',
+ argumentCount: const i6.AllowedArgumentCount(2),
+ function: (args) {
+ final arg0 = args[0] as int;
+ final arg1 = args[1] as String?;
+ return lexoRankNext(arg0, arg1);
+ },
+ );
+ createFunction(
+ functionName: 'lexo_rank_previous',
+ argumentCount: const i6.AllowedArgumentCount(2),
+ function: (args) {
+ final arg0 = args[0] as int;
+ final arg1 = args[1] as String?;
+ return lexoRankPrevious(arg0, arg1);
+ },
+ );
+ createFunction(
+ functionName: 'lexo_rank_reorder_after',
+ argumentCount: const i6.AllowedArgumentCount(2),
+ function: (args) {
+ final arg0 = args[0] as String?;
+ final arg1 = args[1] as String?;
+ return lexoRankReorderAfter(arg0, arg1);
+ },
+ );
+ createFunction(
+ functionName: 'lexo_rank_reorder_before',
+ argumentCount: const i6.AllowedArgumentCount(2),
+ function: (args) {
+ final arg0 = args[0] as String?;
+ final arg1 = args[1] as String?;
+ return lexoRankReorderBefore(arg0, arg1);
+ },
+ );
+ }
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift b/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift
new file mode 100644
index 00000000..ab1ebe9a
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift
@@ -0,0 +1,65 @@
+import 'package:weblibre/data/database/converters/uri.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart';
+
+CREATE TABLE top_site (
+ id TEXT PRIMARY KEY NOT NULL,
+ title TEXT NOT NULL,
+ url TEXT NOT NULL MAPPED BY `const UriConverter()`,
+ source ENUM(StoredTopSiteSource) NOT NULL,
+ order_key TEXT NOT NULL,
+ created_at DATETIME NOT NULL,
+ UNIQUE(url)
+);
+
+CREATE INDEX idx_top_site_order_key ON top_site(order_key);
+
+CREATE TABLE top_site_seed_state (
+ seed_id TEXT PRIMARY KEY NOT NULL,
+ applied_at DATETIME NOT NULL
+);
+
+leadingOrderKey(:bucket AS INTEGER):
+ SELECT lexo_rank_previous(
+ :bucket,
+ (
+ SELECT order_key
+ FROM top_site
+ ORDER BY order_key
+ LIMIT 1
+ )
+ );
+
+trailingOrderKey(:bucket AS INTEGER):
+ SELECT lexo_rank_next(
+ :bucket,
+ (
+ SELECT order_key
+ FROM top_site
+ ORDER BY order_key DESC
+ LIMIT 1
+ )
+ );
+
+orderKeyAfterSite(:site_id AS TEXT):
+ WITH ordered_table AS (
+ SELECT
+ id,
+ order_key,
+ LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
+ FROM top_site
+ )
+ SELECT lexo_rank_reorder_after(order_key, next_order_key)
+ FROM ordered_table
+ WHERE id = :site_id;
+
+orderKeyBeforeSite(:site_id AS TEXT):
+ WITH ordered_table AS (
+ SELECT
+ id,
+ order_key,
+ LAG(order_key) OVER (ORDER BY order_key) AS prev_order_key
+ FROM top_site
+ )
+ SELECT lexo_rank_reorder_before(order_key, prev_order_key)
+ FROM ordered_table
+ WHERE id = :site_id;
diff --git a/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift.dart b/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift.dart
new file mode 100644
index 00000000..75f8cfb0
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/database/definitions.drift.dart
@@ -0,0 +1,983 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart'
+ as i1;
+import 'package:weblibre/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart'
+ as i2;
+import 'package:weblibre/data/database/converters/uri.dart' as i3;
+import 'package:drift/internal/modular.dart' as i4;
+
+typedef $TopSiteCreateCompanionBuilder =
+ i1.TopSiteCompanion Function({
+ required String id,
+ required String title,
+ required Uri url,
+ required i2.StoredTopSiteSource source,
+ required String orderKey,
+ required DateTime createdAt,
+ i0.Value rowid,
+ });
+typedef $TopSiteUpdateCompanionBuilder =
+ i1.TopSiteCompanion Function({
+ i0.Value id,
+ i0.Value title,
+ i0.Value url,
+ i0.Value source,
+ i0.Value orderKey,
+ i0.Value createdAt,
+ i0.Value rowid,
+ });
+
+class $TopSiteFilterComposer
+ extends i0.Composer {
+ $TopSiteFilterComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnFilters get id => $composableBuilder(
+ column: $table.id,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get title => $composableBuilder(
+ column: $table.title,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnWithTypeConverterFilters get url =>
+ $composableBuilder(
+ column: $table.url,
+ builder: (column) => i0.ColumnWithTypeConverterFilters(column),
+ );
+
+ i0.ColumnWithTypeConverterFilters<
+ i2.StoredTopSiteSource,
+ i2.StoredTopSiteSource,
+ int
+ >
+ get source => $composableBuilder(
+ column: $table.source,
+ builder: (column) => i0.ColumnWithTypeConverterFilters(column),
+ );
+
+ i0.ColumnFilters get orderKey => $composableBuilder(
+ column: $table.orderKey,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get createdAt => $composableBuilder(
+ column: $table.createdAt,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+}
+
+class $TopSiteOrderingComposer
+ extends i0.Composer {
+ $TopSiteOrderingComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnOrderings get id => $composableBuilder(
+ column: $table.id,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get title => $composableBuilder(
+ column: $table.title,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get url => $composableBuilder(
+ column: $table.url,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get source => $composableBuilder(
+ column: $table.source,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get orderKey => $composableBuilder(
+ column: $table.orderKey,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get createdAt => $composableBuilder(
+ column: $table.createdAt,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+}
+
+class $TopSiteAnnotationComposer
+ extends i0.Composer {
+ $TopSiteAnnotationComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.GeneratedColumn get id =>
+ $composableBuilder(column: $table.id, builder: (column) => column);
+
+ i0.GeneratedColumn get title =>
+ $composableBuilder(column: $table.title, builder: (column) => column);
+
+ i0.GeneratedColumnWithTypeConverter get url =>
+ $composableBuilder(column: $table.url, builder: (column) => column);
+
+ i0.GeneratedColumnWithTypeConverter get source =>
+ $composableBuilder(column: $table.source, builder: (column) => column);
+
+ i0.GeneratedColumn get orderKey =>
+ $composableBuilder(column: $table.orderKey, builder: (column) => column);
+
+ i0.GeneratedColumn get createdAt =>
+ $composableBuilder(column: $table.createdAt, builder: (column) => column);
+}
+
+class $TopSiteTableManager
+ extends
+ i0.RootTableManager<
+ i0.GeneratedDatabase,
+ i1.TopSite,
+ i1.TopSiteData,
+ i1.$TopSiteFilterComposer,
+ i1.$TopSiteOrderingComposer,
+ i1.$TopSiteAnnotationComposer,
+ $TopSiteCreateCompanionBuilder,
+ $TopSiteUpdateCompanionBuilder,
+ (
+ i1.TopSiteData,
+ i0.BaseReferences,
+ ),
+ i1.TopSiteData,
+ i0.PrefetchHooks Function()
+ > {
+ $TopSiteTableManager(i0.GeneratedDatabase db, i1.TopSite table)
+ : super(
+ i0.TableManagerState(
+ db: db,
+ table: table,
+ createFilteringComposer: () =>
+ i1.$TopSiteFilterComposer($db: db, $table: table),
+ createOrderingComposer: () =>
+ i1.$TopSiteOrderingComposer($db: db, $table: table),
+ createComputedFieldComposer: () =>
+ i1.$TopSiteAnnotationComposer($db: db, $table: table),
+ updateCompanionCallback:
+ ({
+ i0.Value id = const i0.Value.absent(),
+ i0.Value title = const i0.Value.absent(),
+ i0.Value url = const i0.Value.absent(),
+ i0.Value source =
+ const i0.Value.absent(),
+ i0.Value orderKey = const i0.Value.absent(),
+ i0.Value createdAt = const i0.Value.absent(),
+ i0.Value rowid = const i0.Value.absent(),
+ }) => i1.TopSiteCompanion(
+ id: id,
+ title: title,
+ url: url,
+ source: source,
+ orderKey: orderKey,
+ createdAt: createdAt,
+ rowid: rowid,
+ ),
+ createCompanionCallback:
+ ({
+ required String id,
+ required String title,
+ required Uri url,
+ required i2.StoredTopSiteSource source,
+ required String orderKey,
+ required DateTime createdAt,
+ i0.Value rowid = const i0.Value.absent(),
+ }) => i1.TopSiteCompanion.insert(
+ id: id,
+ title: title,
+ url: url,
+ source: source,
+ orderKey: orderKey,
+ createdAt: createdAt,
+ rowid: rowid,
+ ),
+ withReferenceMapper: (p0) => p0
+ .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
+ .toList(),
+ prefetchHooksCallback: null,
+ ),
+ );
+}
+
+typedef $TopSiteProcessedTableManager =
+ i0.ProcessedTableManager<
+ i0.GeneratedDatabase,
+ i1.TopSite,
+ i1.TopSiteData,
+ i1.$TopSiteFilterComposer,
+ i1.$TopSiteOrderingComposer,
+ i1.$TopSiteAnnotationComposer,
+ $TopSiteCreateCompanionBuilder,
+ $TopSiteUpdateCompanionBuilder,
+ (
+ i1.TopSiteData,
+ i0.BaseReferences,
+ ),
+ i1.TopSiteData,
+ i0.PrefetchHooks Function()
+ >;
+typedef $TopSiteSeedStateCreateCompanionBuilder =
+ i1.TopSiteSeedStateCompanion Function({
+ required String seedId,
+ required DateTime appliedAt,
+ i0.Value rowid,
+ });
+typedef $TopSiteSeedStateUpdateCompanionBuilder =
+ i1.TopSiteSeedStateCompanion Function({
+ i0.Value seedId,
+ i0.Value appliedAt,
+ i0.Value rowid,
+ });
+
+class $TopSiteSeedStateFilterComposer
+ extends i0.Composer {
+ $TopSiteSeedStateFilterComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnFilters get seedId => $composableBuilder(
+ column: $table.seedId,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get appliedAt => $composableBuilder(
+ column: $table.appliedAt,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+}
+
+class $TopSiteSeedStateOrderingComposer
+ extends i0.Composer {
+ $TopSiteSeedStateOrderingComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnOrderings get seedId => $composableBuilder(
+ column: $table.seedId,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get appliedAt => $composableBuilder(
+ column: $table.appliedAt,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+}
+
+class $TopSiteSeedStateAnnotationComposer
+ extends i0.Composer {
+ $TopSiteSeedStateAnnotationComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.GeneratedColumn get seedId =>
+ $composableBuilder(column: $table.seedId, builder: (column) => column);
+
+ i0.GeneratedColumn get appliedAt =>
+ $composableBuilder(column: $table.appliedAt, builder: (column) => column);
+}
+
+class $TopSiteSeedStateTableManager
+ extends
+ i0.RootTableManager<
+ i0.GeneratedDatabase,
+ i1.TopSiteSeedState,
+ i1.TopSiteSeedStateData,
+ i1.$TopSiteSeedStateFilterComposer,
+ i1.$TopSiteSeedStateOrderingComposer,
+ i1.$TopSiteSeedStateAnnotationComposer,
+ $TopSiteSeedStateCreateCompanionBuilder,
+ $TopSiteSeedStateUpdateCompanionBuilder,
+ (
+ i1.TopSiteSeedStateData,
+ i0.BaseReferences<
+ i0.GeneratedDatabase,
+ i1.TopSiteSeedState,
+ i1.TopSiteSeedStateData
+ >,
+ ),
+ i1.TopSiteSeedStateData,
+ i0.PrefetchHooks Function()
+ > {
+ $TopSiteSeedStateTableManager(
+ i0.GeneratedDatabase db,
+ i1.TopSiteSeedState table,
+ ) : super(
+ i0.TableManagerState(
+ db: db,
+ table: table,
+ createFilteringComposer: () =>
+ i1.$TopSiteSeedStateFilterComposer($db: db, $table: table),
+ createOrderingComposer: () =>
+ i1.$TopSiteSeedStateOrderingComposer($db: db, $table: table),
+ createComputedFieldComposer: () =>
+ i1.$TopSiteSeedStateAnnotationComposer($db: db, $table: table),
+ updateCompanionCallback:
+ ({
+ i0.Value seedId = const i0.Value.absent(),
+ i0.Value appliedAt = const i0.Value.absent(),
+ i0.Value rowid = const i0.Value.absent(),
+ }) => i1.TopSiteSeedStateCompanion(
+ seedId: seedId,
+ appliedAt: appliedAt,
+ rowid: rowid,
+ ),
+ createCompanionCallback:
+ ({
+ required String seedId,
+ required DateTime appliedAt,
+ i0.Value rowid = const i0.Value.absent(),
+ }) => i1.TopSiteSeedStateCompanion.insert(
+ seedId: seedId,
+ appliedAt: appliedAt,
+ rowid: rowid,
+ ),
+ withReferenceMapper: (p0) => p0
+ .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
+ .toList(),
+ prefetchHooksCallback: null,
+ ),
+ );
+}
+
+typedef $TopSiteSeedStateProcessedTableManager =
+ i0.ProcessedTableManager<
+ i0.GeneratedDatabase,
+ i1.TopSiteSeedState,
+ i1.TopSiteSeedStateData,
+ i1.$TopSiteSeedStateFilterComposer,
+ i1.$TopSiteSeedStateOrderingComposer,
+ i1.$TopSiteSeedStateAnnotationComposer,
+ $TopSiteSeedStateCreateCompanionBuilder,
+ $TopSiteSeedStateUpdateCompanionBuilder,
+ (
+ i1.TopSiteSeedStateData,
+ i0.BaseReferences<
+ i0.GeneratedDatabase,
+ i1.TopSiteSeedState,
+ i1.TopSiteSeedStateData
+ >,
+ ),
+ i1.TopSiteSeedStateData,
+ i0.PrefetchHooks Function()
+ >;
+
+class TopSite extends i0.Table with i0.TableInfo {
+ @override
+ final i0.GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ TopSite(this.attachedDatabase, [this._alias]);
+ late final i0.GeneratedColumn id = i0.GeneratedColumn(
+ 'id',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ $customConstraints: 'PRIMARY KEY NOT NULL',
+ );
+ late final i0.GeneratedColumn title = i0.GeneratedColumn(
+ 'title',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ );
+ late final i0.GeneratedColumnWithTypeConverter url =
+ i0.GeneratedColumn(
+ 'url',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ ).withConverter(i1.TopSite.$converterurl);
+ late final i0.GeneratedColumnWithTypeConverter
+ source = i0.GeneratedColumn(
+ 'source',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ ).withConverter(i1.TopSite.$convertersource);
+ late final i0.GeneratedColumn orderKey = i0.GeneratedColumn(
+ 'order_key',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ );
+ late final i0.GeneratedColumn createdAt =
+ i0.GeneratedColumn(
+ 'created_at',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ );
+ @override
+ List get $columns => [
+ id,
+ title,
+ url,
+ source,
+ orderKey,
+ createdAt,
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'top_site';
+ @override
+ Set get $primaryKey => {id};
+ @override
+ List> get uniqueKeys => [
+ {url},
+ ];
+ @override
+ i1.TopSiteData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return i1.TopSiteData(
+ id: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}id'],
+ )!,
+ title: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}title'],
+ )!,
+ url: i1.TopSite.$converterurl.fromSql(
+ attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}url'],
+ )!,
+ ),
+ source: i1.TopSite.$convertersource.fromSql(
+ attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}source'],
+ )!,
+ ),
+ orderKey: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}order_key'],
+ )!,
+ createdAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime,
+ data['${effectivePrefix}created_at'],
+ )!,
+ );
+ }
+
+ @override
+ TopSite createAlias(String alias) {
+ return TopSite(attachedDatabase, alias);
+ }
+
+ static i0.TypeConverter $converterurl = const i3.UriConverter();
+ static i0.JsonTypeConverter2
+ $convertersource = const i0.EnumIndexConverter(
+ i2.StoredTopSiteSource.values,
+ );
+ @override
+ List get customConstraints => const ['UNIQUE(url)'];
+ @override
+ bool get dontWriteConstraints => true;
+}
+
+class TopSiteData extends i0.DataClass
+ implements i0.Insertable {
+ final String id;
+ final String title;
+ final Uri url;
+ final i2.StoredTopSiteSource source;
+ final String orderKey;
+ final DateTime createdAt;
+ const TopSiteData({
+ required this.id,
+ required this.title,
+ required this.url,
+ required this.source,
+ required this.orderKey,
+ required this.createdAt,
+ });
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['id'] = i0.Variable(id);
+ map['title'] = i0.Variable(title);
+ {
+ map['url'] = i0.Variable(i1.TopSite.$converterurl.toSql(url));
+ }
+ {
+ map['source'] = i0.Variable(
+ i1.TopSite.$convertersource.toSql(source),
+ );
+ }
+ map['order_key'] = i0.Variable(orderKey);
+ map['created_at'] = i0.Variable(createdAt);
+ return map;
+ }
+
+ factory TopSiteData.fromJson(
+ Map json, {
+ i0.ValueSerializer? serializer,
+ }) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return TopSiteData(
+ id: serializer.fromJson(json['id']),
+ title: serializer.fromJson(json['title']),
+ url: serializer.fromJson(json['url']),
+ source: i1.TopSite.$convertersource.fromJson(
+ serializer.fromJson(json['source']),
+ ),
+ orderKey: serializer.fromJson(json['order_key']),
+ createdAt: serializer.fromJson(json['created_at']),
+ );
+ }
+ @override
+ Map toJson({i0.ValueSerializer? serializer}) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return {
+ 'id': serializer.toJson(id),
+ 'title': serializer.toJson(title),
+ 'url': serializer.toJson(url),
+ 'source': serializer.toJson(
+ i1.TopSite.$convertersource.toJson(source),
+ ),
+ 'order_key': serializer.toJson(orderKey),
+ 'created_at': serializer.toJson(createdAt),
+ };
+ }
+
+ i1.TopSiteData copyWith({
+ String? id,
+ String? title,
+ Uri? url,
+ i2.StoredTopSiteSource? source,
+ String? orderKey,
+ DateTime? createdAt,
+ }) => i1.TopSiteData(
+ id: id ?? this.id,
+ title: title ?? this.title,
+ url: url ?? this.url,
+ source: source ?? this.source,
+ orderKey: orderKey ?? this.orderKey,
+ createdAt: createdAt ?? this.createdAt,
+ );
+ TopSiteData copyWithCompanion(i1.TopSiteCompanion data) {
+ return TopSiteData(
+ id: data.id.present ? data.id.value : this.id,
+ title: data.title.present ? data.title.value : this.title,
+ url: data.url.present ? data.url.value : this.url,
+ source: data.source.present ? data.source.value : this.source,
+ orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey,
+ createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('TopSiteData(')
+ ..write('id: $id, ')
+ ..write('title: $title, ')
+ ..write('url: $url, ')
+ ..write('source: $source, ')
+ ..write('orderKey: $orderKey, ')
+ ..write('createdAt: $createdAt')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(id, title, url, source, orderKey, createdAt);
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is i1.TopSiteData &&
+ other.id == this.id &&
+ other.title == this.title &&
+ other.url == this.url &&
+ other.source == this.source &&
+ other.orderKey == this.orderKey &&
+ other.createdAt == this.createdAt);
+}
+
+class TopSiteCompanion extends i0.UpdateCompanion {
+ final i0.Value id;
+ final i0.Value title;
+ final i0.Value url;
+ final i0.Value source;
+ final i0.Value orderKey;
+ final i0.Value createdAt;
+ final i0.Value rowid;
+ const TopSiteCompanion({
+ this.id = const i0.Value.absent(),
+ this.title = const i0.Value.absent(),
+ this.url = const i0.Value.absent(),
+ this.source = const i0.Value.absent(),
+ this.orderKey = const i0.Value.absent(),
+ this.createdAt = const i0.Value.absent(),
+ this.rowid = const i0.Value.absent(),
+ });
+ TopSiteCompanion.insert({
+ required String id,
+ required String title,
+ required Uri url,
+ required i2.StoredTopSiteSource source,
+ required String orderKey,
+ required DateTime createdAt,
+ this.rowid = const i0.Value.absent(),
+ }) : id = i0.Value(id),
+ title = i0.Value(title),
+ url = i0.Value(url),
+ source = i0.Value(source),
+ orderKey = i0.Value(orderKey),
+ createdAt = i0.Value(createdAt);
+ static i0.Insertable custom({
+ i0.Expression? id,
+ i0.Expression? title,
+ i0.Expression? url,
+ i0.Expression? source,
+ i0.Expression? orderKey,
+ i0.Expression? createdAt,
+ i0.Expression? rowid,
+ }) {
+ return i0.RawValuesInsertable({
+ if (id != null) 'id': id,
+ if (title != null) 'title': title,
+ if (url != null) 'url': url,
+ if (source != null) 'source': source,
+ if (orderKey != null) 'order_key': orderKey,
+ if (createdAt != null) 'created_at': createdAt,
+ if (rowid != null) 'rowid': rowid,
+ });
+ }
+
+ i1.TopSiteCompanion copyWith({
+ i0.Value? id,
+ i0.Value? title,
+ i0.Value? url,
+ i0.Value? source,
+ i0.Value? orderKey,
+ i0.Value? createdAt,
+ i0.Value? rowid,
+ }) {
+ return i1.TopSiteCompanion(
+ id: id ?? this.id,
+ title: title ?? this.title,
+ url: url ?? this.url,
+ source: source ?? this.source,
+ orderKey: orderKey ?? this.orderKey,
+ createdAt: createdAt ?? this.createdAt,
+ rowid: rowid ?? this.rowid,
+ );
+ }
+
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ if (id.present) {
+ map['id'] = i0.Variable(id.value);
+ }
+ if (title.present) {
+ map['title'] = i0.Variable(title.value);
+ }
+ if (url.present) {
+ map['url'] = i0.Variable(
+ i1.TopSite.$converterurl.toSql(url.value),
+ );
+ }
+ if (source.present) {
+ map['source'] = i0.Variable(
+ i1.TopSite.$convertersource.toSql(source.value),
+ );
+ }
+ if (orderKey.present) {
+ map['order_key'] = i0.Variable(orderKey.value);
+ }
+ if (createdAt.present) {
+ map['created_at'] = i0.Variable(createdAt.value);
+ }
+ if (rowid.present) {
+ map['rowid'] = i0.Variable(rowid.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('TopSiteCompanion(')
+ ..write('id: $id, ')
+ ..write('title: $title, ')
+ ..write('url: $url, ')
+ ..write('source: $source, ')
+ ..write('orderKey: $orderKey, ')
+ ..write('createdAt: $createdAt, ')
+ ..write('rowid: $rowid')
+ ..write(')'))
+ .toString();
+ }
+}
+
+i0.Index get idxTopSiteOrderKey => i0.Index(
+ 'idx_top_site_order_key',
+ 'CREATE INDEX idx_top_site_order_key ON top_site (order_key)',
+);
+
+class TopSiteSeedState extends i0.Table
+ with i0.TableInfo {
+ @override
+ final i0.GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ TopSiteSeedState(this.attachedDatabase, [this._alias]);
+ late final i0.GeneratedColumn seedId = i0.GeneratedColumn(
+ 'seed_id',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ $customConstraints: 'PRIMARY KEY NOT NULL',
+ );
+ late final i0.GeneratedColumn appliedAt =
+ i0.GeneratedColumn(
+ 'applied_at',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: true,
+ $customConstraints: 'NOT NULL',
+ );
+ @override
+ List get $columns => [seedId, appliedAt];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'top_site_seed_state';
+ @override
+ Set get $primaryKey => {seedId};
+ @override
+ i1.TopSiteSeedStateData map(
+ Map data, {
+ String? tablePrefix,
+ }) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return i1.TopSiteSeedStateData(
+ seedId: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}seed_id'],
+ )!,
+ appliedAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime,
+ data['${effectivePrefix}applied_at'],
+ )!,
+ );
+ }
+
+ @override
+ TopSiteSeedState createAlias(String alias) {
+ return TopSiteSeedState(attachedDatabase, alias);
+ }
+
+ @override
+ bool get dontWriteConstraints => true;
+}
+
+class TopSiteSeedStateData extends i0.DataClass
+ implements i0.Insertable {
+ final String seedId;
+ final DateTime appliedAt;
+ const TopSiteSeedStateData({required this.seedId, required this.appliedAt});
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['seed_id'] = i0.Variable(seedId);
+ map['applied_at'] = i0.Variable(appliedAt);
+ return map;
+ }
+
+ factory TopSiteSeedStateData.fromJson(
+ Map json, {
+ i0.ValueSerializer? serializer,
+ }) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return TopSiteSeedStateData(
+ seedId: serializer.fromJson(json['seed_id']),
+ appliedAt: serializer.fromJson(json['applied_at']),
+ );
+ }
+ @override
+ Map toJson({i0.ValueSerializer? serializer}) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return {
+ 'seed_id': serializer.toJson(seedId),
+ 'applied_at': serializer.toJson(appliedAt),
+ };
+ }
+
+ i1.TopSiteSeedStateData copyWith({String? seedId, DateTime? appliedAt}) =>
+ i1.TopSiteSeedStateData(
+ seedId: seedId ?? this.seedId,
+ appliedAt: appliedAt ?? this.appliedAt,
+ );
+ TopSiteSeedStateData copyWithCompanion(i1.TopSiteSeedStateCompanion data) {
+ return TopSiteSeedStateData(
+ seedId: data.seedId.present ? data.seedId.value : this.seedId,
+ appliedAt: data.appliedAt.present ? data.appliedAt.value : this.appliedAt,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('TopSiteSeedStateData(')
+ ..write('seedId: $seedId, ')
+ ..write('appliedAt: $appliedAt')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(seedId, appliedAt);
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is i1.TopSiteSeedStateData &&
+ other.seedId == this.seedId &&
+ other.appliedAt == this.appliedAt);
+}
+
+class TopSiteSeedStateCompanion
+ extends i0.UpdateCompanion {
+ final i0.Value seedId;
+ final i0.Value appliedAt;
+ final i0.Value rowid;
+ const TopSiteSeedStateCompanion({
+ this.seedId = const i0.Value.absent(),
+ this.appliedAt = const i0.Value.absent(),
+ this.rowid = const i0.Value.absent(),
+ });
+ TopSiteSeedStateCompanion.insert({
+ required String seedId,
+ required DateTime appliedAt,
+ this.rowid = const i0.Value.absent(),
+ }) : seedId = i0.Value(seedId),
+ appliedAt = i0.Value(appliedAt);
+ static i0.Insertable custom({
+ i0.Expression? seedId,
+ i0.Expression? appliedAt,
+ i0.Expression? rowid,
+ }) {
+ return i0.RawValuesInsertable({
+ if (seedId != null) 'seed_id': seedId,
+ if (appliedAt != null) 'applied_at': appliedAt,
+ if (rowid != null) 'rowid': rowid,
+ });
+ }
+
+ i1.TopSiteSeedStateCompanion copyWith({
+ i0.Value? seedId,
+ i0.Value? appliedAt,
+ i0.Value? rowid,
+ }) {
+ return i1.TopSiteSeedStateCompanion(
+ seedId: seedId ?? this.seedId,
+ appliedAt: appliedAt ?? this.appliedAt,
+ rowid: rowid ?? this.rowid,
+ );
+ }
+
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ if (seedId.present) {
+ map['seed_id'] = i0.Variable(seedId.value);
+ }
+ if (appliedAt.present) {
+ map['applied_at'] = i0.Variable(appliedAt.value);
+ }
+ if (rowid.present) {
+ map['rowid'] = i0.Variable(rowid.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('TopSiteSeedStateCompanion(')
+ ..write('seedId: $seedId, ')
+ ..write('appliedAt: $appliedAt, ')
+ ..write('rowid: $rowid')
+ ..write(')'))
+ .toString();
+ }
+}
+
+class DefinitionsDrift extends i4.ModularAccessor {
+ DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
+ i0.Selectable leadingOrderKey({required int bucket}) {
+ return customSelect(
+ 'SELECT lexo_rank_previous(?1, (SELECT order_key FROM top_site ORDER BY order_key LIMIT 1)) AS _c0',
+ variables: [i0.Variable(bucket)],
+ readsFrom: {topSite},
+ ).map((i0.QueryRow row) => row.read('_c0'));
+ }
+
+ i0.Selectable trailingOrderKey({required int bucket}) {
+ return customSelect(
+ 'SELECT lexo_rank_next(?1, (SELECT order_key FROM top_site ORDER BY order_key DESC LIMIT 1)) AS _c0',
+ variables: [i0.Variable(bucket)],
+ readsFrom: {topSite},
+ ).map((i0.QueryRow row) => row.read('_c0'));
+ }
+
+ i0.Selectable orderKeyAfterSite({required String siteId}) {
+ return customSelect(
+ 'WITH ordered_table AS (SELECT id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM top_site) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE id = ?1',
+ variables: [i0.Variable(siteId)],
+ readsFrom: {topSite},
+ ).map((i0.QueryRow row) => row.read('_c0'));
+ }
+
+ i0.Selectable orderKeyBeforeSite({required String siteId}) {
+ return customSelect(
+ 'WITH ordered_table AS (SELECT id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM top_site) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE id = ?1',
+ variables: [i0.Variable(siteId)],
+ readsFrom: {topSite},
+ ).map((i0.QueryRow row) => row.read('_c0'));
+ }
+
+ i1.TopSite get topSite => i4.ReadDatabaseContainer(
+ attachedDatabase,
+ ).resultSet('top_site');
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart b/app/lib/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart
new file mode 100644
index 00000000..61f65edd
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+enum StoredTopSiteSource { seeded, pinned }
diff --git a/app/lib/features/geckoview/features/top_sites/data/providers.dart b/app/lib/features/geckoview/features/top_sites/data/providers.dart
new file mode 100644
index 00000000..29bb9209
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/providers.dart
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'dart:io';
+
+import 'package:drift/drift.dart';
+import 'package:drift/native.dart';
+import 'package:path/path.dart' as p;
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
+
+import 'package:weblibre/core/filesystem.dart';
+import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart';
+
+part 'providers.g.dart';
+
+@Riverpod(keepAlive: true)
+TopSiteDatabase topSiteDatabase(Ref ref) {
+ final db = TopSiteDatabase(
+ LazyDatabase(() async {
+ final file = File(
+ p.join(filesystem.profileDatabasesDir.path, 'top_site.db'),
+ );
+
+ if (Platform.isAndroid) {
+ await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
+ }
+
+ return NativeDatabase.createInBackground(
+ file,
+ setup: (database) {
+ registerLexorankFunctions(database);
+ },
+ );
+ }),
+ );
+
+ ref.onDispose(() async {
+ await db.close();
+ });
+
+ return db;
+}
diff --git a/app/lib/features/geckoview/features/top_sites/data/providers.g.dart b/app/lib/features/geckoview/features/top_sites/data/providers.g.dart
new file mode 100644
index 00000000..c3fc666e
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/data/providers.g.dart
@@ -0,0 +1,52 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'providers.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint, type=warning
+
+@ProviderFor(topSiteDatabase)
+final topSiteDatabaseProvider = TopSiteDatabaseProvider._();
+
+final class TopSiteDatabaseProvider
+ extends
+ $FunctionalProvider
+ with $Provider {
+ TopSiteDatabaseProvider._()
+ : super(
+ from: null,
+ argument: null,
+ retry: null,
+ name: r'topSiteDatabaseProvider',
+ isAutoDispose: false,
+ dependencies: null,
+ $allTransitiveDependencies: null,
+ );
+
+ @override
+ String debugGetCreateSourceHash() => _$topSiteDatabaseHash();
+
+ @$internal
+ @override
+ $ProviderElement $createElement($ProviderPointer pointer) =>
+ $ProviderElement(pointer);
+
+ @override
+ TopSiteDatabase create(Ref ref) {
+ return topSiteDatabase(ref);
+ }
+
+ /// {@macro riverpod.override_with_value}
+ Override overrideWithValue(TopSiteDatabase value) {
+ return $ProviderOverride(
+ origin: this,
+ providerOverride: $SyncValueProvider(value),
+ );
+ }
+}
+
+String _$topSiteDatabaseHash() => r'6371e1784bd1272bdfc656caaa56e786e7503c78';
diff --git a/app/lib/features/geckoview/features/top_sites/domain/entities/top_site_item.dart b/app/lib/features/geckoview/features/top_sites/domain/entities/top_site_item.dart
new file mode 100644
index 00000000..64627176
--- /dev/null
+++ b/app/lib/features/geckoview/features/top_sites/domain/entities/top_site_item.dart
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2024-2026 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:fast_equatable/fast_equatable.dart';
+import 'package:weblibre/features/geckoview/features/top_sites/domain/entities/top_site_source.dart';
+
+class TopSiteItem with FastEquatable {
+ final String? id;
+ final String title;
+ final Uri url;
+ final TopSiteSource source;
+ final String? orderKey;
+ final DateTime? createdAt;
+ final String? previewImageUrl;
+ final double? historyScore;
+ final int? historyPlaceId;
+
+ TopSiteItem({
+ this.id,
+ required this.title,
+ required this.url,
+ required this.source,
+ this.orderKey,
+ this.createdAt,
+ this.previewImageUrl,
+ this.historyScore,
+ this.historyPlaceId,
+ });
+
+ bool get isPersisted =>
+ source == TopSiteSource.seeded || source == TopSiteSource.pinned;
+
+ bool get isReorderable => isPersisted;
+
+ bool get isEditable => isPersisted;
+
+ bool get isRemovable => isPersisted;
+
+ @override
+ List