One-time Voice: Add ability to create one-time voice (#4232)

This commit is contained in:
Alexander Zinchuk 2024-02-23 14:05:38 +01:00
parent e638a537dd
commit 9e9c9bd672
16 changed files with 217 additions and 152 deletions

View File

@ -929,6 +929,7 @@ function buildUploadingMedia(
audio, audio,
shouldSendAsFile, shouldSendAsFile,
shouldSendAsSpoiler, shouldSendAsSpoiler,
ttlSeconds,
} = attachment; } = attachment;
if (!shouldSendAsFile) { if (!shouldSendAsFile) {
@ -973,6 +974,7 @@ function buildUploadingMedia(
duration, duration,
waveform: inputWaveform, waveform: inputWaveform,
}, },
ttlSeconds,
}; };
} }
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) { if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {

View File

@ -624,7 +624,7 @@ export async function rescheduleMessage({
async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) { async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) {
const { const {
filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile, shouldSendAsSpoiler, filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile, shouldSendAsSpoiler, ttlSeconds,
} = attachment; } = attachment;
const patchedOnProgress: ApiOnProgress = (progress) => { const patchedOnProgress: ApiOnProgress = (progress) => {
@ -698,6 +698,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
thumb, thumb,
forceFile: shouldSendAsFile, forceFile: shouldSendAsFile,
spoiler: shouldSendAsSpoiler, spoiler: shouldSendAsSpoiler,
ttlSeconds,
}); });
} }

View File

