Skip to content

Commit

Permalink
feat: Reaction Tries
Browse files Browse the repository at this point in the history
Added trie
Used emoji trie to handle searches and not require debounce
  • Loading branch information
alexrisch committed Dec 22, 2024
1 parent 13b0666 commit e6b841c
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 84 deletions.
7 changes: 4 additions & 3 deletions containers/GroupScreenAddition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import type { ConversationTopic } from "@xmtp/react-native-sdk";
import * as Haptics from "expo-haptics";
import { FC, useCallback, useMemo, useState } from "react";
import {
Alert,
Platform,
StyleSheet,
TouchableOpacity,
Expand All @@ -42,6 +41,7 @@ import {
saveGroupInviteLink,
saveInviteIdByGroupId,
} from "../features/GroupInvites/groupInvites.utils";
import { captureErrorWithToast } from "@/utils/capture-error";

type GroupScreenAdditionProps = {
topic: ConversationTopic;
Expand Down Expand Up @@ -113,8 +113,9 @@ export const GroupScreenAddition: FC<GroupScreenAdditionProps> = ({
setSnackMessage(translate("group_invite_link_created_copied"));
})
.catch((err) => {
console.error("Error creating group invite", err);
Alert.alert("An error occurred");
captureErrorWithToast(err, {
message: translate("group_opertation_an_error_occurred"),
});
});
}, [
currentAccount,
Expand Down
8 changes: 4 additions & 4 deletions containers/GroupScreenDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { captureErrorWithToast } from "@/utils/capture-error";
import { useCurrentAccount } from "@data/store/accountsStore";
import { useGroupDescription } from "@hooks/useGroupDescription";
import { useGroupMembers } from "@hooks/useGroupMembers";
Expand All @@ -9,11 +10,9 @@ import {
getAddressIsSuperAdmin,
} from "@utils/groupUtils/adminUtils";
import { memberCanUpdateGroup } from "@utils/groupUtils/memberCanUpdateGroup";
import logger from "@utils/logger";
import type { ConversationTopic } from "@xmtp/react-native-sdk";
import { FC, useCallback, useMemo, useState } from "react";
import {
Alert,
Pressable,
StyleSheet,
Text,
Expand Down Expand Up @@ -58,8 +57,9 @@ export const GroupScreenDescription: FC<GroupScreenDescriptionProps> = ({
try {
await setGroupDescription(editedDescription);
} catch (e) {
logger.error(e);
Alert.alert("An error occurred");
captureErrorWithToast(e, {
message: translate("group_opertation_an_error_occurred"),
});
}
}, [editedDescription, setGroupDescription]);

Expand Down
9 changes: 5 additions & 4 deletions containers/GroupScreenName.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useGroupName } from "@/hooks/useGroupName";
import { translate } from "@/i18n";
import { captureErrorWithToast } from "@/utils/capture-error";
import { useCurrentAccount } from "@data/store/accountsStore";
import { useGroupMembers } from "@hooks/useGroupMembers";
import { useGroupPermissions } from "@hooks/useGroupPermissions";
Expand All @@ -8,12 +10,10 @@ import {
getAddressIsSuperAdmin,
} from "@utils/groupUtils/adminUtils";
import { memberCanUpdateGroup } from "@utils/groupUtils/memberCanUpdateGroup";
import logger from "@utils/logger";
import { formatGroupName } from "@utils/str";
import type { ConversationTopic } from "@xmtp/react-native-sdk";
import React, { FC, useCallback, useMemo, useState } from "react";
import {
Alert,
Pressable,
StyleSheet,
Text,
Expand Down Expand Up @@ -51,8 +51,9 @@ export const GroupScreenName: FC<GroupScreenNameProps> = ({ topic }) => {
setEditing(false);
await updateGroupName(editedName);
} catch (e) {
logger.error(e);
Alert.alert("An error occurred");
captureErrorWithToast(e, {
message: translate("group_opertation_an_error_occurred"),
});
}
}, [editedName, updateGroupName]);
const canEditGroupName = memberCanUpdateGroup(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AnimatedVStack } from "@/design-system/VStack";
import { BottomSheetFlashList } from "@design-system/BottomSheet/BottomSheetFlashList";
import { BottomSheetFlatList } from "@design-system/BottomSheet/BottomSheetFlatList";
import { ListRenderItem as FlashListRenderItem } from "@shopify/flash-list";
import { CategorizedEmojisRecord } from "@utils/emojis/interfaces";
import { ICategorizedEmojisRecord } from "@utils/emojis/emoji-types";
import React, { FC, useCallback, useEffect } from "react";
import {
ListRenderItem,
Expand All @@ -20,7 +20,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { EmojiRow } from "./conversation-message-context-menu-emoji-picker-row";

type EmojiRowListProps = {
emojis: CategorizedEmojisRecord[];
emojis: ICategorizedEmojisRecord[];
ListHeader?: React.ReactNode;
onPress: (emoji: string) => void;
};
Expand Down Expand Up @@ -51,8 +51,8 @@ export const EmojiRowList: FC<EmojiRowListProps> = ({
);
}, [emojis.length, height, windowHeight]);

const renderItem: ListRenderItem<CategorizedEmojisRecord> &
FlashListRenderItem<CategorizedEmojisRecord> = useCallback(
const renderItem: ListRenderItem<ICategorizedEmojisRecord> &
FlashListRenderItem<ICategorizedEmojisRecord> = useCallback(
({ item }) => <EmojiRow onPress={onPress} item={item} />,
[onPress]
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { CategorizedEmojisRecord, Emoji } from "@utils/emojis/interfaces";
import { ICategorizedEmojisRecord, IEmoji } from "@utils/emojis/emoji-types";
import { FC, memo, useMemo } from "react";
import { Platform, Pressable, StyleSheet, Text, View } from "react-native";

type EmojiRowProps = {
item: CategorizedEmojisRecord;
item: ICategorizedEmojisRecord;
onPress: (emoji: string) => void;
};

export const EmojiRow: FC<EmojiRowProps> = memo(({ item, onPress }) => {
const items = useMemo(() => {
const sliced: (string | Emoji)[] = item.emojis.slice(0, 6);
const sliced: (string | IEmoji)[] = item.emojis.slice(0, 6);
while (sliced.length < 6) {
sliced.push("");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EmojiRowList } from "@/features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker-list";
import { messageContextMenuEmojiPickerBottomSheetRef } from "@/features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker-utils";
import { emojiTrie } from "@/utils/emojis/emoji-trie";
import { BottomSheetContentContainer } from "@design-system/BottomSheet/BottomSheetContentContainer";
import { BottomSheetHeader } from "@design-system/BottomSheet/BottomSheetHeader";
import { BottomSheetModal } from "@design-system/BottomSheet/BottomSheetModal";
Expand All @@ -9,16 +10,14 @@ import { VStack } from "@design-system/VStack";
import { translate } from "@i18n";
import { ThemedStyle, useAppTheme } from "@theme/useAppTheme";
import { emojis } from "@utils/emojis/emojis";
import { CategorizedEmojisRecord, Emoji } from "@utils/emojis/interfaces";
import { matchSorter } from "match-sorter";
import { debounce } from "perfect-debounce";
import { memo, useCallback, useMemo, useRef, useState } from "react";
import { ICategorizedEmojisRecord, IEmoji } from "@utils/emojis/emoji-types";
import { memo, useCallback, useRef, useState } from "react";
import { TextInput, TextStyle, ViewStyle } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

const flatEmojis = emojis.flatMap((category) => category.data);

const categorizedEmojis: CategorizedEmojisRecord[] = [];
const categorizedEmojis: ICategorizedEmojisRecord[] = [];
emojis.forEach((category, index) => {
for (let i = 0; i < category.data.length; i += 6) {
const slicedEmojis = category.data.slice(i, i + 6).map((emoji) => emoji);
Expand All @@ -30,8 +29,8 @@ emojis.forEach((category, index) => {
}
});

const sliceEmojis = (emojis: Emoji[]) => {
const slicedEmojis: CategorizedEmojisRecord[] = [];
const sliceEmojis = (emojis: IEmoji[]) => {
const slicedEmojis: ICategorizedEmojisRecord[] = [];
for (let i = 0; i < emojis.length; i += 6) {
const sliced = emojis.slice(i, i + 6).map((emoji) => emoji);
slicedEmojis.push({
Expand All @@ -43,19 +42,6 @@ const sliceEmojis = (emojis: Emoji[]) => {
return slicedEmojis;
};

const filterEmojis = (text: string) => {
const cleanedSearch = text.toLowerCase().trim();
if (cleanedSearch.length === 0) {
return defaultEmojis;
}
return sliceEmojis(
matchSorter(flatEmojis, cleanedSearch, {
keys: ["keywords", "name", "emoji"],
threshold: matchSorter.rankings.CONTAINS, // Use a less strict threshold
})
);
};

const defaultEmojis = sliceEmojis(flatEmojis);

export const MessageContextMenuEmojiPicker = memo(
Expand Down Expand Up @@ -84,28 +70,26 @@ export const MessageContextMenuEmojiPicker = memo(
[onSelectReaction, closeMenu]
);

const debouncedFilter = useMemo(
() =>
debounce((value: string) => {
const filtered = filterEmojis(value);
setFilteredReactions(filtered);
setHasInput(value.length > 0);
}, 300),
[]
);

const onTextInputChange = useCallback(
(value: string) => {
if (value.trim() === "") {
// Reset immediately when input is cleared
setFilteredReactions(defaultEmojis);
setHasInput(false);
} else {
debouncedFilter(value);
}
},
[debouncedFilter]
);
const onTextInputChange = useCallback((value: string) => {
if (value.trim() === "") {
// Reset immediately when input is cleared
setFilteredReactions(defaultEmojis);
setHasInput(false);
} else {
const emojiSet = new Set();
const emojis = emojiTrie.findAllWithPrefix(value);
const dedupedEmojis = emojis.filter((emoji) => {
if (emojiSet.has(emoji.emoji)) {
return false;
}
emojiSet.add(emoji.emoji);
return true;
});
const sliced = sliceEmojis(dedupedEmojis);
setFilteredReactions(sliced);
setHasInput(true);
}
}, []);

return (
<BottomSheetModal
Expand Down
1 change: 1 addition & 0 deletions i18n/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export const en = {
approve: "Approve",
deny: "Deny",
approve_member_to_this_group: "Approve {{name}} to this group",
group_opertation_an_error_occurred: "An error occurred",

// New Group
new_group: {
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@
"i18n-js": "3.9.2",
"ipfs-car": "^1.0.0",
"libphonenumber-js": "^1.10.49",
"match-sorter": "^6.3.4",
"mime": "^4.0.4",
"os-browserify": "^0.3.0",
"patch-package": "^6.5.1",
Expand Down
13 changes: 13 additions & 0 deletions utils/emojis/emoji-trie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Trie } from "../trie";
import { emojis } from "./emojis";
import { IEmoji } from "./emoji-types";

export const emojiTrie = new Trie<IEmoji>();

emojis.forEach((emojiSections) => {
emojiSections.data.forEach((emoji) => {
emoji.keywords.forEach((keyword) => {
emojiTrie.insert(keyword, emoji);
});
});
});
7 changes: 4 additions & 3 deletions utils/emojis/interfaces.ts → utils/emojis/emoji-types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export type Emoji = {
export type IEmoji = {
emoji: string;
name: string;
v: string;
toneEnabled: boolean;
keywords: string[];
};

export type CategorizedEmojisRecord = {
export type ICategorizedEmojisRecord = {
id: string;
category: string;
emojis: Emoji[];
emojis: IEmoji[];
};
Loading

0 comments on commit e6b841c

Please sign in to comment.