mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-25 04:36:46 +00:00
Show ready-to-download documents in media gallery
This commit is contained in:
88
ts/hooks/useAttachmentStatus.ts
Normal file
88
ts/hooks/useAttachmentStatus.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { getUrl, type AttachmentForUIType } from '../types/Attachment.js';
|
||||
import { MediaTier } from '../types/AttachmentDownload.js';
|
||||
import { missingCaseError } from '../util/missingCaseError.js';
|
||||
import { getAttachmentCiphertextSize } from '../util/AttachmentCrypto.js';
|
||||
import { useDelayedValue } from './useDelayedValue.js';
|
||||
|
||||
const TRANSITION_DELAY = 200;
|
||||
|
||||
type InternalState = 'NeedsDownload' | 'Downloading' | 'ReadyToShow';
|
||||
|
||||
export type AttachmentStatusType = Readonly<
|
||||
| {
|
||||
state: 'NeedsDownload';
|
||||
}
|
||||
| {
|
||||
state: 'Downloading';
|
||||
totalDownloaded: number | undefined;
|
||||
size: number;
|
||||
}
|
||||
| {
|
||||
state: 'ReadyToShow';
|
||||
}
|
||||
>;
|
||||
|
||||
export function useAttachmentStatus(
|
||||
attachment: AttachmentForUIType
|
||||
): AttachmentStatusType {
|
||||
const isAttachmentNotAvailable =
|
||||
attachment.isPermanentlyUndownloadable && !attachment.wasTooBig;
|
||||
|
||||
const url = getUrl(attachment);
|
||||
|
||||
let nextState: InternalState = 'ReadyToShow';
|
||||
if (attachment && isAttachmentNotAvailable) {
|
||||
nextState = 'ReadyToShow';
|
||||
} else if (attachment && url == null && !attachment.pending) {
|
||||
nextState = 'NeedsDownload';
|
||||
} else if (attachment && url == null && attachment.pending) {
|
||||
nextState = 'Downloading';
|
||||
}
|
||||
|
||||
const state = useDelayedValue(nextState, TRANSITION_DELAY);
|
||||
|
||||
// Idle
|
||||
if (state === 'NeedsDownload' && nextState === state) {
|
||||
return { state: 'NeedsDownload' };
|
||||
}
|
||||
|
||||
const { size: unpaddedPlaintextSize, totalDownloaded } = attachment;
|
||||
const size = getAttachmentCiphertextSize({
|
||||
unpaddedPlaintextSize,
|
||||
mediaTier: MediaTier.STANDARD,
|
||||
});
|
||||
|
||||
// Transition
|
||||
if (state !== nextState) {
|
||||
if (nextState === 'NeedsDownload') {
|
||||
return { state: 'NeedsDownload' };
|
||||
}
|
||||
|
||||
if (nextState === 'Downloading') {
|
||||
return { state: 'Downloading', size, totalDownloaded };
|
||||
}
|
||||
|
||||
if (nextState === 'ReadyToShow') {
|
||||
return { state: 'Downloading', size, totalDownloaded: size };
|
||||
}
|
||||
|
||||
throw missingCaseError(nextState);
|
||||
}
|
||||
|
||||
if (state === 'NeedsDownload') {
|
||||
return { state: 'NeedsDownload' };
|
||||
}
|
||||
|
||||
if (state === 'Downloading') {
|
||||
return { state: 'Downloading', size, totalDownloaded };
|
||||
}
|
||||
|
||||
if (state === 'ReadyToShow') {
|
||||
return { state: 'ReadyToShow' };
|
||||
}
|
||||
|
||||
throw missingCaseError(state);
|
||||
}
|
||||
63
ts/hooks/useDelayedValue.ts
Normal file
63
ts/hooks/useDelayedValue.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
type InternalState<Value> = Readonly<
|
||||
| {
|
||||
type: 'transition';
|
||||
from: Value;
|
||||
to: Value;
|
||||
}
|
||||
| {
|
||||
type: 'idle';
|
||||
value: Value;
|
||||
}
|
||||
>;
|
||||
|
||||
export function useDelayedValue<Value>(newValue: Value, delay: number): Value {
|
||||
const [state, setState] = useState<InternalState<Value>>({
|
||||
type: 'idle',
|
||||
value: newValue,
|
||||
});
|
||||
|
||||
const currentValue = state.type === 'idle' ? state.value : state.from;
|
||||
|
||||
useEffect(() => {
|
||||
if (state.type === 'idle') {
|
||||
return noop;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setState({
|
||||
type: 'idle',
|
||||
value: state.to,
|
||||
});
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [state, delay]);
|
||||
|
||||
useEffect(() => {
|
||||
setState(prevState => {
|
||||
if (prevState.type === 'transition') {
|
||||
return {
|
||||
type: 'transition',
|
||||
from: prevState.from,
|
||||
to: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'transition',
|
||||
from: prevState.value,
|
||||
to: newValue,
|
||||
};
|
||||
});
|
||||
}, [newValue]);
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
Reference in New Issue
Block a user