Skip to content

Commit

Permalink
refactor: refactor fuzzy search for users and conversations (#1684)
Browse files Browse the repository at this point in the history
* refactor: refactor fuzzy search for users and conversations

- Added fuzzySearchUserItem and fuzzySearchConversationItem methods in the database layer for improved search functionality.
- Updated ConversationDao and UserDao to utilize the new search methods.
- Introduced SearchItem class to encapsulate search results.
- Refactored UI components to display search results using SearchItemWidget.
- Enhanced SQL queries in common.drift for better search performance and accuracy.

* refactor: improve command palette search functionality

- Moved search logic into dedicated _useSearchState and _useNavigationState hooks for better organization and readability.
- Updated CommandPalettePage to utilize the new hooks, enhancing the separation of concerns.
- Removed redundant code related to user and conversation search, streamlining the implementation.
- Ensured that search results are displayed correctly using the updated state management approach.

* feat: enable fatal warnings in build configuration and improve command palette navigation

- Added 'fatal_warnings' option in build.yaml to enhance build error handling.
- Updated command palette navigation logic to handle empty item lists gracefully, preventing potential runtime errors.

* refactor: update SQL queries for improved search logic

- Modified SQL queries in mixin_database.g.dart and common.drift to enhance the handling of the 'enableNameLike' condition.
- Changed the condition from 'WHEN :enableNameLike = TRUE' to 'WHEN :enableNameLike != TRUE' for better clarity and functionality.
- Ensured consistency in query logic across both files to improve search performance and accuracy.

* refactor: enhance fuzzy search logic and SQL query structure

- Updated fuzzy search methods in ConversationDao and UserDao to improve search accuracy by incorporating LIKE escape functionality.
- Removed unnecessary parameters from fuzzy search queries to streamline logic.
- Refactored SQL queries in mixin_database.g.dart and common.drift for better performance and clarity, ensuring consistent handling of search conditions.
- Introduced LikeEscapeOperator and LikeEscapeExpression for improved LIKE query handling in the database layer.
  • Loading branch information
YeungKC authored Jan 16, 2025
1 parent 327ee4f commit 605afc4
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 175 deletions.
1 change: 1 addition & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ targets:
generate_values_in_copy_with: true
override_hash_and_equals_in_result_sets: true
scoped_dart_components: true
fatal_warnings: true
sqlite_modules:
- fts5
- moor_ffi
Expand Down
16 changes: 16 additions & 0 deletions lib/db/dao/conversation_dao.dart
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,22 @@ class ConversationDao extends DatabaseAccessor<MixinDatabase>
]);
});

Selectable<SearchItem> fuzzySearchConversationItem(String keyword) =>
db.fuzzySearchConversationItem(
keyword,
(conversation, user) =>
conversation.category.equalsValue(ConversationCategory.group) &
conversation.name.likeEscape('%$keyword%'),
(conversation, user) => maxLimit,
);

Selectable<SearchItem> fuzzySearchConversationItemByIds(List<String> ids) =>
db.fuzzySearchConversationItem(
'',
(conversation, user) => conversation.conversationId.isIn(ids),
(conversation, user) => maxLimit,
);

Selectable<String?> announcement(String conversationId) =>
(db.selectOnly(db.conversations)
..addColumns([db.conversations.announcement])
Expand Down
12 changes: 12 additions & 0 deletions lib/db/dao/user_dao.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../../utils/extension/extension.dart';
import '../database_event_bus.dart';
import '../extension/db.dart';
import '../mixin_database.dart';
import '../util/util.dart';

part 'user_dao.g.dart';

Expand Down Expand Up @@ -157,6 +158,17 @@ class UserDao extends DatabaseAccessor<MixinDatabase> with _$UserDaoMixin {
});
}

Selectable<SearchItem> fuzzySearchUserItem(
String keyword, String currentUserId) =>
db.fuzzySearchUserItem(
keyword,
(users) =>
users.userId.equals(currentUserId).not() &
users.identityNumber.equals('0').not() &
(users.fullName.likeEscape('%$keyword%') |
users.identityNumber.likeEscape('%$keyword%')),
(users) => maxLimit);

