From 23b758e7295a4b41888a7ee845748a297ba552bb Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Fri, 21 Nov 2025 05:27:44 +0100 Subject: [PATCH] implement user bangs --- app/drift_schemas/bangs/drift_schema_v3.json | 1 + app/lib/core/routing/routes.bangs.dart | 79 +- app/lib/core/routing/routes.dart | 6 +- app/lib/core/routing/routes.g.dart | 141 +- .../bangs/data/database/daos/bang.dart | 4 + .../bangs/data/database/database.dart | 61 +- .../bangs/data/database/database.steps.dart | 308 ++- .../bangs/data/database/definitions.drift | 1 + .../data/database/definitions.drift.dart | 62 + app/lib/features/bangs/data/models/bang.dart | 10 + .../features/bangs/data/models/bang.g.dart | 14 + .../features/bangs/data/models/bang_data.dart | 2 + .../bangs/data/models/bang_data.g.dart | 12 + .../bangs/data/models/bang_group.dart | 10 +- .../bangs/domain/repositories/data.dart | 9 + .../bangs/domain/repositories/data.g.dart | 2 +- .../bangs/domain/repositories/sync.dart | 18 +- .../bangs/domain/repositories/sync.g.dart | 2 +- .../screens/{list.dart => category.dart} | 4 +- .../bangs/presentation/screens/edit.dart | 307 +++ .../bangs/presentation/screens/menu.dart | 43 + .../bangs/presentation/screens/user.dart | 76 + .../browser_modules/bottom_app_bar.dart | 2 +- .../widgets/browser_modules/browser_view.dart | 6 +- .../search/presentation/screens/search.dart | 12 +- .../features/tabs/data/database/database.dart | 10 +- .../presentation/screens/bang_settings.dart | 5 - .../features/user/data/database/database.dart | 45 +- .../web_feed/data/database/database.dart | 8 + app/pubspec.yaml | 2 +- app/test/drift/bangs/generated/schema.dart | 5 +- app/test/drift/bangs/generated/schema_v3.dart | 2228 +++++++++++++++++ 32 files changed, 3428 insertions(+), 67 deletions(-) create mode 100644 app/drift_schemas/bangs/drift_schema_v3.json rename app/lib/features/bangs/presentation/screens/{list.dart => category.dart} (96%) create mode 100644 app/lib/features/bangs/presentation/screens/edit.dart create mode 100644 app/lib/features/bangs/presentation/screens/menu.dart create mode 100644 app/lib/features/bangs/presentation/screens/user.dart create mode 100644 app/test/drift/bangs/generated/schema_v3.dart diff --git a/app/drift_schemas/bangs/drift_schema_v3.json b/app/drift_schemas/bangs/drift_schema_v3.json new file mode 100644 index 00000000..54d8150a --- /dev/null +++ b/app/drift_schemas/bangs/drift_schema_v3.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"bang","was_declared_in_moor":true,"columns":[{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"website_name","getter_name":"websiteName","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"domain","getter_name":"domain","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url_template","getter_name":"urlTemplate","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sub_category","getter_name":"subCategory","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"format","getter_name":"format","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const BangFormatConverter()","dart_type_name":"Set?"}},{"name":"additional_triggers","getter_name":"additionalTriggers","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const TriggerListConverter()","dart_type_name":"Set?"}},{"name":"searxng_api","getter_name":"searxngApi","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL DEFAULT FALSE","default_dart":"const CustomExpression('FALSE')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["PRIMARY KEY(\"trigger\", \"group\")"],"explicit_pk":["trigger","group"]}},{"id":1,"references":[0],"type":"table","data":{"name":"bang_triggers","was_declared_in_moor":true,"columns":[{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"additional_trigger","getter_name":"additionalTrigger","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["PRIMARY KEY(\"trigger\", \"group\", additional_trigger)","FOREIGN KEY(\"trigger\", \"group\")REFERENCES bang(\"trigger\", \"group\")ON DELETE CASCADE"],"explicit_pk":["trigger","group","additional_trigger"]}},{"id":2,"references":[1],"type":"index","data":{"on":1,"name":"idx_bang_triggers_lookup","sql":"CREATE INDEX idx_bang_triggers_lookup ON bang_triggers (additional_trigger, \"group\");","unique":false,"columns":[]}},{"id":3,"references":[0,1],"type":"trigger","data":{"on":0,"references_in_body":[0,1],"name":"bang_triggers_after_insert","sql":"CREATE TRIGGER bang_triggers_after_insert AFTER INSERT ON bang \nWHEN new.additional_triggers IS NOT NULL\nBEGIN\n INSERT INTO bang_triggers(\"trigger\", \"group\", additional_trigger)\n SELECT \n new.\"trigger\",\n new.\"group\",\n json_each.value\n FROM json_each(new.additional_triggers);\nEND;"}},{"id":4,"references":[0,1],"type":"trigger","data":{"on":0,"references_in_body":[0,1],"name":"bang_triggers_after_update","sql":"CREATE TRIGGER bang_triggers_after_update AFTER UPDATE ON bang BEGIN\n -- Delete old additional triggers\n DELETE FROM bang_triggers \n WHERE \"trigger\" = old.\"trigger\" AND \"group\" = old.\"group\";\n\n -- Insert new additional triggers if they exist\n INSERT INTO bang_triggers(\"trigger\", \"group\", additional_trigger)\n SELECT \n new.\"trigger\",\n new.\"group\",\n json_each.value\n FROM json_each(new.additional_triggers)\n WHERE new.additional_triggers IS NOT NULL;\nEND;"}},{"id":5,"references":[],"type":"table","data":{"name":"bang_sync","was_declared_in_moor":true,"columns":[{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":"PRIMARY KEY NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"last_sync","getter_name":"lastSync","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":6,"references":[0],"type":"table","data":{"name":"bang_frequency","was_declared_in_moor":true,"columns":[{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"frequency","getter_name":"frequency","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_used","getter_name":"lastUsed","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["PRIMARY KEY(\"trigger\", \"group\")","FOREIGN KEY(\"trigger\", \"group\")REFERENCES bang(\"trigger\", \"group\")ON DELETE CASCADE"],"explicit_pk":["trigger","group"]}},{"id":7,"references":[0],"type":"table","data":{"name":"bang_history","was_declared_in_moor":true,"columns":[{"name":"search_query","getter_name":"searchQuery","moor_type":"string","nullable":false,"customConstraints":"UNIQUE NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":["unique"]},{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"search_date","getter_name":"searchDate","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["FOREIGN KEY(\"trigger\", \"group\")REFERENCES bang(\"trigger\", \"group\")"]}},{"id":8,"references":[0],"type":"table","data":{"name":"bang_fts","was_declared_in_moor":true,"columns":[{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"website_name","getter_name":"websiteName","moor_type":"string","nullable":false,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":true,"create_virtual_stmt":"CREATE VIRTUAL TABLE \"bang_fts\" USING fts5(trigger, website_name, content=bang, prefix='2 3')","without_rowid":false,"constraints":[]}},{"id":9,"references":[1],"type":"table","data":{"name":"bang_triggers_fts","was_declared_in_moor":true,"columns":[{"name":"additional_trigger","getter_name":"additionalTrigger","moor_type":"string","nullable":false,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":true,"create_virtual_stmt":"CREATE VIRTUAL TABLE \"bang_triggers_fts\" USING fts5(additional_trigger, content=bang_triggers, prefix='2 3')","without_rowid":false,"constraints":[]}},{"id":10,"references":[0,6],"type":"view","data":{"name":"bang_data_view","sql":"CREATE VIEW bang_data_view AS SELECT b.*, bf.frequency, bf.last_used FROM bang AS b LEFT JOIN bang_frequency AS bf ON b.\"trigger\" = bf.\"trigger\" AND b.\"group\" = bf.\"group\";","dart_info_name":"BangDataView","columns":[{"name":"trigger","getter_name":"trigger","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group","getter_name":"group","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BangGroup.values)","dart_type_name":"BangGroup"}},{"name":"website_name","getter_name":"websiteName","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"domain","getter_name":"domain","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url_template","getter_name":"urlTemplate","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sub_category","getter_name":"subCategory","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"format","getter_name":"format","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const BangFormatConverter()","dart_type_name":"Set?"}},{"name":"additional_triggers","getter_name":"additionalTriggers","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const TriggerListConverter()","dart_type_name":"Set?"}},{"name":"searxng_api","getter_name":"searxngApi","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"searxng_api\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"searxng_api\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"frequency","getter_name":"frequency","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_used","getter_name":"lastUsed","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}]}},{"id":11,"references":[0,8],"type":"trigger","data":{"on":0,"references_in_body":[0,8],"name":"bang_after_insert","sql":"CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN\n INSERT INTO \n bang_fts(rowid, \"trigger\", website_name) \n VALUES (new.rowid, new.\"trigger\", new.website_name);\nEND;"}},{"id":12,"references":[0,8],"type":"trigger","data":{"on":0,"references_in_body":[0,8],"name":"bang_after_delete","sql":"CREATE TRIGGER bang_after_delete AFTER DELETE ON bang BEGIN\n INSERT INTO \n bang_fts(bang_fts, rowid, \"trigger\", website_name) \n VALUES('delete', old.rowid, old.\"trigger\", old.website_name);\nEND;"}},{"id":13,"references":[0,8],"type":"trigger","data":{"on":0,"references_in_body":[0,8],"name":"bang_after_update","sql":"CREATE TRIGGER bang_after_update AFTER UPDATE ON bang BEGIN\n INSERT INTO \n bang_fts(bang_fts, rowid, \"trigger\", website_name) \n VALUES('delete', old.rowid, old.\"trigger\", old.website_name);\n INSERT INTO \n bang_fts(rowid, \"trigger\", website_name) \n VALUES (new.rowid, new.\"trigger\", new.website_name);\nEND;"}},{"id":14,"references":[1,9],"type":"trigger","data":{"on":1,"references_in_body":[1,9],"name":"bang_triggers_after_insert_fts","sql":"CREATE TRIGGER bang_triggers_after_insert_fts AFTER INSERT ON bang_triggers BEGIN\n INSERT INTO \n bang_triggers_fts(rowid, additional_trigger) \n VALUES (new.rowid, new.additional_trigger);\nEND;"}},{"id":15,"references":[1,9],"type":"trigger","data":{"on":1,"references_in_body":[1,9],"name":"bang_triggers_after_delete_fts","sql":"CREATE TRIGGER bang_triggers_after_delete_fts AFTER DELETE ON bang_triggers BEGIN\n INSERT INTO \n bang_triggers_fts(bang_triggers_fts, rowid, additional_trigger) \n VALUES('delete', old.rowid, old.additional_trigger);\nEND;"}},{"id":16,"references":[1,9],"type":"trigger","data":{"on":1,"references_in_body":[1,9],"name":"bang_triggers_after_update_fts","sql":"CREATE TRIGGER bang_triggers_after_update_fts AFTER UPDATE ON bang_triggers BEGIN\n INSERT INTO \n bang_triggers_fts(bang_triggers_fts, rowid, additional_trigger) \n VALUES('delete', old.rowid, old.additional_trigger);\n INSERT INTO \n bang_triggers_fts(rowid, additional_trigger) \n VALUES (new.rowid, new.additional_trigger);\nEND;"}}]} \ No newline at end of file diff --git a/app/lib/core/routing/routes.bangs.dart b/app/lib/core/routing/routes.bangs.dart index 97cc4e57..8ee413fd 100644 --- a/app/lib/core/routing/routes.bangs.dart +++ b/app/lib/core/routing/routes.bangs.dart @@ -19,27 +19,55 @@ */ part of 'routes.dart'; -@TypedGoRoute( +@TypedGoRoute( name: 'BangRoute', path: '/bangs', routes: [ + TypedGoRoute( + name: 'UserBangsRoute', + path: 'user', + routes: [ + TypedGoRoute(name: 'NewUserBangRoute', path: 'new'), + TypedGoRoute( + name: 'EditUserBangRoute', + path: 'edit', + ), + ], + ), TypedGoRoute( name: 'BangSearchRoute', path: 'search/:searchText', ), - TypedGoRoute( - name: 'BangCategoryRoute', - path: 'category/:category', + TypedGoRoute( + name: 'BangCategoriesRoute', + path: 'categories', routes: [ - TypedGoRoute( - name: 'BangSubCategoryRoute', - path: ':subCategory', + TypedGoRoute( + name: 'BangCategoryRoute', + path: 'category/:category', + routes: [ + TypedGoRoute( + name: 'BangSubCategoryRoute', + path: ':subCategory', + ), + ], ), ], ), ], ) +class BangMenuRoute extends GoRouteData with $BangMenuRoute { + const BangMenuRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const BangMenuScreen(); + } +} + class BangCategoriesRoute extends GoRouteData with $BangCategoriesRoute { + const BangCategoriesRoute(); + @override Widget build(BuildContext context, GoRouterState state) { return const BangCategoriesScreen(); @@ -53,7 +81,7 @@ class BangCategoryRoute extends GoRouteData with $BangCategoryRoute { @override Widget build(BuildContext context, GoRouterState state) { - return BangListScreen(category: category); + return BangCategoryScreen(category: category); } } @@ -68,7 +96,7 @@ class BangSubCategoryRoute extends GoRouteData with $BangSubCategoryRoute { @override Widget build(BuildContext context, GoRouterState state) { - return BangListScreen(category: category, subCategory: subCategory); + return BangCategoryScreen(category: category, subCategory: subCategory); } } @@ -89,3 +117,36 @@ class BangSearchRoute extends GoRouteData with $BangSearchRoute { ); } } + +class UserBangsRoute extends GoRouteData with $UserBangsRoute { + const UserBangsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const UserBangs(); + } +} + +class NewUserBangRoute extends GoRouteData with $NewUserBangRoute { + const NewUserBangRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const EditBangScreen(initialBang: null); + } +} + +class EditUserBangRoute extends GoRouteData with $EditUserBangRoute { + final String initialBang; + + const EditUserBangRoute({required this.initialBang}); + + @override + Widget build(BuildContext context, GoRouterState state) { + return EditBangScreen( + initialBang: Bang.fromJson( + jsonDecode(initialBang) as Map, + ), + ); + } +} diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index 0218f205..c29cca79 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -24,9 +24,13 @@ import 'package:go_router/go_router.dart'; import 'package:nullability/nullability.dart'; import 'package:weblibre/core/routing/widgets/dialog_page.dart'; import 'package:weblibre/features/about/presentation/screens/about.dart'; +import 'package:weblibre/features/bangs/data/models/bang.dart'; import 'package:weblibre/features/bangs/presentation/screens/categories.dart'; -import 'package:weblibre/features/bangs/presentation/screens/list.dart'; +import 'package:weblibre/features/bangs/presentation/screens/category.dart'; +import 'package:weblibre/features/bangs/presentation/screens/edit.dart'; +import 'package:weblibre/features/bangs/presentation/screens/menu.dart'; import 'package:weblibre/features/bangs/presentation/screens/search.dart'; +import 'package:weblibre/features/bangs/presentation/screens/user.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart'; diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index ade4917c..9aa8ea75 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -11,7 +11,7 @@ List get $appRoutes => [ $onboardingRoute, $settingsRoute, $browserRoute, - $bangCategoriesRoute, + $bangMenuRoute, $feedListRoute, ]; @@ -791,34 +791,57 @@ extension on Map { entries.where((element) => element.value == value).firstOrNull?.key; } -RouteBase get $bangCategoriesRoute => GoRouteData.$route( +RouteBase get $bangMenuRoute => GoRouteData.$route( path: '/bangs', name: 'BangRoute', - factory: $BangCategoriesRoute._fromState, + factory: $BangMenuRoute._fromState, routes: [ + GoRouteData.$route( + path: 'user', + name: 'UserBangsRoute', + factory: $UserBangsRoute._fromState, + routes: [ + GoRouteData.$route( + path: 'new', + name: 'NewUserBangRoute', + factory: $NewUserBangRoute._fromState, + ), + GoRouteData.$route( + path: 'edit', + name: 'EditUserBangRoute', + factory: $EditUserBangRoute._fromState, + ), + ], + ), GoRouteData.$route( path: 'search/:searchText', name: 'BangSearchRoute', factory: $BangSearchRoute._fromState, ), GoRouteData.$route( - path: 'category/:category', - name: 'BangCategoryRoute', - factory: $BangCategoryRoute._fromState, + path: 'categories', + name: 'BangCategoriesRoute', + factory: $BangCategoriesRoute._fromState, routes: [ GoRouteData.$route( - path: ':subCategory', - name: 'BangSubCategoryRoute', - factory: $BangSubCategoryRoute._fromState, + path: 'category/:category', + name: 'BangCategoryRoute', + factory: $BangCategoryRoute._fromState, + routes: [ + GoRouteData.$route( + path: ':subCategory', + name: 'BangSubCategoryRoute', + factory: $BangSubCategoryRoute._fromState, + ), + ], ), ], ), ], ); -mixin $BangCategoriesRoute on GoRouteData { - static BangCategoriesRoute _fromState(GoRouterState state) => - BangCategoriesRoute(); +mixin $BangMenuRoute on GoRouteData { + static BangMenuRoute _fromState(GoRouterState state) => const BangMenuRoute(); @override String get location => GoRouteData.$location('/bangs'); @@ -837,6 +860,75 @@ mixin $BangCategoriesRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin $UserBangsRoute on GoRouteData { + static UserBangsRoute _fromState(GoRouterState state) => + const UserBangsRoute(); + + @override + String get location => GoRouteData.$location('/bangs/user'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $NewUserBangRoute on GoRouteData { + static NewUserBangRoute _fromState(GoRouterState state) => + const NewUserBangRoute(); + + @override + String get location => GoRouteData.$location('/bangs/user/new'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $EditUserBangRoute on GoRouteData { + static EditUserBangRoute _fromState(GoRouterState state) => EditUserBangRoute( + initialBang: state.uri.queryParameters['initial-bang']!, + ); + + EditUserBangRoute get _self => this as EditUserBangRoute; + + @override + String get location => GoRouteData.$location( + '/bangs/user/edit', + queryParams: {'initial-bang': _self.initialBang}, + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + mixin $BangSearchRoute on GoRouteData { static BangSearchRoute _fromState(GoRouterState state) => BangSearchRoute( searchText: @@ -864,6 +956,27 @@ mixin $BangSearchRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin $BangCategoriesRoute on GoRouteData { + static BangCategoriesRoute _fromState(GoRouterState state) => + const BangCategoriesRoute(); + + @override + String get location => GoRouteData.$location('/bangs/categories'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + mixin $BangCategoryRoute on GoRouteData { static BangCategoryRoute _fromState(GoRouterState state) => BangCategoryRoute(category: state.pathParameters['category']!); @@ -872,7 +985,7 @@ mixin $BangCategoryRoute on GoRouteData { @override String get location => GoRouteData.$location( - '/bangs/category/${Uri.encodeComponent(_self.category)}', + '/bangs/categories/category/${Uri.encodeComponent(_self.category)}', ); @override @@ -900,7 +1013,7 @@ mixin $BangSubCategoryRoute on GoRouteData { @override String get location => GoRouteData.$location( - '/bangs/category/${Uri.encodeComponent(_self.category)}/${Uri.encodeComponent(_self.subCategory)}', + '/bangs/categories/category/${Uri.encodeComponent(_self.category)}/${Uri.encodeComponent(_self.subCategory)}', ); @override diff --git a/app/lib/features/bangs/data/database/daos/bang.dart b/app/lib/features/bangs/data/database/daos/bang.dart index 5fb00ab3..d0563b7e 100644 --- a/app/lib/features/bangs/data/database/daos/bang.dart +++ b/app/lib/features/bangs/data/database/daos/bang.dart @@ -48,6 +48,10 @@ class BangDao extends DatabaseAccessor with $BangDaoMixin { ); } + Future upsertBang(Bang bang) { + return db.bang.insertOne(bang, mode: InsertMode.insertOrReplace); + } + SingleOrNullSelectable getBangData( BangGroup group, String trigger, diff --git a/app/lib/features/bangs/data/database/database.dart b/app/lib/features/bangs/data/database/database.dart index 27723c51..6e13f61c 100644 --- a/app/lib/features/bangs/data/database/database.dart +++ b/app/lib/features/bangs/data/database/database.dart @@ -18,6 +18,9 @@ * along with this program. If not, see . */ 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/bangs/data/database/daos/bang.dart'; import 'package:weblibre/features/bangs/data/database/daos/sync.dart'; import 'package:weblibre/features/bangs/data/database/database.drift.dart'; @@ -27,7 +30,7 @@ import 'package:weblibre/features/search/domain/fts_tokenizer.dart'; @DriftDatabase(include: {'definitions.drift'}, daos: [BangDao, SyncDao]) class BangDatabase extends $BangDatabase with PrefixQueryBuilderMixin { @override - final int schemaVersion = 2; + final int schemaVersion = 3; @override final int ftsTokenLimit = 6; @@ -36,15 +39,57 @@ class BangDatabase extends $BangDatabase with PrefixQueryBuilderMixin { @override MigrationStrategy get migration => MigrationStrategy( - beforeOpen: (details) async { - await customStatement('PRAGMA foreign_keys = ON;'); + 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'); + }, + beforeOpen: (details) async { + if (kDebugMode) { + // This check pulls in a fair amount of code that's not needed + // anywhere else, so we recommend only doing it in debug builds. + await validateDatabaseSchema(); + } + + await customStatement('PRAGMA foreign_keys = ON'); + + if (details.hadUpgrade && details.versionBefore != null) { + if (details.versionBefore! < 3) { + await bang.deleteWhere((t) => t.group.equals(3)); + } + } }, - onUpgrade: stepByStep( - from1To2: (m, schema) async { - //Too many changes, we switch to a new database - }, - ), ); BangDatabase(super.e); + + static final _upgrade = migrationSteps( + from1To2: (m, schema) async { + //Too many changes, we switch to a new database + }, + from2To3: (m, schema) async { + final bangAtV3 = schema.bang; + await m.addColumn(bangAtV3, bangAtV3.searxngApi); + }, + ); } diff --git a/app/lib/features/bangs/data/database/database.steps.dart b/app/lib/features/bangs/data/database/database.steps.dart index 7e897515..f3a5837a 100644 --- a/app/lib/features/bangs/data/database/database.steps.dart +++ b/app/lib/features/bangs/data/database/database.steps.dart @@ -408,8 +408,308 @@ i1.GeneratedColumn _column_25(String aliasedName) => true, type: i1.DriftSqlType.dateTime, ); + +final class Schema3 extends i0.VersionedSchema { + Schema3({required super.database}) : super(version: 3); + @override + late final List entities = [ + bang, + bangTriggers, + idxBangTriggersLookup, + bangTriggersAfterInsert, + bangTriggersAfterUpdate, + bangSync, + bangFrequency, + bangHistory, + bangFts, + bangTriggersFts, + bangDataView, + bangAfterInsert, + bangAfterDelete, + bangAfterUpdate, + bangTriggersAfterInsertFts, + bangTriggersAfterDeleteFts, + bangTriggersAfterUpdateFts, + ]; + late final Shape6 bang = Shape6( + source: i0.VersionedTable( + entityName: 'bang', + withoutRowId: false, + isStrict: false, + tableConstraints: ['PRIMARY KEY("trigger", "group")'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_26, + _column_27, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 bangTriggers = Shape7( + source: i0.VersionedTable( + entityName: 'bang_triggers', + withoutRowId: false, + isStrict: false, + tableConstraints: [ + 'PRIMARY KEY("trigger", "group", additional_trigger)', + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")ON DELETE CASCADE', + ], + columns: [_column_0, _column_1, _column_28], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxBangTriggersLookup = i1.Index( + 'idx_bang_triggers_lookup', + 'CREATE INDEX idx_bang_triggers_lookup ON bang_triggers (additional_trigger, "group")', + ); + final i1.Trigger bangTriggersAfterInsert = i1.Trigger( + 'CREATE TRIGGER bang_triggers_after_insert AFTER INSERT ON bang WHEN new.additional_triggers IS NOT NULL BEGIN INSERT INTO bang_triggers ("trigger", "group", additional_trigger) SELECT new."trigger", new."group", json_each.value FROM json_each(new.additional_triggers);END', + 'bang_triggers_after_insert', + ); + final i1.Trigger bangTriggersAfterUpdate = i1.Trigger( + 'CREATE TRIGGER bang_triggers_after_update AFTER UPDATE ON bang BEGIN DELETE FROM bang_triggers WHERE "trigger" = old."trigger" AND "group" = old."group";INSERT INTO bang_triggers ("trigger", "group", additional_trigger) SELECT new."trigger", new."group", json_each.value FROM json_each(new.additional_triggers)WHERE new.additional_triggers IS NOT NULL;END', + 'bang_triggers_after_update', + ); + late final Shape1 bangSync = Shape1( + source: i0.VersionedTable( + entityName: 'bang_sync', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_8, _column_9], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 bangFrequency = Shape2( + source: i0.VersionedTable( + entityName: 'bang_frequency', + withoutRowId: false, + isStrict: false, + tableConstraints: [ + 'PRIMARY KEY("trigger", "group")', + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")ON DELETE CASCADE', + ], + columns: [_column_0, _column_1, _column_10, _column_11], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 bangHistory = Shape3( + source: i0.VersionedTable( + entityName: 'bang_history', + withoutRowId: false, + isStrict: false, + tableConstraints: [ + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")', + ], + columns: [_column_12, _column_0, _column_1, _column_13], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 bangFts = Shape4( + source: i0.VersionedVirtualTable( + entityName: 'bang_fts', + moduleAndArgs: + 'fts5(trigger, website_name, content=bang, prefix=\'2 3\')', + columns: [_column_14, _column_15], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 bangTriggersFts = Shape8( + source: i0.VersionedVirtualTable( + entityName: 'bang_triggers_fts', + moduleAndArgs: + 'fts5(additional_trigger, content=bang_triggers, prefix=\'2 3\')', + columns: [_column_29], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 bangDataView = Shape9( + source: i0.VersionedView( + entityName: 'bang_data_view', + createViewStmt: + 'CREATE VIEW bang_data_view AS SELECT b.*, bf.frequency, bf.last_used FROM bang AS b LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger" AND b."group" = bf."group";', + columns: [ + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + _column_30, + _column_31, + _column_24, + _column_25, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Trigger bangAfterInsert = i1.Trigger( + 'CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN INSERT INTO bang_fts ("rowid", "trigger", website_name) VALUES (new."rowid", new."trigger", new.website_name);END', + 'bang_after_insert', + ); + final i1.Trigger bangAfterDelete = i1.Trigger( + 'CREATE TRIGGER bang_after_delete AFTER DELETE ON bang BEGIN INSERT INTO bang_fts (bang_fts, "rowid", "trigger", website_name) VALUES (\'delete\', old."rowid", old."trigger", old.website_name);END', + 'bang_after_delete', + ); + final i1.Trigger bangAfterUpdate = i1.Trigger( + 'CREATE TRIGGER bang_after_update AFTER UPDATE ON bang BEGIN INSERT INTO bang_fts (bang_fts, "rowid", "trigger", website_name) VALUES (\'delete\', old."rowid", old."trigger", old.website_name);INSERT INTO bang_fts ("rowid", "trigger", website_name) VALUES (new."rowid", new."trigger", new.website_name);END', + 'bang_after_update', + ); + final i1.Trigger bangTriggersAfterInsertFts = i1.Trigger( + 'CREATE TRIGGER bang_triggers_after_insert_fts AFTER INSERT ON bang_triggers BEGIN INSERT INTO bang_triggers_fts ("rowid", additional_trigger) VALUES (new."rowid", new.additional_trigger);END', + 'bang_triggers_after_insert_fts', + ); + final i1.Trigger bangTriggersAfterDeleteFts = i1.Trigger( + 'CREATE TRIGGER bang_triggers_after_delete_fts AFTER DELETE ON bang_triggers BEGIN INSERT INTO bang_triggers_fts (bang_triggers_fts, "rowid", additional_trigger) VALUES (\'delete\', old."rowid", old.additional_trigger);END', + 'bang_triggers_after_delete_fts', + ); + final i1.Trigger bangTriggersAfterUpdateFts = i1.Trigger( + 'CREATE TRIGGER bang_triggers_after_update_fts AFTER UPDATE ON bang_triggers BEGIN INSERT INTO bang_triggers_fts (bang_triggers_fts, "rowid", additional_trigger) VALUES (\'delete\', old."rowid", old.additional_trigger);INSERT INTO bang_triggers_fts ("rowid", additional_trigger) VALUES (new."rowid", new.additional_trigger);END', + 'bang_triggers_after_update_fts', + ); +} + +class Shape6 extends i0.VersionedTable { + Shape6({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get trigger => + columnsByName['trigger']! as i1.GeneratedColumn; + i1.GeneratedColumn get group => + columnsByName['group']! as i1.GeneratedColumn; + i1.GeneratedColumn get websiteName => + columnsByName['website_name']! as i1.GeneratedColumn; + i1.GeneratedColumn get domain => + columnsByName['domain']! as i1.GeneratedColumn; + i1.GeneratedColumn get urlTemplate => + columnsByName['url_template']! as i1.GeneratedColumn; + i1.GeneratedColumn get category => + columnsByName['category']! as i1.GeneratedColumn; + i1.GeneratedColumn get subCategory => + columnsByName['sub_category']! as i1.GeneratedColumn; + i1.GeneratedColumn get format => + columnsByName['format']! as i1.GeneratedColumn; + i1.GeneratedColumn get additionalTriggers => + columnsByName['additional_triggers']! as i1.GeneratedColumn; + i1.GeneratedColumn get searxngApi => + columnsByName['searxng_api']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_26(String aliasedName) => + i1.GeneratedColumn( + 'additional_triggers', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: '', + ); +i1.GeneratedColumn _column_27(String aliasedName) => + i1.GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: i1.DriftSqlType.bool, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const CustomExpression('FALSE'), + ); + +class Shape7 extends i0.VersionedTable { + Shape7({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get trigger => + columnsByName['trigger']! as i1.GeneratedColumn; + i1.GeneratedColumn get group => + columnsByName['group']! as i1.GeneratedColumn; + i1.GeneratedColumn get additionalTrigger => + columnsByName['additional_trigger']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_28(String aliasedName) => + i1.GeneratedColumn( + 'additional_trigger', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: 'NOT NULL', + ); + +class Shape8 extends i0.VersionedVirtualTable { + Shape8({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get additionalTrigger => + columnsByName['additional_trigger']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_29(String aliasedName) => + i1.GeneratedColumn( + 'additional_trigger', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: '', + ); + +class Shape9 extends i0.VersionedView { + Shape9({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get trigger => + columnsByName['trigger']! as i1.GeneratedColumn; + i1.GeneratedColumn get group => + columnsByName['group']! as i1.GeneratedColumn; + i1.GeneratedColumn get websiteName => + columnsByName['website_name']! as i1.GeneratedColumn; + i1.GeneratedColumn get domain => + columnsByName['domain']! as i1.GeneratedColumn; + i1.GeneratedColumn get urlTemplate => + columnsByName['url_template']! as i1.GeneratedColumn; + i1.GeneratedColumn get category => + columnsByName['category']! as i1.GeneratedColumn; + i1.GeneratedColumn get subCategory => + columnsByName['sub_category']! as i1.GeneratedColumn; + i1.GeneratedColumn get format => + columnsByName['format']! as i1.GeneratedColumn; + i1.GeneratedColumn get additionalTriggers => + columnsByName['additional_triggers']! as i1.GeneratedColumn; + i1.GeneratedColumn get searxngApi => + columnsByName['searxng_api']! as i1.GeneratedColumn; + i1.GeneratedColumn get frequency => + columnsByName['frequency']! as i1.GeneratedColumn; + i1.GeneratedColumn get lastUsed => + columnsByName['last_used']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_30(String aliasedName) => + i1.GeneratedColumn( + 'additional_triggers', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_31(String aliasedName) => + i1.GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("searxng_api" IN (0, 1))', + ), + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -418,6 +718,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from1To2(migrator, schema); return 2; + 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'); } @@ -426,6 +731,7 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from1To2: from1To2), + step: migrationSteps(from1To2: from1To2, from2To3: from2To3), ); diff --git a/app/lib/features/bangs/data/database/definitions.drift b/app/lib/features/bangs/data/database/definitions.drift index 0b0d20fe..cbd23aaa 100644 --- a/app/lib/features/bangs/data/database/definitions.drift +++ b/app/lib/features/bangs/data/database/definitions.drift @@ -15,6 +15,7 @@ CREATE TABLE bang ( sub_category TEXT, format TEXT MAPPED BY `const BangFormatConverter()`, additional_triggers TEXT MAPPED BY `const TriggerListConverter()`, + searxng_api BOOL NOT NULL DEFAULT FALSE, PRIMARY KEY ("trigger", "group") ) WITH Bang; diff --git a/app/lib/features/bangs/data/database/definitions.drift.dart b/app/lib/features/bangs/data/database/definitions.drift.dart index a5858154..97b44548 100644 --- a/app/lib/features/bangs/data/database/definitions.drift.dart +++ b/app/lib/features/bangs/data/database/definitions.drift.dart @@ -25,6 +25,7 @@ typedef $BangTableCreateCompanionBuilder = i0.Value subCategory, i0.Value?> format, i0.Value?> additionalTriggers, + i0.Value searxngApi, i0.Value rowid, }); typedef $BangTableUpdateCompanionBuilder = @@ -38,6 +39,7 @@ typedef $BangTableUpdateCompanionBuilder = i0.Value subCategory, i0.Value?> format, i0.Value?> additionalTriggers, + i0.Value searxngApi, i0.Value rowid, }); @@ -101,6 +103,11 @@ class $BangTableFilterComposer column: $table.additionalTriggers, builder: (column) => i0.ColumnWithTypeConverterFilters(column), ); + + i0.ColumnFilters get searxngApi => $composableBuilder( + column: $table.searxngApi, + builder: (column) => i0.ColumnFilters(column), + ); } class $BangTableOrderingComposer @@ -156,6 +163,11 @@ class $BangTableOrderingComposer column: $table.additionalTriggers, builder: (column) => i0.ColumnOrderings(column), ); + + i0.ColumnOrderings get searxngApi => $composableBuilder( + column: $table.searxngApi, + builder: (column) => i0.ColumnOrderings(column), + ); } class $BangTableAnnotationComposer @@ -202,6 +214,11 @@ class $BangTableAnnotationComposer column: $table.additionalTriggers, builder: (column) => column, ); + + i0.GeneratedColumn get searxngApi => $composableBuilder( + column: $table.searxngApi, + builder: (column) => column, + ); } class $BangTableTableManager @@ -245,6 +262,7 @@ class $BangTableTableManager i0.Value?> format = const i0.Value.absent(), i0.Value?> additionalTriggers = const i0.Value.absent(), + i0.Value searxngApi = const i0.Value.absent(), i0.Value rowid = const i0.Value.absent(), }) => i3.BangCompanion( trigger: trigger, @@ -256,6 +274,7 @@ class $BangTableTableManager subCategory: subCategory, format: format, additionalTriggers: additionalTriggers, + searxngApi: searxngApi, rowid: rowid, ), createCompanionCallback: @@ -270,6 +289,7 @@ class $BangTableTableManager i0.Value?> format = const i0.Value.absent(), i0.Value?> additionalTriggers = const i0.Value.absent(), + i0.Value searxngApi = const i0.Value.absent(), i0.Value rowid = const i0.Value.absent(), }) => i3.BangCompanion.insert( trigger: trigger, @@ -281,6 +301,7 @@ class $BangTableTableManager subCategory: subCategory, format: format, additionalTriggers: additionalTriggers, + searxngApi: searxngApi, rowid: rowid, ), withReferenceMapper: (p0) => p0 @@ -1364,6 +1385,15 @@ class BangTable extends i0.Table with i0.TableInfo { requiredDuringInsert: false, $customConstraints: '', ).withConverter?>(i3.BangTable.$converteradditionalTriggers); + late final i0.GeneratedColumn searxngApi = i0.GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE'), + ); @override List get $columns => [ trigger, @@ -1375,6 +1405,7 @@ class BangTable extends i0.Table with i0.TableInfo { subCategory, format, additionalTriggers, + searxngApi, ]; @override String get aliasedName => _alias ?? actualTableName; @@ -1403,6 +1434,10 @@ class BangTable extends i0.Table with i0.TableInfo { i0.DriftSqlType.string, data['${effectivePrefix}url_template'], )!, + searxngApi: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}searxng_api'], + )!, group: i3.BangTable.$convertergroup.fromSql( attachedDatabase.typeMapping.read( i0.DriftSqlType.int, @@ -1461,6 +1496,7 @@ class BangCompanion extends i0.UpdateCompanion { final i0.Value subCategory; final i0.Value?> format; final i0.Value?> additionalTriggers; + final i0.Value searxngApi; final i0.Value rowid; const BangCompanion({ this.trigger = const i0.Value.absent(), @@ -1472,6 +1508,7 @@ class BangCompanion extends i0.UpdateCompanion { this.subCategory = const i0.Value.absent(), this.format = const i0.Value.absent(), this.additionalTriggers = const i0.Value.absent(), + this.searxngApi = const i0.Value.absent(), this.rowid = const i0.Value.absent(), }); BangCompanion.insert({ @@ -1484,6 +1521,7 @@ class BangCompanion extends i0.UpdateCompanion { this.subCategory = const i0.Value.absent(), this.format = const i0.Value.absent(), this.additionalTriggers = const i0.Value.absent(), + this.searxngApi = const i0.Value.absent(), this.rowid = const i0.Value.absent(), }) : trigger = i0.Value(trigger), group = i0.Value(group), @@ -1500,6 +1538,7 @@ class BangCompanion extends i0.UpdateCompanion { i0.Expression? subCategory, i0.Expression? format, i0.Expression? additionalTriggers, + i0.Expression? searxngApi, i0.Expression? rowid, }) { return i0.RawValuesInsertable({ @@ -1512,6 +1551,7 @@ class BangCompanion extends i0.UpdateCompanion { if (subCategory != null) 'sub_category': subCategory, if (format != null) 'format': format, if (additionalTriggers != null) 'additional_triggers': additionalTriggers, + if (searxngApi != null) 'searxng_api': searxngApi, if (rowid != null) 'rowid': rowid, }); } @@ -1526,6 +1566,7 @@ class BangCompanion extends i0.UpdateCompanion { i0.Value? subCategory, i0.Value?>? format, i0.Value?>? additionalTriggers, + i0.Value? searxngApi, i0.Value? rowid, }) { return i3.BangCompanion( @@ -1538,6 +1579,7 @@ class BangCompanion extends i0.UpdateCompanion { subCategory: subCategory ?? this.subCategory, format: format ?? this.format, additionalTriggers: additionalTriggers ?? this.additionalTriggers, + searxngApi: searxngApi ?? this.searxngApi, rowid: rowid ?? this.rowid, ); } @@ -1580,6 +1622,9 @@ class BangCompanion extends i0.UpdateCompanion { ), ); } + if (searxngApi.present) { + map['searxng_api'] = i0.Variable(searxngApi.value); + } if (rowid.present) { map['rowid'] = i0.Variable(rowid.value); } @@ -1598,6 +1643,7 @@ class BangCompanion extends i0.UpdateCompanion { ..write('subCategory: $subCategory, ') ..write('format: $format, ') ..write('additionalTriggers: $additionalTriggers, ') + ..write('searxngApi: $searxngApi, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -2986,6 +3032,7 @@ class BangDataView extends i0.ViewInfo subCategory, format, additionalTriggers, + searxngApi, frequency, lastUsed, ]; @@ -3026,6 +3073,10 @@ class BangDataView extends i0.ViewInfo data['${effectivePrefix}group'], )!, ), + searxngApi: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}searxng_api'], + )!, category: attachedDatabase.typeMapping.read( i0.DriftSqlType.string, data['${effectivePrefix}category'], @@ -3117,6 +3168,15 @@ class BangDataView extends i0.ViewInfo true, type: i0.DriftSqlType.string, ).withConverter?>(i3.BangTable.$converteradditionalTriggers); + late final i0.GeneratedColumn searxngApi = i0.GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: i0.DriftSqlType.bool, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("searxng_api" IN (0, 1))', + ), + ); late final i0.GeneratedColumn frequency = i0.GeneratedColumn( 'frequency', aliasedName, @@ -3196,6 +3256,7 @@ class DefinitionsDrift extends i7.ModularAccessor { trigger: row.read('trigger'), urlTemplate: row.read('url_template'), group: i3.BangTable.$convertergroup.fromSql(row.read('group')), + searxngApi: row.read('searxng_api'), category: row.readNullable('category'), subCategory: row.readNullable('sub_category'), format: i3.BangTable.$converterformat.fromSql( @@ -3222,6 +3283,7 @@ class DefinitionsDrift extends i7.ModularAccessor { trigger: row.read('trigger'), urlTemplate: row.read('url_template'), group: i3.BangTable.$convertergroup.fromSql(row.read('group')), + searxngApi: row.read('searxng_api'), category: row.readNullable('category'), subCategory: row.readNullable('sub_category'), format: i3.BangTable.$converterformat.fromSql( diff --git a/app/lib/features/bangs/data/models/bang.dart b/app/lib/features/bangs/data/models/bang.dart index 243cabe8..f59777d0 100644 --- a/app/lib/features/bangs/data/models/bang.dart +++ b/app/lib/features/bangs/data/models/bang.dart @@ -21,6 +21,7 @@ import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:drift/drift.dart' show Expression, Insertable, Value; import 'package:fast_equatable/fast_equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:weblibre/extensions/uri.dart'; import 'package:weblibre/features/bangs/data/database/definitions.drift.dart'; import 'package:weblibre/features/bangs/data/models/bang_group.dart'; @@ -83,6 +84,8 @@ class Bang with FastEquatable implements Insertable { @JsonKey(name: 'ts') final Set? additionalTriggers; + final bool searxngApi; + String formatQuery(String input) { return (format == null || format!.contains(BangFormat.urlEncodePlaceholder) == true) @@ -92,6 +95,7 @@ class Bang with FastEquatable implements Insertable { : Uri.encodeComponent(input) : input; } + Uri getDefaultUrl() { return getTemplateUrl(''); } @@ -108,6 +112,10 @@ class Bang with FastEquatable implements Insertable { ).replace(path: template.path, query: template.query); } + if (format?.contains(BangFormat.openBasePath) == true) { + return template.base; + } + return template; } @@ -124,6 +132,7 @@ class Bang with FastEquatable implements Insertable { required this.domain, required this.trigger, required this.urlTemplate, + required this.searxngApi, this.group, this.category, this.subCategory, @@ -146,6 +155,7 @@ class Bang with FastEquatable implements Insertable { subCategory, format, additionalTriggers, + searxngApi, ]; @override diff --git a/app/lib/features/bangs/data/models/bang.g.dart b/app/lib/features/bangs/data/models/bang.g.dart index f61da16c..b45e7e2f 100644 --- a/app/lib/features/bangs/data/models/bang.g.dart +++ b/app/lib/features/bangs/data/models/bang.g.dart @@ -15,6 +15,8 @@ abstract class _$BangCWProxy { Bang urlTemplate(String urlTemplate); + Bang searxngApi(bool searxngApi); + Bang group(BangGroup? group); Bang category(String? category); @@ -37,6 +39,7 @@ abstract class _$BangCWProxy { String domain, String trigger, String urlTemplate, + bool searxngApi, BangGroup? group, String? category, String? subCategory, @@ -64,6 +67,9 @@ class _$BangCWProxyImpl implements _$BangCWProxy { @override Bang urlTemplate(String urlTemplate) => call(urlTemplate: urlTemplate); + @override + Bang searxngApi(bool searxngApi) => call(searxngApi: searxngApi); + @override Bang group(BangGroup? group) => call(group: group); @@ -93,6 +99,7 @@ class _$BangCWProxyImpl implements _$BangCWProxy { Object? domain = const $CopyWithPlaceholder(), Object? trigger = const $CopyWithPlaceholder(), Object? urlTemplate = const $CopyWithPlaceholder(), + Object? searxngApi = const $CopyWithPlaceholder(), Object? group = const $CopyWithPlaceholder(), Object? category = const $CopyWithPlaceholder(), Object? subCategory = const $CopyWithPlaceholder(), @@ -118,6 +125,11 @@ class _$BangCWProxyImpl implements _$BangCWProxy { ? _value.urlTemplate // ignore: cast_nullable_to_non_nullable : urlTemplate as String, + searxngApi: + searxngApi == const $CopyWithPlaceholder() || searxngApi == null + ? _value.searxngApi + // ignore: cast_nullable_to_non_nullable + : searxngApi as bool, group: group == const $CopyWithPlaceholder() ? _value.group // ignore: cast_nullable_to_non_nullable @@ -158,6 +170,7 @@ Bang _$BangFromJson(Map json) => Bang( domain: json['d'] as String, trigger: json['t'] as String, urlTemplate: json['u'] as String, + searxngApi: json['searxngApi'] as bool, category: json['c'] as String?, subCategory: json['sc'] as String?, format: (json['fmt'] as List?) @@ -177,6 +190,7 @@ Map _$BangToJson(Bang instance) => { 'sc': instance.subCategory, 'fmt': instance.format?.map((e) => _$BangFormatEnumMap[e]!).toList(), 'ts': instance.additionalTriggers?.toList(), + 'searxngApi': instance.searxngApi, }; const _$BangFormatEnumMap = { diff --git a/app/lib/features/bangs/data/models/bang_data.dart b/app/lib/features/bangs/data/models/bang_data.dart index 1ed32b52..47696a64 100644 --- a/app/lib/features/bangs/data/models/bang_data.dart +++ b/app/lib/features/bangs/data/models/bang_data.dart @@ -41,6 +41,7 @@ class BangData extends Bang { required super.trigger, required super.urlTemplate, required super.group, + required super.searxngApi, super.category, super.subCategory, super.format, @@ -56,6 +57,7 @@ class BangData extends Bang { required super.domain, required super.trigger, required super.urlTemplate, + required super.searxngApi, super.category, super.subCategory, super.format, diff --git a/app/lib/features/bangs/data/models/bang_data.g.dart b/app/lib/features/bangs/data/models/bang_data.g.dart index d842d561..67a76e05 100644 --- a/app/lib/features/bangs/data/models/bang_data.g.dart +++ b/app/lib/features/bangs/data/models/bang_data.g.dart @@ -15,6 +15,8 @@ abstract class _$BangDataCWProxy { BangData urlTemplate(String urlTemplate); + BangData searxngApi(bool searxngApi); + BangData category(String? category); BangData subCategory(String? subCategory); @@ -41,6 +43,7 @@ abstract class _$BangDataCWProxy { String domain, String trigger, String urlTemplate, + bool searxngApi, String? category, String? subCategory, Set? format, @@ -70,6 +73,9 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy { @override BangData urlTemplate(String urlTemplate) => call(urlTemplate: urlTemplate); + @override + BangData searxngApi(bool searxngApi) => call(searxngApi: searxngApi); + @override BangData category(String? category) => call(category: category); @@ -105,6 +111,7 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy { Object? domain = const $CopyWithPlaceholder(), Object? trigger = const $CopyWithPlaceholder(), Object? urlTemplate = const $CopyWithPlaceholder(), + Object? searxngApi = const $CopyWithPlaceholder(), Object? category = const $CopyWithPlaceholder(), Object? subCategory = const $CopyWithPlaceholder(), Object? format = const $CopyWithPlaceholder(), @@ -132,6 +139,11 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy { ? _value.urlTemplate // ignore: cast_nullable_to_non_nullable : urlTemplate as String, + searxngApi: + searxngApi == const $CopyWithPlaceholder() || searxngApi == null + ? _value.searxngApi + // ignore: cast_nullable_to_non_nullable + : searxngApi as bool, category: category == const $CopyWithPlaceholder() ? _value.category // ignore: cast_nullable_to_non_nullable diff --git a/app/lib/features/bangs/data/models/bang_group.dart b/app/lib/features/bangs/data/models/bang_group.dart index a0817713..36c521dc 100644 --- a/app/lib/features/bangs/data/models/bang_group.dart +++ b/app/lib/features/bangs/data/models/bang_group.dart @@ -33,14 +33,10 @@ enum BangGroup { 'https://raw.githubusercontent.com/FaFre/bangs/main/data/kagi_bangs.json', bundled: 'assets/bangs/kagi_bangs.json', ), - custom( - remote: - 'https://raw.githubusercontent.com/FaFre/bangs/refs/heads/custom/data/custom.json', - bundled: 'assets/bangs/custom.json', - ); + user(remote: null, bundled: null); - final String bundled; - final String remote; + final String? bundled; + final String? remote; const BangGroup({required this.bundled, required this.remote}); } diff --git a/app/lib/features/bangs/domain/repositories/data.dart b/app/lib/features/bangs/domain/repositories/data.dart index 12bb93e5..f9cc78e7 100644 --- a/app/lib/features/bangs/domain/repositories/data.dart +++ b/app/lib/features/bangs/domain/repositories/data.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weblibre/features/bangs/data/models/bang.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/bangs/data/models/bang_group.dart'; import 'package:weblibre/features/bangs/data/models/bang_key.dart'; @@ -143,4 +144,12 @@ class BangDataRepository extends _$BangDataRepository { .bangFrequency .deleteWhere((t) => t.trigger.equals(trigger)); } + + Future upsertBang(Bang bang) { + return ref.read(bangDatabaseProvider).bangDao.upsertBang(bang); + } + + Future deleteBang(BangGroup group, String trigger) { + return ref.read(bangDatabaseProvider).syncDao.deleteBangs(group, [trigger]); + } } diff --git a/app/lib/features/bangs/domain/repositories/data.g.dart b/app/lib/features/bangs/domain/repositories/data.g.dart index 0e00a75d..32b763e8 100644 --- a/app/lib/features/bangs/domain/repositories/data.g.dart +++ b/app/lib/features/bangs/domain/repositories/data.g.dart @@ -42,7 +42,7 @@ final class BangDataRepositoryProvider } String _$bangDataRepositoryHash() => - r'93f1338da86c693f524df377fe01d0ecf7f5ff80'; + r'0001f3e21fc1bff4fc942e6e468fb580cf2afe4b'; abstract class _$BangDataRepository extends $Notifier { void build(); diff --git a/app/lib/features/bangs/domain/repositories/sync.dart b/app/lib/features/bangs/domain/repositories/sync.dart index 60097d0a..b6d57f6c 100644 --- a/app/lib/features/bangs/domain/repositories/sync.dart +++ b/app/lib/features/bangs/domain/repositories/sync.dart @@ -64,6 +64,18 @@ class BangSyncRepository extends _$BangSyncRepository { required BangDatabase db, required BangGroup group, }) async { + if (group.bundled == null) { + return Result.failure( + const ErrorMessage(source: 'BangSync', message: 'Not bundled'), + ); + } + + if (group.remote == null) { + return Result.failure( + const ErrorMessage(source: 'BangSync', message: 'No remote source'), + ); + } + final lastSync = await db.syncDao .getLastSyncOfGroup(group) .getSingleOrNull(); @@ -78,7 +90,7 @@ class BangSyncRepository extends _$BangSyncRepository { return Result.success(null); } - final result = await sourceService.getBundledBangs(group.bundled, group); + final result = await sourceService.getBundledBangs(group.bundled!, group); return result.flatMapAsync((remoteBangs) async { await db.syncDao.syncBangs( group: group, @@ -107,7 +119,7 @@ class BangSyncRepository extends _$BangSyncRepository { bangDataSourceServiceProvider.notifier, ), db: db, - url: Uri.parse(group.remote), + url: Uri.parse(group.remote!), group: group, syncInterval: syncInterval, ); @@ -163,7 +175,7 @@ class BangSyncRepository extends _$BangSyncRepository { Set? groups, }) async { //Default to all sources - groups ??= BangGroup.values.toSet(); + groups ??= BangGroup.values.where((e) => e.bundled != null).toSet(); //Run isolated operations final futures = groups.map( diff --git a/app/lib/features/bangs/domain/repositories/sync.g.dart b/app/lib/features/bangs/domain/repositories/sync.g.dart index f9cc3f84..f9947401 100644 --- a/app/lib/features/bangs/domain/repositories/sync.g.dart +++ b/app/lib/features/bangs/domain/repositories/sync.g.dart @@ -42,7 +42,7 @@ final class BangSyncRepositoryProvider } String _$bangSyncRepositoryHash() => - r'e3ce355e5332ff1fe7ac8ecaa277a431b74f2da0'; + r'1347dcbd03f6a1a4fa3bbbae5d9302098d260c50'; abstract class _$BangSyncRepository extends $Notifier { void build(); diff --git a/app/lib/features/bangs/presentation/screens/list.dart b/app/lib/features/bangs/presentation/screens/category.dart similarity index 96% rename from app/lib/features/bangs/presentation/screens/list.dart rename to app/lib/features/bangs/presentation/screens/category.dart index 116b543b..b0b81cdd 100644 --- a/app/lib/features/bangs/presentation/screens/list.dart +++ b/app/lib/features/bangs/presentation/screens/category.dart @@ -28,11 +28,11 @@ import 'package:weblibre/features/geckoview/features/browser/domain/providers.da import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; -class BangListScreen extends HookConsumerWidget { +class BangCategoryScreen extends HookConsumerWidget { final String? category; final String? subCategory; - const BangListScreen({this.category, this.subCategory, super.key}); + const BangCategoryScreen({this.category, this.subCategory, super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/app/lib/features/bangs/presentation/screens/edit.dart b/app/lib/features/bangs/presentation/screens/edit.dart new file mode 100644 index 00000000..f5fc5e11 --- /dev/null +++ b/app/lib/features/bangs/presentation/screens/edit.dart @@ -0,0 +1,307 @@ +import 'package:fast_equatable/fast_equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nullability/nullability.dart'; +import 'package:weblibre/features/bangs/data/models/bang.dart'; +import 'package:weblibre/features/bangs/data/models/bang_group.dart'; +import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; +import 'package:weblibre/features/bangs/domain/repositories/data.dart'; +import 'package:weblibre/utils/form_validators.dart'; + +class EditBangScreen extends HookConsumerWidget { + final Bang? initialBang; + + const EditBangScreen({super.key, required this.initialBang}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = useMemoized(() => GlobalKey()); + final categories = ref.watch( + bangCategoriesProvider.select((value) => value.value), + ); + + final nameTextController = useTextEditingController( + text: initialBang?.websiteName, + ); + final triggerTextController = useTextEditingController( + text: initialBang?.trigger, + ); + final urlTextController = useTextEditingController( + text: initialBang?.urlTemplate, + ); + + final category = useState(initialBang?.category); + final subCategory = useState(initialBang?.subCategory); + final formatFlags = useState(initialBang?.format); + + return Scaffold( + appBar: AppBar( + title: Text(initialBang == null ? 'New Bang' : 'Edit Bang'), + actions: [ + IconButton( + onPressed: () async { + if (formKey.currentState?.validate() ?? false) { + final uri = Uri.parse(urlTextController.text); + + final bang = Bang( + group: BangGroup.user, + trigger: triggerTextController.text, + websiteName: nameTextController.text, + domain: uri.host, + urlTemplate: urlTextController.text, + searxngApi: false, + category: category.value, + subCategory: subCategory.value, + additionalTriggers: initialBang?.additionalTriggers, + format: formatFlags.value.isNotEmpty + ? formatFlags.value + : null, + ); + + if (initialBang != null && + initialBang!.trigger != bang.trigger) { + await ref + .read(bangDataRepositoryProvider.notifier) + .deleteBang(BangGroup.user, initialBang!.trigger); + } + + await ref + .read(bangDataRepositoryProvider.notifier) + .upsertBang(bang); + + if (context.mounted) { + context.pop(); + } + } + }, + icon: const Icon(Icons.check), + ), + ], + ), + + body: SafeArea( + child: Form( + key: formKey, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: ListView( + children: [ + TextFormField( + controller: nameTextController, + decoration: const InputDecoration( + label: Text('Name'), + helper: Text( + 'The name of the website associated with the bang', + ), + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + validator: validateRequired, + ), + const SizedBox(height: 16), + TextFormField( + controller: triggerTextController, + decoration: const InputDecoration( + label: Text('Trigger'), + helper: Text( + 'The specific trigger word or phrase used to invoke the bang.', + ), + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + validator: validateRequired, + ), + const SizedBox(height: 16), + TextFormField( + controller: urlTextController, + keyboardType: TextInputType.url, + decoration: const InputDecoration( + label: Text('URL'), + helper: Text( + "The URL template to use when the bang is invoked, where `{{{s}}}` is replaced by the user's query.", + ), + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + validator: (value) { + if (value?.contains('{{{s}}}') != true) { + return 'Must contain the query placeholder {{{s}}}'; + } + + return validateUrl( + value, + eagerParsing: false, + onlyHttpProtocol: true, + ); + }, + ), + const SizedBox(height: 24), + DropdownMenuFormField( + key: ValueKey(EquatableValue([category.value, categories])), + enableFilter: true, + requestFocusOnTap: true, + label: const Text('Category'), + expandedInsets: EdgeInsets.zero, + initialSelection: category.value, + dropdownMenuEntries: [ + ...?categories?.keys.map( + (e) => DropdownMenuEntry(value: e, label: e), + ), + ], + onSelected: (value) { + if (category.value != value) { + category.value = value; + subCategory.value = null; + } + }, + ), + const SizedBox(height: 16), + DropdownMenuFormField( + key: ValueKey( + EquatableValue([subCategory.value, categories]), + ), + enableFilter: true, + requestFocusOnTap: true, + label: const Text('Sub Category'), + expandedInsets: EdgeInsets.zero, + initialSelection: subCategory.value, + dropdownMenuEntries: [ + ...?categories?[category.value]?.map( + (e) => DropdownMenuEntry(value: e, label: e), + ), + ], + onSelected: (value) { + if (subCategory.value != value) { + subCategory.value = value; + } + }, + ), + const SizedBox(height: 16), + Text('Flags', style: Theme.of(context).textTheme.labelMedium), + const SizedBox(height: 4), + CheckboxListTile( + value: + formatFlags.value?.contains(BangFormat.openBasePath) ?? + false, + title: const Text('Open Base Path'), + subtitle: const Text( + 'When the bang is invoked with no query, opens the base path of the URL (/) instead of any path given in the template (g., /search)', + ), + onChanged: (value) { + if (value != null) { + formatFlags.value = + value + ? { + ...?formatFlags.value, + BangFormat.openBasePath, + } + : {...?formatFlags.value} + ..remove(BangFormat.openBasePath); + } + }, + ), + CheckboxListTile( + value: + formatFlags.value?.contains( + BangFormat.urlEncodePlaceholder, + ) ?? + false, + title: const Text('URL Encode Placeholder'), + subtitle: const Text( + 'URL encode the search terms. Some sites do not work with this, so it can be disabled by omitting this.', + ), + onChanged: (value) { + if (value != null) { + formatFlags.value = + value + ? { + ...?formatFlags.value, + BangFormat.urlEncodePlaceholder, + } + : {...?formatFlags.value} + ..remove(BangFormat.urlEncodePlaceholder); + } + }, + ), + CheckboxListTile( + value: + formatFlags.value?.contains( + BangFormat.urlEncodeSpaceToPlus, + ) ?? + false, + title: const Text('URL Encode Space to Plus'), + subtitle: const Text( + 'URL encodes spaces as +, instead of %20. Some sites only work correctly with one or the other.', + ), + onChanged: (value) { + if (value != null) { + formatFlags.value = + value + ? { + ...?formatFlags.value, + BangFormat.urlEncodeSpaceToPlus, + } + : {...?formatFlags.value} + ..remove(BangFormat.urlEncodeSpaceToPlus); + } + }, + ), + if (initialBang != null) + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( + side: BorderSide( + color: Theme.of(context).colorScheme.error, + ), + foregroundColor: Theme.of(context).colorScheme.error, + iconColor: Theme.of(context).colorScheme.error, + ), + label: const Text('Delete'), + icon: const Icon(Icons.delete), + onPressed: () async { + final result = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete Bang'), + content: const Text( + 'Are you sure you want to delete this Bang?', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context, false); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: const Text('Delete'), + ), + ], + ); + }, + ); + + if (result == true) { + await ref + .read(bangDataRepositoryProvider.notifier) + .deleteBang(BangGroup.user, initialBang!.trigger); + + if (context.mounted) { + context.pop(); + } + } + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/app/lib/features/bangs/presentation/screens/menu.dart b/app/lib/features/bangs/presentation/screens/menu.dart new file mode 100644 index 00000000..c1cc4b3f --- /dev/null +++ b/app/lib/features/bangs/presentation/screens/menu.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/routing/routes.dart'; + +class BangMenuScreen extends HookConsumerWidget { + const BangMenuScreen(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar(title: const Text('Bangs')), + body: ListView( + children: [ + ListTile( + leading: const Icon(MdiIcons.accountAlert), + title: const Text('Manage User Bangs'), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + await const UserBangsRoute().push(context); + }, + ), + ListTile( + leading: const Icon(Icons.search), + title: const Text('Search Bangs'), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + await const BangSearchRoute().push(context); + }, + ), + ListTile( + leading: const Icon(MdiIcons.fileTree), + title: const Text('Browse Categories'), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + await const BangCategoriesRoute().push(context); + }, + ), + ], + ), + ); + } +} diff --git a/app/lib/features/bangs/presentation/screens/user.dart b/app/lib/features/bangs/presentation/screens/user.dart new file mode 100644 index 00000000..25ba4d91 --- /dev/null +++ b/app/lib/features/bangs/presentation/screens/user.dart @@ -0,0 +1,76 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/routing/routes.dart'; +import 'package:weblibre/features/bangs/data/models/bang_group.dart'; +import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; +import 'package:weblibre/features/bangs/domain/repositories/data.dart'; +import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart'; +import 'package:weblibre/presentation/widgets/failure_widget.dart'; + +class UserBangs extends HookConsumerWidget { + static const _userGroupFilter = [BangGroup.user]; + + const UserBangs(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bangsAsync = ref.watch(bangListProvider(groups: _userGroupFilter)); + + return Scaffold( + appBar: AppBar(title: const Text('User Bangs')), + body: bangsAsync.when( + data: (bangs) { + return ListView.builder( + itemCount: bangs.length, + itemBuilder: (context, index) { + final bang = bangs[index]; + return Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + onPressed: (context) async { + await ref + .read(bangDataRepositoryProvider.notifier) + .deleteBang(BangGroup.user, bang.trigger); + }, + backgroundColor: Theme.of( + context, + ).colorScheme.errorContainer, + foregroundColor: Theme.of( + context, + ).colorScheme.onErrorContainer, + icon: Icons.delete, + label: 'Delete', + ), + ], + ), + child: BangDetails( + bang, + onTap: () async { + await EditUserBangRoute( + initialBang: jsonEncode(bang.toJson()), + ).push(context); + }, + ), + ); + }, + ); + }, + error: (error, stackTrace) => Center( + child: FailureWidget(title: 'Failed to load Bangs', exception: error), + ), + loading: () => const Center(child: CircularProgressIndicator()), + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () async { + await const NewUserBangRoute().push(context); + }, + ), + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index 8cce66a9..45844a7f 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -426,7 +426,7 @@ class BrowserBottomAppBar extends HookConsumerWidget { const Divider(), MenuItemButton( onPressed: () async { - await BangCategoriesRoute().push(context); + await const BangMenuRoute().push(context); }, leadingIcon: const Icon(MdiIcons.exclamationThick), child: const Text('Bangs'), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index dbd3af85..6e7ff847 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -173,16 +173,14 @@ class _BrowserViewState extends ConsumerState launchedFromIntent: true, ); case SharedText(): - final defaultSearchBang = + final bang = ref.read(selectedBangDataProvider()) ?? await ref.read(defaultSearchBangDataProvider.future); await ref .read(tabRepositoryProvider.notifier) .addTab( - url: defaultSearchBang?.getTemplateUrl( - sharedContent.text, - ), + url: bang?.getTemplateUrl(sharedContent.text), private: settings.tabIntentOpenSetting == TabIntentOpenSetting.private, diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart index 6bc6c2ec..1a8bd2f3 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -198,13 +198,21 @@ class SearchScreen extends HookConsumerWidget { ); if (newUrl == null) { - final defaultSearchBang = + final bang = ref.read(selectedBangDataProvider()) ?? await ref.read( defaultSearchBangDataProvider.future, ); - newUrl = defaultSearchBang?.getTemplateUrl(value); + if (bang != null) { + newUrl = bang.getTemplateUrl(value); + + if (!privateTabMode) { + await ref + .read(bangSearchProvider.notifier) + .triggerBangSearch(bang, value); + } + } } if (newUrl != null) { diff --git a/app/lib/features/geckoview/features/tabs/data/database/database.dart b/app/lib/features/geckoview/features/tabs/data/database/database.dart index e43aecc5..38d1091c 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/database.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/database.dart @@ -18,6 +18,8 @@ * 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/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'; @@ -36,7 +38,13 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin { @override MigrationStrategy get migration => MigrationStrategy( beforeOpen: (details) async { - await customStatement('PRAGMA foreign_keys = ON;'); + if (kDebugMode) { + // This check pulls in a fair amount of code that's not needed + // anywhere else, so we recommend only doing it in debug builds. + await validateDatabaseSchema(); + } + + await customStatement('PRAGMA foreign_keys = ON'); await definitionsDrift.optimizeFtsIndex(); }, ); diff --git a/app/lib/features/settings/presentation/screens/bang_settings.dart b/app/lib/features/settings/presentation/screens/bang_settings.dart index 5f4dee52..23a01a95 100644 --- a/app/lib/features/settings/presentation/screens/bang_settings.dart +++ b/app/lib/features/settings/presentation/screens/bang_settings.dart @@ -68,11 +68,6 @@ class BangSettingsScreen extends HookConsumerWidget { title: 'Kagi Bangs', subtitle: 'Sync on-demand from GitHub', ), - const BangGroupListTile( - group: BangGroup.custom, - title: 'WebLibre Bangs', - subtitle: 'Sync on-demand from GitHub', - ), ], ); }, diff --git a/app/lib/features/user/data/database/database.dart b/app/lib/features/user/data/database/database.dart index 564ebc13..e6839c4e 100644 --- a/app/lib/features/user/data/database/database.dart +++ b/app/lib/features/user/data/database/database.dart @@ -18,6 +18,9 @@ * along with this program. If not, see . */ 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/user/data/database/daos/cache.dart'; import 'package:weblibre/features/user/data/database/daos/onboarding.dart'; import 'package:weblibre/features/user/data/database/daos/setting.dart'; @@ -35,14 +38,46 @@ class UserDatabase extends $UserDatabase { @override MigrationStrategy get migration => MigrationStrategy( beforeOpen: (details) async { + if (kDebugMode) { + // This check pulls in a fair amount of code that's not needed + // anywhere else, so we recommend only doing it in debug builds. + await validateDatabaseSchema(); + } + await customStatement('PRAGMA foreign_keys = ON;'); }, - onUpgrade: stepByStep( - from1To2: (m, schema) async { - await m.createTable(schema.riverpod); - }, - ), + 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'); + }, ); UserDatabase(super.e); + + static final _upgrade = migrationSteps( + from1To2: (m, schema) async { + await m.createTable(schema.riverpod); + }, + ); } diff --git a/app/lib/features/web_feed/data/database/database.dart b/app/lib/features/web_feed/data/database/database.dart index 145b16dd..115c4a27 100644 --- a/app/lib/features/web_feed/data/database/database.dart +++ b/app/lib/features/web_feed/data/database/database.dart @@ -18,6 +18,8 @@ * 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/search/domain/fts_tokenizer.dart'; import 'package:weblibre/features/web_feed/data/database/daos/article.dart'; import 'package:weblibre/features/web_feed/data/database/daos/feed.dart'; @@ -36,6 +38,12 @@ class FeedDatabase extends $FeedDatabase with TrigramQueryBuilderMixin { @override MigrationStrategy get migration => MigrationStrategy( beforeOpen: (details) async { + if (kDebugMode) { + // This check pulls in a fair amount of code that's not needed + // anywhere else, so we recommend only doing it in debug builds. + await validateDatabaseSchema(); + } + await customStatement('PRAGMA foreign_keys = ON;'); await definitionsDrift.optimizeFtsIndex(); }, diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 614fd2cf..0ec1cc00 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: copy_with_extension: ^10.0.1 country_flags: ^4.1.0 drift: ^2.29.0 + drift_dev: ^2.29.0 dynamic_color: ^1.8.1 exceptions: ^0.6.1 fading_scroll: ^0.9.1 @@ -91,7 +92,6 @@ dev_dependencies: copy_with_extension_gen: ^10.0.1 custom_lint: ^0.8.1 dependency_validator: ^5.0.3 - drift_dev: ^2.29.0 fast_equatable_lint: ^0.5.0 flutter_launcher_icons: ^0.14.4 flutter_test: diff --git a/app/test/drift/bangs/generated/schema.dart b/app/test/drift/bangs/generated/schema.dart index b2b7404b..209e70d7 100644 --- a/app/test/drift/bangs/generated/schema.dart +++ b/app/test/drift/bangs/generated/schema.dart @@ -5,6 +5,7 @@ import 'package:drift/drift.dart'; import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; +import 'schema_v3.dart' as v3; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -14,10 +15,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v1.DatabaseAtV1(db); case 2: return v2.DatabaseAtV2(db); + case 3: + return v3.DatabaseAtV3(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2]; + static const versions = const [1, 2, 3]; } diff --git a/app/test/drift/bangs/generated/schema_v3.dart b/app/test/drift/bangs/generated/schema_v3.dart new file mode 100644 index 00000000..a46d3a62 --- /dev/null +++ b/app/test/drift/bangs/generated/schema_v3.dart @@ -0,0 +1,2228 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Bang extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Bang(this.attachedDatabase, [this._alias]); + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn websiteName = GeneratedColumn( + 'website_name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn domain = GeneratedColumn( + 'domain', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn urlTemplate = GeneratedColumn( + 'url_template', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn category = GeneratedColumn( + 'category', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn subCategory = GeneratedColumn( + 'sub_category', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn format = GeneratedColumn( + 'format', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn additionalTriggers = + GeneratedColumn( + 'additional_triggers', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn searxngApi = GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const CustomExpression('FALSE'), + ); + @override + List get $columns => [ + trigger, + group, + websiteName, + domain, + urlTemplate, + category, + subCategory, + format, + additionalTriggers, + searxngApi, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang'; + @override + Set get $primaryKey => {trigger, group}; + @override + BangData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangData( + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + websiteName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}website_name'], + )!, + domain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}domain'], + )!, + urlTemplate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url_template'], + )!, + category: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}category'], + ), + subCategory: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}sub_category'], + ), + format: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}format'], + ), + additionalTriggers: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}additional_triggers'], + ), + searxngApi: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}searxng_api'], + )!, + ); + } + + @override + Bang createAlias(String alias) { + return Bang(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'PRIMARY KEY("trigger", "group")', + ]; + @override + bool get dontWriteConstraints => true; +} + +class BangData extends DataClass implements Insertable { + final String trigger; + final int group; + final String websiteName; + final String domain; + final String urlTemplate; + final String? category; + final String? subCategory; + final String? format; + final String? additionalTriggers; + final bool searxngApi; + const BangData({ + required this.trigger, + required this.group, + required this.websiteName, + required this.domain, + required this.urlTemplate, + this.category, + this.subCategory, + this.format, + this.additionalTriggers, + required this.searxngApi, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['trigger'] = Variable(trigger); + map['group'] = Variable(group); + map['website_name'] = Variable(websiteName); + map['domain'] = Variable(domain); + map['url_template'] = Variable(urlTemplate); + if (!nullToAbsent || category != null) { + map['category'] = Variable(category); + } + if (!nullToAbsent || subCategory != null) { + map['sub_category'] = Variable(subCategory); + } + if (!nullToAbsent || format != null) { + map['format'] = Variable(format); + } + if (!nullToAbsent || additionalTriggers != null) { + map['additional_triggers'] = Variable(additionalTriggers); + } + map['searxng_api'] = Variable(searxngApi); + return map; + } + + factory BangData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangData( + trigger: serializer.fromJson(json['trigger']), + group: serializer.fromJson(json['group']), + websiteName: serializer.fromJson(json['websiteName']), + domain: serializer.fromJson(json['domain']), + urlTemplate: serializer.fromJson(json['urlTemplate']), + category: serializer.fromJson(json['category']), + subCategory: serializer.fromJson(json['subCategory']), + format: serializer.fromJson(json['format']), + additionalTriggers: serializer.fromJson( + json['additionalTriggers'], + ), + searxngApi: serializer.fromJson(json['searxngApi']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'trigger': serializer.toJson(trigger), + 'group': serializer.toJson(group), + 'websiteName': serializer.toJson(websiteName), + 'domain': serializer.toJson(domain), + 'urlTemplate': serializer.toJson(urlTemplate), + 'category': serializer.toJson(category), + 'subCategory': serializer.toJson(subCategory), + 'format': serializer.toJson(format), + 'additionalTriggers': serializer.toJson(additionalTriggers), + 'searxngApi': serializer.toJson(searxngApi), + }; + } + + BangData copyWith({ + String? trigger, + int? group, + String? websiteName, + String? domain, + String? urlTemplate, + Value category = const Value.absent(), + Value subCategory = const Value.absent(), + Value format = const Value.absent(), + Value additionalTriggers = const Value.absent(), + bool? searxngApi, + }) => BangData( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + websiteName: websiteName ?? this.websiteName, + domain: domain ?? this.domain, + urlTemplate: urlTemplate ?? this.urlTemplate, + category: category.present ? category.value : this.category, + subCategory: subCategory.present ? subCategory.value : this.subCategory, + format: format.present ? format.value : this.format, + additionalTriggers: additionalTriggers.present + ? additionalTriggers.value + : this.additionalTriggers, + searxngApi: searxngApi ?? this.searxngApi, + ); + BangData copyWithCompanion(BangCompanion data) { + return BangData( + trigger: data.trigger.present ? data.trigger.value : this.trigger, + group: data.group.present ? data.group.value : this.group, + websiteName: data.websiteName.present + ? data.websiteName.value + : this.websiteName, + domain: data.domain.present ? data.domain.value : this.domain, + urlTemplate: data.urlTemplate.present + ? data.urlTemplate.value + : this.urlTemplate, + category: data.category.present ? data.category.value : this.category, + subCategory: data.subCategory.present + ? data.subCategory.value + : this.subCategory, + format: data.format.present ? data.format.value : this.format, + additionalTriggers: data.additionalTriggers.present + ? data.additionalTriggers.value + : this.additionalTriggers, + searxngApi: data.searxngApi.present + ? data.searxngApi.value + : this.searxngApi, + ); + } + + @override + String toString() { + return (StringBuffer('BangData(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('websiteName: $websiteName, ') + ..write('domain: $domain, ') + ..write('urlTemplate: $urlTemplate, ') + ..write('category: $category, ') + ..write('subCategory: $subCategory, ') + ..write('format: $format, ') + ..write('additionalTriggers: $additionalTriggers, ') + ..write('searxngApi: $searxngApi') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + trigger, + group, + websiteName, + domain, + urlTemplate, + category, + subCategory, + format, + additionalTriggers, + searxngApi, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangData && + other.trigger == this.trigger && + other.group == this.group && + other.websiteName == this.websiteName && + other.domain == this.domain && + other.urlTemplate == this.urlTemplate && + other.category == this.category && + other.subCategory == this.subCategory && + other.format == this.format && + other.additionalTriggers == this.additionalTriggers && + other.searxngApi == this.searxngApi); +} + +class BangCompanion extends UpdateCompanion { + final Value trigger; + final Value group; + final Value websiteName; + final Value domain; + final Value urlTemplate; + final Value category; + final Value subCategory; + final Value format; + final Value additionalTriggers; + final Value searxngApi; + final Value rowid; + const BangCompanion({ + this.trigger = const Value.absent(), + this.group = const Value.absent(), + this.websiteName = const Value.absent(), + this.domain = const Value.absent(), + this.urlTemplate = const Value.absent(), + this.category = const Value.absent(), + this.subCategory = const Value.absent(), + this.format = const Value.absent(), + this.additionalTriggers = const Value.absent(), + this.searxngApi = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangCompanion.insert({ + required String trigger, + required int group, + required String websiteName, + required String domain, + required String urlTemplate, + this.category = const Value.absent(), + this.subCategory = const Value.absent(), + this.format = const Value.absent(), + this.additionalTriggers = const Value.absent(), + this.searxngApi = const Value.absent(), + this.rowid = const Value.absent(), + }) : trigger = Value(trigger), + group = Value(group), + websiteName = Value(websiteName), + domain = Value(domain), + urlTemplate = Value(urlTemplate); + static Insertable custom({ + Expression? trigger, + Expression? group, + Expression? websiteName, + Expression? domain, + Expression? urlTemplate, + Expression? category, + Expression? subCategory, + Expression? format, + Expression? additionalTriggers, + Expression? searxngApi, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (trigger != null) 'trigger': trigger, + if (group != null) 'group': group, + if (websiteName != null) 'website_name': websiteName, + if (domain != null) 'domain': domain, + if (urlTemplate != null) 'url_template': urlTemplate, + if (category != null) 'category': category, + if (subCategory != null) 'sub_category': subCategory, + if (format != null) 'format': format, + if (additionalTriggers != null) 'additional_triggers': additionalTriggers, + if (searxngApi != null) 'searxng_api': searxngApi, + if (rowid != null) 'rowid': rowid, + }); + } + + BangCompanion copyWith({ + Value? trigger, + Value? group, + Value? websiteName, + Value? domain, + Value? urlTemplate, + Value? category, + Value? subCategory, + Value? format, + Value? additionalTriggers, + Value? searxngApi, + Value? rowid, + }) { + return BangCompanion( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + websiteName: websiteName ?? this.websiteName, + domain: domain ?? this.domain, + urlTemplate: urlTemplate ?? this.urlTemplate, + category: category ?? this.category, + subCategory: subCategory ?? this.subCategory, + format: format ?? this.format, + additionalTriggers: additionalTriggers ?? this.additionalTriggers, + searxngApi: searxngApi ?? this.searxngApi, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (group.present) { + map['group'] = Variable(group.value); + } + if (websiteName.present) { + map['website_name'] = Variable(websiteName.value); + } + if (domain.present) { + map['domain'] = Variable(domain.value); + } + if (urlTemplate.present) { + map['url_template'] = Variable(urlTemplate.value); + } + if (category.present) { + map['category'] = Variable(category.value); + } + if (subCategory.present) { + map['sub_category'] = Variable(subCategory.value); + } + if (format.present) { + map['format'] = Variable(format.value); + } + if (additionalTriggers.present) { + map['additional_triggers'] = Variable(additionalTriggers.value); + } + if (searxngApi.present) { + map['searxng_api'] = Variable(searxngApi.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangCompanion(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('websiteName: $websiteName, ') + ..write('domain: $domain, ') + ..write('urlTemplate: $urlTemplate, ') + ..write('category: $category, ') + ..write('subCategory: $subCategory, ') + ..write('format: $format, ') + ..write('additionalTriggers: $additionalTriggers, ') + ..write('searxngApi: $searxngApi, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangTriggers extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangTriggers(this.attachedDatabase, [this._alias]); + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn additionalTrigger = + GeneratedColumn( + 'additional_trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [trigger, group, additionalTrigger]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_triggers'; + @override + Set get $primaryKey => {trigger, group, additionalTrigger}; + @override + BangTriggersData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangTriggersData( + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + additionalTrigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}additional_trigger'], + )!, + ); + } + + @override + BangTriggers createAlias(String alias) { + return BangTriggers(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'PRIMARY KEY("trigger", "group", additional_trigger)', + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")ON DELETE CASCADE', + ]; + @override + bool get dontWriteConstraints => true; +} + +class BangTriggersData extends DataClass + implements Insertable { + final String trigger; + final int group; + final String additionalTrigger; + const BangTriggersData({ + required this.trigger, + required this.group, + required this.additionalTrigger, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['trigger'] = Variable(trigger); + map['group'] = Variable(group); + map['additional_trigger'] = Variable(additionalTrigger); + return map; + } + + factory BangTriggersData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangTriggersData( + trigger: serializer.fromJson(json['trigger']), + group: serializer.fromJson(json['group']), + additionalTrigger: serializer.fromJson(json['additionalTrigger']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'trigger': serializer.toJson(trigger), + 'group': serializer.toJson(group), + 'additionalTrigger': serializer.toJson(additionalTrigger), + }; + } + + BangTriggersData copyWith({ + String? trigger, + int? group, + String? additionalTrigger, + }) => BangTriggersData( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + additionalTrigger: additionalTrigger ?? this.additionalTrigger, + ); + BangTriggersData copyWithCompanion(BangTriggersCompanion data) { + return BangTriggersData( + trigger: data.trigger.present ? data.trigger.value : this.trigger, + group: data.group.present ? data.group.value : this.group, + additionalTrigger: data.additionalTrigger.present + ? data.additionalTrigger.value + : this.additionalTrigger, + ); + } + + @override + String toString() { + return (StringBuffer('BangTriggersData(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('additionalTrigger: $additionalTrigger') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(trigger, group, additionalTrigger); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangTriggersData && + other.trigger == this.trigger && + other.group == this.group && + other.additionalTrigger == this.additionalTrigger); +} + +class BangTriggersCompanion extends UpdateCompanion { + final Value trigger; + final Value group; + final Value additionalTrigger; + final Value rowid; + const BangTriggersCompanion({ + this.trigger = const Value.absent(), + this.group = const Value.absent(), + this.additionalTrigger = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangTriggersCompanion.insert({ + required String trigger, + required int group, + required String additionalTrigger, + this.rowid = const Value.absent(), + }) : trigger = Value(trigger), + group = Value(group), + additionalTrigger = Value(additionalTrigger); + static Insertable custom({ + Expression? trigger, + Expression? group, + Expression? additionalTrigger, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (trigger != null) 'trigger': trigger, + if (group != null) 'group': group, + if (additionalTrigger != null) 'additional_trigger': additionalTrigger, + if (rowid != null) 'rowid': rowid, + }); + } + + BangTriggersCompanion copyWith({ + Value? trigger, + Value? group, + Value? additionalTrigger, + Value? rowid, + }) { + return BangTriggersCompanion( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + additionalTrigger: additionalTrigger ?? this.additionalTrigger, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (group.present) { + map['group'] = Variable(group.value); + } + if (additionalTrigger.present) { + map['additional_trigger'] = Variable(additionalTrigger.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangTriggersCompanion(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('additionalTrigger: $additionalTrigger, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangSync extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangSync(this.attachedDatabase, [this._alias]); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn lastSync = GeneratedColumn( + 'last_sync', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [group, lastSync]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_sync'; + @override + Set get $primaryKey => {group}; + @override + BangSyncData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangSyncData( + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + lastSync: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_sync'], + )!, + ); + } + + @override + BangSync createAlias(String alias) { + return BangSync(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class BangSyncData extends DataClass implements Insertable { + final int group; + final DateTime lastSync; + const BangSyncData({required this.group, required this.lastSync}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['group'] = Variable(group); + map['last_sync'] = Variable(lastSync); + return map; + } + + factory BangSyncData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangSyncData( + group: serializer.fromJson(json['group']), + lastSync: serializer.fromJson(json['lastSync']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'group': serializer.toJson(group), + 'lastSync': serializer.toJson(lastSync), + }; + } + + BangSyncData copyWith({int? group, DateTime? lastSync}) => BangSyncData( + group: group ?? this.group, + lastSync: lastSync ?? this.lastSync, + ); + BangSyncData copyWithCompanion(BangSyncCompanion data) { + return BangSyncData( + group: data.group.present ? data.group.value : this.group, + lastSync: data.lastSync.present ? data.lastSync.value : this.lastSync, + ); + } + + @override + String toString() { + return (StringBuffer('BangSyncData(') + ..write('group: $group, ') + ..write('lastSync: $lastSync') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(group, lastSync); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangSyncData && + other.group == this.group && + other.lastSync == this.lastSync); +} + +class BangSyncCompanion extends UpdateCompanion { + final Value group; + final Value lastSync; + const BangSyncCompanion({ + this.group = const Value.absent(), + this.lastSync = const Value.absent(), + }); + BangSyncCompanion.insert({ + this.group = const Value.absent(), + required DateTime lastSync, + }) : lastSync = Value(lastSync); + static Insertable custom({ + Expression? group, + Expression? lastSync, + }) { + return RawValuesInsertable({ + if (group != null) 'group': group, + if (lastSync != null) 'last_sync': lastSync, + }); + } + + BangSyncCompanion copyWith({Value? group, Value? lastSync}) { + return BangSyncCompanion( + group: group ?? this.group, + lastSync: lastSync ?? this.lastSync, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (group.present) { + map['group'] = Variable(group.value); + } + if (lastSync.present) { + map['last_sync'] = Variable(lastSync.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangSyncCompanion(') + ..write('group: $group, ') + ..write('lastSync: $lastSync') + ..write(')')) + .toString(); + } +} + +class BangFrequency extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangFrequency(this.attachedDatabase, [this._alias]); + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn frequency = GeneratedColumn( + 'frequency', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn lastUsed = GeneratedColumn( + 'last_used', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [trigger, group, frequency, lastUsed]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_frequency'; + @override + Set get $primaryKey => {trigger, group}; + @override + BangFrequencyData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangFrequencyData( + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + frequency: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}frequency'], + )!, + lastUsed: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_used'], + )!, + ); + } + + @override + BangFrequency createAlias(String alias) { + return BangFrequency(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'PRIMARY KEY("trigger", "group")', + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")ON DELETE CASCADE', + ]; + @override + bool get dontWriteConstraints => true; +} + +class BangFrequencyData extends DataClass + implements Insertable { + final String trigger; + final int group; + final int frequency; + final DateTime lastUsed; + const BangFrequencyData({ + required this.trigger, + required this.group, + required this.frequency, + required this.lastUsed, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['trigger'] = Variable(trigger); + map['group'] = Variable(group); + map['frequency'] = Variable(frequency); + map['last_used'] = Variable(lastUsed); + return map; + } + + factory BangFrequencyData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangFrequencyData( + trigger: serializer.fromJson(json['trigger']), + group: serializer.fromJson(json['group']), + frequency: serializer.fromJson(json['frequency']), + lastUsed: serializer.fromJson(json['lastUsed']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'trigger': serializer.toJson(trigger), + 'group': serializer.toJson(group), + 'frequency': serializer.toJson(frequency), + 'lastUsed': serializer.toJson(lastUsed), + }; + } + + BangFrequencyData copyWith({ + String? trigger, + int? group, + int? frequency, + DateTime? lastUsed, + }) => BangFrequencyData( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + frequency: frequency ?? this.frequency, + lastUsed: lastUsed ?? this.lastUsed, + ); + BangFrequencyData copyWithCompanion(BangFrequencyCompanion data) { + return BangFrequencyData( + trigger: data.trigger.present ? data.trigger.value : this.trigger, + group: data.group.present ? data.group.value : this.group, + frequency: data.frequency.present ? data.frequency.value : this.frequency, + lastUsed: data.lastUsed.present ? data.lastUsed.value : this.lastUsed, + ); + } + + @override + String toString() { + return (StringBuffer('BangFrequencyData(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('frequency: $frequency, ') + ..write('lastUsed: $lastUsed') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(trigger, group, frequency, lastUsed); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangFrequencyData && + other.trigger == this.trigger && + other.group == this.group && + other.frequency == this.frequency && + other.lastUsed == this.lastUsed); +} + +class BangFrequencyCompanion extends UpdateCompanion { + final Value trigger; + final Value group; + final Value frequency; + final Value lastUsed; + final Value rowid; + const BangFrequencyCompanion({ + this.trigger = const Value.absent(), + this.group = const Value.absent(), + this.frequency = const Value.absent(), + this.lastUsed = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangFrequencyCompanion.insert({ + required String trigger, + required int group, + required int frequency, + required DateTime lastUsed, + this.rowid = const Value.absent(), + }) : trigger = Value(trigger), + group = Value(group), + frequency = Value(frequency), + lastUsed = Value(lastUsed); + static Insertable custom({ + Expression? trigger, + Expression? group, + Expression? frequency, + Expression? lastUsed, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (trigger != null) 'trigger': trigger, + if (group != null) 'group': group, + if (frequency != null) 'frequency': frequency, + if (lastUsed != null) 'last_used': lastUsed, + if (rowid != null) 'rowid': rowid, + }); + } + + BangFrequencyCompanion copyWith({ + Value? trigger, + Value? group, + Value? frequency, + Value? lastUsed, + Value? rowid, + }) { + return BangFrequencyCompanion( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + frequency: frequency ?? this.frequency, + lastUsed: lastUsed ?? this.lastUsed, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (group.present) { + map['group'] = Variable(group.value); + } + if (frequency.present) { + map['frequency'] = Variable(frequency.value); + } + if (lastUsed.present) { + map['last_used'] = Variable(lastUsed.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangFrequencyCompanion(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('frequency: $frequency, ') + ..write('lastUsed: $lastUsed, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangHistory extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangHistory(this.attachedDatabase, [this._alias]); + late final GeneratedColumn searchQuery = GeneratedColumn( + 'search_query', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'UNIQUE NOT NULL', + ); + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn searchDate = GeneratedColumn( + 'search_date', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + searchQuery, + trigger, + group, + searchDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_history'; + @override + Set get $primaryKey => const {}; + @override + BangHistoryData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangHistoryData( + searchQuery: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}search_query'], + )!, + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + searchDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}search_date'], + )!, + ); + } + + @override + BangHistory createAlias(String alias) { + return BangHistory(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'FOREIGN KEY("trigger", "group")REFERENCES bang("trigger", "group")', + ]; + @override + bool get dontWriteConstraints => true; +} + +class BangHistoryData extends DataClass implements Insertable { + final String searchQuery; + final String trigger; + final int group; + final DateTime searchDate; + const BangHistoryData({ + required this.searchQuery, + required this.trigger, + required this.group, + required this.searchDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['search_query'] = Variable(searchQuery); + map['trigger'] = Variable(trigger); + map['group'] = Variable(group); + map['search_date'] = Variable(searchDate); + return map; + } + + factory BangHistoryData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangHistoryData( + searchQuery: serializer.fromJson(json['searchQuery']), + trigger: serializer.fromJson(json['trigger']), + group: serializer.fromJson(json['group']), + searchDate: serializer.fromJson(json['searchDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'searchQuery': serializer.toJson(searchQuery), + 'trigger': serializer.toJson(trigger), + 'group': serializer.toJson(group), + 'searchDate': serializer.toJson(searchDate), + }; + } + + BangHistoryData copyWith({ + String? searchQuery, + String? trigger, + int? group, + DateTime? searchDate, + }) => BangHistoryData( + searchQuery: searchQuery ?? this.searchQuery, + trigger: trigger ?? this.trigger, + group: group ?? this.group, + searchDate: searchDate ?? this.searchDate, + ); + BangHistoryData copyWithCompanion(BangHistoryCompanion data) { + return BangHistoryData( + searchQuery: data.searchQuery.present + ? data.searchQuery.value + : this.searchQuery, + trigger: data.trigger.present ? data.trigger.value : this.trigger, + group: data.group.present ? data.group.value : this.group, + searchDate: data.searchDate.present + ? data.searchDate.value + : this.searchDate, + ); + } + + @override + String toString() { + return (StringBuffer('BangHistoryData(') + ..write('searchQuery: $searchQuery, ') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('searchDate: $searchDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(searchQuery, trigger, group, searchDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangHistoryData && + other.searchQuery == this.searchQuery && + other.trigger == this.trigger && + other.group == this.group && + other.searchDate == this.searchDate); +} + +class BangHistoryCompanion extends UpdateCompanion { + final Value searchQuery; + final Value trigger; + final Value group; + final Value searchDate; + final Value rowid; + const BangHistoryCompanion({ + this.searchQuery = const Value.absent(), + this.trigger = const Value.absent(), + this.group = const Value.absent(), + this.searchDate = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangHistoryCompanion.insert({ + required String searchQuery, + required String trigger, + required int group, + required DateTime searchDate, + this.rowid = const Value.absent(), + }) : searchQuery = Value(searchQuery), + trigger = Value(trigger), + group = Value(group), + searchDate = Value(searchDate); + static Insertable custom({ + Expression? searchQuery, + Expression? trigger, + Expression? group, + Expression? searchDate, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (searchQuery != null) 'search_query': searchQuery, + if (trigger != null) 'trigger': trigger, + if (group != null) 'group': group, + if (searchDate != null) 'search_date': searchDate, + if (rowid != null) 'rowid': rowid, + }); + } + + BangHistoryCompanion copyWith({ + Value? searchQuery, + Value? trigger, + Value? group, + Value? searchDate, + Value? rowid, + }) { + return BangHistoryCompanion( + searchQuery: searchQuery ?? this.searchQuery, + trigger: trigger ?? this.trigger, + group: group ?? this.group, + searchDate: searchDate ?? this.searchDate, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (searchQuery.present) { + map['search_query'] = Variable(searchQuery.value); + } + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (group.present) { + map['group'] = Variable(group.value); + } + if (searchDate.present) { + map['search_date'] = Variable(searchDate.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangHistoryCompanion(') + ..write('searchQuery: $searchQuery, ') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('searchDate: $searchDate, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangFts extends Table + with + TableInfo, + VirtualTableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangFts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: '', + ); + late final GeneratedColumn websiteName = GeneratedColumn( + 'website_name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: '', + ); + @override + List get $columns => [trigger, websiteName]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_fts'; + @override + Set get $primaryKey => const {}; + @override + BangFtsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangFtsData( + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + websiteName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}website_name'], + )!, + ); + } + + @override + BangFts createAlias(String alias) { + return BangFts(attachedDatabase, alias); + } + + @override + String get moduleAndArgs => + 'fts5(trigger, website_name, content=bang, prefix=\'2 3\')'; +} + +class BangFtsData extends DataClass implements Insertable { + final String trigger; + final String websiteName; + const BangFtsData({required this.trigger, required this.websiteName}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['trigger'] = Variable(trigger); + map['website_name'] = Variable(websiteName); + return map; + } + + factory BangFtsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangFtsData( + trigger: serializer.fromJson(json['trigger']), + websiteName: serializer.fromJson(json['websiteName']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'trigger': serializer.toJson(trigger), + 'websiteName': serializer.toJson(websiteName), + }; + } + + BangFtsData copyWith({String? trigger, String? websiteName}) => BangFtsData( + trigger: trigger ?? this.trigger, + websiteName: websiteName ?? this.websiteName, + ); + BangFtsData copyWithCompanion(BangFtsCompanion data) { + return BangFtsData( + trigger: data.trigger.present ? data.trigger.value : this.trigger, + websiteName: data.websiteName.present + ? data.websiteName.value + : this.websiteName, + ); + } + + @override + String toString() { + return (StringBuffer('BangFtsData(') + ..write('trigger: $trigger, ') + ..write('websiteName: $websiteName') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(trigger, websiteName); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangFtsData && + other.trigger == this.trigger && + other.websiteName == this.websiteName); +} + +class BangFtsCompanion extends UpdateCompanion { + final Value trigger; + final Value websiteName; + final Value rowid; + const BangFtsCompanion({ + this.trigger = const Value.absent(), + this.websiteName = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangFtsCompanion.insert({ + required String trigger, + required String websiteName, + this.rowid = const Value.absent(), + }) : trigger = Value(trigger), + websiteName = Value(websiteName); + static Insertable custom({ + Expression? trigger, + Expression? websiteName, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (trigger != null) 'trigger': trigger, + if (websiteName != null) 'website_name': websiteName, + if (rowid != null) 'rowid': rowid, + }); + } + + BangFtsCompanion copyWith({ + Value? trigger, + Value? websiteName, + Value? rowid, + }) { + return BangFtsCompanion( + trigger: trigger ?? this.trigger, + websiteName: websiteName ?? this.websiteName, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (websiteName.present) { + map['website_name'] = Variable(websiteName.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangFtsCompanion(') + ..write('trigger: $trigger, ') + ..write('websiteName: $websiteName, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangTriggersFts extends Table + with + TableInfo, + VirtualTableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BangTriggersFts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn additionalTrigger = + GeneratedColumn( + 'additional_trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: '', + ); + @override + List get $columns => [additionalTrigger]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'bang_triggers_fts'; + @override + Set get $primaryKey => const {}; + @override + BangTriggersFtsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangTriggersFtsData( + additionalTrigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}additional_trigger'], + )!, + ); + } + + @override + BangTriggersFts createAlias(String alias) { + return BangTriggersFts(attachedDatabase, alias); + } + + @override + String get moduleAndArgs => + 'fts5(additional_trigger, content=bang_triggers, prefix=\'2 3\')'; +} + +class BangTriggersFtsData extends DataClass + implements Insertable { + final String additionalTrigger; + const BangTriggersFtsData({required this.additionalTrigger}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['additional_trigger'] = Variable(additionalTrigger); + return map; + } + + factory BangTriggersFtsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangTriggersFtsData( + additionalTrigger: serializer.fromJson(json['additionalTrigger']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'additionalTrigger': serializer.toJson(additionalTrigger), + }; + } + + BangTriggersFtsData copyWith({String? additionalTrigger}) => + BangTriggersFtsData( + additionalTrigger: additionalTrigger ?? this.additionalTrigger, + ); + BangTriggersFtsData copyWithCompanion(BangTriggersFtsCompanion data) { + return BangTriggersFtsData( + additionalTrigger: data.additionalTrigger.present + ? data.additionalTrigger.value + : this.additionalTrigger, + ); + } + + @override + String toString() { + return (StringBuffer('BangTriggersFtsData(') + ..write('additionalTrigger: $additionalTrigger') + ..write(')')) + .toString(); + } + + @override + int get hashCode => additionalTrigger.hashCode; + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangTriggersFtsData && + other.additionalTrigger == this.additionalTrigger); +} + +class BangTriggersFtsCompanion extends UpdateCompanion { + final Value additionalTrigger; + final Value rowid; + const BangTriggersFtsCompanion({ + this.additionalTrigger = const Value.absent(), + this.rowid = const Value.absent(), + }); + BangTriggersFtsCompanion.insert({ + required String additionalTrigger, + this.rowid = const Value.absent(), + }) : additionalTrigger = Value(additionalTrigger); + static Insertable custom({ + Expression? additionalTrigger, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (additionalTrigger != null) 'additional_trigger': additionalTrigger, + if (rowid != null) 'rowid': rowid, + }); + } + + BangTriggersFtsCompanion copyWith({ + Value? additionalTrigger, + Value? rowid, + }) { + return BangTriggersFtsCompanion( + additionalTrigger: additionalTrigger ?? this.additionalTrigger, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (additionalTrigger.present) { + map['additional_trigger'] = Variable(additionalTrigger.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BangTriggersFtsCompanion(') + ..write('additionalTrigger: $additionalTrigger, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class BangDataViewData extends DataClass { + final String trigger; + final int group; + final String websiteName; + final String domain; + final String urlTemplate; + final String? category; + final String? subCategory; + final String? format; + final String? additionalTriggers; + final bool searxngApi; + final int? frequency; + final DateTime? lastUsed; + const BangDataViewData({ + required this.trigger, + required this.group, + required this.websiteName, + required this.domain, + required this.urlTemplate, + this.category, + this.subCategory, + this.format, + this.additionalTriggers, + required this.searxngApi, + this.frequency, + this.lastUsed, + }); + factory BangDataViewData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BangDataViewData( + trigger: serializer.fromJson(json['trigger']), + group: serializer.fromJson(json['group']), + websiteName: serializer.fromJson(json['websiteName']), + domain: serializer.fromJson(json['domain']), + urlTemplate: serializer.fromJson(json['urlTemplate']), + category: serializer.fromJson(json['category']), + subCategory: serializer.fromJson(json['subCategory']), + format: serializer.fromJson(json['format']), + additionalTriggers: serializer.fromJson( + json['additionalTriggers'], + ), + searxngApi: serializer.fromJson(json['searxngApi']), + frequency: serializer.fromJson(json['frequency']), + lastUsed: serializer.fromJson(json['lastUsed']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'trigger': serializer.toJson(trigger), + 'group': serializer.toJson(group), + 'websiteName': serializer.toJson(websiteName), + 'domain': serializer.toJson(domain), + 'urlTemplate': serializer.toJson(urlTemplate), + 'category': serializer.toJson(category), + 'subCategory': serializer.toJson(subCategory), + 'format': serializer.toJson(format), + 'additionalTriggers': serializer.toJson(additionalTriggers), + 'searxngApi': serializer.toJson(searxngApi), + 'frequency': serializer.toJson(frequency), + 'lastUsed': serializer.toJson(lastUsed), + }; + } + + BangDataViewData copyWith({ + String? trigger, + int? group, + String? websiteName, + String? domain, + String? urlTemplate, + Value category = const Value.absent(), + Value subCategory = const Value.absent(), + Value format = const Value.absent(), + Value additionalTriggers = const Value.absent(), + bool? searxngApi, + Value frequency = const Value.absent(), + Value lastUsed = const Value.absent(), + }) => BangDataViewData( + trigger: trigger ?? this.trigger, + group: group ?? this.group, + websiteName: websiteName ?? this.websiteName, + domain: domain ?? this.domain, + urlTemplate: urlTemplate ?? this.urlTemplate, + category: category.present ? category.value : this.category, + subCategory: subCategory.present ? subCategory.value : this.subCategory, + format: format.present ? format.value : this.format, + additionalTriggers: additionalTriggers.present + ? additionalTriggers.value + : this.additionalTriggers, + searxngApi: searxngApi ?? this.searxngApi, + frequency: frequency.present ? frequency.value : this.frequency, + lastUsed: lastUsed.present ? lastUsed.value : this.lastUsed, + ); + @override + String toString() { + return (StringBuffer('BangDataViewData(') + ..write('trigger: $trigger, ') + ..write('group: $group, ') + ..write('websiteName: $websiteName, ') + ..write('domain: $domain, ') + ..write('urlTemplate: $urlTemplate, ') + ..write('category: $category, ') + ..write('subCategory: $subCategory, ') + ..write('format: $format, ') + ..write('additionalTriggers: $additionalTriggers, ') + ..write('searxngApi: $searxngApi, ') + ..write('frequency: $frequency, ') + ..write('lastUsed: $lastUsed') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + trigger, + group, + websiteName, + domain, + urlTemplate, + category, + subCategory, + format, + additionalTriggers, + searxngApi, + frequency, + lastUsed, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BangDataViewData && + other.trigger == this.trigger && + other.group == this.group && + other.websiteName == this.websiteName && + other.domain == this.domain && + other.urlTemplate == this.urlTemplate && + other.category == this.category && + other.subCategory == this.subCategory && + other.format == this.format && + other.additionalTriggers == this.additionalTriggers && + other.searxngApi == this.searxngApi && + other.frequency == this.frequency && + other.lastUsed == this.lastUsed); +} + +class BangDataView extends ViewInfo + implements HasResultSet { + final String? _alias; + @override + final DatabaseAtV3 attachedDatabase; + BangDataView(this.attachedDatabase, [this._alias]); + @override + List get $columns => [ + trigger, + group, + websiteName, + domain, + urlTemplate, + category, + subCategory, + format, + additionalTriggers, + searxngApi, + frequency, + lastUsed, + ]; + @override + String get aliasedName => _alias ?? entityName; + @override + String get entityName => 'bang_data_view'; + @override + Map get createViewStatements => { + SqlDialect.sqlite: + 'CREATE VIEW bang_data_view AS SELECT b.*, bf.frequency, bf.last_used FROM bang AS b LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger" AND b."group" = bf."group";', + }; + @override + BangDataView get asDslTable => this; + @override + BangDataViewData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BangDataViewData( + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + group: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}group'], + )!, + websiteName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}website_name'], + )!, + domain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}domain'], + )!, + urlTemplate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url_template'], + )!, + category: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}category'], + ), + subCategory: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}sub_category'], + ), + format: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}format'], + ), + additionalTriggers: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}additional_triggers'], + ), + searxngApi: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}searxng_api'], + )!, + frequency: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}frequency'], + ), + lastUsed: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_used'], + ), + ); + } + + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + ); + late final GeneratedColumn group = GeneratedColumn( + 'group', + aliasedName, + false, + type: DriftSqlType.int, + ); + late final GeneratedColumn websiteName = GeneratedColumn( + 'website_name', + aliasedName, + false, + type: DriftSqlType.string, + ); + late final GeneratedColumn domain = GeneratedColumn( + 'domain', + aliasedName, + false, + type: DriftSqlType.string, + ); + late final GeneratedColumn urlTemplate = GeneratedColumn( + 'url_template', + aliasedName, + false, + type: DriftSqlType.string, + ); + late final GeneratedColumn category = GeneratedColumn( + 'category', + aliasedName, + true, + type: DriftSqlType.string, + ); + late final GeneratedColumn subCategory = GeneratedColumn( + 'sub_category', + aliasedName, + true, + type: DriftSqlType.string, + ); + late final GeneratedColumn format = GeneratedColumn( + 'format', + aliasedName, + true, + type: DriftSqlType.string, + ); + late final GeneratedColumn additionalTriggers = + GeneratedColumn( + 'additional_triggers', + aliasedName, + true, + type: DriftSqlType.string, + ); + late final GeneratedColumn searxngApi = GeneratedColumn( + 'searxng_api', + aliasedName, + false, + type: DriftSqlType.bool, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("searxng_api" IN (0, 1))', + ), + ); + late final GeneratedColumn frequency = GeneratedColumn( + 'frequency', + aliasedName, + true, + type: DriftSqlType.int, + ); + late final GeneratedColumn lastUsed = GeneratedColumn( + 'last_used', + aliasedName, + true, + type: DriftSqlType.dateTime, + ); + @override + BangDataView createAlias(String alias) { + return BangDataView(attachedDatabase, alias); + } + + @override + Query? get query => null; + @override + Set get readTables => const {}; +} + +class DatabaseAtV3 extends GeneratedDatabase { + DatabaseAtV3(QueryExecutor e) : super(e); + late final Bang bang = Bang(this); + late final BangTriggers bangTriggers = BangTriggers(this); + late final Index idxBangTriggersLookup = Index( + 'idx_bang_triggers_lookup', + 'CREATE INDEX idx_bang_triggers_lookup ON bang_triggers (additional_trigger, "group")', + ); + late final Trigger bangTriggersAfterInsert = Trigger( + 'CREATE TRIGGER bang_triggers_after_insert AFTER INSERT ON bang WHEN new.additional_triggers IS NOT NULL BEGIN INSERT INTO bang_triggers ("trigger", "group", additional_trigger) SELECT new."trigger", new."group", json_each.value FROM json_each(new.additional_triggers);END', + 'bang_triggers_after_insert', + ); + late final Trigger bangTriggersAfterUpdate = Trigger( + 'CREATE TRIGGER bang_triggers_after_update AFTER UPDATE ON bang BEGIN DELETE FROM bang_triggers WHERE "trigger" = old."trigger" AND "group" = old."group";INSERT INTO bang_triggers ("trigger", "group", additional_trigger) SELECT new."trigger", new."group", json_each.value FROM json_each(new.additional_triggers)WHERE new.additional_triggers IS NOT NULL;END', + 'bang_triggers_after_update', + ); + late final BangSync bangSync = BangSync(this); + late final BangFrequency bangFrequency = BangFrequency(this); + late final BangHistory bangHistory = BangHistory(this); + late final BangFts bangFts = BangFts(this); + late final BangTriggersFts bangTriggersFts = BangTriggersFts(this); + late final BangDataView bangDataView = BangDataView(this); + late final Trigger bangAfterInsert = Trigger( + 'CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN INSERT INTO bang_fts ("rowid", "trigger", website_name) VALUES (new."rowid", new."trigger", new.website_name);END', + 'bang_after_insert', + ); + late final Trigger bangAfterDelete = Trigger( + 'CREATE TRIGGER bang_after_delete AFTER DELETE ON bang BEGIN INSERT INTO bang_fts (bang_fts, "rowid", "trigger", website_name) VALUES (\'delete\', old."rowid", old."trigger", old.website_name);END', + 'bang_after_delete', + ); + late final Trigger bangAfterUpdate = Trigger( + 'CREATE TRIGGER bang_after_update AFTER UPDATE ON bang BEGIN INSERT INTO bang_fts (bang_fts, "rowid", "trigger", website_name) VALUES (\'delete\', old."rowid", old."trigger", old.website_name);INSERT INTO bang_fts ("rowid", "trigger", website_name) VALUES (new."rowid", new."trigger", new.website_name);END', + 'bang_after_update', + ); + late final Trigger bangTriggersAfterInsertFts = Trigger( + 'CREATE TRIGGER bang_triggers_after_insert_fts AFTER INSERT ON bang_triggers BEGIN INSERT INTO bang_triggers_fts ("rowid", additional_trigger) VALUES (new."rowid", new.additional_trigger);END', + 'bang_triggers_after_insert_fts', + ); + late final Trigger bangTriggersAfterDeleteFts = Trigger( + 'CREATE TRIGGER bang_triggers_after_delete_fts AFTER DELETE ON bang_triggers BEGIN INSERT INTO bang_triggers_fts (bang_triggers_fts, "rowid", additional_trigger) VALUES (\'delete\', old."rowid", old.additional_trigger);END', + 'bang_triggers_after_delete_fts', + ); + late final Trigger bangTriggersAfterUpdateFts = Trigger( + 'CREATE TRIGGER bang_triggers_after_update_fts AFTER UPDATE ON bang_triggers BEGIN INSERT INTO bang_triggers_fts (bang_triggers_fts, "rowid", additional_trigger) VALUES (\'delete\', old."rowid", old.additional_trigger);INSERT INTO bang_triggers_fts ("rowid", additional_trigger) VALUES (new."rowid", new.additional_trigger);END', + 'bang_triggers_after_update_fts', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + bang, + bangTriggers, + idxBangTriggersLookup, + bangTriggersAfterInsert, + bangTriggersAfterUpdate, + bangSync, + bangFrequency, + bangHistory, + bangFts, + bangTriggersFts, + bangDataView, + bangAfterInsert, + bangAfterDelete, + bangAfterUpdate, + bangTriggersAfterInsertFts, + bangTriggersAfterDeleteFts, + bangTriggersAfterUpdateFts, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang_triggers', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang_triggers', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'bang_triggers', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + ]); + @override + int get schemaVersion => 3; +}