parent
b6c6ac35d9
commit
20eed13884
8 changed files with 142 additions and 116 deletions
|
|
@ -5,6 +5,7 @@ import "package:nexus/controllers/members_by_status.dart";
|
|||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/via.dart";
|
||||
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/models/bot_argument_suggestion.dart";
|
||||
import "package:nexus/models/bot_command_draft.dart";
|
||||
import "package:nexus/models/content/bot_command_description.dart";
|
||||
|
|
@ -91,13 +92,10 @@ class BotArgumentSuggestionsController(final String roomId)
|
|||
)
|
||||
.nonNulls,
|
||||
)
|
||||
.where(
|
||||
(suggestion) =>
|
||||
!picked.contains(suggestion.text) &&
|
||||
[
|
||||
suggestion.title,
|
||||
suggestion.subtitle,
|
||||
].any((text) => text?.toLowerCase().contains(query) == true),
|
||||
.where((suggestion) => !picked.contains(suggestion.text))
|
||||
.search(
|
||||
query,
|
||||
(suggestion) => [(suggestion.title, 2), (suggestion.subtitle, 1)],
|
||||
)
|
||||
.take(8)
|
||||
.toIList();
|
||||
|
|
|
|||
47
lib/helpers/extensions/weighted_search.dart
Normal file
47
lib/helpers/extensions/weighted_search.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
|
||||
extension WeightedSearch<T> on Iterable<T> {
|
||||
/// The items with a field matching [query], best match first.
|
||||
IList<T> search(
|
||||
String query,
|
||||
Iterable<(String?, int)> Function(T item) fieldsOf,
|
||||
) {
|
||||
final needle = query.trim().toLowerCase();
|
||||
if (needle.isEmpty) return toIList();
|
||||
|
||||
int qualityOf(String text) {
|
||||
final haystack = text.toLowerCase();
|
||||
if (haystack == needle) return 4;
|
||||
if (haystack.startsWith(needle)) return 3;
|
||||
if (haystack
|
||||
.split(RegExp(r"[\s_\-:/.@#!]+"))
|
||||
.any((w) => w.startsWith(needle))) {
|
||||
return 2;
|
||||
}
|
||||
if (haystack.contains(needle)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return .new(
|
||||
map(
|
||||
(item) => (
|
||||
item,
|
||||
fieldsOf(item)
|
||||
.map(
|
||||
(field) => switch (field) {
|
||||
(final text?, final weight) =>
|
||||
qualityOf(text) * weight,
|
||||
(null, _) => 0,
|
||||
},
|
||||
)
|
||||
.maxOrNull ??
|
||||
0,
|
||||
),
|
||||
)
|
||||
.where((scored) => scored.$2 > 0)
|
||||
.sortedBy((scored) => -scored.$2)
|
||||
.map((scored) => scored.$1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
|||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/all_emoji.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/models/content/image_pack.dart";
|
||||
import "package:nexus/models/image_pack.dart";
|
||||
|
||||
|
|
@ -21,11 +22,13 @@ class const EmojiOverlay(
|
|||
children: emojis
|
||||
.map((element) => element.emojis)
|
||||
.flattened
|
||||
.where(
|
||||
(emoji) =>
|
||||
emoji.aliases.join().contains(query) ||
|
||||
emoji.description.contains(query) ||
|
||||
emoji.tags.join().contains(query),
|
||||
.search(
|
||||
query,
|
||||
(emoji) => [
|
||||
...emoji.aliases.map((alias) => (alias, 3)),
|
||||
...emoji.tags.map((tag) => (tag, 2)),
|
||||
(emoji.description, 1),
|
||||
],
|
||||
)
|
||||
.map(
|
||||
(emoji) => ListTile(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
|||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/via.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
|
||||
class const RoomOverlay(
|
||||
|
|
@ -14,14 +15,15 @@ class const RoomOverlay(
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final rooms = ref.watch(RoomsController.provider);
|
||||
return ListView(
|
||||
children:
|
||||
(query.isEmpty
|
||||
? rooms.values
|
||||
: rooms.values.where(
|
||||
(room) => (room.metadata?.name ?? room.metadata?.id ?? "")
|
||||
.toLowerCase()
|
||||
.contains(query.toLowerCase()),
|
||||
))
|
||||
children: rooms.values
|
||||
.search(
|
||||
query,
|
||||
(room) => [
|
||||
(room.metadata?.name, 3),
|
||||
(room.metadata?.canonicalAlias, 2),
|
||||
(room.metadata?.id, 1),
|
||||
],
|
||||
)
|
||||
.map((room) {
|
||||
final name =
|
||||
room.metadata?.name ??
|
||||
|
|
@ -39,9 +41,7 @@ class const RoomOverlay(
|
|||
? null
|
||||
: Text(room.metadata!.topic!, maxLines: 1),
|
||||
onTap: () {
|
||||
final vias = ref
|
||||
.watch(ViaController.provider(room))
|
||||
.viaQuery;
|
||||
final vias = ref.watch(ViaController.provider(room)).viaQuery;
|
||||
addTag(
|
||||
id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)",
|
||||
name:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import "package:material_ui/material_ui.dart";
|
|||
import "package:nexus/controllers/members_by_status.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/models/content/membership.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
|
||||
|
|
@ -20,24 +21,15 @@ class const UserOverlay(
|
|||
)
|
||||
.betterWhen(
|
||||
data: (members) => ListView(
|
||||
children:
|
||||
(query.isEmpty
|
||||
? members
|
||||
: members.where(
|
||||
(member) =>
|
||||
member.stateKey?.toLowerCase().contains(
|
||||
query.toLowerCase(),
|
||||
) ==
|
||||
true ||
|
||||
switch (member.content) {
|
||||
MembershipContent(:final displayName) =>
|
||||
displayName?.toLowerCase().contains(
|
||||
query.toLowerCase(),
|
||||
) ==
|
||||
true,
|
||||
_ => false,
|
||||
},
|
||||
))
|
||||
children: members
|
||||
.search(
|
||||
query,
|
||||
(member) => [
|
||||
if (member.content case MembershipContent(:final displayName))
|
||||
(displayName, 2),
|
||||
(member.stateKey, 1),
|
||||
],
|
||||
)
|
||||
.map(
|
||||
(member) => switch (member.content) {
|
||||
MembershipContent(:final displayName, :final avatarUrl) =>
|
||||
|
|
@ -46,9 +38,7 @@ class const UserOverlay(
|
|||
avatarUrl,
|
||||
displayName ?? member.stateKey!.localpart,
|
||||
),
|
||||
title: Text(
|
||||
displayName ?? member.stateKey!.localpart,
|
||||
),
|
||||
title: Text(displayName ?? member.stateKey!.localpart),
|
||||
subtitle: Text(member.stateKey!),
|
||||
onTap: () => addTag(
|
||||
id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
|
||||
/// The [chosen] items, above a field searching [options] to choose more.
|
||||
class const SearchChooser({
|
||||
|
|
@ -30,15 +31,10 @@ class const SearchChooser({
|
|||
}) extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final query = useValueListenable(controller).text.trim().toLowerCase();
|
||||
final query = useValueListenable(controller).text;
|
||||
final shown = options
|
||||
.where(
|
||||
(id) =>
|
||||
!chosen.contains(id) &&
|
||||
(nameOf(id).toLowerCase().contains(query) ||
|
||||
id.toLowerCase().contains(query)),
|
||||
)
|
||||
.toIList();
|
||||
.where((id) => !chosen.contains(id))
|
||||
.search(query, (id) => [(nameOf(id), 2), (id, 1)]);
|
||||
|
||||
ListTile tileOf(String id, {Widget? trailing, VoidCallback? onTap}) =>
|
||||
ListTile(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import "package:hooks_riverpod/misc.dart";
|
|||
import "package:m3e_card_list/m3e_card_list.dart";
|
||||
import "package:navigation_rail_m3e/navigation_rail_m3e.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/models/settings_category.dart";
|
||||
import "package:nexus/widgets/pages/settings_category.dart";
|
||||
import "package:nexus/widgets/divider_text.dart";
|
||||
|
|
@ -43,32 +44,22 @@ class const SettingsLayout({
|
|||
// Only searched once shown, by which point these have loaded
|
||||
final categories =
|
||||
ref.read(sectionsProvider).value ?? const IMap.empty();
|
||||
final query = controller.text.toLowerCase();
|
||||
|
||||
final matches = categories.values.flattenedToList
|
||||
.asMap()
|
||||
.entries
|
||||
.expand(
|
||||
(categoryEntry) => categoryEntry.value.settings
|
||||
.asMap()
|
||||
.entries
|
||||
.where(
|
||||
(settingEntry) =>
|
||||
settingEntry.value.title.toLowerCase().contains(
|
||||
query,
|
||||
) ||
|
||||
settingEntry.value.description
|
||||
.toLowerCase()
|
||||
.contains(query),
|
||||
)
|
||||
.map(
|
||||
(categoryEntry) =>
|
||||
categoryEntry.value.settings.asMap().entries.map(
|
||||
(settingEntry) => (
|
||||
(categoryEntry.key, settingEntry.key),
|
||||
settingEntry.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toIList();
|
||||
.search(
|
||||
controller.text,
|
||||
(match) => [(match.$2.title, 2), (match.$2.description, 1)],
|
||||
);
|
||||
|
||||
return matches.map(
|
||||
(match) => ListTile(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
|||
import "package:material_ui/material_ui.dart";
|
||||
import "package:material_adaptive_sheets/material_adaptive_sheets.dart";
|
||||
import "package:nexus/controllers/timezones.dart";
|
||||
import "package:nexus/helpers/extensions/weighted_search.dart";
|
||||
import "package:nexus/helpers/hooks/saving.dart";
|
||||
import "package:nexus/widgets/settings/inline_text_field.dart";
|
||||
import "package:timezone/timezone.dart";
|
||||
|
|
@ -84,7 +85,7 @@ class const TimezoneField({
|
|||
onTap: () => choose(controller, null),
|
||||
),
|
||||
...timezones
|
||||
.where((timezone) => timezone.toLowerCase().contains(query))
|
||||
.search(query, (timezone) => [(timezone, 1)])
|
||||
.map(
|
||||
(timezone) => ListTile(
|
||||
leading: Icon(
|
||||
|
|
|
|||
Loading…
Reference in a new issue