Add Supa account and search changes

This commit is contained in:
Fabian Freund
2026-05-22 18:10:22 +02:00
parent 3a19865b2e
commit 51289f1266
374 changed files with 54061 additions and 5013 deletions
@@ -159,6 +159,11 @@ extension DefineFunctions on i6.CommonDatabase {
required String Function(int, String?) lexoRankPrevious,
required String Function(String?, String?) lexoRankReorderAfter,
required String Function(String?, String?) lexoRankReorderBefore,
required int Function() generateContentHash,
required bool Function(String?) urlIndexable,
required String Function(String?) urlCanonical,
required String Function(String?) urlHost,
required String Function(String?) urlPath,
}) {
createFunction(
functionName: 'lexo_rank_next',
@@ -196,5 +201,44 @@ extension DefineFunctions on i6.CommonDatabase {
return lexoRankReorderBefore(arg0, arg1);
},
);
createFunction(
functionName: 'generate_content_hash',
argumentCount: const i6.AllowedArgumentCount(0),
function: (args) {
return generateContentHash();
},
);
createFunction(
functionName: 'url_indexable',
argumentCount: const i6.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlIndexable(arg0);
},
);
createFunction(
functionName: 'url_canonical',
argumentCount: const i6.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlCanonical(arg0);
},
);
createFunction(
functionName: 'url_host',
argumentCount: const i6.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlHost(arg0);
},
);
createFunction(
functionName: 'url_path',
argumentCount: const i6.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlPath(arg0);
},
);
}
}
@@ -28,7 +28,8 @@ enum BangGroup {
'https://raw.githubusercontent.com/FaFre/bangs/main/data/kagi_bangs.json',
bundled: 'assets/bangs/kagi_bangs.json',
),
user(remote: null, bundled: null);
user(remote: null, bundled: null),
weblibre(remote: null, bundled: null);
final String? bundled;
final String? remote;
@@ -0,0 +1,24 @@
import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
const webSearchBangTrigger = 'wl';
final webSearchBang = BangData(
websiteName: 'WebLibre Search',
domain: 'weblibre.eu',
trigger: webSearchBangTrigger,
// Placeholder template — selecting this bang routes the query through the
// token-based search backend instead of opening this URL.
urlTemplate: 'https://weblibre.eu/?q={{{s}}}',
group: BangGroup.weblibre,
searxngApi: false,
);
final webSearchBangKey = BangKey(
group: BangGroup.weblibre,
trigger: webSearchBangTrigger,
);
bool isWebSearchBang(BangData? bang) =>
bang != null && bang.group == BangGroup.weblibre;
@@ -47,6 +47,12 @@ class BangSearch extends _$BangSearch {
return bang.getTemplateUrl(searchQuery);
}
Future<void> removeSearchEntry(String searchQuery) {
return ref
.read(bangDataRepositoryProvider.notifier)
.removeSearchEntry(searchQuery);
}
Future<void> search(String input) async {
if (input.isNotEmpty) {
await ref.read(bangDatabaseProvider).bangDao.queryBangs(input).get().then(
@@ -33,7 +33,7 @@ final class BangSearchProvider
BangSearch create() => BangSearch();
}
String _$bangSearchHash() => r'feed24edfe703b0697f4a855be9c7359c456b0f2';
String _$bangSearchHash() => r'a541da1cfd8155abf03bbe19ab7c7200febd821c';
abstract class _$BangSearch extends $StreamNotifier<List<BangData>> {
Stream<List<BangData>> build();
@@ -99,11 +99,17 @@ class BangDataRepository extends _$BangDataRepository {
}
Stream<List<SearchHistoryEntry>> watchSearchHistory({required int limit}) {
return ref
final query = ref
.read(bangDatabaseProvider)
.definitionsDrift
.searchHistoryEntries(limit: limit)
.watch();
.searchHistoryEntries(limit: limit);
return () async* {
// Emit a direct read first so Recent Searches is populated immediately
// on app start instead of waiting for the next bang_history write.
yield await query.get();
yield* query.watch();
}();
}
Future<void> increaseFrequency(BangKey key) {
@@ -42,7 +42,7 @@ final class BangDataRepositoryProvider
}
String _$bangDataRepositoryHash() =>
r'c562ef10d75ca6dee13805f84d2491cf2a89aaac';
r'c8e2b17214117a8d88b5246bdcc565bc15e8e4ac';
abstract class _$BangDataRepository extends $Notifier<void> {
void build();
@@ -21,6 +21,7 @@ import 'package:exceptions/exceptions.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/bangs/data/database/database.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/web_search_bang.dart';
import 'package:weblibre/features/bangs/data/providers.dart';
import 'package:weblibre/features/bangs/data/services/data_source.dart';
@@ -170,6 +171,21 @@ class BangSyncRepository extends _$BangSyncRepository {
.watchSingleOrNull();
}
Future<Result<void>> _syncSyntheticBangs() async {
try {
await ref.read(bangDatabaseProvider).bangDao.upsertBang(webSearchBang);
return Result.success(null);
} catch (e) {
return Result.failure(
ErrorMessage(
message: 'Failed to sync synthetic Bangs',
source: 'BangSync',
details: e,
),
);
}
}
Future<Map<BangGroup, Result<void>>> syncBundledBangGroups({
Set<BangGroup>? groups,
}) async {
@@ -183,7 +199,10 @@ class BangSyncRepository extends _$BangSyncRepository {
).then((result) => MapEntry(source, result)),
);
return Map.fromEntries(await Future.wait(futures));
return {
...Map.fromEntries(await Future.wait(futures)),
BangGroup.weblibre: await _syncSyntheticBangs(),
};
}
@override
@@ -42,7 +42,7 @@ final class BangSyncRepositoryProvider
}
String _$bangSyncRepositoryHash() =>
r'1347dcbd03f6a1a4fa3bbbae5d9302098d260c50';
r'cdb78ef0243224fdb7b3a8660f0920c471987296';
abstract class _$BangSyncRepository extends $Notifier<void> {
void build();
@@ -24,6 +24,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/domain/providers/search.dart';
import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart';
import 'package:weblibre/features/user/domain/providers.dart';
@@ -36,7 +37,7 @@ class BangSearchScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final resultsAsync = ref.watch(bangSearchProvider);
final resultsAsync = ref.watch(seamlessBangProvider);
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
final focusNode = useFocusNode();
@@ -45,11 +46,9 @@ class BangSearchScreen extends HookConsumerWidget {
);
useOnListenableChange(textEditingController, () {
unawaited(
ref
.read(bangSearchProvider.notifier)
.search(textEditingController.text),
);
ref
.read(seamlessBangProvider.notifier)
.search(textEditingController.text);
});
return Scaffold(
@@ -79,24 +78,26 @@ class BangSearchScreen extends HookConsumerWidget {
body: SafeArea(
child: resultsAsync.when(
skipLoadingOnReload: true,
data: (bangs) => FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return ListView.builder(
controller: controller,
itemCount: bangs.length,
itemBuilder: (context, index) {
final bang = bangs[index];
return BangDetails(
bang,
onTap: () {
context.pop(bang.toKey());
},
);
},
);
},
),
data: (bangs) {
return FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return ListView.builder(
controller: controller,
itemCount: bangs.length,
itemBuilder: (context, index) {
final bang = bangs[index];
return BangDetails(
bang,
onTap: () {
context.pop(bang.toKey());
},
);
},
);
},
);
},
error: (error, stackTrace) => Center(
child: FailureWidget(title: 'Bang Search failed', exception: error),
),