diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e331268bb..6f0ecce8d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -4776,11 +4776,11 @@ "description": "Hint under the progressbar in the backup import screen when download size is not yet known" }, "icu:BackupImportScreen__description": { - "messageformat": "This may take a few minutes depending on the size of your backup", + "messageformat": "This may take a few minutes depending on the size of your message history", "description": "Description at the bottom of backup import screen" }, "icu:BackupImportScreen__cancel": { - "messageformat": "Cancel transfer", + "messageformat": "Cancel", "description": "Text of the cancel button at the bottom of backup import screen" }, "icu:BackupImportScreen__security-description": { @@ -4792,7 +4792,7 @@ "description": "Title of the cancel confirmation modal in the backup import screen" }, "icu:BackupImportScreen__cancel-confirmation__body": { - "messageformat": "Your messages and media have not completed restoring. If you choose to cancel, you can transfer again from Settings.", + "messageformat": "Your messages and media have not completed restoring. If you choose to cancel, your device will be linked without your message history.", "description": "Body of the cancel confirmation modal in the backup import screen" }, "icu:BackupImportScreen__cancel-confirmation__cancel": { diff --git a/images/signal-logo-and-wordmark.svg b/images/signal-logo-and-wordmark.svg new file mode 100644 index 000000000..e90dedda6 --- /dev/null +++ b/images/signal-logo-and-wordmark.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss index ff245aed2..e9670e328 100644 --- a/stylesheets/_mixins.scss +++ b/stylesheets/_mixins.scss @@ -285,6 +285,21 @@ $rtl-icon-map: ( @return false; } +@mixin color-svg-themed( + $svg, + $light-theme-color, + $dark-theme-color, + $stretch: true, + $mask-origin: null +) { + @include light-theme() { + @include color-svg($svg, $light-theme-color, $stretch, $mask-origin); + } + @include dark-theme() { + @include color-svg($svg, $dark-theme-color, $stretch, $mask-origin); + } +} + @mixin color-svg($svg, $color, $stretch: true, $mask-origin: null) { & { -webkit-mask: url($svg) no-repeat center; diff --git a/stylesheets/components/InstallScreenBackupImportStep.scss b/stylesheets/components/InstallScreenBackupImportStep.scss index af0072d93..7a7b6657e 100644 --- a/stylesheets/components/InstallScreenBackupImportStep.scss +++ b/stylesheets/components/InstallScreenBackupImportStep.scss @@ -20,7 +20,7 @@ flex-direction: column; justify-content: center; text-align: center; - margin-top: 84px; + margin-top: calc(64px + var(--title-bar-drag-area-height)); flex: 1; } @@ -30,15 +30,16 @@ } .InstallScreenBackupImportStep .ProgressBar { - margin-block-end: 14px; + margin-block-end: 18px; } .InstallScreenBackupImportStep__progressbar-hint { @include mixins.font-caption; - margin-block-end: 22px; + font-weight: 500; + margin-block-end: 8px; @include mixins.light-theme { - color: rgba(variables.$color-gray-60, 0.8); + color: rgba(variables.$color-black-alpha-40, 0.8); } @include mixins.dark-theme { @@ -47,7 +48,7 @@ } .InstallScreenBackupImportStep__description { - @include mixins.font-body-1; + @include mixins.font-caption; @include mixins.light-theme { color: variables.$color-gray-60; @@ -61,16 +62,18 @@ .InstallScreenBackupImportStep__cancel { @include mixins.button-reset(); & { - @include mixins.button-focus-outline; @include mixins.font-body-1-bold; + @include mixins.button-blue-text(); + @include mixins.button-focus-outline; + @include mixins.dark-theme { + color: variables.$color-gray-80; + } } - - @include mixins.light-theme() { - color: variables.$color-ultramarine; - } - - @include mixins.dark-theme() { - color: variables.$color-ultramarine-light; + & { + padding-block: 8px; + padding-inline: 24px; + border-radius: 48px; + background-color: variables.$color-gray-05; } } @@ -79,7 +82,7 @@ flex-direction: column; align-items: center; margin-bottom: 36px; - min-height: 94px; + min-height: 110px; gap: 26px; } diff --git a/stylesheets/components/InstallScreenSignalLogo.scss b/stylesheets/components/InstallScreenSignalLogo.scss index bbdeb79cd..1586adaeb 100644 --- a/stylesheets/components/InstallScreenSignalLogo.scss +++ b/stylesheets/components/InstallScreenSignalLogo.scss @@ -14,16 +14,16 @@ inset-inline-start: 32px; &::before { - @include mixins.color-svg( - '../images/signal-logo.svg', - variables.$color-ultramarine-logo + @include mixins.color-svg-themed( + '../images/signal-logo-and-wordmark.svg', + variables.$color-ultramarine-logo, + variables.$color-white ); & { content: ''; display: block; height: 32px; - margin-inline-end: 6px; - width: 32px; + width: 112px; } } } diff --git a/stylesheets/components/ProgressBar.scss b/stylesheets/components/ProgressBar.scss index 699b0a586..d5545667f 100644 --- a/stylesheets/components/ProgressBar.scss +++ b/stylesheets/components/ProgressBar.scss @@ -18,5 +18,45 @@ display: block; height: 100%; width: 100%; - transition: transform 500ms ease-out; + transition: transform 0.2s; + // stylelint-disable-next-line liberty/use-logical-spec + left: 0; + + --spinning-bar-width-fraction: 0.36; + + @keyframes progress-bar-spinning-rtl { + from { + // stylelint-disable-next-line declaration-property-value-disallowed-list + transform: translateX( + calc(100% * (1 / var(--spinning-bar-width-fraction))) + ); + } + to { + // stylelint-disable-next-line declaration-property-value-disallowed-list + transform: translateX(-100%); + } + } + + @keyframes progress-bar-spinning { + from { + // stylelint-disable-next-line declaration-property-value-disallowed-list + transform: translateX(-100%); + } + to { + // stylelint-disable-next-line declaration-property-value-disallowed-list + transform: translateX( + calc(100% * (1 / var(--spinning-bar-width-fraction))) + ); + } + } + + &--spinning { + width: calc(var(--spinning-bar-width-fraction) * 100%); + &:dir(rtl) { + animation: progress-bar-spinning-rtl 2s ease-in-out infinite; + } + &:dir(ltr) { + animation: progress-bar-spinning 2s ease-in-out infinite; + } + } } diff --git a/ts/components/ProgressBar.stories.tsx b/ts/components/ProgressBar.stories.tsx index a6fb341ae..30f295644 100644 --- a/ts/components/ProgressBar.stories.tsx +++ b/ts/components/ProgressBar.stories.tsx @@ -16,6 +16,9 @@ export default { }, } satisfies ComponentMeta; +export function Spinning(args: Props): JSX.Element { + return ; +} export function Zero(args: Props): JSX.Element { return ; } diff --git a/ts/components/ProgressBar.tsx b/ts/components/ProgressBar.tsx index f73425b27..0b96cb174 100644 --- a/ts/components/ProgressBar.tsx +++ b/ts/components/ProgressBar.tsx @@ -7,9 +7,16 @@ export function ProgressBar({ fractionComplete, isRTL, }: { - fractionComplete: number; + fractionComplete: number | null; isRTL: boolean; }): JSX.Element { + if (fractionComplete == null) { + return ( +
+
+
+ ); + } return (
= (args: PropsType) => { ); }; -export const NoBytes = Template.bind({}); -NoBytes.args = { - backupStep: InstallScreenBackupStep.Download, - currentBytes: undefined, - totalBytes: undefined, +export function FullFlow(): JSX.Element { + const [backupStep, setBackupStep] = useState( + InstallScreenBackupStep.WaitForBackup + ); + const [currentBytes, setCurrentBytes] = useState(0); + const [totalBytes, setTotalBytes] = useState(0); + const TOTAL_BYTES = 1024 * 1024; + + useEffect(() => { + setTimeout(() => { + setBackupStep(InstallScreenBackupStep.Download); + setCurrentBytes(0); + setTotalBytes(TOTAL_BYTES); + for (let i = 0; i < 4; i += 1) { + setTimeout(() => { + setCurrentBytes(TOTAL_BYTES / (4 - i)); + }, i * 900); + } + }, 1000); + }, [TOTAL_BYTES]); + + return ( + + ); +} + +export const Waiting = Template.bind({}); +Waiting.args = { + backupStep: InstallScreenBackupStep.WaitForBackup, }; export const Bytes = Template.bind({}); diff --git a/ts/components/installScreen/InstallScreenBackupImportStep.tsx b/ts/components/installScreen/InstallScreenBackupImportStep.tsx index 559ba6943..ff8ca4169 100644 --- a/ts/components/installScreen/InstallScreenBackupImportStep.tsx +++ b/ts/components/installScreen/InstallScreenBackupImportStep.tsx @@ -24,40 +24,50 @@ import { InstallScreenUpdateDialog } from './InstallScreenUpdateDialog'; // We can't always use destructuring assignment because of the complexity of this props // type. -export type PropsType = Readonly<{ - i18n: LocalizerType; - backupStep: InstallScreenBackupStep; - currentBytes?: number; - totalBytes?: number; - error?: InstallScreenBackupError; - onCancel: () => void; - onRetry: () => void; - onRestartLink: () => void; +export type PropsType = Readonly< + { + i18n: LocalizerType; - // Updater UI - updates: UpdatesStateType; - currentVersion: string; - OS: string; - startUpdate: () => void; - forceUpdate: () => void; -}>; + error?: InstallScreenBackupError; + onCancel: () => void; + onRetry: () => void; + onRestartLink: () => void; -export function InstallScreenBackupImportStep({ - i18n, - backupStep, - currentBytes, - totalBytes, - error, - onCancel, - onRetry, - onRestartLink, + // Updater UI + updates: UpdatesStateType; + currentVersion: string; + OS: string; + startUpdate: () => void; + forceUpdate: () => void; + } & ( + | { + backupStep: InstallScreenBackupStep.WaitForBackup; + } + | { + backupStep: + | InstallScreenBackupStep.Download + | InstallScreenBackupStep.Process; + currentBytes: number; + totalBytes: number; + } + ) +>; + +export function InstallScreenBackupImportStep(props: PropsType): JSX.Element { + const { + i18n, + backupStep, + error, + onCancel, + onRetry, + onRestartLink, + updates, + currentVersion, + OS, + startUpdate, + forceUpdate, + } = props; - updates, - currentVersion, - OS, - startUpdate, - forceUpdate, -}: PropsType): JSX.Element { const [isConfirmingCancel, setIsConfirmingCancel] = useState(false); const [isConfirmingSkip, setIsConfirmingSkip] = useState(false); @@ -92,52 +102,6 @@ export function InstallScreenBackupImportStep({ setIsConfirmingSkip(false); }, [onRetry]); - let progress: JSX.Element; - if (currentBytes != null && totalBytes != null) { - const fractionComplete = roundFractionForProgressBar( - currentBytes / totalBytes - ); - - let hint: string; - if (backupStep === InstallScreenBackupStep.Download) { - hint = i18n('icu:BackupImportScreen__progressbar-hint', { - currentSize: formatFileSize(currentBytes), - totalSize: formatFileSize(totalBytes), - fractionComplete, - }); - } else if (backupStep === InstallScreenBackupStep.Process) { - hint = i18n('icu:BackupImportScreen__progressbar-hint--processing'); - } else { - throw missingCaseError(backupStep); - } - - progress = ( - <> - -
- {hint} -
- - ); - } else { - progress = ( - <> - -
- {i18n('icu:BackupImportScreen__progressbar-hint--preparing')} -
- - ); - } - const learnMoreLink = (parts: Array) => ( {parts} @@ -209,13 +173,12 @@ export function InstallScreenBackupImportStep({ return (
-

{i18n('icu:BackupImportScreen__title')}

- {progress} +
{i18n('icu:BackupImportScreen__description')}
@@ -289,3 +252,76 @@ export function InstallScreenBackupImportStep({
); } + +type ProgressBarPropsType = Readonly< + { + i18n: LocalizerType; + } & ( + | { + backupStep: InstallScreenBackupStep.WaitForBackup; + } + | { + backupStep: + | InstallScreenBackupStep.Download + | InstallScreenBackupStep.Process; + currentBytes: number; + totalBytes: number; + } + ) +>; + +function ProgressBarAndDescription(props: ProgressBarPropsType): JSX.Element { + const { backupStep, i18n } = props; + if (backupStep === InstallScreenBackupStep.WaitForBackup) { + return ( + <> + +
+ {i18n('icu:BackupImportScreen__progressbar-hint--preparing')} +
+ + ); + } + + const { currentBytes, totalBytes } = props; + + const fractionComplete = roundFractionForProgressBar( + currentBytes / totalBytes + ); + + if (backupStep === InstallScreenBackupStep.Download) { + return ( + <> + +
+ {i18n('icu:BackupImportScreen__progressbar-hint', { + currentSize: formatFileSize(currentBytes), + totalSize: formatFileSize(totalBytes), + fractionComplete, + })} +
+ + ); + // eslint-disable-next-line no-else-return + } else if (backupStep === InstallScreenBackupStep.Process) { + return ( + <> + +
+ {i18n('icu:BackupImportScreen__progressbar-hint--processing')} +
+ + ); + } else { + throw missingCaseError(backupStep); + } +} diff --git a/ts/components/installScreen/InstallScreenSignalLogo.tsx b/ts/components/installScreen/InstallScreenSignalLogo.tsx index 37fde673b..03e3dd87b 100644 --- a/ts/components/installScreen/InstallScreenSignalLogo.tsx +++ b/ts/components/installScreen/InstallScreenSignalLogo.tsx @@ -5,5 +5,5 @@ import type { ReactElement } from 'react'; import React from 'react'; export function InstallScreenSignalLogo(): ReactElement { - return
Signal
; + return
; } diff --git a/ts/state/ducks/installer.ts b/ts/state/ducks/installer.ts index 97408d840..c4da8cf44 100644 --- a/ts/state/ducks/installer.ts +++ b/ts/state/ducks/installer.ts @@ -63,13 +63,22 @@ export type InstallerStateType = ReadonlyDeep< | { step: InstallScreenStep.LinkInProgress; } - | { + | ({ step: InstallScreenStep.BackupImport; backupStep: InstallScreenBackupStep; - currentBytes?: number; - totalBytes?: number; error?: InstallScreenBackupError; - } + } & ( + | { + backupStep: + | InstallScreenBackupStep.Download + | InstallScreenBackupStep.Process; + currentBytes: number; + totalBytes: number; + } + | { + backupStep: InstallScreenBackupStep.WaitForBackup; + } + )) >; export type RetryBackupImportValue = ReadonlyDeep<'retry' | 'cancel'>; @@ -594,7 +603,7 @@ export function reducer( return { step: InstallScreenStep.BackupImport, - backupStep: InstallScreenBackupStep.Download, + backupStep: InstallScreenBackupStep.WaitForBackup, }; } diff --git a/ts/state/smart/InstallScreen.tsx b/ts/state/smart/InstallScreen.tsx index 287bc7e94..ebd485002 100644 --- a/ts/state/smart/InstallScreen.tsx +++ b/ts/state/smart/InstallScreen.tsx @@ -110,10 +110,7 @@ export const SmartInstallScreen = memo(function SmartInstallScreen() { step: InstallScreenStep.BackupImport, screenSpecificProps: { i18n, - backupStep: installerState.backupStep, - currentBytes: installerState.currentBytes, - totalBytes: installerState.totalBytes, - error: installerState.error, + ...installerState, onCancel: onCancelBackupImport, onRetry: retryBackupImport, onRestartLink: startInstaller, diff --git a/ts/types/InstallScreen.ts b/ts/types/InstallScreen.ts index f71e9b06d..b4678e5c4 100644 --- a/ts/types/InstallScreen.ts +++ b/ts/types/InstallScreen.ts @@ -13,6 +13,7 @@ export enum InstallScreenStep { } export enum InstallScreenBackupStep { + WaitForBackup = 'WaitForBackup', Download = 'Download', Process = 'Process', }