feat: enhance user profile customization with avatar border color and gradient options

- Added support for setting avatar border color and gradient in user profile settings.
- Introduced new hooks for managing user profile styles and fetching metadata from avatars.
- Implemented an AngleSelector component for selecting gradient direction.
- Updated RoomNavItem to display parent space information for direct messages.
- Improved caching mechanism for user banners and profile styles.
- Refactored image metadata handling to include new profile style attributes.
This commit is contained in:
2026-03-12 20:50:00 +11:00
parent fc30d81f8f
commit 834de012b4
13 changed files with 911 additions and 90 deletions

View File

@@ -206,7 +206,7 @@ export function useUserBanner(): [
}
// Cache for user banners to avoid refetching for each message
const userBannerCache = new Map<string, { banner: string | undefined; timestamp: number }>();
const userBannerCache = new Map<string, { bannerMxc: string | undefined; timestamp: number }>();
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
/**
@@ -228,38 +228,46 @@ export function useOtherUserBanner(userId: string, avatarMxc: string | undefined
return;
}
// Check cache first
const cacheKey = `${userId}:${avatarMxc}`;
const cached = userBannerCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
setBannerBlobUrl(cached.banner);
return;
}
const loadBanner = async () => {
const httpUrl = mxcUrlToHttp(mx, avatarMxc, useAuthentication);
if (!httpUrl) {
setBannerBlobUrl(undefined);
return;
}
const accessToken = mx.getAccessToken();
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
// Check cache first
const cacheKey = `${userId}:${avatarMxc}`;
const cached = userBannerCache.get(cacheKey);
if (!metadata.banner) {
// Cache the result
userBannerCache.set(cacheKey, { banner: undefined, timestamp: Date.now() });
let bannerMxc: string | undefined;
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
// Use cached banner MXC
bannerMxc = cached.bannerMxc;
} else {
// Fetch banner MXC from avatar metadata
const httpUrl = mxcUrlToHttp(mx, avatarMxc, useAuthentication);
if (!httpUrl) {
setBannerBlobUrl(undefined);
return;
}
const accessToken = mx.getAccessToken();
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
bannerMxc = metadata.banner;
// Cache the banner MXC (not the blob URL)
userBannerCache.set(cacheKey, { bannerMxc, timestamp: Date.now() });
}
if (!bannerMxc) {
setBannerBlobUrl(undefined);
return;
}
// Fetch the banner image data with authentication
const bannerHttpUrl = mxcUrlToHttp(mx, metadata.banner, useAuthentication);
const bannerHttpUrl = mxcUrlToHttp(mx, bannerMxc, useAuthentication);
if (!bannerHttpUrl) {
setBannerBlobUrl(undefined);
return;
}
const accessToken = mx.getAccessToken();
const headers: HeadersInit = {};
if (useAuthentication && accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
@@ -274,10 +282,6 @@ export function useOtherUserBanner(userId: string, avatarMxc: string | undefined
const blob = await response.blob();
blobUrl = URL.createObjectURL(blob);
// Cache the blob URL
userBannerCache.set(cacheKey, { banner: blobUrl, timestamp: Date.now() });
setBannerBlobUrl(blobUrl);
};