feat: voice message recording + per-member encryption verification
CI / Build & Quality Checks (push) Successful in 10m20s
CI / Build & Quality Checks (push) Successful in 10m20s
- Add VoiceMessageRecorder component: mic button in composer toolbar, live waveform + timer, preview before send, MSC3245-compliant content (org.matrix.msc3245.voice, org.matrix.msc1767.audio with waveform), E2EE support via encryptFile before upload - Add useUserVerifiedStatus hook: uses crypto.getUserVerificationStatus, reacts live to CryptoEvent.UserTrustStatusChanged - MembersDrawer: show green/yellow shield badge per member in encrypted rooms (cross-signing verified/unverified), E2EE status banner in header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@ import { useTheme } from '../../hooks/useTheme';
|
||||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
import { useComposingCheck } from '../../hooks/useComposingCheck';
|
||||
import { VoiceMessageRecorder } from '../../components/VoiceMessageRecorder';
|
||||
|
||||
const GifPicker = React.lazy(() =>
|
||||
import('../../components/GifPicker').then((m) => ({ default: m.GifPicker })),
|
||||
@@ -201,6 +202,38 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
);
|
||||
};
|
||||
|
||||
const handleVoiceSend = useCallback(
|
||||
async (blob: Blob, mimeType: string, durationMs: number, waveform: number[]) => {
|
||||
const baseContent: IContent = {
|
||||
msgtype: MsgType.Audio,
|
||||
body: 'Voice message',
|
||||
filename: 'voice-message.ogg',
|
||||
'org.matrix.msc3245.voice': {},
|
||||
'org.matrix.msc1767.audio': { duration: durationMs, waveform },
|
||||
info: { mimetype: mimeType, size: blob.size, duration: durationMs },
|
||||
};
|
||||
|
||||
if (room.hasEncryptionStateEvent()) {
|
||||
const { encInfo, file: encBlob } = await encryptFile(blob);
|
||||
const uploadResult = await mx.uploadContent(encBlob);
|
||||
mx.sendMessage(roomId, {
|
||||
...baseContent,
|
||||
file: { ...encInfo, url: uploadResult.content_uri },
|
||||
} as any);
|
||||
} else {
|
||||
const uploadResult = await mx.uploadContent(blob, {
|
||||
name: 'voice-message.ogg',
|
||||
type: mimeType,
|
||||
});
|
||||
mx.sendMessage(roomId, {
|
||||
...baseContent,
|
||||
url: uploadResult.content_uri,
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
[mx, room, roomId],
|
||||
);
|
||||
|
||||
const [autocompleteQuery, setAutocompleteQuery] =
|
||||
useState<AutocompleteQuery<AutocompletePrefix>>();
|
||||
|
||||
@@ -849,6 +882,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
<Icon src={Icons.Pin} size="100" />
|
||||
)}
|
||||
</IconButton>
|
||||
<VoiceMessageRecorder onSend={handleVoiceSend} />
|
||||
<IconButton
|
||||
onClick={submit}
|
||||
variant="SurfaceVariant"
|
||||
|
||||
Reference in New Issue
Block a user