Files
cinny-mobile/overlay/src/app/components/message/FileHeader.tsx
litruv 8cf31b929d
All checks were successful
Build / increment-version (push) Successful in 5s
Build / build-android (push) Successful in 5m5s
Fix Android media downloads and dismiss notifs when read elsewhere.
FileSaver fails in Capacitor WebView, so save via MediaStore; clear tray notifications when sync reports rooms as read on another device, and improve collapsed notification avatars with conversation shortcuts.
2026-07-24 15:26:35 +10:00

85 lines
2.8 KiB
TypeScript

import { Badge, Box, IconButton, Spinner, Text, as, toRem } from 'folds';
import { Icon, Icons } from '../icons';
import React, { ReactNode, useCallback } from 'react';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { mimeTypeToExt } from '../../utils/mimeTypes';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../utils/matrix';
import { getCurrentAccessToken } from '../../utils/auth';
import { saveMediaBlob } from '../../utils/saveMedia';
const badgeStyles = { maxWidth: toRem(100) };
type FileDownloadButtonProps = {
filename: string;
url: string;
mimeType: string;
encInfo?: EncryptedAttachmentInfo;
};
export function FileDownloadButton({ filename, url, mimeType, encInfo }: FileDownloadButtonProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [downloadState, download] = useAsyncCallback(
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
// Always use current session's token to avoid stale tokens during account switches
const accessToken = getCurrentAccessToken();
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
: await downloadMedia(mediaUrl, accessToken);
await saveMediaBlob(fileContent, filename);
return true;
}, [mx, url, useAuthentication, mimeType, encInfo, filename])
);
const downloading = downloadState.status === AsyncStatus.Loading;
const hasError = downloadState.status === AsyncStatus.Error;
return (
<IconButton
disabled={downloading}
onClick={download}
variant={hasError ? 'Critical' : 'SurfaceVariant'}
size="300"
radii="300"
>
{downloading ? (
<Spinner size="100" variant={hasError ? 'Critical' : 'Secondary'} />
) : (
<Icon size="100" src={Icons.Download} />
)}
</IconButton>
);
}
export type FileHeaderProps = {
body: string;
mimeType: string;
after?: ReactNode;
};
export const FileHeader = as<'div', FileHeaderProps>(({ body, mimeType, after, ...props }, ref) => (
<Box alignItems="Center" gap="200" grow="Yes" {...props} ref={ref}>
<Box shrink="No">
<Badge style={badgeStyles} variant="Secondary" radii="Pill">
<Text size="O400" truncate>
{mimeTypeToExt(mimeType)}
</Text>
</Badge>
</Box>
<Box grow="Yes">
<Text size="T300" truncate>
{body}
</Text>
</Box>
{after}
</Box>
));