feat: add user color customization and metadata handling

- Implemented `useUserColor` hook to manage user profile color stored in avatar PNG metadata.
- Added `useOtherUserColor` hook to fetch and cache colors from other users' avatars.
- Created utility functions for reading and writing PNG metadata, enabling color embedding and extraction.
- Refactored `SearchResultGroup`, `RoomInput`, `Message`, `RoomPinMenu`, and `Notifications` components to utilize user color for display.
- Introduced `UserColorPicker` component in settings for users to select and save their profile color.
- Enhanced notification and message rendering to prioritize custom user colors over legacy colors.
This commit is contained in:
2026-02-06 05:53:37 +11:00
parent 4ca4af0e8b
commit 516000a25f
11 changed files with 1061 additions and 222 deletions

View File

@@ -117,6 +117,8 @@ import { useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { useComposingCheck } from '../../hooks/useComposingCheck';
import { useOtherUserColor } from '../../hooks/useUserColor';
import { getMemberAvatarMxc } from '../../utils/room';
interface RoomInputProps {
editor: Editor;
@@ -142,6 +144,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(roomId));
const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId));
const replyUserID = replyDraft?.userId;
const replyAvatarMxc = replyUserID ? getMemberAvatarMxc(room, replyUserID) : undefined;
const powerLevelTags = usePowerLevelTags(room, powerLevels);
const creatorsTag = useRoomCreatorsTag();
@@ -153,12 +156,16 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
powerLevelTags
);
// Get custom user color from avatar metadata
const replyCustomColor = useOtherUserColor(replyUserID ?? '', replyAvatarMxc);
const replyPowerTag = replyUserID ? getMemberPowerTag(replyUserID) : undefined;
const replyPowerColor = replyPowerTag?.color
? accessibleTagColors.get(replyPowerTag.color)
: undefined;
// Priority: custom user color > legacy/direct colorMXID > tag color
const replyUsernameColor =
legacyUsernameColor || direct ? colorMXID(replyUserID ?? '') : replyPowerColor;
replyCustomColor ?? (legacyUsernameColor || direct ? colorMXID(replyUserID ?? '') : replyPowerColor);
const [uploadBoard, setUploadBoard] = useState(true);
const [selectedFiles, setSelectedFiles] = useAtom(roomIdToUploadItemsAtomFamily(roomId));