diff --git a/apps/weblibre/drift_schemas/user/drift_schema_v9.json b/apps/weblibre/drift_schemas/user/drift_schema_v9.json new file mode 100644 index 00000000..a9bd9a55 --- /dev/null +++ b/apps/weblibre/drift_schemas/user/drift_schema_v9.json @@ -0,0 +1,651 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": false + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "setting", + "was_declared_in_moor": true, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": "PRIMARY KEY NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "partition_key", + "getter_name": "partitionKey", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "any", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [], + "strict": true + } + }, + { + "id": 1, + "references": [], + "type": "table", + "data": { + "name": "proxy_profile", + "was_declared_in_moor": true, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL PRIMARY KEY", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SingboxProxyProfileType.values)", + "dart_type_name": "SingboxProxyProfileType" + } + }, + { + "name": "config_json", + "getter_name": "configJson", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "dns_override_json", + "getter_name": "dnsOverrideJson", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": "NOT NULL DEFAULT CURRENT_TIMESTAMP", + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": "NOT NULL DEFAULT CURRENT_TIMESTAMP", + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 2, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_proxy_profile_updated_at", + "sql": "CREATE INDEX idx_proxy_profile_updated_at ON proxy_profile(updated_at);", + "unique": false, + "columns": [] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "icon_cache", + "was_declared_in_moor": true, + "columns": [ + { + "name": "origin", + "getter_name": "origin", + "moor_type": "string", + "nullable": false, + "customConstraints": "PRIMARY KEY NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "icon_data", + "getter_name": "iconData", + "moor_type": "blob", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "fetch_date", + "getter_name": "fetchDate", + "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": 4, + "references": [], + "type": "table", + "data": { + "name": "onboarding", + "was_declared_in_moor": true, + "columns": [ + { + "name": "revision", + "getter_name": "revision", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "completion_date", + "getter_name": "completionDate", + "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": 5, + "references": [], + "type": "table", + "data": { + "name": "riverpod", + "was_declared_in_moor": true, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": "PRIMARY KEY NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "json", + "getter_name": "json", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "expireAt", + "getter_name": "expireAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "destroyKey", + "getter_name": "destroyKey", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [] + } + }, + { + "id": 6, + "references": [], + "type": "table", + "data": { + "name": "toolbar_button_configs", + "was_declared_in_moor": true, + "columns": [ + { + "name": "button_id", + "getter_name": "buttonId", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL PRIMARY KEY", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "order_key", + "getter_name": "orderKey", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": "NOT NULL DEFAULT TRUE", + "default_dart": "const CustomExpression('TRUE')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "fallback_id", + "getter_name": "fallbackId", + "moor_type": "string", + "nullable": true, + "customConstraints": "REFERENCES toolbar_button_configs(button_id)ON DELETE SET NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_toolbar_order_key", + "sql": "CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs(order_key);", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [], + "type": "table", + "data": { + "name": "quick_switcher_button_configs", + "was_declared_in_moor": true, + "columns": [ + { + "name": "button_id", + "getter_name": "buttonId", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL PRIMARY KEY", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "order_key", + "getter_name": "orderKey", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": "NOT NULL DEFAULT FALSE", + "default_dart": "const CustomExpression('FALSE')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "fallback_id", + "getter_name": "fallbackId", + "moor_type": "string", + "nullable": true, + "customConstraints": "REFERENCES quick_switcher_button_configs(button_id)ON DELETE SET NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 9, + "references": [ + 8 + ], + "type": "index", + "data": { + "on": 8, + "name": "idx_quick_switcher_order_key", + "sql": "CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs(order_key);", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [], + "type": "table", + "data": { + "name": "search_tokens", + "was_declared_in_moor": true, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": "PRIMARY KEY AUTOINCREMENT", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "auto-increment" + ] + }, + { + "name": "token", + "getter_name": "token", + "moor_type": "blob", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "inserted_at", + "getter_name": "insertedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "issuer_key_version", + "getter_name": "issuerKeyVersion", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "reserved_at", + "getter_name": "reservedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 11, + "references": [ + 10 + ], + "type": "index", + "data": { + "on": 10, + "name": "idx_search_tokens_inserted_at", + "sql": "CREATE INDEX idx_search_tokens_inserted_at ON search_tokens(inserted_at);", + "unique": false, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 10 + ], + "type": "index", + "data": { + "on": 10, + "name": "idx_search_tokens_reserved_at", + "sql": "CREATE INDEX idx_search_tokens_reserved_at ON search_tokens(reserved_at);", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "setting", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"setting\" (\"key\" TEXT PRIMARY KEY NOT NULL, \"partition_key\" TEXT, \"value\" ANY) STRICT;" + } + ] + }, + { + "name": "proxy_profile", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"proxy_profile\" (\"id\" TEXT NOT NULL PRIMARY KEY, \"name\" TEXT NOT NULL, \"type\" TEXT NOT NULL, \"config_json\" TEXT NOT NULL, \"dns_override_json\" TEXT, \"created_at\" INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, \"updated_at\" INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP);" + } + ] + }, + { + "name": "idx_proxy_profile_updated_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX idx_proxy_profile_updated_at ON proxy_profile (updated_at)" + } + ] + }, + { + "name": "icon_cache", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"icon_cache\" (\"origin\" TEXT PRIMARY KEY NOT NULL, \"icon_data\" BLOB NOT NULL, \"fetch_date\" INTEGER NOT NULL);" + } + ] + }, + { + "name": "onboarding", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"onboarding\" (\"revision\" INTEGER NOT NULL, \"completion_date\" INTEGER NOT NULL);" + } + ] + }, + { + "name": "riverpod", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"riverpod\" (\"key\" TEXT PRIMARY KEY NOT NULL, \"json\" TEXT NOT NULL, \"expireAt\" INTEGER, \"destroyKey\" TEXT) WITHOUT ROWID;" + } + ] + }, + { + "name": "toolbar_button_configs", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"toolbar_button_configs\" (\"button_id\" TEXT NOT NULL PRIMARY KEY, \"order_key\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT TRUE, \"fallback_id\" TEXT REFERENCES toolbar_button_configs(button_id)ON DELETE SET NULL);" + } + ] + }, + { + "name": "idx_toolbar_order_key", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs (order_key)" + } + ] + }, + { + "name": "quick_switcher_button_configs", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"quick_switcher_button_configs\" (\"button_id\" TEXT NOT NULL PRIMARY KEY, \"order_key\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT FALSE, \"fallback_id\" TEXT REFERENCES quick_switcher_button_configs(button_id)ON DELETE SET NULL);" + } + ] + }, + { + "name": "idx_quick_switcher_order_key", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs (order_key)" + } + ] + }, + { + "name": "search_tokens", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"search_tokens\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, \"token\" BLOB NOT NULL, \"inserted_at\" INTEGER NOT NULL, \"issuer_key_version\" TEXT NOT NULL, \"reserved_at\" INTEGER);" + } + ] + }, + { + "name": "idx_search_tokens_inserted_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX idx_search_tokens_inserted_at ON search_tokens (inserted_at)" + } + ] + }, + { + "name": "idx_search_tokens_reserved_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX idx_search_tokens_reserved_at ON search_tokens (reserved_at)" + } + ] + } + ] +} \ No newline at end of file diff --git a/apps/weblibre/lib/core/routing/routes.dart b/apps/weblibre/lib/core/routing/routes.dart index eaeb5a61..03ebe30d 100644 --- a/apps/weblibre/lib/core/routing/routes.dart +++ b/apps/weblibre/lib/core/routing/routes.dart @@ -45,6 +45,7 @@ import 'package:weblibre/features/geckoview/features/bookmarks/domain/entities/b import 'package:weblibre/features/geckoview/features/bookmarks/presentation/screens/bookmark_entry_edit.dart'; import 'package:weblibre/features/geckoview/features/bookmarks/presentation/screens/bookmark_folder_edit.dart'; import 'package:weblibre/features/geckoview/features/bookmarks/presentation/screens/bookmark_list.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/screens/tab_view.dart'; diff --git a/apps/weblibre/lib/core/routing/routes.g.dart b/apps/weblibre/lib/core/routing/routes.g.dart index 04a16a47..b59bb5e3 100644 --- a/apps/weblibre/lib/core/routing/routes.g.dart +++ b/apps/weblibre/lib/core/routing/routes.g.dart @@ -1674,6 +1674,11 @@ RouteBase get $settingsRoute => GoRouteData.$route( name: 'ContextualToolbarSettingsRoute', factory: $ContextualToolbarSettingsRoute._fromState, ), + GoRouteData.$route( + path: 'quick_switcher_toolbar', + name: 'QuickSwitcherToolbarSettingsRoute', + factory: $QuickSwitcherToolbarSettingsRoute._fromState, + ), GoRouteData.$route( path: 'desktop_mode_sites', name: 'DesktopModeSitesRoute', @@ -2285,6 +2290,28 @@ mixin $ContextualToolbarSettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin $QuickSwitcherToolbarSettingsRoute on GoRouteData { + static QuickSwitcherToolbarSettingsRoute _fromState(GoRouterState state) => + const QuickSwitcherToolbarSettingsRoute(); + + @override + String get location => + GoRouteData.$location('/settings/quick_switcher_toolbar'); + + @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 $DesktopModeSitesRoute on GoRouteData { static DesktopModeSitesRoute _fromState(GoRouterState state) => const DesktopModeSitesRoute(); diff --git a/apps/weblibre/lib/core/routing/routes.settings.dart b/apps/weblibre/lib/core/routing/routes.settings.dart index b2e4d94d..a9409225 100644 --- a/apps/weblibre/lib/core/routing/routes.settings.dart +++ b/apps/weblibre/lib/core/routing/routes.settings.dart @@ -117,6 +117,10 @@ part of 'routes.dart'; name: 'ContextualToolbarSettingsRoute', path: 'contextual_toolbar', ), + TypedGoRoute( + name: 'QuickSwitcherToolbarSettingsRoute', + path: 'quick_switcher_toolbar', + ), TypedGoRoute( name: 'DesktopModeSitesRoute', path: 'desktop_mode_sites', @@ -354,6 +358,19 @@ class ContextualToolbarSettingsRoute extends GoRouteData } } +class QuickSwitcherToolbarSettingsRoute extends GoRouteData + with $QuickSwitcherToolbarSettingsRoute { + const QuickSwitcherToolbarSettingsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const ContextualToolbarSettingsScreen( + location: ToolbarConfigLocation.quickSwitcher, + title: 'Customize Switcher Buttons', + ); + } +} + class DesktopModeSitesRoute extends GoRouteData with $DesktopModeSitesRoute { const DesktopModeSitesRoute(); diff --git a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart index e9caacdc..85410d80 100644 --- a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'dd2f9c776be1d5437141e94dbd0b7ef382093b63'; +String _$tabRepositoryHash() => r'749b131347685933ac36a3afcdd49f7def7155a9'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart index a7ebe890..6788f361 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart @@ -21,14 +21,17 @@ import 'package:fast_equatable/fast_equatable.dart'; import 'package:lexo_rank/lexo_rank.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/user/data/database/definitions.drift.dart' show ToolbarButtonConfig; part 'toolbar_button_configs.g.dart'; -List _buildDefaultToolbarButtonConfigs() { +List _buildDefaultToolbarButtonConfigs({ + required bool allHidden, +}) { String? lastKey; return toolbarButtonSpecs.map((spec) { @@ -41,19 +44,38 @@ List _buildDefaultToolbarButtonConfigs() { return ToolbarButtonConfig( buttonId: spec.id.name, orderKey: key, - isVisible: spec.defaultVisible, - fallbackId: spec.defaultFallback?.name, + isVisible: allHidden ? false : spec.defaultVisible, + fallbackId: allHidden ? null : spec.defaultFallback?.name, ); }).toList(); } -final defaultToolbarButtonConfigs = EquatableValue( - _buildDefaultToolbarButtonConfigs(), +final _contextualDefaultConfigs = EquatableValue( + _buildDefaultToolbarButtonConfigs(allHidden: false), ); +final _quickSwitcherDefaultConfigs = EquatableValue( + _buildDefaultToolbarButtonConfigs(allHidden: true), +); + +/// Default configuration set for [location], used as the reset target and as the +/// fallback while the persisted set is still loading. The quick switcher starts +/// with every button hidden. +EquatableValue> defaultToolbarButtonConfigsFor( + ToolbarConfigLocation location, +) { + return switch (location) { + ToolbarConfigLocation.contextual => _contextualDefaultConfigs, + ToolbarConfigLocation.quickSwitcher => _quickSwitcherDefaultConfigs, + }; +} + @Riverpod(keepAlive: true) -Stream> toolbarButtonConfigs(Ref ref) async* { - final repository = ref.watch(contextualToolbarConfigRepositoryProvider); +Stream> toolbarButtonConfigs( + Ref ref, + ToolbarConfigLocation location, +) async* { + final repository = ref.watch(toolbarConfigRepositoryProvider(location)); await repository.seedMissingDefaults(); yield* repository.watchAll(); } @@ -61,8 +83,9 @@ Stream> toolbarButtonConfigs(Ref ref) async* { @Riverpod(keepAlive: true) EquatableValue> effectiveToolbarButtonConfigs( Ref ref, + ToolbarConfigLocation location, ) { - final configsAsync = ref.watch(toolbarButtonConfigsProvider); + final configsAsync = ref.watch(toolbarButtonConfigsProvider(location)); return configsAsync.when( data: (configs) { @@ -71,12 +94,12 @@ EquatableValue> effectiveToolbarButtonConfigs( .toList(); if (filtered.isEmpty) { - return defaultToolbarButtonConfigs; + return defaultToolbarButtonConfigsFor(location); } return EquatableValue(filtered); }, - loading: () => defaultToolbarButtonConfigs, - error: (_, _) => defaultToolbarButtonConfigs, + loading: () => defaultToolbarButtonConfigsFor(location), + error: (_, _) => defaultToolbarButtonConfigsFor(location), ); } diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.g.dart index fe4637c0..bc9ae3ce 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.g.dart @@ -10,7 +10,7 @@ part of 'toolbar_button_configs.dart'; // ignore_for_file: type=lint, type=warning @ProviderFor(toolbarButtonConfigs) -final toolbarButtonConfigsProvider = ToolbarButtonConfigsProvider._(); +final toolbarButtonConfigsProvider = ToolbarButtonConfigsFamily._(); final class ToolbarButtonConfigsProvider extends @@ -22,20 +22,27 @@ final class ToolbarButtonConfigsProvider with $FutureModifier>, $StreamProvider> { - ToolbarButtonConfigsProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'toolbarButtonConfigsProvider', - isAutoDispose: false, - dependencies: null, - $allTransitiveDependencies: null, - ); + ToolbarButtonConfigsProvider._({ + required ToolbarButtonConfigsFamily super.from, + required ToolbarConfigLocation super.argument, + }) : super( + retry: null, + name: r'toolbarButtonConfigsProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); @override String debugGetCreateSourceHash() => _$toolbarButtonConfigsHash(); + @override + String toString() { + return r'toolbarButtonConfigsProvider' + '' + '($argument)'; + } + @$internal @override $StreamProviderElement> $createElement( @@ -44,16 +51,49 @@ final class ToolbarButtonConfigsProvider @override Stream> create(Ref ref) { - return toolbarButtonConfigs(ref); + final argument = this.argument as ToolbarConfigLocation; + return toolbarButtonConfigs(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is ToolbarButtonConfigsProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; } } String _$toolbarButtonConfigsHash() => - r'02f79c2087a3a5413fe879405c05f4a4d1eaffeb'; + r'913d2c8ca3b8b3f39f2247cafac068dc26abc566'; + +final class ToolbarButtonConfigsFamily extends $Family + with + $FunctionalFamilyOverride< + Stream>, + ToolbarConfigLocation + > { + ToolbarButtonConfigsFamily._() + : super( + retry: null, + name: r'toolbarButtonConfigsProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: false, + ); + + ToolbarButtonConfigsProvider call(ToolbarConfigLocation location) => + ToolbarButtonConfigsProvider._(argument: location, from: this); + + @override + String toString() => r'toolbarButtonConfigsProvider'; +} @ProviderFor(effectiveToolbarButtonConfigs) final effectiveToolbarButtonConfigsProvider = - EffectiveToolbarButtonConfigsProvider._(); + EffectiveToolbarButtonConfigsFamily._(); final class EffectiveToolbarButtonConfigsProvider extends @@ -63,20 +103,27 @@ final class EffectiveToolbarButtonConfigsProvider EquatableValue> > with $Provider>> { - EffectiveToolbarButtonConfigsProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'effectiveToolbarButtonConfigsProvider', - isAutoDispose: false, - dependencies: null, - $allTransitiveDependencies: null, - ); + EffectiveToolbarButtonConfigsProvider._({ + required EffectiveToolbarButtonConfigsFamily super.from, + required ToolbarConfigLocation super.argument, + }) : super( + retry: null, + name: r'effectiveToolbarButtonConfigsProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); @override String debugGetCreateSourceHash() => _$effectiveToolbarButtonConfigsHash(); + @override + String toString() { + return r'effectiveToolbarButtonConfigsProvider' + '' + '($argument)'; + } + @$internal @override $ProviderElement>> $createElement( @@ -85,7 +132,8 @@ final class EffectiveToolbarButtonConfigsProvider @override EquatableValue> create(Ref ref) { - return effectiveToolbarButtonConfigs(ref); + final argument = this.argument as ToolbarConfigLocation; + return effectiveToolbarButtonConfigs(ref, argument); } /// {@macro riverpod.override_with_value} @@ -96,7 +144,40 @@ final class EffectiveToolbarButtonConfigsProvider $SyncValueProvider>>(value), ); } + + @override + bool operator ==(Object other) { + return other is EffectiveToolbarButtonConfigsProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } } String _$effectiveToolbarButtonConfigsHash() => - r'dd876160f586c64237a42a29eb63cb119f962b02'; + r'6cd77c8af6df8943db6778b5222bb6ae9fb40025'; + +final class EffectiveToolbarButtonConfigsFamily extends $Family + with + $FunctionalFamilyOverride< + EquatableValue>, + ToolbarConfigLocation + > { + EffectiveToolbarButtonConfigsFamily._() + : super( + retry: null, + name: r'effectiveToolbarButtonConfigsProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: false, + ); + + EffectiveToolbarButtonConfigsProvider call(ToolbarConfigLocation location) => + EffectiveToolbarButtonConfigsProvider._(argument: location, from: this); + + @override + String toString() => r'effectiveToolbarButtonConfigsProvider'; +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart index 8553abc4..1c8be9de 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart @@ -19,6 +19,7 @@ */ import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.dart'; import 'package:weblibre/features/user/data/database/definitions.drift.dart' @@ -27,37 +28,46 @@ import 'package:weblibre/features/user/data/providers.dart'; part 'contextual_toolbar_config_repository.g.dart'; -class ContextualToolbarConfigRepository { +class ContextualToolbarConfigRepository + implements ToolbarButtonConfigRepository { ContextualToolbarConfigRepository(this._dao); final ToolbarButtonConfigDao _dao; + @override Stream> watchAll() => _dao.watchAll(); + @override Future replaceAll(List configs) { return _dao.replaceAll(configs); } + @override Future assignOrderKey(String buttonId, {required String orderKey}) { return _dao.assignOrderKey(buttonId, orderKey: orderKey); } + @override Future assignVisibility(String buttonId, {required bool visible}) { return _dao.assignVisibility(buttonId, visible: visible); } + @override Future assignFallback(String buttonId, String? fallbackId) { return _dao.assignFallback(buttonId, fallbackId); } + @override Future generateLeadingOrderKey({required bool isVisible}) { return _dao.generateLeadingOrderKey(isVisible: isVisible).getSingle(); } + @override Future generateTrailingOrderKey({required bool isVisible}) { return _dao.generateTrailingOrderKey(isVisible: isVisible).getSingle(); } + @override Future generateOrderKeyAfterButtonId( String buttonId, { required bool isVisible, @@ -67,6 +77,7 @@ class ContextualToolbarConfigRepository { .getSingleOrNull(); } + @override Future generateOrderKeyBeforeButtonId( String buttonId, { required bool isVisible, @@ -76,6 +87,7 @@ class ContextualToolbarConfigRepository { .getSingle(); } + @override Future seedMissingDefaults() { return _dao.seedMissing( toolbarButtonSpecs diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.dart new file mode 100644 index 00000000..46fb65c9 --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.dart @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; +import 'package:weblibre/features/user/data/database/daos/quick_switcher_button_config.dart'; +import 'package:weblibre/features/user/data/database/definitions.drift.dart' + show QuickSwitcherButtonConfig, ToolbarButtonConfig; +import 'package:weblibre/features/user/data/providers.dart'; + +part 'quick_switcher_toolbar_config_repository.g.dart'; + +/// Quick tab switcher counterpart to [ContextualToolbarConfigRepository]. Backed +/// by the separate `quick_switcher_button_configs` table and mapping its row +/// type to the shared [ToolbarButtonConfig] transport type. +class QuickSwitcherToolbarConfigRepository + implements ToolbarButtonConfigRepository { + QuickSwitcherToolbarConfigRepository(this._dao); + + final QuickSwitcherButtonConfigDao _dao; + + ToolbarButtonConfig _toShared(QuickSwitcherButtonConfig config) => + ToolbarButtonConfig( + buttonId: config.buttonId, + orderKey: config.orderKey, + isVisible: config.isVisible, + fallbackId: config.fallbackId, + ); + + QuickSwitcherButtonConfig _fromShared(ToolbarButtonConfig config) => + QuickSwitcherButtonConfig( + buttonId: config.buttonId, + orderKey: config.orderKey, + isVisible: config.isVisible, + fallbackId: config.fallbackId, + ); + + @override + Stream> watchAll() => + _dao.watchAll().map((rows) => rows.map(_toShared).toList()); + + @override + Future replaceAll(List configs) { + return _dao.replaceAll(configs.map(_fromShared).toList()); + } + + @override + Future assignOrderKey(String buttonId, {required String orderKey}) { + return _dao.assignOrderKey(buttonId, orderKey: orderKey); + } + + @override + Future assignVisibility(String buttonId, {required bool visible}) { + return _dao.assignVisibility(buttonId, visible: visible); + } + + @override + Future assignFallback(String buttonId, String? fallbackId) { + return _dao.assignFallback(buttonId, fallbackId); + } + + @override + Future generateLeadingOrderKey({required bool isVisible}) { + return _dao.generateLeadingOrderKey(isVisible: isVisible).getSingle(); + } + + @override + Future generateTrailingOrderKey({required bool isVisible}) { + return _dao.generateTrailingOrderKey(isVisible: isVisible).getSingle(); + } + + @override + Future generateOrderKeyAfterButtonId( + String buttonId, { + required bool isVisible, + }) { + return _dao + .generateOrderKeyAfterButtonId(buttonId, isVisible: isVisible) + .getSingleOrNull(); + } + + @override + Future generateOrderKeyBeforeButtonId( + String buttonId, { + required bool isVisible, + }) { + return _dao + .generateOrderKeyBeforeButtonId(buttonId, isVisible: isVisible) + .getSingle(); + } + + @override + Future seedMissingDefaults() { + // The quick switcher cluster starts empty: every known button is seeded + // hidden with no fallback, so nothing renders until the user enables it. + return _dao.seedMissing( + toolbarButtonSpecs + .map( + (spec) => ( + buttonId: spec.id.name, + defaultVisible: false, + defaultFallback: null, + ), + ) + .toList(), + ); + } +} + +@Riverpod(keepAlive: true) +QuickSwitcherToolbarConfigRepository quickSwitcherToolbarConfigRepository( + Ref ref, +) { + return QuickSwitcherToolbarConfigRepository( + ref.watch(userDatabaseProvider).quickSwitcherButtonConfigDao, + ); +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.g.dart new file mode 100644 index 00000000..d0f4c5aa --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quick_switcher_toolbar_config_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(quickSwitcherToolbarConfigRepository) +final quickSwitcherToolbarConfigRepositoryProvider = + QuickSwitcherToolbarConfigRepositoryProvider._(); + +final class QuickSwitcherToolbarConfigRepositoryProvider + extends + $FunctionalProvider< + QuickSwitcherToolbarConfigRepository, + QuickSwitcherToolbarConfigRepository, + QuickSwitcherToolbarConfigRepository + > + with $Provider { + QuickSwitcherToolbarConfigRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'quickSwitcherToolbarConfigRepositoryProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => + _$quickSwitcherToolbarConfigRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + QuickSwitcherToolbarConfigRepository create(Ref ref) { + return quickSwitcherToolbarConfigRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(QuickSwitcherToolbarConfigRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: + $SyncValueProvider(value), + ); + } +} + +String _$quickSwitcherToolbarConfigRepositoryHash() => + r'5459350ebde214186e882d8316e66d438c6913d8'; diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart new file mode 100644 index 00000000..85f26ebd --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/quick_switcher_toolbar_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; +import 'package:weblibre/features/user/data/database/definitions.drift.dart' + show ToolbarButtonConfig; + +part 'toolbar_button_config_repository.g.dart'; + +/// Shared contract for a single toolbar button configuration set. Both the +/// contextual toolbar and the quick tab switcher cluster persist to their own +/// table but expose this same interface, so the resolution logic, providers and +/// customization UI are location-agnostic. [ToolbarButtonConfig] (the contextual +/// drift row) is used as the shared transport type; the quick switcher +/// repository maps its own row type at the boundary. +abstract interface class ToolbarButtonConfigRepository { + Stream> watchAll(); + + Future replaceAll(List configs); + + Future assignOrderKey(String buttonId, {required String orderKey}); + + Future assignVisibility(String buttonId, {required bool visible}); + + Future assignFallback(String buttonId, String? fallbackId); + + Future generateLeadingOrderKey({required bool isVisible}); + + Future generateTrailingOrderKey({required bool isVisible}); + + Future generateOrderKeyAfterButtonId( + String buttonId, { + required bool isVisible, + }); + + Future generateOrderKeyBeforeButtonId( + String buttonId, { + required bool isVisible, + }); + + Future seedMissingDefaults(); +} + +@Riverpod(keepAlive: true) +ToolbarButtonConfigRepository toolbarConfigRepository( + Ref ref, + ToolbarConfigLocation location, +) { + return switch (location) { + ToolbarConfigLocation.contextual => ref.watch( + contextualToolbarConfigRepositoryProvider, + ), + ToolbarConfigLocation.quickSwitcher => ref.watch( + quickSwitcherToolbarConfigRepositoryProvider, + ), + }; +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.g.dart new file mode 100644 index 00000000..cb802aa7 --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.g.dart @@ -0,0 +1,101 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'toolbar_button_config_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(toolbarConfigRepository) +final toolbarConfigRepositoryProvider = ToolbarConfigRepositoryFamily._(); + +final class ToolbarConfigRepositoryProvider + extends + $FunctionalProvider< + ToolbarButtonConfigRepository, + ToolbarButtonConfigRepository, + ToolbarButtonConfigRepository + > + with $Provider { + ToolbarConfigRepositoryProvider._({ + required ToolbarConfigRepositoryFamily super.from, + required ToolbarConfigLocation super.argument, + }) : super( + retry: null, + name: r'toolbarConfigRepositoryProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$toolbarConfigRepositoryHash(); + + @override + String toString() { + return r'toolbarConfigRepositoryProvider' + '' + '($argument)'; + } + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + ToolbarButtonConfigRepository create(Ref ref) { + final argument = this.argument as ToolbarConfigLocation; + return toolbarConfigRepository(ref, argument); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ToolbarButtonConfigRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider( + value, + ), + ); + } + + @override + bool operator ==(Object other) { + return other is ToolbarConfigRepositoryProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$toolbarConfigRepositoryHash() => + r'47129b30f01db6ea73d553a8fd1b4f6e8a7b4a3c'; + +final class ToolbarConfigRepositoryFamily extends $Family + with + $FunctionalFamilyOverride< + ToolbarButtonConfigRepository, + ToolbarConfigLocation + > { + ToolbarConfigRepositoryFamily._() + : super( + retry: null, + name: r'toolbarConfigRepositoryProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: false, + ); + + ToolbarConfigRepositoryProvider call(ToolbarConfigLocation location) => + ToolbarConfigRepositoryProvider._(argument: location, from: this); + + @override + String toString() => r'toolbarConfigRepositoryProvider'; +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart new file mode 100644 index 00000000..35036d54 --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/// Identifies which independently-configured toolbar a button configuration +/// belongs to. Each location persists its own enabled set, ordering and +/// fallbacks (backed by a separate drift table) while sharing the button +/// registry, resolution logic and customization UI. +enum ToolbarConfigLocation { + /// The contextual toolbar row that sits below the URL bar. + contextual, + + /// The trailing fixed button cluster on the quick tab switcher bar. + quickSwitcher, +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart index 9fe222aa..cec8b6ec 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart @@ -20,6 +20,7 @@ import 'package:fast_equatable/fast_equatable.dart'; import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; class ContextualToolbarScope with FastEquatable { final String? selectedTabId; @@ -27,11 +28,17 @@ class ContextualToolbarScope with FastEquatable { final TabState? tabState; final bool isPreview; + /// Which configuration set this scope renders for, so shared registry + /// builders that read sibling button config (e.g. the back button's + /// stop-loading fallback) consult the correct location. + final ToolbarConfigLocation location; + ContextualToolbarScope({ required this.selectedTabId, required this.displayedSheet, required this.tabState, required this.isPreview, + this.location = ToolbarConfigLocation.contextual, }); @override @@ -40,5 +47,6 @@ class ContextualToolbarScope with FastEquatable { displayedSheet, tabState, isPreview, + location, ]; } diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart index 09889cde..62537feb 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart @@ -37,6 +37,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/entities/fon import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_id.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/utils/tab_close_confirmation.dart'; @@ -85,12 +86,12 @@ class ToolbarButtonDefinition { }); } -/// Whether a reload button is currently configured visible in the contextual +/// Whether a reload button is currently configured visible in [location]'s /// toolbar. Used to decide whether the back button should fall back to acting /// as a stop-loading control (see issue #351). -bool _isReloadButtonVisible(WidgetRef ref) { +bool _isReloadButtonVisible(WidgetRef ref, ToolbarConfigLocation location) { return ref - .read(effectiveToolbarButtonConfigsProvider) + .read(effectiveToolbarButtonConfigsProvider(location)) .value .any( (config) => @@ -108,7 +109,8 @@ final List toolbarButtonRegistry = [ final isLoading = scope.tabState?.isLoading == true; // The back button only doubles as a stop-loading control when no // dedicated reload button is present to take over that role. - return canGoBack || (isLoading && !_isReloadButtonVisible(ref)); + return canGoBack || + (isLoading && !_isReloadButtonVisible(ref, scope.location)); }, longPressActions: ['History Menu (Previous pages)'], builder: (scope, context, ref) { @@ -123,7 +125,7 @@ final List toolbarButtonRegistry = [ return NavigateBackButton( selectedTabId: scope.selectedTabId, isLoading: scope.tabState?.isLoading ?? false, - stopLoadingFallback: !_isReloadButtonVisible(ref), + stopLoadingFallback: !_isReloadButtonVisible(ref, scope.location), ); }, ), diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart index b304c81a..5bb89bb5 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart @@ -25,6 +25,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/services/toolbar_button_resolution.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart'; @@ -47,7 +48,9 @@ class ContextualToolbar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final tabState = ref.watch(tabStateProvider(selectedTabId)); - final configs = ref.watch(effectiveToolbarButtonConfigsProvider); + final configs = ref.watch( + effectiveToolbarButtonConfigsProvider(ToolbarConfigLocation.contextual), + ); final scope = ContextualToolbarScope( selectedTabId: selectedTabId, @@ -143,10 +146,7 @@ class ContextualToolbarView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ for (final button in buttons) - UnconstrainedBox( - constrainedAxis: Axis.vertical, - child: button, - ), + UnconstrainedBox(constrainedAxis: Axis.vertical, child: button), ], ); }, diff --git a/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/quick_switcher_button_row.dart b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/quick_switcher_button_row.dart new file mode 100644 index 00000000..038c79ea --- /dev/null +++ b/apps/weblibre/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/quick_switcher_button_row.dart @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; +import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/services/toolbar_button_resolution.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart'; + +/// The switcher bar's cross-axis extent (height when horizontal, width on the +/// side rail). Mirrors `BrowserTabBar.quickTabSwitcherHeight`; kept as a local +/// const to avoid a circular import with the bar widget that hosts this row. +const double _switcherExtent = 48.0; + +/// Trailing fixed button cluster for the quick tab switcher bar. Reuses the +/// contextual toolbar's button registry and resolution logic against the +/// independently-persisted [ToolbarConfigLocation.quickSwitcher] configuration. +/// +/// Pins to the trailing end of the bar while the tab chips keep the remaining +/// scrollable space. Each button is scaled to fit the bar's cross-axis extent +/// (reused [ToolbarButton]s are taller than the 48px bar), and the cluster +/// scrolls along the bar axis so enabling many buttons can never overflow — the +/// hosting bar additionally caps how much room the cluster may claim. Renders +/// nothing when no buttons are enabled. +class QuickSwitcherButtonRow extends HookConsumerWidget { + const QuickSwitcherButtonRow({ + super.key, + required this.selectedTabId, + required this.displayedSheet, + this.axis = Axis.horizontal, + }); + + final String? selectedTabId; + final Sheet? displayedSheet; + + /// Layout direction of the switcher bar; vertical for the side rail. + final Axis axis; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final tabState = ref.watch(tabStateProvider(selectedTabId)); + final configs = ref.watch( + effectiveToolbarButtonConfigsProvider( + ToolbarConfigLocation.quickSwitcher, + ), + ); + + final scope = ContextualToolbarScope( + selectedTabId: selectedTabId, + displayedSheet: displayedSheet, + tabState: tabState, + isPreview: false, + location: ToolbarConfigLocation.quickSwitcher, + ); + + final resolvedButtons = useMemoized( + () => resolveVisibleContextualToolbarButtons( + configs: configs.value, + knownButtonIds: knownToolbarButtonIds, + isPrimaryAvailable: (buttonId) { + final def = toolbarButtonRegistryById[buttonId]; + return def?.isPrimaryAvailable?.call(scope, ref) ?? true; + }, + ), + [configs, scope], + ); + + if (resolvedButtons.isEmpty) { + return const SizedBox.shrink(); + } + + final isVertical = axis == Axis.vertical; + + final buttons = resolvedButtons + .map((button) => _buildButton(scope, context, ref, button)) + .toList(); + + // Bound each button to the bar's cross-axis extent and scale down (never + // clip) so a taller [ToolbarButton] can't overflow the switcher bar. + final fittedButtons = [ + for (final button in buttons) + SizedBox( + height: isVertical ? null : _switcherExtent, + width: isVertical ? _switcherExtent : null, + child: FittedBox(fit: BoxFit.scaleDown, child: button), + ), + ]; + + // Scroll along the bar axis: the host caps the cluster's main-axis extent, + // and anything beyond that scrolls instead of stealing the chips' space. + return SizedBox( + height: isVertical ? null : _switcherExtent, + width: isVertical ? _switcherExtent : null, + child: SingleChildScrollView( + scrollDirection: axis, + child: isVertical + ? Column(mainAxisSize: MainAxisSize.min, children: fittedButtons) + : Row(mainAxisSize: MainAxisSize.min, children: fittedButtons), + ), + ); + } + + Widget _buildButton( + ContextualToolbarScope scope, + BuildContext context, + WidgetRef ref, + ContextualToolbarButtonResolution button, + ) { + final def = toolbarButtonRegistryById[button.buttonId]; + if (def == null) return const SizedBox.shrink(); + + final child = def.builder(scope, context, ref); + + if (button.isEnabled) { + return child; + } + + return Opacity(opacity: 0.38, child: IgnorePointer(child: child)); + } +} diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index 4c096d3a..306a20cd 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -36,8 +36,10 @@ import 'package:weblibre/features/geckoview/features/browser/domain/entities/she import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_id.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/quick_switcher_button_row.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/utils/close_tab_helper.dart'; @@ -188,7 +190,8 @@ class BrowserSideRail extends ConsumerWidget { quickTabSwitcherRowCount: quickTabSwitcherRowCount, isSmallWebMode: isSmallWebMode, enableGestures: true, - hideMainToolbarButtonsDuplicatedInContextualToolbar: showContextualToolbar, + hideMainToolbarButtonsDuplicatedInContextualToolbar: + showContextualToolbar, ); } @@ -303,7 +306,11 @@ class BrowserTabBar extends HookConsumerWidget { // Determine which buttons are actually visible in the contextual toolbar // so we only hide them from the main toolbar when they're genuinely present there. final contextualConfigs = ref - .watch(effectiveToolbarButtonConfigsProvider) + .watch( + effectiveToolbarButtonConfigsProvider( + ToolbarConfigLocation.contextual, + ), + ) .value; final tabsCountInContextual = @@ -449,55 +456,69 @@ class BrowserTabBar extends HookConsumerWidget { if (showMainToolbarNavigationButton) NavigationMenuButton(selectedTabId: selectedTabId), ], - quickTabSwitcher: switch (stackingMode) { - TabBarStackingMode.disabled => const SizedBox.shrink(), - TabBarStackingMode.lastUsedTabs => QuickTabSwitcher( - quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs, - axis: switcherAxis, - ), - TabBarStackingMode.containerTabs => QuickTabSwitcher( - quickTabSwitcherMode: QuickTabSwitcherMode.containerTabs, - axis: switcherAxis, - ), - TabBarStackingMode.accordion => AccordionQuickTabSwitcher( - axis: switcherAxis, - ), - // History fallback only on the MRU row, so empty-state history - // chips don't show twice. On the rail the two rows stack as two - // equal-height vertical lists. - TabBarStackingMode.twoLevel => - isVertical - ? Column( - children: [ - Expanded( - child: QuickTabSwitcher( + quickTabSwitcher: _wrapQuickTabSwitcherWithButtonRow( + axis: switcherAxis, + // The button row lives on the switcher bar; when stacking is disabled + // the whole bar is hidden anyway, but skip building it for clarity. + buttonRow: stackingMode == TabBarStackingMode.disabled + ? null + : QuickSwitcherButtonRow( + selectedTabId: selectedTabId, + displayedSheet: displayedSheet, + axis: switcherAxis, + ), + child: switch (stackingMode) { + TabBarStackingMode.disabled => const SizedBox.shrink(), + TabBarStackingMode.lastUsedTabs => QuickTabSwitcher( + quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs, + axis: switcherAxis, + ), + TabBarStackingMode.containerTabs => QuickTabSwitcher( + quickTabSwitcherMode: QuickTabSwitcherMode.containerTabs, + axis: switcherAxis, + ), + TabBarStackingMode.accordion => AccordionQuickTabSwitcher( + axis: switcherAxis, + ), + // History fallback only on the MRU row, so empty-state history + // chips don't show twice. On the rail the two rows stack as two + // equal-height vertical lists. + TabBarStackingMode.twoLevel => + isVertical + ? Column( + children: [ + Expanded( + child: QuickTabSwitcher( + quickTabSwitcherMode: + QuickTabSwitcherMode.containerTabs, + enableHistoryFallback: false, + axis: switcherAxis, + ), + ), + Expanded( + child: QuickTabSwitcher( + quickTabSwitcherMode: + QuickTabSwitcherMode.lastUsedTabs, + axis: switcherAxis, + ), + ), + ], + ) + : const Column( + mainAxisSize: MainAxisSize.min, + children: [ + QuickTabSwitcher( quickTabSwitcherMode: QuickTabSwitcherMode.containerTabs, enableHistoryFallback: false, - axis: switcherAxis, ), - ), - Expanded( - child: QuickTabSwitcher( + QuickTabSwitcher( quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs, - axis: switcherAxis, ), - ), - ], - ) - : const Column( - mainAxisSize: MainAxisSize.min, - children: [ - QuickTabSwitcher( - quickTabSwitcherMode: QuickTabSwitcherMode.containerTabs, - enableHistoryFallback: false, - ), - QuickTabSwitcher( - quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs, - ), - ], - ), - }, + ], + ), + }, + ), contextualToolbar: ContextualToolbar( selectedTabId: selectedTabId, displayedSheet: displayedSheet, @@ -707,6 +728,58 @@ class BrowserTabBarView extends StatelessWidget { } } +/// Pins [buttonRow] to the trailing end of the quick tab switcher bar (right of +/// the horizontal bar / bottom of the side rail) while [child] (the scrollable +/// chips) fills the remaining space. The cluster is capped to a fraction of the +/// bar so it can never starve the chips; [QuickSwitcherButtonRow] scrolls any +/// overflow beyond that cap. [buttonRow] collapses to nothing when no buttons +/// are enabled, so the default state is unchanged. +Widget _wrapQuickTabSwitcherWithButtonRow({ + required Axis axis, + required Widget? buttonRow, + required Widget child, +}) { + if (buttonRow == null) { + return child; + } + + // Never let the button cluster take more than this share of the bar; the tab + // chips keep the rest. + const maxClusterFraction = 0.6; + + return LayoutBuilder( + builder: (context, constraints) { + if (axis == Axis.vertical) { + final maxExtent = constraints.maxHeight.isFinite + ? constraints.maxHeight * maxClusterFraction + : double.infinity; + return Column( + children: [ + Expanded(child: child), + ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxExtent), + child: buttonRow, + ), + ], + ); + } + + final maxExtent = constraints.maxWidth.isFinite + ? constraints.maxWidth * maxClusterFraction + : double.infinity; + return Row( + children: [ + Expanded(child: child), + ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxExtent), + child: buttonRow, + ), + ], + ); + }, + ); +} + class QuickTabSwitcher extends HookConsumerWidget { final QuickTabSwitcherMode quickTabSwitcherMode; @@ -1043,7 +1116,9 @@ class QuickTabSwitcherView extends StatelessWidget { // collapse, since the toolbar height already accounts for both rows. // On the rail the cross-axis width is fixed and the (vertical) list // fills the available height. - return _isVertical ? const SizedBox(width: 48) : const SizedBox(height: 48); + return _isVertical + ? const SizedBox(width: 48) + : const SizedBox(height: 48); } return Padding( diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart index 76f5927f..ce7de63a 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabDataRepositoryProvider } } -String _$tabDataRepositoryHash() => r'5192e4bb2a373bdef4474fb781eaacc64cc35609'; +String _$tabDataRepositoryHash() => r'cb923fb0723271cd64d66a720dc3e1aed2d86c71'; abstract class _$TabDataRepository extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart b/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart index d1b3d2c7..3a562f77 100644 --- a/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart +++ b/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart @@ -23,7 +23,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart'; -import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart'; +import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_fallback_choice.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart'; @@ -33,12 +34,22 @@ import 'package:weblibre/features/settings/presentation/widgets/settings_detail. import 'package:weblibre/features/user/data/database/definitions.drift.dart'; class ContextualToolbarSettingsScreen extends HookConsumerWidget { - const ContextualToolbarSettingsScreen({super.key}); + const ContextualToolbarSettingsScreen({ + super.key, + this.location = ToolbarConfigLocation.contextual, + this.title = 'Customize Toolbar', + }); + + /// Which independently-configured toolbar this screen edits. + final ToolbarConfigLocation location; + + /// App bar title, so the quick switcher variant reads distinctly. + final String title; @override Widget build(BuildContext context, WidgetRef ref) { - final configs = ref.watch(effectiveToolbarButtonConfigsProvider); - final repository = ref.watch(contextualToolbarConfigRepositoryProvider); + final configs = ref.watch(effectiveToolbarButtonConfigsProvider(location)); + final repository = ref.watch(toolbarConfigRepositoryProvider(location)); final search = useSettingsSearch(); final query = search.normalizedQuery; @@ -69,7 +80,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { ); return SettingsCustomScrollScaffold( - title: 'Customize Toolbar', + title: title, searchController: search.controller, searchHintText: 'Search toolbar buttons', actions: [ @@ -87,7 +98,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { menuChildren: [ MenuItemButton( leadingIcon: const Icon(Icons.restore), - onPressed: () => _resetToDefaults(ref), + onPressed: () => _resetToDefaults(ref, location), child: const Text('Reset to Defaults'), ), ], @@ -97,7 +108,10 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { const SliverToBoxAdapter(child: SizedBox(height: 16)), SliverPersistentHeader( pinned: true, - delegate: _ToolbarPreviewDelegate(configs: configs.value), + delegate: _ToolbarPreviewDelegate( + configs: configs.value, + location: location, + ), ), const SliverPadding( padding: EdgeInsets.fromLTRB(16, 20, 16, 0), @@ -193,7 +207,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { List configs, int oldIndex, int newIndex, - ContextualToolbarConfigRepository repository, { + ToolbarButtonConfigRepository repository, { required bool isVisible, }) { if (oldIndex == newIndex) return; @@ -212,7 +226,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { } Future _reorderViaDb( - ContextualToolbarConfigRepository repository, + ToolbarButtonConfigRepository repository, List configs, int oldIndex, int targetIndex, @@ -248,9 +262,12 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget { await repository.assignOrderKey(movedId, orderKey: orderKey); } - Future _resetToDefaults(WidgetRef ref) async { - final repository = ref.read(contextualToolbarConfigRepositoryProvider); - await repository.replaceAll(defaultToolbarButtonConfigs.value); + Future _resetToDefaults( + WidgetRef ref, + ToolbarConfigLocation location, + ) async { + final repository = ref.read(toolbarConfigRepositoryProvider(location)); + await repository.replaceAll(defaultToolbarButtonConfigsFor(location).value); } } @@ -284,7 +301,7 @@ class _ToolbarButtonConfigTile extends HookConsumerWidget { final int index; final ToolbarButtonConfig config; - final ContextualToolbarConfigRepository repository; + final ToolbarButtonConfigRepository repository; @override Widget build(BuildContext context, WidgetRef ref) { @@ -511,9 +528,13 @@ class _FallbackPicker extends StatelessWidget { } class _ToolbarPreviewDelegate extends SliverPersistentHeaderDelegate { - const _ToolbarPreviewDelegate({required this.configs}); + const _ToolbarPreviewDelegate({ + required this.configs, + required this.location, + }); final List configs; + final ToolbarConfigLocation location; static const _previewHeight = BrowserTabBar.contextualToolabarHeight; @@ -532,19 +553,21 @@ class _ToolbarPreviewDelegate extends SliverPersistentHeaderDelegate { height: maxExtent, child: ColoredBox( color: Theme.of(context).colorScheme.surfaceContainer, - child: _ToolbarPreview(configs: configs), + child: _ToolbarPreview(configs: configs, location: location), ), ); } @override - bool shouldRebuild(_ToolbarPreviewDelegate old) => old.configs != configs; + bool shouldRebuild(_ToolbarPreviewDelegate old) => + old.configs != configs || old.location != location; } class _ToolbarPreview extends ConsumerWidget { - const _ToolbarPreview({required this.configs}); + const _ToolbarPreview({required this.configs, required this.location}); final List configs; + final ToolbarConfigLocation location; @override Widget build(BuildContext context, WidgetRef ref) { @@ -555,6 +578,7 @@ class _ToolbarPreview extends ConsumerWidget { displayedSheet: null, tabState: null, isPreview: true, + location: location, ); final buttons = visibleConfigs.map((config) { diff --git a/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_layout_content.dart b/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_layout_content.dart index 3e4a5227..868a1aa1 100644 --- a/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_layout_content.dart +++ b/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_layout_content.dart @@ -92,6 +92,12 @@ const List toolbarLayoutSettingsSections = [ ], child: _TabBarStackingModeSection(), ), + SettingsEntryDefinition( + title: 'Customize Switcher Buttons', + subtitle: 'Choose which action buttons appear at the end of the bar', + keywords: ['buttons', 'new tab', 'actions', 'trailing'], + child: _CustomizeQuickSwitcherButtonsTile(), + ), SettingsEntryDefinition( title: 'Close Buttons on All Tabs', subtitle: 'Show a close button on every switcher chip', @@ -328,6 +334,33 @@ class _CustomizeToolbarButtonsTile extends HookConsumerWidget { } } +class _CustomizeQuickSwitcherButtonsTile extends HookConsumerWidget { + const _CustomizeQuickSwitcherButtonsTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final switcherEnabled = ref.watch( + generalSettingsWithDefaultsProvider.select( + (s) => s.effectiveTabBarStackingMode() != TabBarStackingMode.disabled, + ), + ); + + return ListTile( + leading: const Icon(Icons.tune), + title: const Text('Customize Switcher Buttons'), + subtitle: const Text( + 'Action buttons pinned at the end of the switcher bar (independent of ' + 'the contextual toolbar)', + ), + trailing: const Icon(Icons.chevron_right), + enabled: switcherEnabled, + onTap: () async { + await const QuickSwitcherToolbarSettingsRoute().push(context); + }, + ); + } +} + class _TabBarStackingModeSection extends HookConsumerWidget { const _TabBarStackingModeSection(); diff --git a/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.dart b/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.dart new file mode 100644 index 00000000..e3dc135d --- /dev/null +++ b/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.dart @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import 'package:drift/drift.dart'; +import 'package:weblibre/features/user/data/database/daos/quick_switcher_button_config.drift.dart'; +import 'package:weblibre/features/user/data/database/database.dart'; +import 'package:weblibre/features/user/data/database/definitions.drift.dart'; + +/// Mirror of [ToolbarButtonConfigDao] against the separate +/// `quick_switcher_button_configs` table. See that DAO for the rationale behind +/// each visibility-partitioned order-key operation. +@DriftAccessor() +class QuickSwitcherButtonConfigDao extends DatabaseAccessor + with $QuickSwitcherButtonConfigDaoMixin { + QuickSwitcherButtonConfigDao(super.attachedDatabase); + + Selectable selectAll() => + db.quickSwitcherButtonConfigs.select() + ..orderBy([(t) => OrderingTerm.asc(t.orderKey)]); + + Stream> watchAll() => selectAll().watch(); + + Future> getAll() => selectAll().get(); + + Future upsert(QuickSwitcherButtonConfig config) => + into(db.quickSwitcherButtonConfigs).insertOnConflictUpdate(config); + + Future assignOrderKey(String buttonId, {required String orderKey}) => + (update( + db.quickSwitcherButtonConfigs, + )..where((t) => t.buttonId.equals(buttonId))).write( + QuickSwitcherButtonConfigsCompanion(orderKey: Value(orderKey)), + ); + + Future assignVisibility(String buttonId, {required bool visible}) => + transaction(() async { + // Land the toggled button at the trailing edge of its *new* section so + // its order_key always stays inside the visibility partition. + final orderKey = await generateTrailingOrderKey( + isVisible: visible, + ).getSingle(); + + await (update( + db.quickSwitcherButtonConfigs, + )..where((t) => t.buttonId.equals(buttonId))).write( + QuickSwitcherButtonConfigsCompanion( + isVisible: Value(visible), + orderKey: Value(orderKey), + ), + ); + }); + + Future assignFallback(String buttonId, String? fallbackId) => + (update( + db.quickSwitcherButtonConfigs, + )..where((t) => t.buttonId.equals(buttonId))).write( + QuickSwitcherButtonConfigsCompanion(fallbackId: Value(fallbackId)), + ); + + Future replaceAll(List configs) => + transaction(() async { + await delete(db.quickSwitcherButtonConfigs).go(); + await _insertWithDeferredFallbacks(configs); + }); + + Future seedMissing( + List<({String buttonId, bool defaultVisible, String? defaultFallback})> + defaults, + ) async { + final existing = await getAll(); + final existingIds = {for (final r in existing) r.buttonId}; + + final missing = defaults + .where((d) => !existingIds.contains(d.buttonId)) + .toList(); + if (missing.isEmpty) return; + + await transaction(() async { + final inserted = []; + for (final def in missing) { + final orderKey = await generateTrailingOrderKey( + isVisible: def.defaultVisible, + ).getSingle(); + inserted.add( + QuickSwitcherButtonConfig( + buttonId: def.buttonId, + orderKey: orderKey, + isVisible: def.defaultVisible, + fallbackId: def.defaultFallback, + ), + ); + await into(db.quickSwitcherButtonConfigs).insert( + QuickSwitcherButtonConfig( + buttonId: def.buttonId, + orderKey: orderKey, + isVisible: def.defaultVisible, + ), + ); + } + + await _assignFallbacks(inserted); + }); + } + + SingleSelectable generateLeadingOrderKey({ + int bucket = 0, + required bool isVisible, + }) => db.definitionsDrift.quickSwitcherLeadingOrderKey( + bucket: bucket, + isVisible: isVisible, + ); + + SingleSelectable generateTrailingOrderKey({ + int bucket = 0, + required bool isVisible, + }) => db.definitionsDrift.quickSwitcherTrailingOrderKey( + bucket: bucket, + isVisible: isVisible, + ); + + SingleOrNullSelectable generateOrderKeyAfterButtonId( + String buttonId, { + required bool isVisible, + }) => db.definitionsDrift.quickSwitcherOrderKeyAfterButton( + buttonId: buttonId, + isVisible: isVisible, + ); + + SingleSelectable generateOrderKeyBeforeButtonId( + String buttonId, { + required bool isVisible, + }) => db.definitionsDrift.quickSwitcherOrderKeyBeforeButton( + buttonId: buttonId, + isVisible: isVisible, + ); + + Future _insertWithDeferredFallbacks( + List configs, + ) async { + for (final config in configs) { + await into(db.quickSwitcherButtonConfigs).insert( + QuickSwitcherButtonConfig( + buttonId: config.buttonId, + orderKey: config.orderKey, + isVisible: config.isVisible, + ), + ); + } + + await _assignFallbacks(configs); + } + + Future _assignFallbacks(List configs) async { + for (final config in configs) { + if (config.fallbackId == null) continue; + await assignFallback(config.buttonId, config.fallbackId); + } + } +} diff --git a/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.drift.dart b/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.drift.dart new file mode 100644 index 00000000..ef4256fd --- /dev/null +++ b/apps/weblibre/lib/features/user/data/database/daos/quick_switcher_button_config.drift.dart @@ -0,0 +1,15 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:weblibre/features/user/data/database/database.dart' as i1; + +mixin $QuickSwitcherButtonConfigDaoMixin + on i0.DatabaseAccessor { + QuickSwitcherButtonConfigDaoManager get managers => + QuickSwitcherButtonConfigDaoManager(this); +} + +class QuickSwitcherButtonConfigDaoManager { + final $QuickSwitcherButtonConfigDaoMixin _db; + QuickSwitcherButtonConfigDaoManager(this._db); +} diff --git a/apps/weblibre/lib/features/user/data/database/database.dart b/apps/weblibre/lib/features/user/data/database/database.dart index 81d8009c..ad51667b 100644 --- a/apps/weblibre/lib/features/user/data/database/database.dart +++ b/apps/weblibre/lib/features/user/data/database/database.dart @@ -24,6 +24,7 @@ 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/proxy_profile.dart'; +import 'package:weblibre/features/user/data/database/daos/quick_switcher_button_config.dart'; import 'package:weblibre/features/user/data/database/daos/search_tokens.dart'; import 'package:weblibre/features/user/data/database/daos/setting.dart'; import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.dart'; @@ -37,13 +38,14 @@ import 'package:weblibre/features/user/data/database/database.steps.dart'; CacheDao, OnboardingDao, ToolbarButtonConfigDao, + QuickSwitcherButtonConfigDao, SearchTokensDao, ProxyProfileDao, ], ) class UserDatabase extends $UserDatabase { @override - final int schemaVersion = 8; + final int schemaVersion = 9; @override MigrationStrategy get migration => MigrationStrategy( @@ -121,5 +123,9 @@ class UserDatabase extends $UserDatabase { 'DROP TABLE IF EXISTS proxy_routing_setting', ); }, + from8To9: (m, schema) async { + await m.createTable(schema.quickSwitcherButtonConfigs); + await m.createIndex(schema.idxQuickSwitcherOrderKey); + }, ); } diff --git a/apps/weblibre/lib/features/user/data/database/database.drift.dart b/apps/weblibre/lib/features/user/data/database/database.drift.dart index 80313043..20865482 100644 --- a/apps/weblibre/lib/features/user/data/database/database.drift.dart +++ b/apps/weblibre/lib/features/user/data/database/database.drift.dart @@ -10,12 +10,14 @@ import 'package:weblibre/features/user/data/database/daos/onboarding.dart' as i5; import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.dart' as i6; -import 'package:weblibre/features/user/data/database/daos/search_tokens.dart' +import 'package:weblibre/features/user/data/database/daos/quick_switcher_button_config.dart' as i7; -import 'package:weblibre/features/user/data/database/daos/proxy_profile.dart' +import 'package:weblibre/features/user/data/database/daos/search_tokens.dart' as i8; -import 'package:drift/internal/modular.dart' as i9; -import 'package:sqlite3/common.dart' as i10; +import 'package:weblibre/features/user/data/database/daos/proxy_profile.dart' + as i9; +import 'package:drift/internal/modular.dart' as i10; +import 'package:sqlite3/common.dart' as i11; abstract class $UserDatabase extends i0.GeneratedDatabase { $UserDatabase(i0.QueryExecutor e) : super(e); @@ -27,6 +29,8 @@ abstract class $UserDatabase extends i0.GeneratedDatabase { late final i1.Riverpod riverpod = i1.Riverpod(this); late final i1.ToolbarButtonConfigs toolbarButtonConfigs = i1.ToolbarButtonConfigs(this); + late final i1.QuickSwitcherButtonConfigs quickSwitcherButtonConfigs = + i1.QuickSwitcherButtonConfigs(this); late final i1.SearchTokens searchTokens = i1.SearchTokens(this); late final i2.SettingDao settingDao = i2.SettingDao(this as i3.UserDatabase); late final i4.CacheDao cacheDao = i4.CacheDao(this as i3.UserDatabase); @@ -35,13 +39,15 @@ abstract class $UserDatabase extends i0.GeneratedDatabase { ); late final i6.ToolbarButtonConfigDao toolbarButtonConfigDao = i6.ToolbarButtonConfigDao(this as i3.UserDatabase); - late final i7.SearchTokensDao searchTokensDao = i7.SearchTokensDao( + late final i7.QuickSwitcherButtonConfigDao quickSwitcherButtonConfigDao = + i7.QuickSwitcherButtonConfigDao(this as i3.UserDatabase); + late final i8.SearchTokensDao searchTokensDao = i8.SearchTokensDao( this as i3.UserDatabase, ); - late final i8.ProxyProfileDao proxyProfileDao = i8.ProxyProfileDao( + late final i9.ProxyProfileDao proxyProfileDao = i9.ProxyProfileDao( this as i3.UserDatabase, ); - i1.DefinitionsDrift get definitionsDrift => i9.ReadDatabaseContainer( + i1.DefinitionsDrift get definitionsDrift => i10.ReadDatabaseContainer( this, ).accessor(i1.DefinitionsDrift.new); @override @@ -57,6 +63,8 @@ abstract class $UserDatabase extends i0.GeneratedDatabase { riverpod, toolbarButtonConfigs, i1.idxToolbarOrderKey, + quickSwitcherButtonConfigs, + i1.idxQuickSwitcherOrderKey, searchTokens, i1.idxSearchTokensInsertedAt, i1.idxSearchTokensReservedAt, @@ -78,11 +86,16 @@ class $UserDatabaseManager { i1.$RiverpodTableManager(_db, _db.riverpod); i1.$ToolbarButtonConfigsTableManager get toolbarButtonConfigs => i1.$ToolbarButtonConfigsTableManager(_db, _db.toolbarButtonConfigs); + i1.$QuickSwitcherButtonConfigsTableManager get quickSwitcherButtonConfigs => + i1.$QuickSwitcherButtonConfigsTableManager( + _db, + _db.quickSwitcherButtonConfigs, + ); i1.$SearchTokensTableManager get searchTokens => i1.$SearchTokensTableManager(_db, _db.searchTokens); } -extension DefineFunctions on i10.CommonDatabase { +extension DefineFunctions on i11.CommonDatabase { void defineFunctions({ required String Function(int, String?) lexoRankNext, required String Function(int, String?) lexoRankPrevious, @@ -96,7 +109,7 @@ extension DefineFunctions on i10.CommonDatabase { }) { createFunction( functionName: 'lexo_rank_next', - argumentCount: const i10.AllowedArgumentCount(2), + argumentCount: const i11.AllowedArgumentCount(2), function: (args) { final arg0 = args[0] as int; final arg1 = args[1] as String?; @@ -105,7 +118,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'lexo_rank_previous', - argumentCount: const i10.AllowedArgumentCount(2), + argumentCount: const i11.AllowedArgumentCount(2), function: (args) { final arg0 = args[0] as int; final arg1 = args[1] as String?; @@ -114,7 +127,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'lexo_rank_reorder_after', - argumentCount: const i10.AllowedArgumentCount(2), + argumentCount: const i11.AllowedArgumentCount(2), function: (args) { final arg0 = args[0] as String?; final arg1 = args[1] as String?; @@ -123,7 +136,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'lexo_rank_reorder_before', - argumentCount: const i10.AllowedArgumentCount(2), + argumentCount: const i11.AllowedArgumentCount(2), function: (args) { final arg0 = args[0] as String?; final arg1 = args[1] as String?; @@ -132,14 +145,14 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'generate_content_hash', - argumentCount: const i10.AllowedArgumentCount(0), + argumentCount: const i11.AllowedArgumentCount(0), function: (args) { return generateContentHash(); }, ); createFunction( functionName: 'url_indexable', - argumentCount: const i10.AllowedArgumentCount(1), + argumentCount: const i11.AllowedArgumentCount(1), function: (args) { final arg0 = args[0] as String?; return urlIndexable(arg0); @@ -147,7 +160,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'url_canonical', - argumentCount: const i10.AllowedArgumentCount(1), + argumentCount: const i11.AllowedArgumentCount(1), function: (args) { final arg0 = args[0] as String?; return urlCanonical(arg0); @@ -155,7 +168,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'url_host', - argumentCount: const i10.AllowedArgumentCount(1), + argumentCount: const i11.AllowedArgumentCount(1), function: (args) { final arg0 = args[0] as String?; return urlHost(arg0); @@ -163,7 +176,7 @@ extension DefineFunctions on i10.CommonDatabase { ); createFunction( functionName: 'url_path', - argumentCount: const i10.AllowedArgumentCount(1), + argumentCount: const i11.AllowedArgumentCount(1), function: (args) { final arg0 = args[0] as String?; return urlPath(arg0); diff --git a/apps/weblibre/lib/features/user/data/database/database.steps.dart b/apps/weblibre/lib/features/user/data/database/database.steps.dart index d70e8cca..f0ff3945 100644 --- a/apps/weblibre/lib/features/user/data/database/database.steps.dart +++ b/apps/weblibre/lib/features/user/data/database/database.steps.dart @@ -1085,6 +1085,161 @@ final class Schema8 extends i0.VersionedSchema { ); } +final class Schema9 extends i0.VersionedSchema { + Schema9({required super.database}) : super(version: 9); + @override + late final List entities = [ + setting, + proxyProfile, + idxProxyProfileUpdatedAt, + iconCache, + onboarding, + riverpod, + toolbarButtonConfigs, + idxToolbarOrderKey, + quickSwitcherButtonConfigs, + idxQuickSwitcherOrderKey, + searchTokens, + idxSearchTokensInsertedAt, + idxSearchTokensReservedAt, + ]; + late final Shape0 setting = Shape0( + source: i0.VersionedTable( + entityName: 'setting', + withoutRowId: false, + isStrict: true, + tableConstraints: [], + columns: [_column_0, _column_1, _column_2], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 proxyProfile = Shape9( + source: i0.VersionedTable( + entityName: 'proxy_profile', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_20, + _column_21, + _column_22, + _column_23, + _column_30, + _column_24, + _column_25, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxProxyProfileUpdatedAt = i1.Index( + 'idx_proxy_profile_updated_at', + 'CREATE INDEX idx_proxy_profile_updated_at ON proxy_profile (updated_at)', + ); + late final Shape1 iconCache = Shape1( + source: i0.VersionedTable( + entityName: 'icon_cache', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_3, _column_4, _column_5], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 onboarding = Shape2( + source: i0.VersionedTable( + entityName: 'onboarding', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_6, _column_7], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 riverpod = Shape3( + source: i0.VersionedTable( + entityName: 'riverpod', + withoutRowId: true, + isStrict: false, + tableConstraints: [], + columns: [_column_0, _column_8, _column_9, _column_10], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 toolbarButtonConfigs = Shape4( + source: i0.VersionedTable( + entityName: 'toolbar_button_configs', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_11, _column_12, _column_13, _column_14], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxToolbarOrderKey = i1.Index( + 'idx_toolbar_order_key', + 'CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs (order_key)', + ); + late final Shape4 quickSwitcherButtonConfigs = Shape4( + source: i0.VersionedTable( + entityName: 'quick_switcher_button_configs', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_11, _column_12, _column_31, _column_32], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxQuickSwitcherOrderKey = i1.Index( + 'idx_quick_switcher_order_key', + 'CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs (order_key)', + ); + late final Shape6 searchTokens = Shape6( + source: i0.VersionedTable( + entityName: 'search_tokens', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_15, _column_16, _column_17, _column_18, _column_19], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxSearchTokensInsertedAt = i1.Index( + 'idx_search_tokens_inserted_at', + 'CREATE INDEX idx_search_tokens_inserted_at ON search_tokens (inserted_at)', + ); + final i1.Index idxSearchTokensReservedAt = i1.Index( + 'idx_search_tokens_reserved_at', + 'CREATE INDEX idx_search_tokens_reserved_at ON search_tokens (reserved_at)', + ); +} + +i1.GeneratedColumn _column_31(String aliasedName) => + i1.GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i1.CustomExpression('FALSE'), + ); +i1.GeneratedColumn _column_32( + String aliasedName, +) => i1.GeneratedColumn( + 'fallback_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: + 'REFERENCES quick_switcher_button_configs(button_id)ON DELETE SET NULL', +); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -1093,6 +1248,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema6 schema) from5To6, required Future Function(i1.Migrator m, Schema7 schema) from6To7, required Future Function(i1.Migrator m, Schema8 schema) from7To8, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -1131,6 +1287,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from7To8(migrator, schema); return 8; + case 8: + final schema = Schema9(database: database); + final migrator = i1.Migrator(database, schema); + await from8To9(migrator, schema); + return 9; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -1145,6 +1306,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema6 schema) from5To6, required Future Function(i1.Migrator m, Schema7 schema) from6To7, required Future Function(i1.Migrator m, Schema8 schema) from7To8, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -1154,5 +1316,6 @@ i1.OnUpgrade stepByStep({ from5To6: from5To6, from6To7: from6To7, from7To8: from7To8, + from8To9: from8To9, ), ); diff --git a/apps/weblibre/lib/features/user/data/database/definitions.drift b/apps/weblibre/lib/features/user/data/database/definitions.drift index 12badf24..886cde06 100644 --- a/apps/weblibre/lib/features/user/data/database/definitions.drift +++ b/apps/weblibre/lib/features/user/data/database/definitions.drift @@ -45,6 +45,18 @@ CREATE TABLE toolbar_button_configs ( CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs(order_key); +-- Independently-configured button set for the quick tab switcher's trailing +-- cluster. Same shape as toolbar_button_configs but a separate table so the two +-- toolbars keep fully isolated ordering/visibility/fallbacks. +CREATE TABLE quick_switcher_button_configs ( + button_id TEXT NOT NULL PRIMARY KEY, + order_key TEXT NOT NULL, + is_visible BOOLEAN NOT NULL DEFAULT FALSE, + fallback_id TEXT REFERENCES quick_switcher_button_configs(button_id) ON DELETE SET NULL +); + +CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs(order_key); + CREATE TABLE search_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, token BLOB NOT NULL, @@ -111,6 +123,58 @@ toolbarOrderKeyBeforeButton(:button_id AS TEXT, :is_visible AS BOOL): FROM ordered_table WHERE button_id = :button_id; +-- Quick-switcher variants of the order-key helpers above, scoped to the +-- quick_switcher_button_configs table (see the toolbar* queries for rationale). +quickSwitcherLeadingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL): + SELECT lexo_rank_previous( + :bucket, + ( + SELECT order_key + FROM quick_switcher_button_configs + WHERE is_visible = :is_visible + ORDER BY order_key + LIMIT 1 + ) + ); + +quickSwitcherTrailingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL): + SELECT lexo_rank_next( + :bucket, + ( + SELECT order_key + FROM quick_switcher_button_configs + WHERE is_visible = :is_visible + ORDER BY order_key DESC + LIMIT 1 + ) + ); + +quickSwitcherOrderKeyAfterButton(:button_id AS TEXT, :is_visible AS BOOL): + WITH ordered_table AS ( + SELECT + button_id, + order_key, + LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key + FROM quick_switcher_button_configs + WHERE is_visible = :is_visible + ) + SELECT lexo_rank_reorder_after(order_key, next_order_key) + FROM ordered_table + WHERE button_id = :button_id; + +quickSwitcherOrderKeyBeforeButton(:button_id AS TEXT, :is_visible AS BOOL): + WITH ordered_table AS ( + SELECT + button_id, + order_key, + LAG(order_key) OVER (ORDER BY order_key) AS prev_order_key + FROM quick_switcher_button_configs + WHERE is_visible = :is_visible + ) + SELECT lexo_rank_reorder_before(order_key, prev_order_key) + FROM ordered_table + WHERE button_id = :button_id; + evictCacheEntries: DELETE FROM icon_cache WHERE rowid IN ( diff --git a/apps/weblibre/lib/features/user/data/database/definitions.drift.dart b/apps/weblibre/lib/features/user/data/database/definitions.drift.dart index 7f175c76..ea3f149f 100644 --- a/apps/weblibre/lib/features/user/data/database/definitions.drift.dart +++ b/apps/weblibre/lib/features/user/data/database/definitions.drift.dart @@ -1123,6 +1123,208 @@ typedef $ToolbarButtonConfigsProcessedTableManager = i1.ToolbarButtonConfig, i0.PrefetchHooks Function() >; +typedef $QuickSwitcherButtonConfigsCreateCompanionBuilder = + i1.QuickSwitcherButtonConfigsCompanion Function({ + required String buttonId, + required String orderKey, + i0.Value isVisible, + i0.Value fallbackId, + i0.Value rowid, + }); +typedef $QuickSwitcherButtonConfigsUpdateCompanionBuilder = + i1.QuickSwitcherButtonConfigsCompanion Function({ + i0.Value buttonId, + i0.Value orderKey, + i0.Value isVisible, + i0.Value fallbackId, + i0.Value rowid, + }); + +class $QuickSwitcherButtonConfigsFilterComposer + extends i0.Composer { + $QuickSwitcherButtonConfigsFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get buttonId => $composableBuilder( + column: $table.buttonId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get orderKey => $composableBuilder( + column: $table.orderKey, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isVisible => $composableBuilder( + column: $table.isVisible, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get fallbackId => $composableBuilder( + column: $table.fallbackId, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $QuickSwitcherButtonConfigsOrderingComposer + extends i0.Composer { + $QuickSwitcherButtonConfigsOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get buttonId => $composableBuilder( + column: $table.buttonId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get orderKey => $composableBuilder( + column: $table.orderKey, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isVisible => $composableBuilder( + column: $table.isVisible, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get fallbackId => $composableBuilder( + column: $table.fallbackId, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $QuickSwitcherButtonConfigsAnnotationComposer + extends i0.Composer { + $QuickSwitcherButtonConfigsAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get buttonId => + $composableBuilder(column: $table.buttonId, builder: (column) => column); + + i0.GeneratedColumn get orderKey => + $composableBuilder(column: $table.orderKey, builder: (column) => column); + + i0.GeneratedColumn get isVisible => + $composableBuilder(column: $table.isVisible, builder: (column) => column); + + i0.GeneratedColumn get fallbackId => $composableBuilder( + column: $table.fallbackId, + builder: (column) => column, + ); +} + +class $QuickSwitcherButtonConfigsTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.QuickSwitcherButtonConfigs, + i1.QuickSwitcherButtonConfig, + i1.$QuickSwitcherButtonConfigsFilterComposer, + i1.$QuickSwitcherButtonConfigsOrderingComposer, + i1.$QuickSwitcherButtonConfigsAnnotationComposer, + $QuickSwitcherButtonConfigsCreateCompanionBuilder, + $QuickSwitcherButtonConfigsUpdateCompanionBuilder, + ( + i1.QuickSwitcherButtonConfig, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.QuickSwitcherButtonConfigs, + i1.QuickSwitcherButtonConfig + >, + ), + i1.QuickSwitcherButtonConfig, + i0.PrefetchHooks Function() + > { + $QuickSwitcherButtonConfigsTableManager( + i0.GeneratedDatabase db, + i1.QuickSwitcherButtonConfigs table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$QuickSwitcherButtonConfigsFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$QuickSwitcherButtonConfigsOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$QuickSwitcherButtonConfigsAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value buttonId = const i0.Value.absent(), + i0.Value orderKey = const i0.Value.absent(), + i0.Value isVisible = const i0.Value.absent(), + i0.Value fallbackId = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => i1.QuickSwitcherButtonConfigsCompanion( + buttonId: buttonId, + orderKey: orderKey, + isVisible: isVisible, + fallbackId: fallbackId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String buttonId, + required String orderKey, + i0.Value isVisible = const i0.Value.absent(), + i0.Value fallbackId = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => i1.QuickSwitcherButtonConfigsCompanion.insert( + buttonId: buttonId, + orderKey: orderKey, + isVisible: isVisible, + fallbackId: fallbackId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $QuickSwitcherButtonConfigsProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.QuickSwitcherButtonConfigs, + i1.QuickSwitcherButtonConfig, + i1.$QuickSwitcherButtonConfigsFilterComposer, + i1.$QuickSwitcherButtonConfigsOrderingComposer, + i1.$QuickSwitcherButtonConfigsAnnotationComposer, + $QuickSwitcherButtonConfigsCreateCompanionBuilder, + $QuickSwitcherButtonConfigsUpdateCompanionBuilder, + ( + i1.QuickSwitcherButtonConfig, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.QuickSwitcherButtonConfigs, + i1.QuickSwitcherButtonConfig + >, + ), + i1.QuickSwitcherButtonConfig, + i0.PrefetchHooks Function() + >; typedef $SearchTokensCreateCompanionBuilder = i1.SearchTokensCompanion Function({ i0.Value id, @@ -2902,6 +3104,283 @@ i0.Index get idxToolbarOrderKey => i0.Index( 'CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs (order_key)', ); +class QuickSwitcherButtonConfigs extends i0.Table + with + i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + QuickSwitcherButtonConfigs(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn buttonId = i0.GeneratedColumn( + 'button_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY', + ); + late final i0.GeneratedColumn orderKey = i0.GeneratedColumn( + 'order_key', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final i0.GeneratedColumn isVisible = i0.GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE'), + ); + late final i0.GeneratedColumn fallbackId = i0.GeneratedColumn( + 'fallback_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'REFERENCES quick_switcher_button_configs(button_id)ON DELETE SET NULL', + ); + @override + List get $columns => [ + buttonId, + orderKey, + isVisible, + fallbackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'quick_switcher_button_configs'; + @override + Set get $primaryKey => {buttonId}; + @override + i1.QuickSwitcherButtonConfig map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.QuickSwitcherButtonConfig( + buttonId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}button_id'], + )!, + orderKey: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}order_key'], + )!, + isVisible: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_visible'], + )!, + fallbackId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}fallback_id'], + ), + ); + } + + @override + QuickSwitcherButtonConfigs createAlias(String alias) { + return QuickSwitcherButtonConfigs(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class QuickSwitcherButtonConfig extends i0.DataClass + implements i0.Insertable { + final String buttonId; + final String orderKey; + final bool isVisible; + final String? fallbackId; + const QuickSwitcherButtonConfig({ + required this.buttonId, + required this.orderKey, + required this.isVisible, + this.fallbackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['button_id'] = i0.Variable(buttonId); + map['order_key'] = i0.Variable(orderKey); + map['is_visible'] = i0.Variable(isVisible); + if (!nullToAbsent || fallbackId != null) { + map['fallback_id'] = i0.Variable(fallbackId); + } + return map; + } + + factory QuickSwitcherButtonConfig.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return QuickSwitcherButtonConfig( + buttonId: serializer.fromJson(json['button_id']), + orderKey: serializer.fromJson(json['order_key']), + isVisible: serializer.fromJson(json['is_visible']), + fallbackId: serializer.fromJson(json['fallback_id']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'button_id': serializer.toJson(buttonId), + 'order_key': serializer.toJson(orderKey), + 'is_visible': serializer.toJson(isVisible), + 'fallback_id': serializer.toJson(fallbackId), + }; + } + + i1.QuickSwitcherButtonConfig copyWith({ + String? buttonId, + String? orderKey, + bool? isVisible, + i0.Value fallbackId = const i0.Value.absent(), + }) => i1.QuickSwitcherButtonConfig( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId.present ? fallbackId.value : this.fallbackId, + ); + QuickSwitcherButtonConfig copyWithCompanion( + i1.QuickSwitcherButtonConfigsCompanion data, + ) { + return QuickSwitcherButtonConfig( + buttonId: data.buttonId.present ? data.buttonId.value : this.buttonId, + orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + fallbackId: data.fallbackId.present + ? data.fallbackId.value + : this.fallbackId, + ); + } + + @override + String toString() { + return (StringBuffer('QuickSwitcherButtonConfig(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(buttonId, orderKey, isVisible, fallbackId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.QuickSwitcherButtonConfig && + other.buttonId == this.buttonId && + other.orderKey == this.orderKey && + other.isVisible == this.isVisible && + other.fallbackId == this.fallbackId); +} + +class QuickSwitcherButtonConfigsCompanion + extends i0.UpdateCompanion { + final i0.Value buttonId; + final i0.Value orderKey; + final i0.Value isVisible; + final i0.Value fallbackId; + final i0.Value rowid; + const QuickSwitcherButtonConfigsCompanion({ + this.buttonId = const i0.Value.absent(), + this.orderKey = const i0.Value.absent(), + this.isVisible = const i0.Value.absent(), + this.fallbackId = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + QuickSwitcherButtonConfigsCompanion.insert({ + required String buttonId, + required String orderKey, + this.isVisible = const i0.Value.absent(), + this.fallbackId = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : buttonId = i0.Value(buttonId), + orderKey = i0.Value(orderKey); + static i0.Insertable custom({ + i0.Expression? buttonId, + i0.Expression? orderKey, + i0.Expression? isVisible, + i0.Expression? fallbackId, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (buttonId != null) 'button_id': buttonId, + if (orderKey != null) 'order_key': orderKey, + if (isVisible != null) 'is_visible': isVisible, + if (fallbackId != null) 'fallback_id': fallbackId, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.QuickSwitcherButtonConfigsCompanion copyWith({ + i0.Value? buttonId, + i0.Value? orderKey, + i0.Value? isVisible, + i0.Value? fallbackId, + i0.Value? rowid, + }) { + return i1.QuickSwitcherButtonConfigsCompanion( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId ?? this.fallbackId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (buttonId.present) { + map['button_id'] = i0.Variable(buttonId.value); + } + if (orderKey.present) { + map['order_key'] = i0.Variable(orderKey.value); + } + if (isVisible.present) { + map['is_visible'] = i0.Variable(isVisible.value); + } + if (fallbackId.present) { + map['fallback_id'] = i0.Variable(fallbackId.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('QuickSwitcherButtonConfigsCompanion(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +i0.Index get idxQuickSwitcherOrderKey => i0.Index( + 'idx_quick_switcher_order_key', + 'CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs (order_key)', +); + class SearchTokens extends i0.Table with i0.TableInfo { @override @@ -3259,6 +3738,50 @@ class DefinitionsDrift extends i4.ModularAccessor { ).map((i0.QueryRow row) => row.read('_c0')); } + i0.Selectable quickSwitcherLeadingOrderKey({ + required int bucket, + required bool isVisible, + }) { + return customSelect( + 'SELECT lexo_rank_previous(?1, (SELECT order_key FROM quick_switcher_button_configs WHERE is_visible = ?2 ORDER BY order_key LIMIT 1)) AS _c0', + variables: [i0.Variable(bucket), i0.Variable(isVisible)], + readsFrom: {quickSwitcherButtonConfigs}, + ).map((i0.QueryRow row) => row.read('_c0')); + } + + i0.Selectable quickSwitcherTrailingOrderKey({ + required int bucket, + required bool isVisible, + }) { + return customSelect( + 'SELECT lexo_rank_next(?1, (SELECT order_key FROM quick_switcher_button_configs WHERE is_visible = ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0', + variables: [i0.Variable(bucket), i0.Variable(isVisible)], + readsFrom: {quickSwitcherButtonConfigs}, + ).map((i0.QueryRow row) => row.read('_c0')); + } + + i0.Selectable quickSwitcherOrderKeyAfterButton({ + required bool isVisible, + required String buttonId, + }) { + return customSelect( + 'WITH ordered_table AS (SELECT button_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM quick_switcher_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2', + variables: [i0.Variable(isVisible), i0.Variable(buttonId)], + readsFrom: {quickSwitcherButtonConfigs}, + ).map((i0.QueryRow row) => row.read('_c0')); + } + + i0.Selectable quickSwitcherOrderKeyBeforeButton({ + required bool isVisible, + required String buttonId, + }) { + return customSelect( + 'WITH ordered_table AS (SELECT button_id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM quick_switcher_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2', + variables: [i0.Variable(isVisible), i0.Variable(buttonId)], + readsFrom: {quickSwitcherButtonConfigs}, + ).map((i0.QueryRow row) => row.read('_c0')); + } + Future evictCacheEntries({required int limit}) { return customUpdate( 'DELETE FROM icon_cache WHERE "rowid" IN (SELECT "rowid" FROM icon_cache ORDER BY fetch_date DESC LIMIT -1 OFFSET ?1)', @@ -3271,6 +3794,12 @@ class DefinitionsDrift extends i4.ModularAccessor { i1.ToolbarButtonConfigs get toolbarButtonConfigs => i4.ReadDatabaseContainer( attachedDatabase, ).resultSet('toolbar_button_configs'); + i1.QuickSwitcherButtonConfigs get quickSwitcherButtonConfigs => + i4.ReadDatabaseContainer( + attachedDatabase, + ).resultSet( + 'quick_switcher_button_configs', + ); i1.IconCache get iconCache => i4.ReadDatabaseContainer( attachedDatabase, ).resultSet('icon_cache'); diff --git a/apps/weblibre/test/drift/user/generated/schema.dart b/apps/weblibre/test/drift/user/generated/schema.dart index f76d308b..3f1b7760 100644 --- a/apps/weblibre/test/drift/user/generated/schema.dart +++ b/apps/weblibre/test/drift/user/generated/schema.dart @@ -12,6 +12,7 @@ import 'schema_v5.dart' as v5; import 'schema_v6.dart' as v6; import 'schema_v7.dart' as v7; import 'schema_v8.dart' as v8; +import 'schema_v9.dart' as v9; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -33,10 +34,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v7.DatabaseAtV7(db); case 8: return v8.DatabaseAtV8(db); + case 9: + return v9.DatabaseAtV9(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7, 8]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9]; } diff --git a/apps/weblibre/test/drift/user/generated/schema_v9.dart b/apps/weblibre/test/drift/user/generated/schema_v9.dart new file mode 100644 index 00000000..aab028e5 --- /dev/null +++ b/apps/weblibre/test/drift/user/generated/schema_v9.dart @@ -0,0 +1,2160 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class Setting extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Setting(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn partitionKey = GeneratedColumn( + 'partition_key', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + true, + type: DriftSqlType.any, + requiredDuringInsert: false, + $customConstraints: '', + ); + @override + List get $columns => [key, partitionKey, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'setting'; + @override + Set get $primaryKey => {key}; + @override + SettingData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + partitionKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}partition_key'], + ), + value: attachedDatabase.typeMapping.read( + DriftSqlType.any, + data['${effectivePrefix}value'], + ), + ); + } + + @override + Setting createAlias(String alias) { + return Setting(attachedDatabase, alias); + } + + @override + bool get isStrict => true; + @override + bool get dontWriteConstraints => true; +} + +class SettingData extends DataClass implements Insertable { + final String key; + final String? partitionKey; + final DriftAny? value; + const SettingData({required this.key, this.partitionKey, this.value}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + if (!nullToAbsent || partitionKey != null) { + map['partition_key'] = Variable(partitionKey); + } + if (!nullToAbsent || value != null) { + map['value'] = Variable(value); + } + return map; + } + + factory SettingData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingData( + key: serializer.fromJson(json['key']), + partitionKey: serializer.fromJson(json['partitionKey']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'partitionKey': serializer.toJson(partitionKey), + 'value': serializer.toJson(value), + }; + } + + SettingData copyWith({ + String? key, + Value partitionKey = const Value.absent(), + Value value = const Value.absent(), + }) => SettingData( + key: key ?? this.key, + partitionKey: partitionKey.present ? partitionKey.value : this.partitionKey, + value: value.present ? value.value : this.value, + ); + SettingData copyWithCompanion(SettingCompanion data) { + return SettingData( + key: data.key.present ? data.key.value : this.key, + partitionKey: data.partitionKey.present + ? data.partitionKey.value + : this.partitionKey, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('SettingData(') + ..write('key: $key, ') + ..write('partitionKey: $partitionKey, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, partitionKey, value); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingData && + other.key == this.key && + other.partitionKey == this.partitionKey && + other.value == this.value); +} + +class SettingCompanion extends UpdateCompanion { + final Value key; + final Value partitionKey; + final Value value; + final Value rowid; + const SettingCompanion({ + this.key = const Value.absent(), + this.partitionKey = const Value.absent(), + this.value = const Value.absent(), + this.rowid = const Value.absent(), + }); + SettingCompanion.insert({ + required String key, + this.partitionKey = const Value.absent(), + this.value = const Value.absent(), + this.rowid = const Value.absent(), + }) : key = Value(key); + static Insertable custom({ + Expression? key, + Expression? partitionKey, + Expression? value, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (partitionKey != null) 'partition_key': partitionKey, + if (value != null) 'value': value, + if (rowid != null) 'rowid': rowid, + }); + } + + SettingCompanion copyWith({ + Value? key, + Value? partitionKey, + Value? value, + Value? rowid, + }) { + return SettingCompanion( + key: key ?? this.key, + partitionKey: partitionKey ?? this.partitionKey, + value: value ?? this.value, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (partitionKey.present) { + map['partition_key'] = Variable(partitionKey.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingCompanion(') + ..write('key: $key, ') + ..write('partitionKey: $partitionKey, ') + ..write('value: $value, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class ProxyProfile extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ProxyProfile(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn configJson = GeneratedColumn( + 'config_json', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn dnsOverrideJson = GeneratedColumn( + 'dns_override_json', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + type, + configJson, + dnsOverrideJson, + createdAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'proxy_profile'; + @override + Set get $primaryKey => {id}; + @override + ProxyProfileData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProxyProfileData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + configJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}config_json'], + )!, + dnsOverrideJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns_override_json'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + ProxyProfile createAlias(String alias) { + return ProxyProfile(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class ProxyProfileData extends DataClass + implements Insertable { + final String id; + final String name; + final String type; + final String configJson; + final String? dnsOverrideJson; + final int createdAt; + final int updatedAt; + const ProxyProfileData({ + required this.id, + required this.name, + required this.type, + required this.configJson, + this.dnsOverrideJson, + required this.createdAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['type'] = Variable(type); + map['config_json'] = Variable(configJson); + if (!nullToAbsent || dnsOverrideJson != null) { + map['dns_override_json'] = Variable(dnsOverrideJson); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory ProxyProfileData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProxyProfileData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + configJson: serializer.fromJson(json['configJson']), + dnsOverrideJson: serializer.fromJson(json['dnsOverrideJson']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'configJson': serializer.toJson(configJson), + 'dnsOverrideJson': serializer.toJson(dnsOverrideJson), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ProxyProfileData copyWith({ + String? id, + String? name, + String? type, + String? configJson, + Value dnsOverrideJson = const Value.absent(), + int? createdAt, + int? updatedAt, + }) => ProxyProfileData( + id: id ?? this.id, + name: name ?? this.name, + type: type ?? this.type, + configJson: configJson ?? this.configJson, + dnsOverrideJson: dnsOverrideJson.present + ? dnsOverrideJson.value + : this.dnsOverrideJson, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + ProxyProfileData copyWithCompanion(ProxyProfileCompanion data) { + return ProxyProfileData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + configJson: data.configJson.present + ? data.configJson.value + : this.configJson, + dnsOverrideJson: data.dnsOverrideJson.present + ? data.dnsOverrideJson.value + : this.dnsOverrideJson, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('ProxyProfileData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('configJson: $configJson, ') + ..write('dnsOverrideJson: $dnsOverrideJson, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + type, + configJson, + dnsOverrideJson, + createdAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProxyProfileData && + other.id == this.id && + other.name == this.name && + other.type == this.type && + other.configJson == this.configJson && + other.dnsOverrideJson == this.dnsOverrideJson && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class ProxyProfileCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value type; + final Value configJson; + final Value dnsOverrideJson; + final Value createdAt; + final Value updatedAt; + final Value rowid; + const ProxyProfileCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.type = const Value.absent(), + this.configJson = const Value.absent(), + this.dnsOverrideJson = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProxyProfileCompanion.insert({ + required String id, + required String name, + required String type, + required String configJson, + this.dnsOverrideJson = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + type = Value(type), + configJson = Value(configJson); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? type, + Expression? configJson, + Expression? dnsOverrideJson, + Expression? createdAt, + Expression? updatedAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (type != null) 'type': type, + if (configJson != null) 'config_json': configJson, + if (dnsOverrideJson != null) 'dns_override_json': dnsOverrideJson, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ProxyProfileCompanion copyWith({ + Value? id, + Value? name, + Value? type, + Value? configJson, + Value? dnsOverrideJson, + Value? createdAt, + Value? updatedAt, + Value? rowid, + }) { + return ProxyProfileCompanion( + id: id ?? this.id, + name: name ?? this.name, + type: type ?? this.type, + configJson: configJson ?? this.configJson, + dnsOverrideJson: dnsOverrideJson ?? this.dnsOverrideJson, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (configJson.present) { + map['config_json'] = Variable(configJson.value); + } + if (dnsOverrideJson.present) { + map['dns_override_json'] = Variable(dnsOverrideJson.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProxyProfileCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('configJson: $configJson, ') + ..write('dnsOverrideJson: $dnsOverrideJson, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class IconCache extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + IconCache(this.attachedDatabase, [this._alias]); + late final GeneratedColumn origin = GeneratedColumn( + 'origin', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn iconData = + GeneratedColumn( + 'icon_data', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn fetchDate = GeneratedColumn( + 'fetch_date', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [origin, iconData, fetchDate]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'icon_cache'; + @override + Set get $primaryKey => {origin}; + @override + IconCacheData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return IconCacheData( + origin: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}origin'], + )!, + iconData: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}icon_data'], + )!, + fetchDate: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}fetch_date'], + )!, + ); + } + + @override + IconCache createAlias(String alias) { + return IconCache(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class IconCacheData extends DataClass implements Insertable { + final String origin; + final i2.Uint8List iconData; + final int fetchDate; + const IconCacheData({ + required this.origin, + required this.iconData, + required this.fetchDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['origin'] = Variable(origin); + map['icon_data'] = Variable(iconData); + map['fetch_date'] = Variable(fetchDate); + return map; + } + + factory IconCacheData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return IconCacheData( + origin: serializer.fromJson(json['origin']), + iconData: serializer.fromJson(json['iconData']), + fetchDate: serializer.fromJson(json['fetchDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'origin': serializer.toJson(origin), + 'iconData': serializer.toJson(iconData), + 'fetchDate': serializer.toJson(fetchDate), + }; + } + + IconCacheData copyWith({ + String? origin, + i2.Uint8List? iconData, + int? fetchDate, + }) => IconCacheData( + origin: origin ?? this.origin, + iconData: iconData ?? this.iconData, + fetchDate: fetchDate ?? this.fetchDate, + ); + IconCacheData copyWithCompanion(IconCacheCompanion data) { + return IconCacheData( + origin: data.origin.present ? data.origin.value : this.origin, + iconData: data.iconData.present ? data.iconData.value : this.iconData, + fetchDate: data.fetchDate.present ? data.fetchDate.value : this.fetchDate, + ); + } + + @override + String toString() { + return (StringBuffer('IconCacheData(') + ..write('origin: $origin, ') + ..write('iconData: $iconData, ') + ..write('fetchDate: $fetchDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(origin, $driftBlobEquality.hash(iconData), fetchDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is IconCacheData && + other.origin == this.origin && + $driftBlobEquality.equals(other.iconData, this.iconData) && + other.fetchDate == this.fetchDate); +} + +class IconCacheCompanion extends UpdateCompanion { + final Value origin; + final Value iconData; + final Value fetchDate; + final Value rowid; + const IconCacheCompanion({ + this.origin = const Value.absent(), + this.iconData = const Value.absent(), + this.fetchDate = const Value.absent(), + this.rowid = const Value.absent(), + }); + IconCacheCompanion.insert({ + required String origin, + required i2.Uint8List iconData, + required int fetchDate, + this.rowid = const Value.absent(), + }) : origin = Value(origin), + iconData = Value(iconData), + fetchDate = Value(fetchDate); + static Insertable custom({ + Expression? origin, + Expression? iconData, + Expression? fetchDate, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (origin != null) 'origin': origin, + if (iconData != null) 'icon_data': iconData, + if (fetchDate != null) 'fetch_date': fetchDate, + if (rowid != null) 'rowid': rowid, + }); + } + + IconCacheCompanion copyWith({ + Value? origin, + Value? iconData, + Value? fetchDate, + Value? rowid, + }) { + return IconCacheCompanion( + origin: origin ?? this.origin, + iconData: iconData ?? this.iconData, + fetchDate: fetchDate ?? this.fetchDate, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (origin.present) { + map['origin'] = Variable(origin.value); + } + if (iconData.present) { + map['icon_data'] = Variable(iconData.value); + } + if (fetchDate.present) { + map['fetch_date'] = Variable(fetchDate.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('IconCacheCompanion(') + ..write('origin: $origin, ') + ..write('iconData: $iconData, ') + ..write('fetchDate: $fetchDate, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class Onboarding extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Onboarding(this.attachedDatabase, [this._alias]); + late final GeneratedColumn revision = GeneratedColumn( + 'revision', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn completionDate = GeneratedColumn( + 'completion_date', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [revision, completionDate]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'onboarding'; + @override + Set get $primaryKey => const {}; + @override + OnboardingData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return OnboardingData( + revision: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}revision'], + )!, + completionDate: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}completion_date'], + )!, + ); + } + + @override + Onboarding createAlias(String alias) { + return Onboarding(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class OnboardingData extends DataClass implements Insertable { + final int revision; + final int completionDate; + const OnboardingData({required this.revision, required this.completionDate}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['revision'] = Variable(revision); + map['completion_date'] = Variable(completionDate); + return map; + } + + factory OnboardingData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return OnboardingData( + revision: serializer.fromJson(json['revision']), + completionDate: serializer.fromJson(json['completionDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'revision': serializer.toJson(revision), + 'completionDate': serializer.toJson(completionDate), + }; + } + + OnboardingData copyWith({int? revision, int? completionDate}) => + OnboardingData( + revision: revision ?? this.revision, + completionDate: completionDate ?? this.completionDate, + ); + OnboardingData copyWithCompanion(OnboardingCompanion data) { + return OnboardingData( + revision: data.revision.present ? data.revision.value : this.revision, + completionDate: data.completionDate.present + ? data.completionDate.value + : this.completionDate, + ); + } + + @override + String toString() { + return (StringBuffer('OnboardingData(') + ..write('revision: $revision, ') + ..write('completionDate: $completionDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(revision, completionDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is OnboardingData && + other.revision == this.revision && + other.completionDate == this.completionDate); +} + +class OnboardingCompanion extends UpdateCompanion { + final Value revision; + final Value completionDate; + final Value rowid; + const OnboardingCompanion({ + this.revision = const Value.absent(), + this.completionDate = const Value.absent(), + this.rowid = const Value.absent(), + }); + OnboardingCompanion.insert({ + required int revision, + required int completionDate, + this.rowid = const Value.absent(), + }) : revision = Value(revision), + completionDate = Value(completionDate); + static Insertable custom({ + Expression? revision, + Expression? completionDate, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (revision != null) 'revision': revision, + if (completionDate != null) 'completion_date': completionDate, + if (rowid != null) 'rowid': rowid, + }); + } + + OnboardingCompanion copyWith({ + Value? revision, + Value? completionDate, + Value? rowid, + }) { + return OnboardingCompanion( + revision: revision ?? this.revision, + completionDate: completionDate ?? this.completionDate, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (completionDate.present) { + map['completion_date'] = Variable(completionDate.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('OnboardingCompanion(') + ..write('revision: $revision, ') + ..write('completionDate: $completionDate, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class Riverpod extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Riverpod(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn json = GeneratedColumn( + 'json', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn expireAt = GeneratedColumn( + 'expireAt', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn destroyKey = GeneratedColumn( + 'destroyKey', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + @override + List get $columns => [key, json, expireAt, destroyKey]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'riverpod'; + @override + Set get $primaryKey => {key}; + @override + RiverpodData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RiverpodData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + json: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}json'], + )!, + expireAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}expireAt'], + ), + destroyKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}destroyKey'], + ), + ); + } + + @override + Riverpod createAlias(String alias) { + return Riverpod(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get dontWriteConstraints => true; +} + +class RiverpodData extends DataClass implements Insertable { + final String key; + final String json; + final int? expireAt; + final String? destroyKey; + const RiverpodData({ + required this.key, + required this.json, + this.expireAt, + this.destroyKey, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['json'] = Variable(json); + if (!nullToAbsent || expireAt != null) { + map['expireAt'] = Variable(expireAt); + } + if (!nullToAbsent || destroyKey != null) { + map['destroyKey'] = Variable(destroyKey); + } + return map; + } + + factory RiverpodData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RiverpodData( + key: serializer.fromJson(json['key']), + json: serializer.fromJson(json['json']), + expireAt: serializer.fromJson(json['expireAt']), + destroyKey: serializer.fromJson(json['destroyKey']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'json': serializer.toJson(json), + 'expireAt': serializer.toJson(expireAt), + 'destroyKey': serializer.toJson(destroyKey), + }; + } + + RiverpodData copyWith({ + String? key, + String? json, + Value expireAt = const Value.absent(), + Value destroyKey = const Value.absent(), + }) => RiverpodData( + key: key ?? this.key, + json: json ?? this.json, + expireAt: expireAt.present ? expireAt.value : this.expireAt, + destroyKey: destroyKey.present ? destroyKey.value : this.destroyKey, + ); + RiverpodData copyWithCompanion(RiverpodCompanion data) { + return RiverpodData( + key: data.key.present ? data.key.value : this.key, + json: data.json.present ? data.json.value : this.json, + expireAt: data.expireAt.present ? data.expireAt.value : this.expireAt, + destroyKey: data.destroyKey.present + ? data.destroyKey.value + : this.destroyKey, + ); + } + + @override + String toString() { + return (StringBuffer('RiverpodData(') + ..write('key: $key, ') + ..write('json: $json, ') + ..write('expireAt: $expireAt, ') + ..write('destroyKey: $destroyKey') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, json, expireAt, destroyKey); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RiverpodData && + other.key == this.key && + other.json == this.json && + other.expireAt == this.expireAt && + other.destroyKey == this.destroyKey); +} + +class RiverpodCompanion extends UpdateCompanion { + final Value key; + final Value json; + final Value expireAt; + final Value destroyKey; + const RiverpodCompanion({ + this.key = const Value.absent(), + this.json = const Value.absent(), + this.expireAt = const Value.absent(), + this.destroyKey = const Value.absent(), + }); + RiverpodCompanion.insert({ + required String key, + required String json, + this.expireAt = const Value.absent(), + this.destroyKey = const Value.absent(), + }) : key = Value(key), + json = Value(json); + static Insertable custom({ + Expression? key, + Expression? json, + Expression? expireAt, + Expression? destroyKey, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (json != null) 'json': json, + if (expireAt != null) 'expireAt': expireAt, + if (destroyKey != null) 'destroyKey': destroyKey, + }); + } + + RiverpodCompanion copyWith({ + Value? key, + Value? json, + Value? expireAt, + Value? destroyKey, + }) { + return RiverpodCompanion( + key: key ?? this.key, + json: json ?? this.json, + expireAt: expireAt ?? this.expireAt, + destroyKey: destroyKey ?? this.destroyKey, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (json.present) { + map['json'] = Variable(json.value); + } + if (expireAt.present) { + map['expireAt'] = Variable(expireAt.value); + } + if (destroyKey.present) { + map['destroyKey'] = Variable(destroyKey.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RiverpodCompanion(') + ..write('key: $key, ') + ..write('json: $json, ') + ..write('expireAt: $expireAt, ') + ..write('destroyKey: $destroyKey') + ..write(')')) + .toString(); + } +} + +class ToolbarButtonConfigs extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ToolbarButtonConfigs(this.attachedDatabase, [this._alias]); + late final GeneratedColumn buttonId = GeneratedColumn( + 'button_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY', + ); + late final GeneratedColumn orderKey = GeneratedColumn( + 'order_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT TRUE', + defaultValue: const CustomExpression('TRUE'), + ); + late final GeneratedColumn fallbackId = GeneratedColumn( + 'fallback_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'REFERENCES toolbar_button_configs(button_id)ON DELETE SET NULL', + ); + @override + List get $columns => [ + buttonId, + orderKey, + isVisible, + fallbackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'toolbar_button_configs'; + @override + Set get $primaryKey => {buttonId}; + @override + ToolbarButtonConfigsData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ToolbarButtonConfigsData( + buttonId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}button_id'], + )!, + orderKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}order_key'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + fallbackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}fallback_id'], + ), + ); + } + + @override + ToolbarButtonConfigs createAlias(String alias) { + return ToolbarButtonConfigs(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class ToolbarButtonConfigsData extends DataClass + implements Insertable { + final String buttonId; + final String orderKey; + final int isVisible; + final String? fallbackId; + const ToolbarButtonConfigsData({ + required this.buttonId, + required this.orderKey, + required this.isVisible, + this.fallbackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['button_id'] = Variable(buttonId); + map['order_key'] = Variable(orderKey); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || fallbackId != null) { + map['fallback_id'] = Variable(fallbackId); + } + return map; + } + + factory ToolbarButtonConfigsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ToolbarButtonConfigsData( + buttonId: serializer.fromJson(json['buttonId']), + orderKey: serializer.fromJson(json['orderKey']), + isVisible: serializer.fromJson(json['isVisible']), + fallbackId: serializer.fromJson(json['fallbackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'buttonId': serializer.toJson(buttonId), + 'orderKey': serializer.toJson(orderKey), + 'isVisible': serializer.toJson(isVisible), + 'fallbackId': serializer.toJson(fallbackId), + }; + } + + ToolbarButtonConfigsData copyWith({ + String? buttonId, + String? orderKey, + int? isVisible, + Value fallbackId = const Value.absent(), + }) => ToolbarButtonConfigsData( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId.present ? fallbackId.value : this.fallbackId, + ); + ToolbarButtonConfigsData copyWithCompanion( + ToolbarButtonConfigsCompanion data, + ) { + return ToolbarButtonConfigsData( + buttonId: data.buttonId.present ? data.buttonId.value : this.buttonId, + orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + fallbackId: data.fallbackId.present + ? data.fallbackId.value + : this.fallbackId, + ); + } + + @override + String toString() { + return (StringBuffer('ToolbarButtonConfigsData(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(buttonId, orderKey, isVisible, fallbackId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ToolbarButtonConfigsData && + other.buttonId == this.buttonId && + other.orderKey == this.orderKey && + other.isVisible == this.isVisible && + other.fallbackId == this.fallbackId); +} + +class ToolbarButtonConfigsCompanion + extends UpdateCompanion { + final Value buttonId; + final Value orderKey; + final Value isVisible; + final Value fallbackId; + final Value rowid; + const ToolbarButtonConfigsCompanion({ + this.buttonId = const Value.absent(), + this.orderKey = const Value.absent(), + this.isVisible = const Value.absent(), + this.fallbackId = const Value.absent(), + this.rowid = const Value.absent(), + }); + ToolbarButtonConfigsCompanion.insert({ + required String buttonId, + required String orderKey, + this.isVisible = const Value.absent(), + this.fallbackId = const Value.absent(), + this.rowid = const Value.absent(), + }) : buttonId = Value(buttonId), + orderKey = Value(orderKey); + static Insertable custom({ + Expression? buttonId, + Expression? orderKey, + Expression? isVisible, + Expression? fallbackId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (buttonId != null) 'button_id': buttonId, + if (orderKey != null) 'order_key': orderKey, + if (isVisible != null) 'is_visible': isVisible, + if (fallbackId != null) 'fallback_id': fallbackId, + if (rowid != null) 'rowid': rowid, + }); + } + + ToolbarButtonConfigsCompanion copyWith({ + Value? buttonId, + Value? orderKey, + Value? isVisible, + Value? fallbackId, + Value? rowid, + }) { + return ToolbarButtonConfigsCompanion( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId ?? this.fallbackId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (buttonId.present) { + map['button_id'] = Variable(buttonId.value); + } + if (orderKey.present) { + map['order_key'] = Variable(orderKey.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (fallbackId.present) { + map['fallback_id'] = Variable(fallbackId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ToolbarButtonConfigsCompanion(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class QuickSwitcherButtonConfigs extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + QuickSwitcherButtonConfigs(this.attachedDatabase, [this._alias]); + late final GeneratedColumn buttonId = GeneratedColumn( + 'button_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY', + ); + late final GeneratedColumn orderKey = GeneratedColumn( + 'order_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const CustomExpression('FALSE'), + ); + late final GeneratedColumn fallbackId = GeneratedColumn( + 'fallback_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'REFERENCES quick_switcher_button_configs(button_id)ON DELETE SET NULL', + ); + @override + List get $columns => [ + buttonId, + orderKey, + isVisible, + fallbackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'quick_switcher_button_configs'; + @override + Set get $primaryKey => {buttonId}; + @override + QuickSwitcherButtonConfigsData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return QuickSwitcherButtonConfigsData( + buttonId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}button_id'], + )!, + orderKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}order_key'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + fallbackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}fallback_id'], + ), + ); + } + + @override + QuickSwitcherButtonConfigs createAlias(String alias) { + return QuickSwitcherButtonConfigs(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class QuickSwitcherButtonConfigsData extends DataClass + implements Insertable { + final String buttonId; + final String orderKey; + final int isVisible; + final String? fallbackId; + const QuickSwitcherButtonConfigsData({ + required this.buttonId, + required this.orderKey, + required this.isVisible, + this.fallbackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['button_id'] = Variable(buttonId); + map['order_key'] = Variable(orderKey); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || fallbackId != null) { + map['fallback_id'] = Variable(fallbackId); + } + return map; + } + + factory QuickSwitcherButtonConfigsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return QuickSwitcherButtonConfigsData( + buttonId: serializer.fromJson(json['buttonId']), + orderKey: serializer.fromJson(json['orderKey']), + isVisible: serializer.fromJson(json['isVisible']), + fallbackId: serializer.fromJson(json['fallbackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'buttonId': serializer.toJson(buttonId), + 'orderKey': serializer.toJson(orderKey), + 'isVisible': serializer.toJson(isVisible), + 'fallbackId': serializer.toJson(fallbackId), + }; + } + + QuickSwitcherButtonConfigsData copyWith({ + String? buttonId, + String? orderKey, + int? isVisible, + Value fallbackId = const Value.absent(), + }) => QuickSwitcherButtonConfigsData( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId.present ? fallbackId.value : this.fallbackId, + ); + QuickSwitcherButtonConfigsData copyWithCompanion( + QuickSwitcherButtonConfigsCompanion data, + ) { + return QuickSwitcherButtonConfigsData( + buttonId: data.buttonId.present ? data.buttonId.value : this.buttonId, + orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + fallbackId: data.fallbackId.present + ? data.fallbackId.value + : this.fallbackId, + ); + } + + @override + String toString() { + return (StringBuffer('QuickSwitcherButtonConfigsData(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(buttonId, orderKey, isVisible, fallbackId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is QuickSwitcherButtonConfigsData && + other.buttonId == this.buttonId && + other.orderKey == this.orderKey && + other.isVisible == this.isVisible && + other.fallbackId == this.fallbackId); +} + +class QuickSwitcherButtonConfigsCompanion + extends UpdateCompanion { + final Value buttonId; + final Value orderKey; + final Value isVisible; + final Value fallbackId; + final Value rowid; + const QuickSwitcherButtonConfigsCompanion({ + this.buttonId = const Value.absent(), + this.orderKey = const Value.absent(), + this.isVisible = const Value.absent(), + this.fallbackId = const Value.absent(), + this.rowid = const Value.absent(), + }); + QuickSwitcherButtonConfigsCompanion.insert({ + required String buttonId, + required String orderKey, + this.isVisible = const Value.absent(), + this.fallbackId = const Value.absent(), + this.rowid = const Value.absent(), + }) : buttonId = Value(buttonId), + orderKey = Value(orderKey); + static Insertable custom({ + Expression? buttonId, + Expression? orderKey, + Expression? isVisible, + Expression? fallbackId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (buttonId != null) 'button_id': buttonId, + if (orderKey != null) 'order_key': orderKey, + if (isVisible != null) 'is_visible': isVisible, + if (fallbackId != null) 'fallback_id': fallbackId, + if (rowid != null) 'rowid': rowid, + }); + } + + QuickSwitcherButtonConfigsCompanion copyWith({ + Value? buttonId, + Value? orderKey, + Value? isVisible, + Value? fallbackId, + Value? rowid, + }) { + return QuickSwitcherButtonConfigsCompanion( + buttonId: buttonId ?? this.buttonId, + orderKey: orderKey ?? this.orderKey, + isVisible: isVisible ?? this.isVisible, + fallbackId: fallbackId ?? this.fallbackId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (buttonId.present) { + map['button_id'] = Variable(buttonId.value); + } + if (orderKey.present) { + map['order_key'] = Variable(orderKey.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (fallbackId.present) { + map['fallback_id'] = Variable(fallbackId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('QuickSwitcherButtonConfigsCompanion(') + ..write('buttonId: $buttonId, ') + ..write('orderKey: $orderKey, ') + ..write('isVisible: $isVisible, ') + ..write('fallbackId: $fallbackId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SearchTokens extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SearchTokens(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT', + ); + late final GeneratedColumn token = + GeneratedColumn( + 'token', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn insertedAt = GeneratedColumn( + 'inserted_at', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn issuerKeyVersion = GeneratedColumn( + 'issuer_key_version', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn reservedAt = GeneratedColumn( + 'reserved_at', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + @override + List get $columns => [ + id, + token, + insertedAt, + issuerKeyVersion, + reservedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'search_tokens'; + @override + Set get $primaryKey => {id}; + @override + SearchTokensData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SearchTokensData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + token: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}token'], + )!, + insertedAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}inserted_at'], + )!, + issuerKeyVersion: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}issuer_key_version'], + )!, + reservedAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}reserved_at'], + ), + ); + } + + @override + SearchTokens createAlias(String alias) { + return SearchTokens(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class SearchTokensData extends DataClass + implements Insertable { + final int id; + final i2.Uint8List token; + final int insertedAt; + final String issuerKeyVersion; + final int? reservedAt; + const SearchTokensData({ + required this.id, + required this.token, + required this.insertedAt, + required this.issuerKeyVersion, + this.reservedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['token'] = Variable(token); + map['inserted_at'] = Variable(insertedAt); + map['issuer_key_version'] = Variable(issuerKeyVersion); + if (!nullToAbsent || reservedAt != null) { + map['reserved_at'] = Variable(reservedAt); + } + return map; + } + + factory SearchTokensData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SearchTokensData( + id: serializer.fromJson(json['id']), + token: serializer.fromJson(json['token']), + insertedAt: serializer.fromJson(json['insertedAt']), + issuerKeyVersion: serializer.fromJson(json['issuerKeyVersion']), + reservedAt: serializer.fromJson(json['reservedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'token': serializer.toJson(token), + 'insertedAt': serializer.toJson(insertedAt), + 'issuerKeyVersion': serializer.toJson(issuerKeyVersion), + 'reservedAt': serializer.toJson(reservedAt), + }; + } + + SearchTokensData copyWith({ + int? id, + i2.Uint8List? token, + int? insertedAt, + String? issuerKeyVersion, + Value reservedAt = const Value.absent(), + }) => SearchTokensData( + id: id ?? this.id, + token: token ?? this.token, + insertedAt: insertedAt ?? this.insertedAt, + issuerKeyVersion: issuerKeyVersion ?? this.issuerKeyVersion, + reservedAt: reservedAt.present ? reservedAt.value : this.reservedAt, + ); + SearchTokensData copyWithCompanion(SearchTokensCompanion data) { + return SearchTokensData( + id: data.id.present ? data.id.value : this.id, + token: data.token.present ? data.token.value : this.token, + insertedAt: data.insertedAt.present + ? data.insertedAt.value + : this.insertedAt, + issuerKeyVersion: data.issuerKeyVersion.present + ? data.issuerKeyVersion.value + : this.issuerKeyVersion, + reservedAt: data.reservedAt.present + ? data.reservedAt.value + : this.reservedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SearchTokensData(') + ..write('id: $id, ') + ..write('token: $token, ') + ..write('insertedAt: $insertedAt, ') + ..write('issuerKeyVersion: $issuerKeyVersion, ') + ..write('reservedAt: $reservedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + $driftBlobEquality.hash(token), + insertedAt, + issuerKeyVersion, + reservedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SearchTokensData && + other.id == this.id && + $driftBlobEquality.equals(other.token, this.token) && + other.insertedAt == this.insertedAt && + other.issuerKeyVersion == this.issuerKeyVersion && + other.reservedAt == this.reservedAt); +} + +class SearchTokensCompanion extends UpdateCompanion { + final Value id; + final Value token; + final Value insertedAt; + final Value issuerKeyVersion; + final Value reservedAt; + const SearchTokensCompanion({ + this.id = const Value.absent(), + this.token = const Value.absent(), + this.insertedAt = const Value.absent(), + this.issuerKeyVersion = const Value.absent(), + this.reservedAt = const Value.absent(), + }); + SearchTokensCompanion.insert({ + this.id = const Value.absent(), + required i2.Uint8List token, + required int insertedAt, + required String issuerKeyVersion, + this.reservedAt = const Value.absent(), + }) : token = Value(token), + insertedAt = Value(insertedAt), + issuerKeyVersion = Value(issuerKeyVersion); + static Insertable custom({ + Expression? id, + Expression? token, + Expression? insertedAt, + Expression? issuerKeyVersion, + Expression? reservedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (token != null) 'token': token, + if (insertedAt != null) 'inserted_at': insertedAt, + if (issuerKeyVersion != null) 'issuer_key_version': issuerKeyVersion, + if (reservedAt != null) 'reserved_at': reservedAt, + }); + } + + SearchTokensCompanion copyWith({ + Value? id, + Value? token, + Value? insertedAt, + Value? issuerKeyVersion, + Value? reservedAt, + }) { + return SearchTokensCompanion( + id: id ?? this.id, + token: token ?? this.token, + insertedAt: insertedAt ?? this.insertedAt, + issuerKeyVersion: issuerKeyVersion ?? this.issuerKeyVersion, + reservedAt: reservedAt ?? this.reservedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (token.present) { + map['token'] = Variable(token.value); + } + if (insertedAt.present) { + map['inserted_at'] = Variable(insertedAt.value); + } + if (issuerKeyVersion.present) { + map['issuer_key_version'] = Variable(issuerKeyVersion.value); + } + if (reservedAt.present) { + map['reserved_at'] = Variable(reservedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SearchTokensCompanion(') + ..write('id: $id, ') + ..write('token: $token, ') + ..write('insertedAt: $insertedAt, ') + ..write('issuerKeyVersion: $issuerKeyVersion, ') + ..write('reservedAt: $reservedAt') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV9 extends GeneratedDatabase { + DatabaseAtV9(QueryExecutor e) : super(e); + late final Setting setting = Setting(this); + late final ProxyProfile proxyProfile = ProxyProfile(this); + late final Index idxProxyProfileUpdatedAt = Index( + 'idx_proxy_profile_updated_at', + 'CREATE INDEX idx_proxy_profile_updated_at ON proxy_profile (updated_at)', + ); + late final IconCache iconCache = IconCache(this); + late final Onboarding onboarding = Onboarding(this); + late final Riverpod riverpod = Riverpod(this); + late final ToolbarButtonConfigs toolbarButtonConfigs = ToolbarButtonConfigs( + this, + ); + late final Index idxToolbarOrderKey = Index( + 'idx_toolbar_order_key', + 'CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs (order_key)', + ); + late final QuickSwitcherButtonConfigs quickSwitcherButtonConfigs = + QuickSwitcherButtonConfigs(this); + late final Index idxQuickSwitcherOrderKey = Index( + 'idx_quick_switcher_order_key', + 'CREATE INDEX idx_quick_switcher_order_key ON quick_switcher_button_configs (order_key)', + ); + late final SearchTokens searchTokens = SearchTokens(this); + late final Index idxSearchTokensInsertedAt = Index( + 'idx_search_tokens_inserted_at', + 'CREATE INDEX idx_search_tokens_inserted_at ON search_tokens (inserted_at)', + ); + late final Index idxSearchTokensReservedAt = Index( + 'idx_search_tokens_reserved_at', + 'CREATE INDEX idx_search_tokens_reserved_at ON search_tokens (reserved_at)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + setting, + proxyProfile, + idxProxyProfileUpdatedAt, + iconCache, + onboarding, + riverpod, + toolbarButtonConfigs, + idxToolbarOrderKey, + quickSwitcherButtonConfigs, + idxQuickSwitcherOrderKey, + searchTokens, + idxSearchTokensInsertedAt, + idxSearchTokensReservedAt, + ]; + @override + int get schemaVersion => 9; +}