Future updateMuteUntil(String userId, String muteUntil) async {
await (update(db.users)..where((tbl) => tbl.userId.equals(userId)))
.write(UsersCompanion(muteUntil: Value(DateTime.tryParse(muteUntil))));
Expand Down
132 changes: 132 additions & 0 deletions lib/db/mixin_database.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions lib/db/moor/dao/common.drift
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,65 @@ ON message.conversation_id = conversation.conversation_id
WHERE pinMessage.conversation_id = :conversationId
ORDER BY $order
LIMIT $limit;


fuzzySearchUserItem AS SearchItem:
SELECT
CASE
WHEN users.app_id IS NOT NULL AND LENGTH(users.app_id) > 0 THEN 'BOT'
ELSE 'USER'
END AS type,
users.user_id AS id,
users.full_name AS name,
users.avatar_url AS avatar_url,
users.is_verified AS is_verified,
users.app_id AS app_id,
users.membership AS membership,
CASE
WHEN users.full_name = :query COLLATE NOCASE THEN 1.0 + 1.0/LENGTH(users.full_name)
WHEN users.identity_number = :query COLLATE NOCASE THEN 0.9 + 1.0/LENGTH(users.identity_number)
WHEN users.full_name LIKE :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.6 + 1.0/LENGTH(users.full_name)
WHEN users.identity_number LIKE :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.5 + 1.0/LENGTH(users.identity_number)
WHEN users.full_name LIKE '%' || :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.3 + 1.0/LENGTH(users.full_name)
WHEN users.identity_number LIKE '%' || :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.2 + 1.0/LENGTH(users.identity_number)
ELSE 0.0
END AS match_score
FROM users
WHERE $where
ORDER BY match_score DESC
limit $limit;


fuzzySearchConversationItem AS SearchItem:
SELECT
CASE
WHEN conversation.category = 'GROUP' THEN 'GROUP'
ELSE 'CONTACT'
END AS type,
conversation.conversation_id AS id,
CASE
WHEN conversation.category = 'GROUP' THEN conversation.name
ELSE owner.full_name
END AS name,
CASE
WHEN conversation.category = 'GROUP' THEN conversation.icon_url
ELSE owner.avatar_url
END AS avatar_url,
owner.is_verified AS is_verified,
owner.app_id AS app_id,
CASE
WHEN conversation.category = 'CONTACT' THEN owner.membership
ELSE NULL
END AS membership,
CASE
WHEN LENGTH(:query) = 0 THEN 0.0
WHEN name = :query COLLATE NOCASE THEN 1.0 + 1.0/LENGTH(name)
WHEN name LIKE :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.6 + 1.0/LENGTH(name)
WHEN name LIKE '%' || :query || '%' ESCAPE '\' COLLATE NOCASE THEN 0.3 + 1.0/LENGTH(name)
ELSE 0.0
END AS match_score
FROM conversations conversation
INNER JOIN users owner ON owner.user_id = conversation.owner_id
WHERE $where
ORDER BY match_score DESC, conversation.pin_time DESC
limit $limit;
17 changes: 9 additions & 8 deletions lib/ui/home/conversation/search_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class SearchList extends HookConsumerWidget {
),
if (users.isEmpty && isUrl)
SliverToBoxAdapter(
child: SearchItem(
child: SearchItemWidget(
name: context.l10n.openLink(keyword),
keyword: keyword,
maxLines: true,
Expand All @@ -185,7 +185,7 @@ class SearchList extends HookConsumerWidget {
),
if (users.isEmpty && isMixinNumber)
SliverToBoxAdapter(
child: SearchItem(
child: SearchItemWidget(
name: context.l10n.searchPlaceholderNumber + keyword,
keyword: keyword,
maxLines: true,
Expand Down Expand Up @@ -229,7 +229,7 @@ class SearchList extends HookConsumerWidget {
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final user = users[index];
return SearchItem(
return SearchItemWidget(
avatar: AvatarWidget(
name: user.fullName,
userId: user.userId,
Expand Down Expand Up @@ -339,7 +339,7 @@ class SearchList extends HookConsumerWidget {

return ConversationMenuWrapper(
searchConversation: conversation,
child: SearchItem(
child: SearchItemWidget(
avatar: ConversationAvatarWidget(
conversationId: conversation.conversationId,
fullName: conversation.validName,
Expand Down Expand Up @@ -454,7 +454,7 @@ class _SearchMaoUserWidget extends StatelessWidget {
child: Text(context.l10n.open),
);
}
return SearchItem(
return SearchItemWidget(
avatar: AvatarWidget(
name: maoUser.user.fullName,
userId: maoUser.user.userId,
Expand Down Expand Up @@ -499,8 +499,8 @@ class _SearchMaoUserWidget extends StatelessWidget {
}
}

class SearchItem extends StatelessWidget {
const SearchItem({
class SearchItemWidget extends StatelessWidget {
const SearchItemWidget({
required this.name,
required this.keyword,
required this.onTap,
Expand Down Expand Up @@ -592,6 +592,7 @@ class SearchItem extends StatelessWidget {
style: TextStyle(
color: context.theme.accent,
),
caseSensitive: false,
),
],
),
Expand Down Expand Up @@ -871,7 +872,7 @@ class SearchMessageItem extends HookConsumerWidget {
size: ConversationPage.conversationItemAvatarSize,
userId: message.ownerId,
);
return SearchItem(
return SearchItemWidget(
avatar: avatar,
name: showSender
? message.senderFullName ?? ''
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/provider/conversation_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class ConversationStateNotifier

unawaited(dismissByConversationId(conversationId));
context.providerContainer
.read(recentConversationIDsProvider)
.read(recentConversationIDsProvider.notifier)
.add(conversationId);
}

Expand Down
Loading

0 comments on commit 605afc4

Please sign in to comment.