implement user bangs
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -19,27 +19,55 @@
|
||||
*/
|
||||
part of 'routes.dart';
|
||||
|
||||
@TypedGoRoute<BangCategoriesRoute>(
|
||||
@TypedGoRoute<BangMenuRoute>(
|
||||
name: 'BangRoute',
|
||||
path: '/bangs',
|
||||
routes: [
|
||||
TypedGoRoute<UserBangsRoute>(
|
||||
name: 'UserBangsRoute',
|
||||
path: 'user',
|
||||
routes: [
|
||||
TypedGoRoute<NewUserBangRoute>(name: 'NewUserBangRoute', path: 'new'),
|
||||
TypedGoRoute<EditUserBangRoute>(
|
||||
name: 'EditUserBangRoute',
|
||||
path: 'edit',
|
||||
),
|
||||
],
|
||||
),
|
||||
TypedGoRoute<BangSearchRoute>(
|
||||
name: 'BangSearchRoute',
|
||||
path: 'search/:searchText',
|
||||
),
|
||||
TypedGoRoute<BangCategoryRoute>(
|
||||
name: 'BangCategoryRoute',
|
||||
path: 'category/:category',
|
||||
TypedGoRoute<BangCategoriesRoute>(
|
||||
name: 'BangCategoriesRoute',
|
||||
path: 'categories',
|
||||
routes: [
|
||||
TypedGoRoute<BangSubCategoryRoute>(
|
||||
name: 'BangSubCategoryRoute',
|
||||
path: ':subCategory',
|
||||
TypedGoRoute<BangCategoryRoute>(
|
||||
name: 'BangCategoryRoute',
|
||||
path: 'category/:category',
|
||||
routes: [
|
||||
TypedGoRoute<BangSubCategoryRoute>(
|
||||
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<String, dynamic>,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -11,7 +11,7 @@ List<RouteBase> get $appRoutes => [
|
||||
$onboardingRoute,
|
||||
$settingsRoute,
|
||||
$browserRoute,
|
||||
$bangCategoriesRoute,
|
||||
$bangMenuRoute,
|
||||
$feedListRoute,
|
||||
];
|
||||
|
||||
@@ -791,34 +791,57 @@ extension<T extends Enum> on Map<T, String> {
|
||||
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<T?> push<T>(BuildContext context) => context.push<T>(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<T?> push<T>(BuildContext context) => context.push<T>(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<T?> push<T>(BuildContext context) => context.push<T>(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<T?> push<T>(BuildContext context) => context.push<T>(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
|
||||
|
||||
@@ -48,6 +48,10 @@ class BangDao extends DatabaseAccessor<BangDatabase> with $BangDaoMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> upsertBang(Bang bang) {
|
||||
return db.bang.insertOne(bang, mode: InsertMode.insertOrReplace);
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<BangData> getBangData(
|
||||
BangGroup group,
|
||||
String trigger,
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/internal/versioned_schema.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:weblibre/features/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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -408,8 +408,308 @@ i1.GeneratedColumn<DateTime> _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<i1.DatabaseSchemaEntity> 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<String> get trigger =>
|
||||
columnsByName['trigger']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get group =>
|
||||
columnsByName['group']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get websiteName =>
|
||||
columnsByName['website_name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get domain =>
|
||||
columnsByName['domain']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get urlTemplate =>
|
||||
columnsByName['url_template']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get category =>
|
||||
columnsByName['category']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get subCategory =>
|
||||
columnsByName['sub_category']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get format =>
|
||||
columnsByName['format']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get additionalTriggers =>
|
||||
columnsByName['additional_triggers']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<bool> get searxngApi =>
|
||||
columnsByName['searxng_api']! as i1.GeneratedColumn<bool>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_26(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'additional_triggers',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: '',
|
||||
);
|
||||
i1.GeneratedColumn<bool> _column_27(String aliasedName) =>
|
||||
i1.GeneratedColumn<bool>(
|
||||
'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<String> get trigger =>
|
||||
columnsByName['trigger']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get group =>
|
||||
columnsByName['group']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get additionalTrigger =>
|
||||
columnsByName['additional_trigger']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_28(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'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<String> get additionalTrigger =>
|
||||
columnsByName['additional_trigger']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_29(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'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<String> get trigger =>
|
||||
columnsByName['trigger']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get group =>
|
||||
columnsByName['group']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get websiteName =>
|
||||
columnsByName['website_name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get domain =>
|
||||
columnsByName['domain']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get urlTemplate =>
|
||||
columnsByName['url_template']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get category =>
|
||||
columnsByName['category']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get subCategory =>
|
||||
columnsByName['sub_category']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get format =>
|
||||
columnsByName['format']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get additionalTriggers =>
|
||||
columnsByName['additional_triggers']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<bool> get searxngApi =>
|
||||
columnsByName['searxng_api']! as i1.GeneratedColumn<bool>;
|
||||
i1.GeneratedColumn<int> get frequency =>
|
||||
columnsByName['frequency']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<DateTime> get lastUsed =>
|
||||
columnsByName['last_used']! as i1.GeneratedColumn<DateTime>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_30(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'additional_triggers',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
);
|
||||
i1.GeneratedColumn<bool> _column_31(String aliasedName) =>
|
||||
i1.GeneratedColumn<bool>(
|
||||
'searxng_api',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.bool,
|
||||
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("searxng_api" IN (0, 1))',
|
||||
),
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> 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<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(from1To2: from1To2),
|
||||
step: migrationSteps(from1To2: from1To2, from2To3: from2To3),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ typedef $BangTableCreateCompanionBuilder =
|
||||
i0.Value<String?> subCategory,
|
||||
i0.Value<Set<i1.BangFormat>?> format,
|
||||
i0.Value<Set<String>?> additionalTriggers,
|
||||
i0.Value<bool> searxngApi,
|
||||
i0.Value<int> rowid,
|
||||
});
|
||||
typedef $BangTableUpdateCompanionBuilder =
|
||||
@@ -38,6 +39,7 @@ typedef $BangTableUpdateCompanionBuilder =
|
||||
i0.Value<String?> subCategory,
|
||||
i0.Value<Set<i1.BangFormat>?> format,
|
||||
i0.Value<Set<String>?> additionalTriggers,
|
||||
i0.Value<bool> searxngApi,
|
||||
i0.Value<int> rowid,
|
||||
});
|
||||
|
||||
@@ -101,6 +103,11 @@ class $BangTableFilterComposer
|
||||
column: $table.additionalTriggers,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> 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<bool> 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<bool> get searxngApi => $composableBuilder(
|
||||
column: $table.searxngApi,
|
||||
builder: (column) => column,
|
||||
);
|
||||
}
|
||||
|
||||
class $BangTableTableManager
|
||||
@@ -245,6 +262,7 @@ class $BangTableTableManager
|
||||
i0.Value<Set<i1.BangFormat>?> format = const i0.Value.absent(),
|
||||
i0.Value<Set<String>?> additionalTriggers =
|
||||
const i0.Value.absent(),
|
||||
i0.Value<bool> searxngApi = const i0.Value.absent(),
|
||||
i0.Value<int> 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<Set<i1.BangFormat>?> format = const i0.Value.absent(),
|
||||
i0.Value<Set<String>?> additionalTriggers =
|
||||
const i0.Value.absent(),
|
||||
i0.Value<bool> searxngApi = const i0.Value.absent(),
|
||||
i0.Value<int> 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<BangTable, i1.Bang> {
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '',
|
||||
).withConverter<Set<String>?>(i3.BangTable.$converteradditionalTriggers);
|
||||
late final i0.GeneratedColumn<bool> searxngApi = i0.GeneratedColumn<bool>(
|
||||
'searxng_api',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL DEFAULT FALSE',
|
||||
defaultValue: const i0.CustomExpression('FALSE'),
|
||||
);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [
|
||||
trigger,
|
||||
@@ -1375,6 +1405,7 @@ class BangTable extends i0.Table with i0.TableInfo<BangTable, i1.Bang> {
|
||||
subCategory,
|
||||
format,
|
||||
additionalTriggers,
|
||||
searxngApi,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@@ -1403,6 +1434,10 @@ class BangTable extends i0.Table with i0.TableInfo<BangTable, i1.Bang> {
|
||||
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<i1.Bang> {
|
||||
final i0.Value<String?> subCategory;
|
||||
final i0.Value<Set<i1.BangFormat>?> format;
|
||||
final i0.Value<Set<String>?> additionalTriggers;
|
||||
final i0.Value<bool> searxngApi;
|
||||
final i0.Value<int> rowid;
|
||||
const BangCompanion({
|
||||
this.trigger = const i0.Value.absent(),
|
||||
@@ -1472,6 +1508,7 @@ class BangCompanion extends i0.UpdateCompanion<i1.Bang> {
|
||||
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<i1.Bang> {
|
||||
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<i1.Bang> {
|
||||
i0.Expression<String>? subCategory,
|
||||
i0.Expression<String>? format,
|
||||
i0.Expression<String>? additionalTriggers,
|
||||
i0.Expression<bool>? searxngApi,
|
||||
i0.Expression<int>? rowid,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
@@ -1512,6 +1551,7 @@ class BangCompanion extends i0.UpdateCompanion<i1.Bang> {
|
||||
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<i1.Bang> {
|
||||
i0.Value<String?>? subCategory,
|
||||
i0.Value<Set<i1.BangFormat>?>? format,
|
||||
i0.Value<Set<String>?>? additionalTriggers,
|
||||
i0.Value<bool>? searxngApi,
|
||||
i0.Value<int>? rowid,
|
||||
}) {
|
||||
return i3.BangCompanion(
|
||||
@@ -1538,6 +1579,7 @@ class BangCompanion extends i0.UpdateCompanion<i1.Bang> {
|
||||
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<i1.Bang> {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (searxngApi.present) {
|
||||
map['searxng_api'] = i0.Variable<bool>(searxngApi.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = i0.Variable<int>(rowid.value);
|
||||
}
|
||||
@@ -1598,6 +1643,7 @@ class BangCompanion extends i0.UpdateCompanion<i1.Bang> {
|
||||
..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<i3.BangDataView, i6.BangData>
|
||||
subCategory,
|
||||
format,
|
||||
additionalTriggers,
|
||||
searxngApi,
|
||||
frequency,
|
||||
lastUsed,
|
||||
];
|
||||
@@ -3026,6 +3073,10 @@ class BangDataView extends i0.ViewInfo<i3.BangDataView, i6.BangData>
|
||||
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<i3.BangDataView, i6.BangData>
|
||||
true,
|
||||
type: i0.DriftSqlType.string,
|
||||
).withConverter<Set<String>?>(i3.BangTable.$converteradditionalTriggers);
|
||||
late final i0.GeneratedColumn<bool> searxngApi = i0.GeneratedColumn<bool>(
|
||||
'searxng_api',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("searxng_api" IN (0, 1))',
|
||||
),
|
||||
);
|
||||
late final i0.GeneratedColumn<int> frequency = i0.GeneratedColumn<int>(
|
||||
'frequency',
|
||||
aliasedName,
|
||||
@@ -3196,6 +3256,7 @@ class DefinitionsDrift extends i7.ModularAccessor {
|
||||
trigger: row.read<String>('trigger'),
|
||||
urlTemplate: row.read<String>('url_template'),
|
||||
group: i3.BangTable.$convertergroup.fromSql(row.read<int>('group')),
|
||||
searxngApi: row.read<bool>('searxng_api'),
|
||||
category: row.readNullable<String>('category'),
|
||||
subCategory: row.readNullable<String>('sub_category'),
|
||||
format: i3.BangTable.$converterformat.fromSql(
|
||||
@@ -3222,6 +3283,7 @@ class DefinitionsDrift extends i7.ModularAccessor {
|
||||
trigger: row.read<String>('trigger'),
|
||||
urlTemplate: row.read<String>('url_template'),
|
||||
group: i3.BangTable.$convertergroup.fromSql(row.read<int>('group')),
|
||||
searxngApi: row.read<bool>('searxng_api'),
|
||||
category: row.readNullable<String>('category'),
|
||||
subCategory: row.readNullable<String>('sub_category'),
|
||||
format: i3.BangTable.$converterformat.fromSql(
|
||||
|
||||
@@ -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<Bang> {
|
||||
@JsonKey(name: 'ts')
|
||||
final Set<String>? 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<Bang> {
|
||||
: Uri.encodeComponent(input)
|
||||
: input;
|
||||
}
|
||||
|
||||
Uri getDefaultUrl() {
|
||||
return getTemplateUrl('');
|
||||
}
|
||||
@@ -108,6 +112,10 @@ class Bang with FastEquatable implements Insertable<Bang> {
|
||||
).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<Bang> {
|
||||
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<Bang> {
|
||||
subCategory,
|
||||
format,
|
||||
additionalTriggers,
|
||||
searxngApi,
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -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<String, dynamic> 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<dynamic>?)
|
||||
@@ -177,6 +190,7 @@ Map<String, dynamic> _$BangToJson(Bang instance) => <String, dynamic>{
|
||||
'sc': instance.subCategory,
|
||||
'fmt': instance.format?.map((e) => _$BangFormatEnumMap[e]!).toList(),
|
||||
'ts': instance.additionalTriggers?.toList(),
|
||||
'searxngApi': instance.searxngApi,
|
||||
};
|
||||
|
||||
const _$BangFormatEnumMap = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<BangFormat>? 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
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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<void> upsertBang(Bang bang) {
|
||||
return ref.read(bangDatabaseProvider).bangDao.upsertBang(bang);
|
||||
}
|
||||
|
||||
Future<void> deleteBang(BangGroup group, String trigger) {
|
||||
return ref.read(bangDatabaseProvider).syncDao.deleteBangs(group, [trigger]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ final class BangDataRepositoryProvider
|
||||
}
|
||||
|
||||
String _$bangDataRepositoryHash() =>
|
||||
r'93f1338da86c693f524df377fe01d0ecf7f5ff80';
|
||||
r'0001f3e21fc1bff4fc942e6e468fb580cf2afe4b';
|
||||
|
||||
abstract class _$BangDataRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -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<BangGroup>? 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(
|
||||
|
||||
@@ -42,7 +42,7 @@ final class BangSyncRepositoryProvider
|
||||
}
|
||||
|
||||
String _$bangSyncRepositoryHash() =>
|
||||
r'e3ce355e5332ff1fe7ac8ecaa277a431b74f2da0';
|
||||
r'1347dcbd03f6a1a4fa3bbbae5d9302098d260c50';
|
||||
|
||||
abstract class _$BangSyncRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
@@ -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<FormState>());
|
||||
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<bool?>(
|
||||
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: <Widget>[
|
||||
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();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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'),
|
||||
|
||||
+2
-4
@@ -173,16 +173,14 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/internal/versioned_schema.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:weblibre/features/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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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();
|
||||
},
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user