@ -52,6 +52,7 @@ export interface ApiAttachment {
shouldSendAsSpoiler?: true; shouldSendAsSpoiler?: true;
uniqueId?: string; uniqueId?: string;
ttlSeconds?: number;
} }
export interface ApiWallpaper { export interface ApiWallpaper {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M0 15C0 6.716 6.716 0 15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15Zm13.636-6.818a1.364 1.364 0 1 0 0 2.727h.682c.377 0 .682.305.682.682v8.864a1.364 1.364 0 1 0 2.727 0V11.59a3.41 3.41 0 0 0-3.409-3.41z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@ -1,6 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill-rule="evenodd" d="M16 2.91C8.77 2.91 2.91 8.77 2.91 16c0 7.23 5.86 13.09 13.09 13.09.698 0 1.383-.054 2.05-.159a1.455 1.455 0 0 1 .452 2.874C17.686 31.934 16.85 32 16 32 7.163 32 0 24.837 0 16S7.163 0 16 0c.85 0 1.686.066 2.502.195a1.455 1.455 0 0 1-.452 2.874 13.2 13.2 0 0 0-2.05-.16Zm7.374.466a1.455 1.455 0 0 1 2.032-.32 16.084 16.084 0 0 1 3.539 3.538 1.455 1.455 0 1 1-2.353 1.712 13.175 13.175 0 0 0-2.898-2.898 1.455 1.455 0 0 1-.32-2.032Zm6.769 8.91a1.455 1.455 0 0 1 1.662 1.212 16.103 16.103 0 0 1 0 5.004 1.455 1.455 0 1 1-2.873-.452 13.206 13.206 0 0 0 0-4.1 1.455 1.455 0 0 1 1.21-1.663Zm-1.519 11.088c.65.473.793 1.383.32 2.032a16.083 16.083 0 0 1-3.538 3.539 1.455 1.455 0 1 1-1.712-2.353 13.174 13.174 0 0 0 2.898-2.898 1.455 1.455 0 0 1 2.032-.32ZM14.546 8.727a1.455 1.455 0 1 0 0 2.91h.727c.401 0 .727.325.727.726v9.455a1.454 1.454 0 1 0 2.91 0v-9.455a3.636 3.636 0 0 0-3.637-3.636h-.727Z" clip-rule="evenodd"/></svg>
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 16 29.328125 C 16.734375 29.328125 17.328125 28.734375 17.328125 28 C 17.328125 27.265625 16.734375 26.671875 16 26.671875 Z M 16 5.328125 C 16.734375 5.328125 17.328125 4.734375 17.328125 4 C 17.328125 3.265625 16.734375 2.671875 16 2.671875 Z M 16 26.671875 C 10.105469 26.671875 5.328125 21.894531 5.328125 16 L 2.671875 16 C 2.671875 23.359375 8.640625 29.328125 16 29.328125 Z M 5.328125 16 C 5.328125 10.105469 10.105469 5.328125 16 5.328125 L 16 2.671875 C 8.640625 2.671875 2.671875 8.640625 2.671875 16 Z M 5.328125 16 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 29.273438 14.777344 C 29.207031 14.046875 28.558594 13.507812 27.828125 13.574219 C 27.097656 13.640625 26.5625 14.289062 26.628906 15.019531 Z M 24.207031 9.179688 C 24.675781 9.742188 25.515625 9.820312 26.078125 9.351562 C 26.640625 8.882812 26.71875 8.042969 26.25 7.480469 Z M 24.519531 5.75 C 23.957031 5.28125 23.117188 5.359375 22.648438 5.921875 C 22.179688 6.484375 22.257812 7.324219 22.820312 7.792969 Z M 16.980469 5.371094 C 17.710938 5.4375 18.359375 4.902344 18.425781 4.171875 C 18.492188 3.441406 17.953125 2.792969 17.222656 2.726562 Z M 14.777344 2.726562 C 14.046875 2.792969 13.507812 3.441406 13.574219 4.171875 C 13.640625 4.902344 14.289062 5.4375 15.019531 5.371094 Z M 9.179688 7.792969 C 9.742188 7.324219 9.820312 6.484375 9.351562 5.921875 C 8.878906 5.359375 8.042969 5.28125 7.480469 5.75 Z M 5.75 7.480469 C 5.28125 8.042969 5.359375 8.882812 5.921875 9.351562 C 6.484375 9.820312 7.324219 9.742188 7.792969 9.179688 Z M 5.371094 15.019531 C 5.4375 14.289062 4.902344 13.640625 4.171875 13.574219 C 3.441406 13.507812 2.792969 14.046875 2.726562 14.777344 Z M 2.726562 17.222656 C 2.792969 17.953125 3.441406 18.492188 4.171875 18.425781 C 4.902344 18.359375 5.4375 17.710938 5.371094 16.980469 Z M 7.792969 22.820312 C 7.324219 22.257812 6.484375 22.179688 5.921875 22.648438 C 5.359375 23.121094 5.28125 23.957031 5.75 24.519531 Z M 7.480469 26.25 C 8.042969 26.71875 8.878906 26.640625 9.351562 26.078125 C 9.820312 25.515625 9.742188 24.675781 9.179688 24.207031 Z M 15.019531 26.628906 C 14.289062 26.5625 13.640625 27.097656 13.574219 27.828125 C 13.507812 28.558594 14.046875 29.207031 14.777344 29.273438 Z M 17.222656 29.273438 C 17.953125 29.207031 18.492188 28.558594 18.425781 27.828125 C 18.359375 27.097656 17.710938 26.5625 16.980469 26.628906 Z M 22.820312 24.207031 C 22.257812 24.675781 22.179688 25.515625 22.648438 26.078125 C 23.117188 26.640625 23.957031 26.71875 24.519531 26.25 Z M 26.25 24.519531 C 26.71875 23.957031 26.640625 23.121094 26.078125 22.648438 C 25.515625 22.179688 24.675781 22.257812 24.207031 22.820312 Z M 26.628906 16.980469 C 26.5625 17.710938 27.097656 18.359375 27.828125 18.425781 C 28.558594 18.492188 29.207031 17.953125 29.273438 17.222656 Z M 29.328125 16 C 29.328125 15.589844 29.308594 15.179688 29.273438 14.777344 L 26.628906 15.019531 C 26.65625 15.34375 26.671875 15.671875 26.671875 16 Z M 26.25 7.480469 C 25.726562 6.851562 25.148438 6.273438 24.519531 5.75 L 22.820312 7.792969 C 23.324219 8.210938 23.789062 8.675781 24.207031 9.179688 Z M 17.222656 2.726562 C 16.816406 2.691406 16.40625 2.671875 16 2.671875 L 16 5.328125 C 16.332031 5.328125 16.660156 5.34375 16.980469 5.371094 Z M 16 2.671875 C 15.589844 2.671875 15.179688 2.691406 14.777344 2.726562 L 15.019531 5.371094 C 15.34375 5.34375 15.671875 5.328125 16 5.328125 Z M 7.480469 5.75 C 6.851562 6.273438 6.273438 6.851562 5.75 7.480469 L 7.792969 9.179688 C 8.210938 8.675781 8.675781 8.210938 9.179688 7.792969 Z M 2.726562 14.777344 C 2.691406 15.183594 2.671875 15.59375 2.671875 16 L 5.328125 16 C 5.328125 15.667969 5.34375 15.339844 5.371094 15.019531 Z M 2.671875 16 C 2.671875 16.410156 2.691406 16.820312 2.726562 17.222656 L 5.371094 16.980469 C 5.34375 16.65625 5.328125 16.328125 5.328125 16 Z M 5.75 24.519531 C 6.273438 25.148438 6.851562 25.726562 7.480469 26.25 L 9.179688 24.207031 C 8.675781 23.789062 8.210938 23.324219 7.792969 22.820312 Z M 14.777344 29.273438 C 15.179688 29.308594 15.589844 29.328125 16 29.328125 L 16 26.671875 C 15.667969 26.671875 15.339844 26.65625 15.019531 26.628906 Z M 16 29.328125 C 16.410156 29.328125 16.820312 29.308594 17.222656 29.273438 L 16.980469 26.628906 C 16.65625 26.65625 16.328125 26.671875 16 26.671875 Z M 24.519531 26.25 C 25.148438 25.726562 25.726562 25.148438 26.25 24.519531 L 24.207031 22.820312 C 23.789062 23.324219 23.324219 23.789062 22.820312 24.207031 Z M 29.273438 17.222656 C 29.308594 16.820312 29.328125 16.410156 29.328125 16 L 26.671875 16 C 26.671875 16.332031 26.65625 16.660156 26.628906 16.980469 Z M 16.726562 22.996094 C 15.890625 22.996094 15.320312 22.433594 15.320312 21.582031 L 15.320312 11.898438 L 15.261719 11.898438 L 13.285156 13.273438 C 13.023438 13.460938 12.824219 13.527344 12.535156 13.527344 C 11.960938 13.527344 11.539062 13.117188 11.539062 12.515625 C 11.539062 12.085938 11.710938 11.765625 12.148438 11.457031 L 14.832031 9.601562 C 15.488281 9.148438 15.9375 9.066406 16.519531 9.066406 C 17.523438 9.066406 18.121094 9.675781 18.121094 10.648438 L 18.121094 21.582031 C 18.121094 22.433594 17.558594 22.996094 16.726562 22.996094 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -26,6 +26,22 @@
.toogle-play-wrapper { .toogle-play-wrapper {
margin: 0; margin: 0;
.icon-view-once {
position: absolute;
padding: 0.125rem;
left: 2rem;
bottom: 0;
font-size: 1rem;
border-radius: 50%;
color: var(--color-white);
background-color: var(--color-primary);
outline: var(--background-color) solid 0.125rem;
z-index: var(--z-badge);
opacity: 1;
transform: scale(1);
transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.toggle-play { .toggle-play {
margin-inline-end: 0.5rem; margin-inline-end: 0.5rem;
@ -66,27 +82,16 @@
transform: scale(1); transform: scale(1);
transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
&.play .icon-pause,
&.pause .icon-play,
&.loading .icon-play,
&.loading .icon-pause,
&.loading .flame {
opacity: 0;
transform: scale(0.5);
}
} }
.icon-view-once { &.play .icon-pause,
position: absolute; &.pause .icon-play,
left: 2rem; &.loading .icon-play,
bottom: 0rem; &.loading .icon-pause,
font-size: 1.1875rem; &.loading .flame,
border-radius: 50%; &.loading .icon-view-once {
color: var(--color-white); opacity: 0;
background-color: var(--color-primary); transform: scale(0.5);
outline: var(--background-color) solid 0.125rem;
z-index: var(--z-badge);
} }
} }

View File

@ -123,7 +123,7 @@ const Audio: FC<OwnProps> = ({
const { isRtl } = lang; const { isRtl } = lang;
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const [isActivated, setIsActivated] = useState(Boolean(autoPlay)); const [isActivated, setIsActivated] = useState(false);
const shouldLoad = isActivated || PRELOAD; const shouldLoad = isActivated || PRELOAD;
const coverHash = getMessageMediaHash(message, 'pictogram'); const coverHash = getMessageMediaHash(message, 'pictogram');
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl); const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);
@ -167,13 +167,14 @@ const Audio: FC<OwnProps> = ({
bufferingHandlers, bufferingHandlers,
undefined, undefined,
checkBuffering, checkBuffering,
isActivated, Boolean(isActivated || autoPlay),
handleForcePlay, handleForcePlay,
handleTrackChange, handleTrackChange,
isMessageLocal(message) || hasTtl, isMessageLocal(message) || hasTtl,
undefined, undefined,
onPause, onPause,
noReset, noReset,
hasTtl && !isInOneTimeModal,
); );
const reversePlayProgress = 1 - playProgress; const reversePlayProgress = 1 - playProgress;
@ -220,8 +221,7 @@ const Audio: FC<OwnProps> = ({
} }
if (hasTtl) { if (hasTtl) {
// Set new date to prevent saving state of the track openOneTimeMediaModal({ message });
openOneTimeMediaModal({ message: { ...message, date: Date.now() } });
onReadMedia?.(); onReadMedia?.();
return; return;
} }
@ -329,7 +329,7 @@ const Audio: FC<OwnProps> = ({
isSelected && 'audio-is-selected', isSelected && 'audio-is-selected',
); );
const buttonClassNames = ['toggle-play']; const buttonClassNames = ['toogle-play-wrapper'];
if (shouldRenderCross) { if (shouldRenderCross) {
buttonClassNames.push('loading'); buttonClassNames.push('loading');
} else { } else {
@ -371,13 +371,13 @@ const Audio: FC<OwnProps> = ({
function renderTooglePlayWrapper() { function renderTooglePlayWrapper() {
return ( return (
<div className="toogle-play-wrapper"> <div className={buildClassName(...buttonClassNames)}>
<Button <Button
round round
ripple={!isMobile} ripple={!isMobile}
size="smaller" size="smaller"
className="toggle-play"
color={coverBlobUrl ? 'translucent-white' : 'primary'} color={coverBlobUrl ? 'translucent-white' : 'primary'}
className={buttonClassNames.join(' ')}
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'} ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
onClick={handleButtonClick} onClick={handleButtonClick}
isRtl={lang.isRtl} isRtl={lang.isRtl}

View File

@ -63,7 +63,9 @@
.icon-schedule, .icon-schedule,
.icon-forward, .icon-forward,
.icon-microphone-alt, .icon-microphone-alt,
.icon-check { .icon-check,
.icon-view-once,
.icon-one-filled {
position: absolute; position: absolute;
} }
@ -123,7 +125,7 @@
} }
} }
&.send { &.send, &.sendOneTime {
.icon-send { .icon-send {
animation: grow-icon 0.4s ease-out; animation: grow-icon 0.4s ease-out;
} }
@ -192,6 +194,23 @@
} }
} }
&.view-once {
.icon-one-filled {
animation: hide-icon 0.4s forwards ease-out;
}
.icon-view-once {
animation: grow-icon 0.4s ease-out;
}
&.active {
.icon-view-once {
animation: hide-icon 0.4s forwards ease-out;
}
.icon-one-filled {
animation: grow-icon 0.4s ease-out;
}
}
}
&.not-ready > .icon { &.not-ready > .icon {
animation-duration: 0ms !important; animation-duration: 0ms !important;
} }

View File

@ -40,6 +40,7 @@ import {
EDITABLE_INPUT_MODAL_ID, EDITABLE_INPUT_MODAL_ID,
HEART_REACTION, HEART_REACTION,
MAX_UPLOAD_FILEPART_SIZE, MAX_UPLOAD_FILEPART_SIZE,
ONE_TIME_MEDIA_TTL_SECONDS,
REPLIES_USER_ID, REPLIES_USER_ID,
SCHEDULED_WHEN_ONLINE, SCHEDULED_WHEN_ONLINE,
SEND_MESSAGE_ACTION_INTERVAL, SEND_MESSAGE_ACTION_INTERVAL,
@ -157,6 +158,7 @@ import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
import Spinner from '../ui/Spinner'; import Spinner from '../ui/Spinner';
import Avatar from './Avatar'; import Avatar from './Avatar';
import DeleteMessageModal from './DeleteMessageModal.async'; import DeleteMessageModal from './DeleteMessageModal.async';
import Icon from './Icon';
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji'; import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
import './Composer.scss'; import './Composer.scss';
@ -203,7 +205,7 @@ type StateProps =
botKeyboardMessageId?: number; botKeyboardMessageId?: number;
botKeyboardPlaceholder?: string; botKeyboardPlaceholder?: string;
withScheduledButton?: boolean; withScheduledButton?: boolean;
shouldSchedule?: boolean; isInScheduledList?: boolean;
canScheduleUntilOnline?: boolean; canScheduleUntilOnline?: boolean;
stickersForEmoji?: ApiSticker[]; stickersForEmoji?: ApiSticker[];
customEmojiForEmoji?: ApiSticker[]; customEmojiForEmoji?: ApiSticker[];
@ -245,6 +247,7 @@ type StateProps =
shouldCollectDebugLogs?: boolean; shouldCollectDebugLogs?: boolean;
sentStoryReaction?: ApiReaction; sentStoryReaction?: ApiReaction;
stealthMode?: ApiStealthMode; stealthMode?: ApiStealthMode;
canSendOneTimeMedia?: boolean;
}; };
enum MainButtonState { enum MainButtonState {
@ -253,6 +256,7 @@ enum MainButtonState {
Edit = 'edit', Edit = 'edit',
Schedule = 'schedule', Schedule = 'schedule',
Forward = 'forward', Forward = 'forward',
SendOneTime = 'sendOneTime',
} }
type ScheduledMessageArgs = TabState['contentToBeScheduled'] | { type ScheduledMessageArgs = TabState['contentToBeScheduled'] | {
@ -273,7 +277,7 @@ const Composer: FC<OwnProps & StateProps> = ({
type, type,
isOnActiveTab, isOnActiveTab,
dropAreaState, dropAreaState,
shouldSchedule, isInScheduledList,
canScheduleUntilOnline, canScheduleUntilOnline,
isReady, isReady,
isMobile, isMobile,
@ -346,6 +350,7 @@ const Composer: FC<OwnProps & StateProps> = ({
shouldCollectDebugLogs, shouldCollectDebugLogs,
sentStoryReaction, sentStoryReaction,
stealthMode, stealthMode,
canSendOneTimeMedia,
onForward, onForward,
}) => { }) => {
const { const {
@ -544,6 +549,9 @@ const Composer: FC<OwnProps & StateProps> = ({
currentRecordTime, currentRecordTime,
recordButtonRef: mainButtonRef, recordButtonRef: mainButtonRef,
startRecordTimeRef, startRecordTimeRef,
isViewOnceEnabled,
setIsViewOnceEnabled,
toogleViewOnceEnabled,
} = useVoiceRecording(); } = useVoiceRecording();
const shouldSendRecordingStatus = isForCurrentMessageList && !isInStoryViewer; const shouldSendRecordingStatus = isForCurrentMessageList && !isInStoryViewer;
@ -753,16 +761,16 @@ const Composer: FC<OwnProps & StateProps> = ({
return MainButtonState.Record; return MainButtonState.Record;
} }
if (shouldSchedule) { if (isInScheduledList) {
return MainButtonState.Schedule; return MainButtonState.Schedule;
} }
return MainButtonState.Send; return MainButtonState.Send;
}, [ }, [
activeVoiceRecording, editingMessage, getHtml, hasAttachments, isForwarding, isInputHasFocus, onForward, activeVoiceRecording, editingMessage, getHtml, hasAttachments, isForwarding, isInputHasFocus, onForward,
shouldForceShowEditing, shouldSchedule, shouldForceShowEditing, isInScheduledList,
]); ]);
const canShowCustomSendMenu = !shouldSchedule; const canShowCustomSendMenu = !isInScheduledList;
const { const {
isContextMenuOpen: isCustomSendMenuOpen, isContextMenuOpen: isCustomSendMenuOpen,
@ -928,12 +936,13 @@ const Composer: FC<OwnProps & StateProps> = ({
if (activeVoiceRecording) { if (activeVoiceRecording) {
const record = await stopRecordingVoice(); const record = await stopRecordingVoice();
const ttlSeconds = isViewOnceEnabled ? ONE_TIME_MEDIA_TTL_SECONDS : undefined;
if (record) { if (record) {
const { blob, duration, waveform } = record; const { blob, duration, waveform } = record;
currentAttachments = [await buildAttachment( currentAttachments = [await buildAttachment(
VOICE_RECORDING_FILENAME, VOICE_RECORDING_FILENAME,
blob, blob,
{ voice: { duration, waveform } }, { voice: { duration, waveform }, ttlSeconds },
)]; )];
} }
} }
@ -1082,7 +1091,7 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
if (shouldSchedule || isScheduleRequested) { if (isInScheduledList || isScheduleRequested) {
forceShowSymbolMenu(); forceShowSymbolMenu();
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
cancelForceShowSymbolMenu(); cancelForceShowSymbolMenu();
@ -1115,7 +1124,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isPreloadedGlobally: true, isPreloadedGlobally: true,
}; };
if (shouldSchedule || isScheduleRequested) { if (isInScheduledList || isScheduleRequested) {
forceShowSymbolMenu(); forceShowSymbolMenu();
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
cancelForceShowSymbolMenu(); cancelForceShowSymbolMenu();
@ -1144,7 +1153,7 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
if (shouldSchedule || isScheduleRequested) { if (isInScheduledList || isScheduleRequested) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
handleMessageSchedule({ handleMessageSchedule({
id: inlineResult.id, id: inlineResult.id,
@ -1184,7 +1193,7 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
if (shouldSchedule) { if (isInScheduledList) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
handleMessageSchedule({ poll }, scheduledAt, currentMessageList); handleMessageSchedule({ poll }, scheduledAt, currentMessageList);
}); });
@ -1196,7 +1205,7 @@ const Composer: FC<OwnProps & StateProps> = ({
}); });
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => { const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
if (shouldSchedule) { if (isInScheduledList) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt, currentMessageList!); handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt, currentMessageList!);
}); });
@ -1340,6 +1349,7 @@ const Composer: FC<OwnProps & StateProps> = ({
showAllowedMessageTypesNotification({ chatId }); showAllowedMessageTypesNotification({ chatId });
} }
} else { } else {
setIsViewOnceEnabled(false);
void startRecordingVoice(); void startRecordingVoice();
} }
break; break;
@ -1513,7 +1523,7 @@ const Composer: FC<OwnProps & StateProps> = ({
shouldForceAsFile={shouldForceAsFile} shouldForceAsFile={shouldForceAsFile}
isForCurrentMessageList={isForCurrentMessageList} isForCurrentMessageList={isForCurrentMessageList}
isForMessage={isInMessageList} isForMessage={isInMessageList}
shouldSchedule={shouldSchedule} shouldSchedule={isInScheduledList}
forceDarkTheme={isInStoryViewer} forceDarkTheme={isInStoryViewer}
onCaptionUpdate={onCaptionUpdate} onCaptionUpdate={onCaptionUpdate}
onSendSilent={handleSendSilentAttachments} onSendSilent={handleSendSilentAttachments}
@ -1762,7 +1772,7 @@ const Composer: FC<OwnProps & StateProps> = ({
canSendAudios={canSendAudios} canSendAudios={canSendAudios}
onFileSelect={handleFileSelect} onFileSelect={handleFileSelect}
onPollCreate={openPollModal} onPollCreate={openPollModal}
isScheduled={shouldSchedule} isScheduled={isInScheduledList}
attachBots={isInMessageList ? attachBots : undefined} attachBots={isInMessageList ? attachBots : undefined}
peerType={attachMenuPeerType} peerType={attachMenuPeerType}
shouldCollectDebugLogs={shouldCollectDebugLogs} shouldCollectDebugLogs={shouldCollectDebugLogs}
@ -1813,6 +1823,18 @@ const Composer: FC<OwnProps & StateProps> = ({
/> />
</div> </div>
</div> </div>
{canSendOneTimeMedia && activeVoiceRecording && (
<Button
className={buildClassName('view-once', isViewOnceEnabled && 'active')}
round
color="secondary"
ariaLabel={lang('Chat.PlayOnceVoiceMessageTooltip')}
onClick={toogleViewOnceEnabled}
>
<Icon name="view-once" />
<Icon name="one-filled" />
</Button>
)}
{activeVoiceRecording && ( {activeVoiceRecording && (
<Button <Button
round round
@ -1883,10 +1905,10 @@ const Composer: FC<OwnProps & StateProps> = ({
{canShowCustomSendMenu && ( {canShowCustomSendMenu && (
<CustomSendMenu <CustomSendMenu
isOpen={isCustomSendMenuOpen} isOpen={isCustomSendMenuOpen}
canSchedule={isInMessageList} canSchedule={isInMessageList && !isViewOnceEnabled}
canScheduleUntilOnline={canScheduleUntilOnline} canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined} onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
onSendSchedule={!shouldSchedule ? handleSendScheduled : undefined} onSendSchedule={!isInScheduledList ? handleSendScheduled : undefined}
onSendWhenOnline={handleSendWhenOnline} onSendWhenOnline={handleSendWhenOnline}
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
@ -1958,6 +1980,7 @@ export default memo(withGlobal<OwnProps>(
const replyToTopic = chat?.isForum && chat.isForumAsMessages && threadId === MAIN_THREAD_ID && replyToMessage const replyToTopic = chat?.isForum && chat.isForumAsMessages && threadId === MAIN_THREAD_ID && replyToMessage
? selectTopicFromMessage(global, replyToMessage) ? selectTopicFromMessage(global, replyToMessage)
: undefined; : undefined;
const isInScheduledList = messageListType === 'scheduled';
return { return {
availableReactions: type === 'story' ? global.availableReactions : undefined, availableReactions: type === 'story' ? global.availableReactions : undefined,
@ -1977,7 +2000,7 @@ export default memo(withGlobal<OwnProps>(
messageListType === 'thread' messageListType === 'thread'
&& Boolean(scheduledIds?.length) && Boolean(scheduledIds?.length)
), ),
shouldSchedule: messageListType === 'scheduled', isInScheduledList,
botKeyboardMessageId, botKeyboardMessageId,
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder, botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
isForwarding: chatId === tabState.forwardMessages.toChatId, isForwarding: chatId === tabState.forwardMessages.toChatId,
@ -2019,6 +2042,7 @@ export default memo(withGlobal<OwnProps>(
isReactionPickerOpen: selectIsReactionPickerOpen(global), isReactionPickerOpen: selectIsReactionPickerOpen(global),
canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global), canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global),
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
canSendOneTimeMedia: isChatWithUser && !isChatWithBot && !isInScheduledList,
shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs, shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs,
sentStoryReaction, sentStoryReaction,
stealthMode: global.stories.stealthMode, stealthMode: global.stories.stealthMode,

View File

@ -17,6 +17,7 @@ const useVoiceRecording = () => {
const [activeVoiceRecording, setActiveVoiceRecording] = useState<ActiveVoiceRecording>(); const [activeVoiceRecording, setActiveVoiceRecording] = useState<ActiveVoiceRecording>();
const startRecordTimeRef = useRef<number>(); const startRecordTimeRef = useRef<number>();
const [currentRecordTime, setCurrentRecordTime] = useState<number | undefined>(); const [currentRecordTime, setCurrentRecordTime] = useState<number | undefined>();
const [isViewOnceEnabled, setIsViewOnceEnabled] = useState(false);
useEffect(() => { useEffect(() => {
// Preloading worker fixes silent first record on iOS // Preloading worker fixes silent first record on iOS
@ -96,6 +97,10 @@ const useVoiceRecording = () => {
return activeVoiceRecording ? captureEscKeyListener(stopRecordingVoice) : undefined; return activeVoiceRecording ? captureEscKeyListener(stopRecordingVoice) : undefined;
}, [activeVoiceRecording, stopRecordingVoice]); }, [activeVoiceRecording, stopRecordingVoice]);
const toogleViewOnceEnabled = useLastCallback(() => {
setIsViewOnceEnabled(!isViewOnceEnabled);
});
return { return {
startRecordingVoice, startRecordingVoice,
pauseRecordingVoice, pauseRecordingVoice,
@ -104,6 +109,9 @@ const useVoiceRecording = () => {
currentRecordTime, currentRecordTime,
recordButtonRef, recordButtonRef,
startRecordTimeRef, startRecordTimeRef,
isViewOnceEnabled,
setIsViewOnceEnabled,
toogleViewOnceEnabled,
}; };
}; };

View File

@ -343,3 +343,5 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
recommendedChannels: [10, 100], recommendedChannels: [10, 100],
savedDialogsPinned: [5, 100], savedDialogsPinned: [5, 100],
}; };
export const ONE_TIME_MEDIA_TTL_SECONDS = 2147483647;

View File

@ -34,6 +34,7 @@ const useAudioPlayer = (
noProgressUpdates = false, noProgressUpdates = false,
onPause?: NoneToVoidFunction, onPause?: NoneToVoidFunction,
noReset = false, noReset = false,
noHandleEvents = false,
) => { ) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const controllerRef = useRef<ReturnType<typeof register>>(null); const controllerRef = useRef<ReturnType<typeof register>>(null);
@ -50,6 +51,9 @@ const useAudioPlayer = (
useSyncEffect(() => { useSyncEffect(() => {
controllerRef.current = register(trackId, trackType, (eventName, e) => { controllerRef.current = register(trackId, trackType, (eventName, e) => {
if (noHandleEvents) {
return;
}
switch (eventName) { switch (eventName) {
case 'onPlay': { case 'onPlay': {
const { const {
@ -67,7 +71,6 @@ const useAudioPlayer = (
if (trackType === 'voice' || duration > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION) { if (trackType === 'voice' || duration > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION) {
setPlaybackRate(audioPlayer.playbackRate); setPlaybackRate(audioPlayer.playbackRate);
} }
setPositionState({ setPositionState({
duration: proxy.duration || 0, duration: proxy.duration || 0,
playbackRate: proxy.playbackRate, playbackRate: proxy.playbackRate,
@ -103,7 +106,6 @@ const useAudioPlayer = (
break; break;
} }
} }
handlers?.[eventName]?.(e); handlers?.[eventName]?.(e);
}, onForcePlay, handleTrackChange); }, onForcePlay, handleTrackChange);
@ -116,7 +118,7 @@ const useAudioPlayer = (
isPlayingSync = true; isPlayingSync = true;
} }
if (onInit) { if (onInit && !noHandleEvents) {
onInit(proxy); onInit(proxy);
} }
}, [trackId]); }, [trackId]);
@ -159,7 +161,7 @@ const useAudioPlayer = (
// Autoplay once `src` is present // Autoplay once `src` is present
useEffectWithPrevDeps(([prevShouldPlay, prevSrc]) => { useEffectWithPrevDeps(([prevShouldPlay, prevSrc]) => {
if (prevShouldPlay === shouldPlay && src === prevSrc && trackType !== 'oneTimeVoice') { if (prevShouldPlay === shouldPlay && src === prevSrc) {
return; return;
} }

View File

@ -157,102 +157,103 @@ $icons-map: (
"next": "\f17e", "next": "\f17e",
"noise-suppression": "\f17f", "noise-suppression": "\f17f",
"non-contacts": "\f180", "non-contacts": "\f180",
"open-in-new-tab": "\f181", "one-filled": "\f181",
"password-off": "\f182", "open-in-new-tab": "\f182",
"pause": "\f183", "password-off": "\f183",
"permissions": "\f184", "pause": "\f184",
"phone-discard-outline": "\f185", "permissions": "\f185",
"phone-discard": "\f186", "phone-discard-outline": "\f186",
"phone": "\f187", "phone-discard": "\f187",
"photo": "\f188", "phone": "\f188",
"pin-badge": "\f189", "photo": "\f189",
"pin-list": "\f18a", "pin-badge": "\f18a",
"pin": "\f18b", "pin-list": "\f18b",
"pinned-chat": "\f18c", "pin": "\f18c",
"pinned-message": "\f18d", "pinned-chat": "\f18d",
"pip": "\f18e", "pinned-message": "\f18e",
"play-story": "\f18f", "pip": "\f18f",
"play": "\f190", "play-story": "\f190",
"poll": "\f191", "play": "\f191",
"premium": "\f192", "poll": "\f192",
"previous": "\f193", "premium": "\f193",
"privacy-policy": "\f194", "previous": "\f194",
"quote-text": "\f195", "privacy-policy": "\f195",
"quote": "\f196", "quote-text": "\f196",
"readchats": "\f197", "quote": "\f197",
"recent": "\f198", "readchats": "\f198",
"reload": "\f199", "recent": "\f199",
"remove": "\f19a", "reload": "\f19a",
"reopen-topic": "\f19b", "remove": "\f19b",
"replace": "\f19c", "reopen-topic": "\f19c",
"replies": "\f19d", "replace": "\f19d",
"reply-filled": "\f19e", "replies": "\f19e",
"reply": "\f19f", "reply-filled": "\f19f",
"revote": "\f1a0", "reply": "\f1a0",
"save-story": "\f1a1", "revote": "\f1a1",
"saved-messages": "\f1a2", "save-story": "\f1a2",
"schedule": "\f1a3", "saved-messages": "\f1a3",
"search": "\f1a4", "schedule": "\f1a4",
"select": "\f1a5", "search": "\f1a5",
"send-outline": "\f1a6", "select": "\f1a6",
"send": "\f1a7", "send-outline": "\f1a7",
"settings-filled": "\f1a8", "send": "\f1a8",
"settings": "\f1a9", "settings-filled": "\f1a9",
"share-filled": "\f1aa", "settings": "\f1aa",
"share-screen-outlined": "\f1ab", "share-filled": "\f1ab",
"share-screen-stop": "\f1ac", "share-screen-outlined": "\f1ac",
"share-screen": "\f1ad", "share-screen-stop": "\f1ad",
"sidebar": "\f1ae", "share-screen": "\f1ae",
"skip-next": "\f1af", "sidebar": "\f1af",
"skip-previous": "\f1b0", "skip-next": "\f1b0",
"smallscreen": "\f1b1", "skip-previous": "\f1b1",
"smile": "\f1b2", "smallscreen": "\f1b2",
"sort": "\f1b3", "smile": "\f1b3",
"speaker-muted-story": "\f1b4", "sort": "\f1b4",
"speaker-outline": "\f1b5", "speaker-muted-story": "\f1b5",
"speaker-story": "\f1b6", "speaker-outline": "\f1b6",
"speaker": "\f1b7", "speaker-story": "\f1b7",
"spoiler-disable": "\f1b8", "speaker": "\f1b8",
"spoiler": "\f1b9", "spoiler-disable": "\f1b9",
"sport": "\f1ba", "spoiler": "\f1ba",
"stats": "\f1bb", "sport": "\f1bb",
"stealth-future": "\f1bc", "stats": "\f1bc",
"stealth-past": "\f1bd", "stealth-future": "\f1bd",
"stickers": "\f1be", "stealth-past": "\f1be",
"stop-raising-hand": "\f1bf", "stickers": "\f1bf",
"stop": "\f1c0", "stop-raising-hand": "\f1c0",
"story-caption": "\f1c1", "stop": "\f1c1",
"story-expired": "\f1c2", "story-caption": "\f1c2",
"story-priority": "\f1c3", "story-expired": "\f1c3",
"story-reply": "\f1c4", "story-priority": "\f1c4",
"strikethrough": "\f1c5", "story-reply": "\f1c5",
"timer": "\f1c6", "strikethrough": "\f1c6",
"transcribe": "\f1c7", "timer": "\f1c7",
"truck": "\f1c8", "transcribe": "\f1c8",
"unarchive": "\f1c9", "truck": "\f1c9",
"underlined": "\f1ca", "unarchive": "\f1ca",
"unlock-badge": "\f1cb", "underlined": "\f1cb",
"unlock": "\f1cc", "unlock-badge": "\f1cc",
"unmute": "\f1cd", "unlock": "\f1cd",
"unpin": "\f1ce", "unmute": "\f1ce",
"unread": "\f1cf", "unpin": "\f1cf",
"up": "\f1d0", "unread": "\f1d0",
"user-filled": "\f1d1", "up": "\f1d1",
"user-online": "\f1d2", "user-filled": "\f1d2",
"user": "\f1d3", "user-online": "\f1d3",
"video-outlined": "\f1d4", "user": "\f1d4",
"video-stop": "\f1d5", "video-outlined": "\f1d5",
"video": "\f1d6", "video-stop": "\f1d6",
"view-once": "\f1d7", "video": "\f1d7",
"voice-chat": "\f1d8", "view-once": "\f1d8",
"volume-1": "\f1d9", "voice-chat": "\f1d9",
"volume-2": "\f1da", "volume-1": "\f1da",
"volume-3": "\f1db", "volume-2": "\f1db",
"web": "\f1dc", "volume-3": "\f1dc",
"webapp": "\f1dd", "web": "\f1dd",
"word-wrap": "\f1de", "webapp": "\f1de",
"zoom-in": "\f1df", "word-wrap": "\f1df",
"zoom-out": "\f1e0", "zoom-in": "\f1e0",
"zoom-out": "\f1e1",
); );
.icon-active-sessions::before { .icon-active-sessions::before {
@ -639,6 +640,9 @@ $icons-map: (
.icon-non-contacts::before { .icon-non-contacts::before {
content: map.get($icons-map, "non-contacts"); content: map.get($icons-map, "non-contacts");
} }
.icon-one-filled::before {
content: map.get($icons-map, "one-filled");
}
.icon-open-in-new-tab::before { .icon-open-in-new-tab::before {
content: map.get($icons-map, "open-in-new-tab"); content: map.get($icons-map, "open-in-new-tab");
} }

Binary file not shown.

Binary file not shown.

View File

@ -127,6 +127,7 @@ export type FontIconName =
| 'next' | 'next'
| 'noise-suppression' | 'noise-suppression'
| 'non-contacts' | 'non-contacts'
| 'one-filled'
| 'open-in-new-tab' | 'open-in-new-tab'
| 'password-off' | 'password-off'
| 'pause' | 'pause'