swith to bang composite key

This commit is contained in:
Fabian Freund
2025-09-29 12:51:35 +02:00
parent d215aad3e4
commit 8c05eff173
29 changed files with 433 additions and 700 deletions
@@ -48,8 +48,12 @@ class BangDao extends DatabaseAccessor<BangDatabase> with $BangDaoMixin {
); );
} }
SingleOrNullSelectable<BangData> getBangData(String trigger) { SingleOrNullSelectable<BangData> getBangData(
return select(db.bangDataView)..where((t) => t.trigger.equals(trigger)); BangGroup group,
String trigger,
) {
return select(db.bangDataView)
..where((t) => t.group.equalsValue(group) & t.trigger.equals(trigger));
} }
Selectable<BangData> getBangDataList({ Selectable<BangData> getBangDataList({
@@ -102,10 +106,11 @@ class BangDao extends DatabaseAccessor<BangDatabase> with $BangDaoMixin {
return selectable; return selectable;
} }
Future<int> increaseBangFrequency(String trigger) { Future<int> increaseBangFrequency(BangGroup group, String trigger) {
return db.bangFrequency.insertOne( return db.bangFrequency.insertOne(
BangFrequencyCompanion.insert( BangFrequencyCompanion.insert(
trigger: trigger, trigger: trigger,
group: group,
frequency: 1, frequency: 1,
lastUsed: DateTime.now(), lastUsed: DateTime.now(),
), ),
@@ -130,17 +135,23 @@ class BangDao extends DatabaseAccessor<BangDatabase> with $BangDaoMixin {
} }
} }
Future<int> addSearchEntry(String trigger, String searchQuery) { Future<int> addSearchEntry(
BangGroup group,
String trigger,
String searchQuery,
) {
return db.bangHistory.insertOne( return db.bangHistory.insertOne(
BangHistoryCompanion.insert( BangHistoryCompanion.insert(
searchQuery: searchQuery, searchQuery: searchQuery,
trigger: trigger, trigger: trigger,
group: group,
searchDate: DateTime.now(), searchDate: DateTime.now(),
), ),
onConflict: DoUpdate( onConflict: DoUpdate(
target: [db.bangHistory.searchQuery], target: [db.bangHistory.searchQuery],
(old) => BangHistoryCompanion( (old) => BangHistoryCompanion(
trigger: Value(trigger), trigger: Value(trigger),
group: Value(group),
searchDate: Value(DateTime.now()), searchDate: Value(DateTime.now()),
), ),
), ),
@@ -55,8 +55,9 @@ class SyncDao extends DatabaseAccessor<BangDatabase> with $SyncDaoMixin {
}); });
} }
Future<int> deleteBangs(Iterable<String> triggers) { Future<int> deleteBangs(BangGroup group, Iterable<String> triggers) {
final statement = delete(db.bang)..where((t) => t.trigger.isIn(triggers)); final statement = delete(db.bang)
..where((t) => t.group.equalsValue(group) & t.trigger.isIn(triggers));
return statement.go(); return statement.go();
} }
@@ -89,7 +90,7 @@ class SyncDao extends DatabaseAccessor<BangDatabase> with $SyncDaoMixin {
.map((e) => remoteBangMap[e]!); .map((e) => remoteBangMap[e]!);
await db.transaction(() async { await db.transaction(() async {
await deleteBangs(removedBangs); await deleteBangs(group, removedBangs);
await insertBangs(addedBangs); await insertBangs(addedBangs);
await replaceBangs(changedBangs); await replaceBangs(changedBangs);
await upsertLastSyncOfGroup(group, syncTime); await upsertLastSyncOfGroup(group, syncTime);
@@ -5,14 +5,15 @@ import 'package:weblibre/features/bangs/data/models/bang.dart';
import 'package:weblibre/features/bangs/data/models/search_history_entry.dart'; import 'package:weblibre/features/bangs/data/models/search_history_entry.dart';
CREATE TABLE bang ( CREATE TABLE bang (
"trigger" TEXT PRIMARY KEY NOT NULL, "trigger" TEXT NOT NULL,
"group" ENUM(BangGroup) NOT NULL, "group" ENUM(BangGroup) NOT NULL,
website_name TEXT NOT NULL, website_name TEXT NOT NULL,
domain TEXT NOT NULL, domain TEXT NOT NULL,
url_template TEXT NOT NULL, url_template TEXT NOT NULL,
category TEXT, category TEXT,
sub_category TEXT, sub_category TEXT,
format TEXT MAPPED BY `const BangFormatConverter()` format TEXT MAPPED BY `const BangFormatConverter()`,
PRIMARY KEY ("trigger", "group")
) WITH Bang; ) WITH Bang;
CREATE TABLE bang_sync ( CREATE TABLE bang_sync (
@@ -21,15 +22,20 @@ CREATE TABLE bang_sync (
); );
CREATE TABLE bang_frequency ( CREATE TABLE bang_frequency (
"trigger" TEXT PRIMARY KEY NOT NULL REFERENCES bang ("trigger") ON DELETE CASCADE, "trigger" TEXT NOT NULL,
"group" ENUM(BangGroup) NOT NULL,
frequency INTEGER NOT NULL, frequency INTEGER NOT NULL,
last_used DATETIME NOT NULL last_used DATETIME NOT NULL,
PRIMARY KEY ("trigger", "group"),
FOREIGN KEY ("trigger", "group") REFERENCES bang ("trigger", "group") ON DELETE CASCADE
); );
CREATE TABLE bang_history ( CREATE TABLE bang_history (
search_query TEXT UNIQUE NOT NULL, search_query TEXT UNIQUE NOT NULL,
"trigger" TEXT NOT NULL REFERENCES bang ("trigger"), "trigger" TEXT NOT NULL,
search_date DATETIME NOT NULL "group" ENUM(BangGroup) NOT NULL,
search_date DATETIME NOT NULL,
FOREIGN KEY ("trigger", "group") REFERENCES bang ("trigger", "group")
); );
CREATE VIRTUAL TABLE bang_fts CREATE VIRTUAL TABLE bang_fts
@@ -48,7 +54,7 @@ CREATE VIEW bang_data_view WITH BangData AS
FROM FROM
bang b bang b
LEFT JOIN LEFT JOIN
bang_frequency bf ON b."trigger" = bf."trigger"; bang_frequency bf ON b."trigger" = bf."trigger" AND b."group" = bf."group";
-- Triggers to keep the FTS index up to date. -- Triggers to keep the FTS index up to date.
CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN
@@ -90,7 +96,7 @@ queryBangs WITH BangData:
INNER JOIN INNER JOIN
bang b ON b.rowid = fts.rowid bang b ON b.rowid = fts.rowid
LEFT JOIN LEFT JOIN
bang_frequency bf ON b."trigger" = bf."trigger" bang_frequency bf ON b."trigger" = bf."trigger" AND b."group" = bf."group"
CROSS JOIN CROSS JOIN
weights weights
ORDER BY ORDER BY
@@ -114,7 +120,7 @@ queryBangsBasic WITH BangData:
INNER JOIN INNER JOIN
bang b ON b.rowid = fts.rowid bang b ON b.rowid = fts.rowid
LEFT JOIN LEFT JOIN
bang_frequency bf ON b."trigger" = bf."trigger" bang_frequency bf ON b."trigger" = bf."trigger" AND b."group" = bf."group"
CROSS JOIN CROSS JOIN
weights weights
WHERE WHERE
@@ -160,4 +166,4 @@ evictHistoryEntries:
FROM bang_history FROM bang_history
ORDER BY search_date DESC ORDER BY search_date DESC
LIMIT -1 OFFSET :limit LIMIT -1 OFFSET :limit
); );
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,8 @@
*/ */
import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:weblibre/features/bangs/data/models/bang.dart'; import 'package:weblibre/features/bangs/data/models/bang.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/geckoview/domain/entities/browser_icon.dart'; import 'package:weblibre/features/geckoview/domain/entities/browser_icon.dart';
part 'bang_data.g.dart'; part 'bang_data.g.dart';
@@ -30,6 +32,9 @@ class BangData extends Bang {
final BrowserIcon? icon; final BrowserIcon? icon;
@override
BangGroup get group => super.group!;
BangData({ BangData({
required super.websiteName, required super.websiteName,
required super.domain, required super.domain,
@@ -43,6 +48,8 @@ class BangData extends Bang {
this.icon, this.icon,
}) : frequency = frequency ?? 0; }) : frequency = frequency ?? 0;
BangKey toKey() => BangKey(group: group, trigger: trigger);
@override @override
List<Object?> get hashParameters => [ List<Object?> get hashParameters => [
...super.hashParameters, ...super.hashParameters,
@@ -0,0 +1,52 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
class BangKey {
final String trigger;
final BangGroup group;
const BangKey({required this.group, required this.trigger});
@override
String toString() {
return '${group.name}::$trigger';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BangKey &&
runtimeType == other.runtimeType &&
trigger == other.trigger &&
group == other.group;
@override
int get hashCode => Object.hash(trigger, group);
static BangKey? tryFromString(String key) {
try {
final [group, trigger] = key.split('::');
return BangKey(
group: BangGroup.values.firstWhere((g) => g.name == group),
trigger: trigger,
);
} catch (_) {
return null;
}
}
}
class BangKeyConverter implements JsonConverter<BangKey?, String?> {
const BangKeyConverter();
@override
BangKey? fromJson(String? json) {
return json.mapNotNull((json) => BangKey.tryFromString(json));
}
@override
String? toJson(BangKey? object) {
return object?.toString();
}
}
@@ -35,7 +35,7 @@ class BangDataSourceService extends _$BangDataSourceService {
@override @override
void build() {} void build() {}
Future<Result<List<Bang>>> fetchRemoteBangs(Uri url, BangGroup? group) { Future<Result<List<Bang>>> fetchRemoteBangs(Uri url, BangGroup group) {
return Result.fromAsync(() async { return Result.fromAsync(() async {
return await compute((args) async { return await compute((args) async {
final client = http.Client(); final client = http.Client();
@@ -51,13 +51,8 @@ class BangDataSourceService extends _$BangDataSourceService {
} }
}, [url.toString()]).then( }, [url.toString()]).then(
(json) => json.map((e) { (json) => json.map((e) {
var bang = Bang.fromJson(e as Map<String, dynamic>); final bang = Bang.fromJson(e as Map<String, dynamic>);
return bang.copyWith.group(group);
if (group != null) {
bang = bang.copyWith.group(group);
}
return bang;
}).toList(), }).toList(),
); );
}, exceptionHandler: handleHttpError); }, exceptionHandler: handleHttpError);
@@ -42,7 +42,7 @@ final class BangDataSourceServiceProvider
} }
String _$bangDataSourceServiceHash() => String _$bangDataSourceServiceHash() =>
r'2ff811ece417ae498dacc04a2fa963cffb34c8a9'; r'b1bd96bbd834de0f71af86d7f791d367d0a837be';
abstract class _$BangDataSourceService extends $Notifier<void> { abstract class _$BangDataSourceService extends $Notifier<void> {
void build(); void build();
@@ -21,6 +21,7 @@ import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart'; import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/data/models/search_history_entry.dart'; import 'package:weblibre/features/bangs/data/models/search_history_entry.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/bangs/domain/repositories/sync.dart'; import 'package:weblibre/features/bangs/domain/repositories/sync.dart';
@@ -30,20 +31,20 @@ part 'bangs.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
Stream<BangData?> defaultSearchBangData(Ref ref) { Stream<BangData?> defaultSearchBangData(Ref ref) {
final trigger = ref.watch( final key = ref.watch(
generalSettingsWithDefaultsProvider.select( generalSettingsWithDefaultsProvider.select(
(value) => value.defaultSearchProvider, (value) => value.defaultSearchProvider,
), ),
); );
final repository = ref.watch(bangDataRepositoryProvider.notifier); final repository = ref.watch(bangDataRepositoryProvider.notifier);
return repository.watchBang(trigger); return repository.watchBang(key);
} }
@Riverpod() @Riverpod()
Stream<BangData?> bangData(Ref ref, String trigger) { Stream<BangData?> bangData(Ref ref, BangKey key) {
final repository = ref.watch(bangDataRepositoryProvider.notifier); final repository = ref.watch(bangDataRepositoryProvider.notifier);
return repository.watchBang(trigger); return repository.watchBang(key);
} }
@Riverpod() @Riverpod()
@@ -42,7 +42,7 @@ final class DefaultSearchBangDataProvider
} }
String _$defaultSearchBangDataHash() => String _$defaultSearchBangDataHash() =>
r'5fa3f588b57a82dd8fffb2082e495108a242ef72'; r'5f43b8989219cf3cb2f5ca65df351b6cb100427f';
@ProviderFor(bangData) @ProviderFor(bangData)
const bangDataProvider = BangDataFamily._(); const bangDataProvider = BangDataFamily._();
@@ -53,7 +53,7 @@ final class BangDataProvider
with $FutureModifier<BangData?>, $StreamProvider<BangData?> { with $FutureModifier<BangData?>, $StreamProvider<BangData?> {
const BangDataProvider._({ const BangDataProvider._({
required BangDataFamily super.from, required BangDataFamily super.from,
required String super.argument, required BangKey super.argument,
}) : super( }) : super(
retry: null, retry: null,
name: r'bangDataProvider', name: r'bangDataProvider',
@@ -79,7 +79,7 @@ final class BangDataProvider
@override @override
Stream<BangData?> create(Ref ref) { Stream<BangData?> create(Ref ref) {
final argument = this.argument as String; final argument = this.argument as BangKey;
return bangData(ref, argument); return bangData(ref, argument);
} }
@@ -94,10 +94,10 @@ final class BangDataProvider
} }
} }
String _$bangDataHash() => r'b10d3a74e19d30fc372b9ecbf7146bccd9fdf83b'; String _$bangDataHash() => r'bd9f5ec8b29aab74620a9b5a4246cb7e3b2fd377';
final class BangDataFamily extends $Family final class BangDataFamily extends $Family
with $FunctionalFamilyOverride<Stream<BangData?>, String> { with $FunctionalFamilyOverride<Stream<BangData?>, BangKey> {
const BangDataFamily._() const BangDataFamily._()
: super( : super(
retry: null, retry: null,
@@ -107,8 +107,8 @@ final class BangDataFamily extends $Family
isAutoDispose: true, isAutoDispose: true,
); );
BangDataProvider call(String trigger) => BangDataProvider call(BangKey key) =>
BangDataProvider._(argument: trigger, from: this); BangDataProvider._(argument: key, from: this);
@override @override
String toString() => r'bangDataProvider'; String toString() => r'bangDataProvider';
@@ -34,8 +34,9 @@ class BangSearch extends _$BangSearch {
Future<Uri> triggerBangSearch(BangData bang, String searchQuery) async { Future<Uri> triggerBangSearch(BangData bang, String searchQuery) async {
final bangDataNotifier = ref.read(bangDataRepositoryProvider.notifier); final bangDataNotifier = ref.read(bangDataRepositoryProvider.notifier);
await bangDataNotifier.increaseFrequency(bang.trigger); await bangDataNotifier.increaseFrequency(bang.group, bang.trigger);
await bangDataNotifier.addSearchEntry( await bangDataNotifier.addSearchEntry(
bang.group,
bang.trigger, bang.trigger,
searchQuery, searchQuery,
maxEntryCount: 3, maxEntryCount: 3,
@@ -33,7 +33,7 @@ final class BangSearchProvider
BangSearch create() => BangSearch(); BangSearch create() => BangSearch();
} }
String _$bangSearchHash() => r'6b7452d48698c01870c5c75f0c10cadaafc481c8'; String _$bangSearchHash() => r'66080335f1677e6a4636c41dd5619de3828375e6';
abstract class _$BangSearch extends $StreamNotifier<List<BangData>> { abstract class _$BangSearch extends $StreamNotifier<List<BangData>> {
Stream<List<BangData>> build(); Stream<List<BangData>> build();
@@ -23,6 +23,7 @@ import 'package:drift/drift.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart'; import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/data/models/search_history_entry.dart'; import 'package:weblibre/features/bangs/data/models/search_history_entry.dart';
import 'package:weblibre/features/bangs/data/providers.dart'; import 'package:weblibre/features/bangs/data/providers.dart';
@@ -33,12 +34,12 @@ class BangDataRepository extends _$BangDataRepository {
@override @override
void build() {} void build() {}
Stream<BangData?> watchBang(String? trigger) { Stream<BangData?> watchBang(BangKey? key) {
if (trigger != null) { if (key != null) {
return ref return ref
.read(bangDatabaseProvider) .read(bangDatabaseProvider)
.bangDao .bangDao
.getBangData(trigger) .getBangData(key.group, key.trigger)
.watchSingleOrNull(); .watchSingleOrNull();
} else { } else {
return Stream.value(null); return Stream.value(null);
@@ -104,14 +105,15 @@ class BangDataRepository extends _$BangDataRepository {
.watch(); .watch();
} }
Future<void> increaseFrequency(String trigger) { Future<void> increaseFrequency(BangGroup group, String trigger) {
return ref return ref
.read(bangDatabaseProvider) .read(bangDatabaseProvider)
.bangDao .bangDao
.increaseBangFrequency(trigger); .increaseBangFrequency(group, trigger);
} }
Future<void> addSearchEntry( Future<void> addSearchEntry(
BangGroup group,
String trigger, String trigger,
String searchQuery, { String searchQuery, {
required int maxEntryCount, required int maxEntryCount,
@@ -119,7 +121,7 @@ class BangDataRepository extends _$BangDataRepository {
final db = ref.read(bangDatabaseProvider); final db = ref.read(bangDatabaseProvider);
//Pack in a transaction to bundle rebuilds of watch() queries //Pack in a transaction to bundle rebuilds of watch() queries
return db.transaction(() async { return db.transaction(() async {
await db.bangDao.addSearchEntry(trigger, searchQuery); await db.bangDao.addSearchEntry(group, trigger, searchQuery);
await db.definitionsDrift.evictHistoryEntries(limit: maxEntryCount); await db.definitionsDrift.evictHistoryEntries(limit: maxEntryCount);
}); });
} }
@@ -42,7 +42,7 @@ final class BangDataRepositoryProvider
} }
String _$bangDataRepositoryHash() => String _$bangDataRepositoryHash() =>
r'5e25ce123d4667a0b12316f5b771b6776c40f280'; r'93f1338da86c693f524df377fe01d0ecf7f5ff80';
abstract class _$BangDataRepository extends $Notifier<void> { abstract class _$BangDataRepository extends $Notifier<void> {
void build(); void build();
@@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart';
@@ -38,7 +39,7 @@ class BangCategoriesScreen extends HookConsumerWidget {
actions: [ actions: [
IconButton( IconButton(
onPressed: () async { onPressed: () async {
final trigger = await const BangSearchRoute().push<String?>( final trigger = await const BangSearchRoute().push<BangKey?>(
context, context,
); );
@@ -60,7 +60,7 @@ class BangListScreen extends HookConsumerWidget {
onTap: () { onTap: () {
ref ref
.read(selectedBangTriggerProvider().notifier) .read(selectedBangTriggerProvider().notifier)
.setTrigger(bang.trigger); .setTrigger(bang.toKey());
final settings = ref.read( final settings = ref.read(
generalSettingsWithDefaultsProvider, generalSettingsWithDefaultsProvider,
@@ -90,7 +90,7 @@ class BangSearchScreen extends HookConsumerWidget {
return BangDetails( return BangDetails(
bang, bang,
onTap: () { onTap: () {
context.pop(bang.trigger); context.pop(bang.toKey());
}, },
); );
}, },
@@ -148,7 +148,7 @@ class SiteSearch extends HookConsumerWidget {
ref ref
.read(selectedBangTriggerProvider(domain: domain).notifier) .read(selectedBangTriggerProvider(domain: domain).notifier)
.setTrigger(bang.trigger); .setTrigger(bang.toKey());
}, },
onDeleted: (bang) { onDeleted: (bang) {
if (ref.read(selectedBangTriggerProvider(domain: domain)) == if (ref.read(selectedBangTriggerProvider(domain: domain)) ==
@@ -41,7 +41,7 @@ final class TabStatesProvider
} }
} }
String _$tabStatesHash() => r'37fbcd50318bb13358e123342b323723641d4f3f'; String _$tabStatesHash() => r'f3835029261f0e2594c804d0555a0db37171ddab';
abstract class _$TabStates extends $Notifier<Map<String, TabState>> { abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
Map<String, TabState> build(); Map<String, TabState> build();
@@ -23,6 +23,7 @@ import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
@@ -40,7 +41,7 @@ part 'providers.g.dart';
class SelectedBangTrigger extends _$SelectedBangTrigger { class SelectedBangTrigger extends _$SelectedBangTrigger {
// ignore: document_ignores api decision // ignore: document_ignores api decision
// ignore: use_setters_to_change_properties // ignore: use_setters_to_change_properties
void setTrigger(String trigger) { void setTrigger(BangKey trigger) {
state = trigger; state = trigger;
} }
@@ -49,7 +50,7 @@ class SelectedBangTrigger extends _$SelectedBangTrigger {
} }
@override @override
String? build({String? domain}) { BangKey? build({String? domain}) {
return null; return null;
} }
} }
@@ -13,7 +13,7 @@ part of 'providers.dart';
const selectedBangTriggerProvider = SelectedBangTriggerFamily._(); const selectedBangTriggerProvider = SelectedBangTriggerFamily._();
final class SelectedBangTriggerProvider final class SelectedBangTriggerProvider
extends $NotifierProvider<SelectedBangTrigger, String?> { extends $NotifierProvider<SelectedBangTrigger, BangKey?> {
const SelectedBangTriggerProvider._({ const SelectedBangTriggerProvider._({
required SelectedBangTriggerFamily super.from, required SelectedBangTriggerFamily super.from,
required String? super.argument, required String? super.argument,
@@ -40,10 +40,10 @@ final class SelectedBangTriggerProvider
SelectedBangTrigger create() => SelectedBangTrigger(); SelectedBangTrigger create() => SelectedBangTrigger();
/// {@macro riverpod.override_with_value} /// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) { Override overrideWithValue(BangKey? value) {
return $ProviderOverride( return $ProviderOverride(
origin: this, origin: this,
providerOverride: $SyncValueProvider<String?>(value), providerOverride: $SyncValueProvider<BangKey?>(value),
); );
} }
@@ -59,15 +59,15 @@ final class SelectedBangTriggerProvider
} }
String _$selectedBangTriggerHash() => String _$selectedBangTriggerHash() =>
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385'; r'162d053d0207b8b98549adc453111111d8ff69d6';
final class SelectedBangTriggerFamily extends $Family final class SelectedBangTriggerFamily extends $Family
with with
$ClassFamilyOverride< $ClassFamilyOverride<
SelectedBangTrigger, SelectedBangTrigger,
String?, BangKey?,
String?, BangKey?,
String?, BangKey?,
String? String?
> { > {
const SelectedBangTriggerFamily._() const SelectedBangTriggerFamily._()
@@ -86,21 +86,21 @@ final class SelectedBangTriggerFamily extends $Family
String toString() => r'selectedBangTriggerProvider'; String toString() => r'selectedBangTriggerProvider';
} }
abstract class _$SelectedBangTrigger extends $Notifier<String?> { abstract class _$SelectedBangTrigger extends $Notifier<BangKey?> {
late final _$args = ref.$arg as String?; late final _$args = ref.$arg as String?;
String? get domain => _$args; String? get domain => _$args;
String? build({String? domain}); BangKey? build({String? domain});
@$mustCallSuper @$mustCallSuper
@override @override
void runBuild() { void runBuild() {
final created = build(domain: _$args); final created = build(domain: _$args);
final ref = this.ref as $Ref<String?, String?>; final ref = this.ref as $Ref<BangKey?, BangKey?>;
final element = final element =
ref.element ref.element
as $ClassProviderElement< as $ClassProviderElement<
AnyNotifier<String?, String?>, AnyNotifier<BangKey?, BangKey?>,
String?, BangKey?,
Object?, Object?,
Object? Object?
>; >;
@@ -22,6 +22,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/providers/search.dart'; import 'package:weblibre/features/bangs/domain/providers/search.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/presentation/hooks/listenable_callback.dart'; import 'package:weblibre/presentation/hooks/listenable_callback.dart';
@@ -121,7 +122,7 @@ class BangChips extends HookConsumerWidget {
searchText: (searchText.isEmpty) searchText: (searchText.isEmpty)
? BangSearchRoute.emptySearchText ? BangSearchRoute.emptySearchText
: searchText!, : searchText!,
).push<String?>(context); ).push<BangKey?>(context);
if (trigger != null) { if (trigger != null) {
ref ref
@@ -138,7 +138,7 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
ref ref
.read(selectedBangTriggerProvider().notifier) .read(selectedBangTriggerProvider().notifier)
.setTrigger(bang.trigger); .setTrigger(bang.toKey());
}, },
onDeleted: (bang) async { onDeleted: (bang) async {
if (ref.read(selectedBangTriggerProvider()) == if (ref.read(selectedBangTriggerProvider()) ==
@@ -22,6 +22,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/search/domain/entities/abstract/i_search_suggestion_provider.dart'; import 'package:weblibre/features/search/domain/entities/abstract/i_search_suggestion_provider.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
@@ -50,12 +51,12 @@ class DefaultSearchPage extends HookConsumerWidget {
), ),
); );
Future<void> updateSearchProvider(String trigger) async { Future<void> updateSearchProvider(BangKey key) async {
await ref await ref
.read(saveGeneralSettingsControllerProvider.notifier) .read(saveGeneralSettingsControllerProvider.notifier)
.save( .save(
(currentSettings) => (currentSettings) =>
currentSettings.copyWith.defaultSearchProvider(trigger), currentSettings.copyWith.defaultSearchProvider(key),
); );
} }
@@ -101,7 +102,7 @@ class DefaultSearchPage extends HookConsumerWidget {
selected: activeBang?.trigger == bang.trigger, selected: activeBang?.trigger == bang.trigger,
onSelected: (selected) async { onSelected: (selected) async {
if (selected) { if (selected) {
await updateSearchProvider(bang.trigger); await updateSearchProvider(bang.toKey());
} }
}, },
), ),
@@ -111,7 +112,7 @@ class DefaultSearchPage extends HookConsumerWidget {
avatar: const Icon(Icons.search), avatar: const Icon(Icons.search),
onPressed: () async { onPressed: () async {
final trigger = await const BangSearchRoute() final trigger = await const BangSearchRoute()
.push<String?>(context); .push<BangKey?>(context);
if (trigger != null) { if (trigger != null) {
await updateSearchProvider(trigger); await updateSearchProvider(trigger);
@@ -18,16 +18,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:exceptions/exceptions.dart'; import 'package:exceptions/exceptions.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
enum SearchSuggestionProviders { enum SearchSuggestionProviders {
none('Disabled', null), none('Disabled', null),
brave('Brave', 'brave'), brave('Brave', BangKey(group: BangGroup.general, trigger: 'brave')),
ddg('DuckDuckGo', 'ddg'), ddg('DuckDuckGo', BangKey(group: BangGroup.general, trigger: 'ddg')),
kagi('Kagi', 'kagi'), kagi('Kagi', BangKey(group: BangGroup.kagi, trigger: 'kagi')),
qwant('Qwant', 'qwant'); qwant('Qwant', BangKey(group: BangGroup.general, trigger: 'qwant'));
final String label; final String label;
final String? relatedBang; final BangKey? relatedBang;
const SearchSuggestionProviders(this.label, this.relatedBang); const SearchSuggestionProviders(this.label, this.relatedBang);
} }
@@ -19,11 +19,12 @@
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart';
class BangIcon extends ConsumerWidget { class BangIcon extends ConsumerWidget {
final String trigger; final BangKey trigger;
final double iconSize; final double iconSize;
const BangIcon({super.key, required this.trigger, this.iconSize = 20}); const BangIcon({super.key, required this.trigger, this.iconSize = 20});
@@ -20,6 +20,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart';
@@ -36,12 +37,12 @@ class DefaultSearchSelector extends HookConsumerWidget {
); );
final availableBangs = ref.watch(frequentBangListProvider); final availableBangs = ref.watch(frequentBangListProvider);
Future<void> updateSearchProvider(String trigger) async { Future<void> updateSearchProvider(BangKey key) async {
await ref await ref
.read(saveGeneralSettingsControllerProvider.notifier) .read(saveGeneralSettingsControllerProvider.notifier)
.save( .save(
(currentSettings) => (currentSettings) =>
currentSettings.copyWith.defaultSearchProvider(trigger), currentSettings.copyWith.defaultSearchProvider(key),
); );
} }
@@ -62,13 +63,13 @@ class DefaultSearchSelector extends HookConsumerWidget {
availableItems: availableBangs, availableItems: availableBangs,
selectedItem: activeBang, selectedItem: activeBang,
onSelected: (bang) async { onSelected: (bang) async {
await updateSearchProvider(bang.trigger); await updateSearchProvider(bang.toKey());
}, },
), ),
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
final trigger = await const BangSearchRoute().push<String?>( final trigger = await const BangSearchRoute().push<BangKey?>(
context, context,
); );
@@ -22,11 +22,16 @@ import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/search/domain/entities/abstract/i_search_suggestion_provider.dart'; import 'package:weblibre/features/search/domain/entities/abstract/i_search_suggestion_provider.dart';
part 'general_settings.g.dart'; part 'general_settings.g.dart';
const _fallbackSearchProvider = 'wikipedia'; final _fallbackSearchProvider = BangKey(
group: BangGroup.general,
trigger: 'wikipedia',
);
const _fallbackAutocompleteProvider = SearchSuggestionProviders.none; const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs } enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
@@ -54,7 +59,8 @@ class GeneralSettings with FastEquatable {
final bool enableReadability; final bool enableReadability;
final bool enforceReadability; final bool enforceReadability;
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit; final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
final String defaultSearchProvider; @BangKeyConverter()
final BangKey? defaultSearchProvider;
final SearchSuggestionProviders defaultSearchSuggestionsProvider; final SearchSuggestionProviders defaultSearchSuggestionsProvider;
final bool createChildTabsOption; final bool createChildTabsOption;
final bool showExtensionShortcut; final bool showExtensionShortcut;
@@ -87,7 +93,7 @@ class GeneralSettings with FastEquatable {
bool? enableReadability, bool? enableReadability,
bool? enforceReadability, bool? enforceReadability,
this.deleteBrowsingDataOnQuit, this.deleteBrowsingDataOnQuit,
String? defaultSearchProvider, BangKey? defaultSearchProvider,
SearchSuggestionProviders? defaultSearchSuggestionsProvider, SearchSuggestionProviders? defaultSearchSuggestionsProvider,
bool? createChildTabsOption, bool? createChildTabsOption,
bool? showExtensionShortcut, bool? showExtensionShortcut,
@@ -17,7 +17,7 @@ abstract class _$GeneralSettingsCWProxy {
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit, Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
); );
GeneralSettings defaultSearchProvider(String defaultSearchProvider); GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider);
GeneralSettings defaultSearchSuggestionsProvider( GeneralSettings defaultSearchSuggestionsProvider(
SearchSuggestionProviders defaultSearchSuggestionsProvider, SearchSuggestionProviders defaultSearchSuggestionsProvider,
@@ -53,7 +53,7 @@ abstract class _$GeneralSettingsCWProxy {
bool enableReadability, bool enableReadability,
bool enforceReadability, bool enforceReadability,
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit, Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
String defaultSearchProvider, BangKey? defaultSearchProvider,
SearchSuggestionProviders defaultSearchSuggestionsProvider, SearchSuggestionProviders defaultSearchSuggestionsProvider,
bool createChildTabsOption, bool createChildTabsOption,
bool showExtensionShortcut, bool showExtensionShortcut,
@@ -90,7 +90,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
) => call(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit); ) => call(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit);
@override @override
GeneralSettings defaultSearchProvider(String defaultSearchProvider) => GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider) =>
call(defaultSearchProvider: defaultSearchProvider); call(defaultSearchProvider: defaultSearchProvider);
@override @override
@@ -178,11 +178,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?, : deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?,
defaultSearchProvider: defaultSearchProvider:
defaultSearchProvider == const $CopyWithPlaceholder() || defaultSearchProvider == const $CopyWithPlaceholder()
defaultSearchProvider == null
? _value.defaultSearchProvider ? _value.defaultSearchProvider
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: defaultSearchProvider as String, : defaultSearchProvider as BangKey?,
defaultSearchSuggestionsProvider: defaultSearchSuggestionsProvider:
defaultSearchSuggestionsProvider == const $CopyWithPlaceholder() || defaultSearchSuggestionsProvider == const $CopyWithPlaceholder() ||
defaultSearchSuggestionsProvider == null defaultSearchSuggestionsProvider == null
@@ -261,7 +260,9 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
(json['deleteBrowsingDataOnQuit'] as List<dynamic>?) (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e)) ?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
.toSet(), .toSet(),
defaultSearchProvider: json['defaultSearchProvider'] as String?, defaultSearchProvider: const BangKeyConverter().fromJson(
json['defaultSearchProvider'] as String?,
),
defaultSearchSuggestionsProvider: $enumDecodeNullable( defaultSearchSuggestionsProvider: $enumDecodeNullable(
_$SearchSuggestionProvidersEnumMap, _$SearchSuggestionProvidersEnumMap,
json['defaultSearchSuggestionsProvider'], json['defaultSearchSuggestionsProvider'],
@@ -298,7 +299,9 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit 'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!) ?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
.toList(), .toList(),
'defaultSearchProvider': instance.defaultSearchProvider, 'defaultSearchProvider': const BangKeyConverter().toJson(
instance.defaultSearchProvider,
),
'defaultSearchSuggestionsProvider': 'defaultSearchSuggestionsProvider':
_$SearchSuggestionProvidersEnumMap[instance _$SearchSuggestionProvidersEnumMap[instance
.defaultSearchSuggestionsProvider]!, .defaultSearchSuggestionsProvider]!,