diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5092002f61..2a1a469048 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -67,6 +67,10 @@ "messageformat": "Sent media quality", "description": "Title for the sent media quality setting" }, + "icu:Preferences__sent-media-quality__description": { + "messageformat": "Sending high quality media will use more data.", + "description": "Additional detail about the sent media quality setting" + }, "icu:sentMediaQualityStandard": { "messageformat": "Standard", "description": "Label text for standard media quality option" @@ -1550,6 +1554,14 @@ "messageformat": "Voice message not available", "description": "Shown in chat timeline for messages with old audio attachments which are no longer available for download." }, + "icu:attachmentNotAvailable__tapToView": { + "messageformat": "View once media is not available", + "description": "Shown in chat timeline for expired View Once messages - never viewed before expiration" + }, + "icu:attachmentNotAvailable__tapToViewCannotDownload": { + "messageformat": "Can't download view once media", + "description": "Shown in chat timeline for invalid View Once messages, or where the attachment cannot be downloaded otherwise" + }, "icu:AttachmentNotAvailableModal__title--file": { "messageformat": "File not available", "description": "Title for info dialog for messages with old file attachments which are no longer available for download." @@ -1590,6 +1602,14 @@ "messageformat": "This voice message was not transferred from your phone when this device was linked. Media and files older than 45 days at the time of device linking can not be synced.", "description": "Body text for info dialog for messages with old voice messages which are no longer available for download." }, + "icu:TapToViewNotAvailableModal__body--expired": { + "messageformat": "This view once media is no longer available to download. Ask {name} to send it again.", + "description": "Body text for info dialog for messages with old view once media which are no longer available for download." + }, + "icu:TapToViewNotAvailableModal__body--error": { + "messageformat": "Can’t download view once media. {name} will need to send it again.", + "description": "Body text for info dialog for messages with old view once media which are no longer available for download." + }, "icu:save": { "messageformat": "Save", "description": "Used on save buttons" @@ -2491,6 +2511,10 @@ "messageformat": "Failed to fetch phone number. Check your connection and try again.", "description": "Shown if request to Signal servers to find phone number fails" }, + "icu:Toast--download-failed": { + "messageformat": "Download failed", + "description": "Shown if user's manual request to download attachment fails" + }, "icu:ToastManager__CannotEditMessage_24": { "messageformat": "Edits can only be applied within 24 hours from the time you sent this message.", "description": "Error message when you try to send an edit after message becomes too old" @@ -3237,11 +3261,19 @@ }, "icu:Message--tap-to-view-expired": { "messageformat": "Viewed", - "description": "Text shown on messages with with individual timers, after user has viewed it" + "description": "Text shown on messages with individual timers, after user has viewed it" + }, + "icu:Message--tap-to-view--viewed": { + "messageformat": "Viewed", + "description": "Text shown on messages with individual timers, after user has viewed it" }, "icu:Message--tap-to-view--outgoing": { "messageformat": "Media", - "description": "Text shown on outgoing messages with with individual timers (inaccessible)" + "description": "Text shown on outgoing messages with individual timers (inaccessible)" + }, + "icu:Message--tap-to-view--media": { + "messageformat": "View Once Media", + "description": "Text shown on outgoing messages with individual timers (inaccessible)" }, "icu:Message--tap-to-view--incoming--expired-toast": { "messageformat": "You already viewed this message.", @@ -3253,11 +3285,23 @@ }, "icu:Message--tap-to-view--incoming": { "messageformat": "View Photo", - "description": "Text shown on photo messages with with individual timers, before user has viewed it" + "description": "Text shown on photo messages with individual timers, before user has viewed it" }, "icu:Message--tap-to-view--incoming-video": { "messageformat": "View Video", - "description": "Text shown on video messages with with individual timers, before user has viewed it" + "description": "Text shown on video messages with individual timers, before user has viewed it" + }, + "icu:Message--tap-to-view--video": { + "messageformat": "View Once Video", + "description": "Text shown on video messages with individual timers, before user has viewed it" + }, + "icu:Message--tap-to-view--photo": { + "messageformat": "View Once Photo", + "description": "Text shown on photo messages with individual timers, before user has viewed it" + }, + "icu:Message--tap-to-view--helper-text": { + "messageformat": "Click to view", + "description": "Text shown below 'View Once Photo' or 'View Once Video' after the file has been downloaded" }, "icu:Conversation--getDraftPreview--attachment": { "messageformat": "(attachment)", @@ -6395,6 +6439,34 @@ "messageformat": "Privacy", "description": "Button to switch the settings view" }, + "icu:Preferences__button--data-usage": { + "messageformat": "Data usage", + "description": "Button to switch the settings view" + }, + "icu:Preferences__media-auto-download": { + "messageformat": "Media auto-download", + "description": "Button to switch the settings view" + }, + "icu:Preferences__media-auto-download__photos": { + "messageformat": "Photos", + "description": "A category of files for media auto-download" + }, + "icu:Preferences__media-auto-download__videos": { + "messageformat": "Videos", + "description": "A category of files for media auto-download" + }, + "icu:Preferences__media-auto-download__audio": { + "messageformat": "Audio", + "description": "A category of files for media auto-download" + }, + "icu:Preferences__media-auto-download__documents": { + "messageformat": "Documents", + "description": "A category of files for media auto-download" + }, + "icu:Preferences__media-auto-download__description": { + "messageformat": "Voice messages and stickers are always auto-downloaded.", + "description": "Additional clarification for how media auto-download will behave" + }, "icu:Preferences--lastSynced": { "messageformat": "Last import at {date} {time}", "description": "Label for date and time of last sync operation" diff --git a/images/file-gradient.svg b/images/file-gradient.svg deleted file mode 100644 index d431b721c1..0000000000 --- a/images/file-gradient.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/images/generic-file.svg b/images/generic-file.svg new file mode 100644 index 0000000000..e01844db9c --- /dev/null +++ b/images/generic-file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/arrow/arrow-down-bold.svg b/images/icons/v3/arrow/arrow-down-bold.svg new file mode 100644 index 0000000000..7cda29759d --- /dev/null +++ b/images/icons/v3/arrow/arrow-down-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/data/data.svg b/images/icons/v3/data/data.svg new file mode 100644 index 0000000000..3a0d46adb5 --- /dev/null +++ b/images/icons/v3/data/data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/view_once/view_once-bold.svg b/images/icons/v3/view_once/view_once-bold.svg new file mode 100644 index 0000000000..0fcb850506 --- /dev/null +++ b/images/icons/v3/view_once/view_once-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/view_once/view_once-dash-bold.svg b/images/icons/v3/view_once/view_once-dash-bold.svg new file mode 100644 index 0000000000..09be030ece --- /dev/null +++ b/images/icons/v3/view_once/view_once-dash-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/view_once/view_once-dash.svg b/images/icons/v3/view_once/view_once-dash.svg deleted file mode 100644 index 936c7c9426..0000000000 --- a/images/icons/v3/view_once/view_once-dash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/images/icons/v3/view_once/view_once-slash-bold.svg b/images/icons/v3/view_once/view_once-slash-bold.svg new file mode 100644 index 0000000000..2879450a52 --- /dev/null +++ b/images/icons/v3/view_once/view_once-slash-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/view_once/view_once-viewed-bold.svg b/images/icons/v3/view_once/view_once-viewed-bold.svg new file mode 100644 index 0000000000..2cbf278415 --- /dev/null +++ b/images/icons/v3/view_once/view_once-viewed-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v3/x/x-bold.svg b/images/icons/v3/x/x-bold.svg index 0b8fc79f83..7c959ebe2b 100644 --- a/images/icons/v3/x/x-bold.svg +++ b/images/icons/v3/x/x-bold.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss index e9670e3287..daab0f5b8d 100644 --- a/stylesheets/_mixins.scss +++ b/stylesheets/_mixins.scss @@ -109,6 +109,12 @@ font-style: italic; } +@mixin font-body-small { + font-size: 12px; + line-height: 16px; + letter-spacing: 0px; +} + @mixin font-subtitle { font-size: 12px; line-height: 16px; diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index b871c2f529..93c91de6e7 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -509,45 +509,6 @@ $message-padding-horizontal: 12px; } } -.module-message__container--with-tap-to-view { - min-width: 148px; - cursor: pointer; - user-select: none; -} - -.module-message__container--with-tap-to-view-pending { - background-color: variables.$color-gray-15; -} - -.module-message__container--with-tap-to-view-pending { - cursor: default; -} - -.module-message__container--with-tap-to-view-expired { - @include mixins.light-theme { - border: 1px solid variables.$color-gray-15; - background-color: variables.$color-white; - } - @include mixins.dark-theme { - border: 1px solid variables.$color-gray-60; - background-color: variables.$color-gray-95; - } -} - -.module-message__container--with-tap-to-view-error { - width: auto; - cursor: default; - - @include mixins.light-theme { - background-color: variables.$color-white; - border: 1px solid variables.$color-deep-red; - } - @include mixins.dark-theme { - background-color: variables.$color-black; - border: 1px solid variables.$color-deep-red; - } -} - .module-message__container--deleted-for-everyone { @include mixins.light-theme { color: variables.$color-gray-90; @@ -619,104 +580,6 @@ $message-padding-horizontal: 12px; border-bottom-right-radius: 4px; } -.module-message__tap-to-view { - margin-top: 2px; - display: flex; - flex-direction: row; - align-items: center; -} -.module-message__tap-to-view--with-content-above { - margin-top: 8px; -} -.module-message__tap-to-view--with-content-below { - margin-bottom: 8px; -} - -.module-message__tap-to-view__spinner-container { - margin-inline-end: 6px; - - flex-grow: 0; - flex-shrink: 0; - - width: 20px; - height: 20px; -} - -.module-message__tap-to-view__icon { - margin-inline-end: 6px; - - flex-grow: 0; - flex-shrink: 0; - width: 20px; - height: 20px; - - @include mixins.light-theme { - @include mixins.color-svg( - '../images/icons/v3/view_once/view_once.svg', - variables.$color-gray-90 - ); - } - @include mixins.dark-theme { - @include mixins.color-svg( - '../images/icons/v3/view_once/view_once.svg', - variables.$color-gray-05 - ); - } -} -.module-message__tap-to-view__icon--expired { - @include mixins.light-theme { - @include mixins.color-svg( - '../images/icons/v3/view_once/view_once-dash.svg', - variables.$color-gray-75 - ); - } - @include mixins.dark-theme { - @include mixins.color-svg( - '../images/icons/v3/view_once/view_once-dash.svg', - variables.$color-gray-05 - ); - } -} -.module-message__tap-to-view__icon--outgoing { - background-color: variables.$color-gray-05; -} -.module-message__tap-to-view__text { - @include mixins.font-body-1-bold; - - color: variables.$color-gray-05; -} -.module-message__tap-to-view__text--incoming { - @include mixins.light-theme { - color: variables.$color-gray-90; - } - @include mixins.dark-theme { - color: variables.$color-gray-05; - } -} -.module-message__tap-to-view__text--incoming-expired { - @include mixins.light-theme { - color: variables.$color-gray-90; - } - @include mixins.dark-theme { - color: variables.$color-gray-05; - } -} -.module-message__tap-to-view__text--incoming-error { - @include mixins.light-theme { - color: variables.$color-gray-60; - } - @include mixins.dark-theme { - color: variables.$color-gray-25; - } -} - -.module-message__tap-to-view__text--outgoing { - color: variables.$color-white; -} -.module-message__tap-to-view__text--outgoing-expired { - color: variables.$color-gray-05; -} - .module-message__attachment-container { // To ensure that images are centered if they aren't full width of bubble text-align: center; @@ -780,16 +643,15 @@ $message-padding-horizontal: 12px; border-color: variables.$color-white-alpha-30; } +.module-message__container--is-clickable { + cursor: pointer; +} + .module-message__undownloadable-attachment--no-text + .module-message__metadata { margin-block-start: -25px; } -.module-message__generic-attachment--undownloadable-no-text - + .module-message__metadata { - margin-block-start: -$message-padding-vertical - 2px; -} - .module-message__sticker-container { // To ensure that images are centered if they aren't full width of bubble text-align: center; @@ -814,14 +676,16 @@ $message-padding-horizontal: 12px; cursor: pointer; } -.module-message__generic-attachment { +.module-message__simple-attachment { @include mixins.button-reset; & { + user-select: none; width: 100%; display: flex; flex-direction: row; align-items: center; + border-radius: 4px; } @include mixins.keyboard-mode { @@ -831,32 +695,28 @@ $message-padding-horizontal: 12px; } } -.module-message__generic-attachment--undownloadable { - min-width: 260px; +.module-message__container--outgoing .module-message__simple-attachment { + @include mixins.keyboard-mode { + &:focus { + box-shadow: 0px 0px 0px 2px variables.$color-white; + } + } } -.module-message__generic-attachment--with-content-below { - padding-bottom: 6px; +.module-message__simple-attachment--with-content-below { + padding-bottom: 8px; } -.module-message__generic-attachment--with-content-above { +.module-message__simple-attachment--with-content-above { padding-top: 4px; } -.module-message__generic-attachment__icon-container { - position: relative; - user-select: none; -} -.module-message__generic-attachment__spinner-container { - padding-inline: 4px; -} - -.module-message__generic-attachment__icon { - background: url('../images/file-gradient.svg') no-repeat center; - height: 44px; - width: 56px; - margin-inline: -13px -14px; - margin-bottom: -4px; +.module-message__simple-attachment__icon { + background: url('../images/generic-file.svg') no-repeat center; + height: 40px; + width: 30px; + margin-inline-start: 3px; + margin-inline-end: 3px; // So we can center the extension text inside this icon display: flex; @@ -864,7 +724,7 @@ $message-padding-horizontal: 12px; align-items: center; } -.module-message__generic-attachment__icon-dangerous-container { +.module-message__simple-attachment__icon-dangerous-container { position: absolute; top: -1px; @@ -877,7 +737,7 @@ $message-padding-horizontal: 12px; background-color: variables.$color-white; } -.module-message__generic-attachment__icon-dangerous { +.module-message__simple-attachment__icon-dangerous { height: 16px; width: 16px; @@ -887,36 +747,38 @@ $message-padding-horizontal: 12px; ); } -.module-message__generic-attachment__icon__extension { - font-size: 10px; - line-height: 13px; - letter-spacing: 0.1px; - text-transform: uppercase; +.module-message__simple-attachment__icon__extension { + @include mixins.font-subtitle; + text-transform: lowercase; + user-select: none; // Along with flow layout in parent item, centers text text-align: center; - width: 25px; margin-inline: auto; - // We don't have much room for text here, cut it off without ellipse - overflow-x: hidden; + // We shouldn't have problems with 1-3 character extensions, but clip if we do! white-space: nowrap; + overflow-x: hidden; text-overflow: clip; color: variables.$color-gray-90; } -$message-attachment-padding-horizontal: 8px; - -.module-message__generic-attachment__text { +// For longer extensions we manually only show the first three characters and an ellipse; +// all we do here is change the font. +.module-message__simple-attachment__icon__extension--more-char { + font-size: 9px; + line-height: 11px; + letter-spacing: -0.2px; +} +.module-message__simple-attachment__text { flex-grow: 1; - margin-inline-start: $message-attachment-padding-horizontal + 2px; - // The width of the icon plus our 8px margin plus 1px leeway - max-width: calc(100% - 36px); + // icon-container is 30px wide, 12px margin + max-width: calc(100% - 42px); } -.module-message__generic-attachment__file-name { - @include mixins.font-body-1; +.module-message__simple-attachment__file-name { + @include mixins.font-body-1-bold; margin-top: 2px; user-select: none; @@ -931,45 +793,65 @@ $message-attachment-padding-horizontal: 8px; color: variables.$color-white; } @include mixins.dark-theme { - color: variables.$color-gray-02; + color: variables.$color-white-alpha-90; } } -.module-message__generic-attachment__file-name--incoming { +.module-message__simple-attachment__file-name--incoming { color: variables.$color-white; @include mixins.light-theme { color: variables.$color-gray-90; } @include mixins.dark-theme { - color: variables.$color-gray-25; + color: variables.$color-gray-05; } } +.module-message__simple-attachment__bottom-row { + display: flex; + flex-direction: row; +} + +.module-message__simple-attachment__metadata-container { + flex-grow: 1; + padding-inline-start: 8px; + padding-top: 6px; + align-self: end; +} + .module-message__container--incoming - .module-message__generic-attachment__file-name--undownloadable { - color: variables.$color-black-alpha-50; -} - -.module-message__generic-attachment__file-size { - @include mixins.font-body-2; - - margin-top: 3px; - user-select: none; - + .module-message__simple-attachment__file-name--undownloadable { @include mixins.light-theme { - color: variables.$color-white; + color: variables.$color-gray-90; } @include mixins.dark-theme { - color: variables.$color-gray-02; + color: variables.$color-gray-05; } } -.module-message__generic-attachment__file-size--incoming { +.module-message__simple-attachment__file-size { + flex-grow: 1; + flex-shrink: 0; + @include mixins.font-body-small; + + margin-top: 2px; + user-select: none; + white-space: no-wrap; + + @include mixins.light-theme { + color: variables.$color-white-alpha-80; + } + @include mixins.dark-theme { + color: variables.$color-white-alpha-60; + } +} + +.module-message__simple-attachment__file-size--incoming { color: variables.$color-white; @include mixins.light-theme { - color: variables.$color-gray-90; + color: variables.$color-gray-60; } @include mixins.dark-theme { color: variables.$color-gray-25; @@ -977,7 +859,7 @@ $message-attachment-padding-horizontal: 8px; } .module-message__undownloadable-attachment__icon-container { - margin-inline-end: $message-attachment-padding-horizontal; + margin-inline-end: 8px; } .module-message__undownloadable-attachment__icon-container--file { @@ -1010,6 +892,13 @@ $message-attachment-padding-horizontal: 8px; ); } + &--tap-to-view { + @include mixins.color-svg( + '../images/icons/v3/view_once/view_once-slash-bold.svg', + currentColor + ); + } + &--small { width: 16px; height: 16px; @@ -1022,7 +911,12 @@ $message-attachment-padding-horizontal: 8px; .module-message__container--incoming .module-message__undownloadable-attachment-info--file { - color: variables.$color-black-alpha-90; + @include mixins.light-theme { + color: variables.$color-gray-80; + } + @include mixins.dark-theme { + color: variables.$color-gray-25; + } } .module-message__undownloadable-attachment { @@ -1227,23 +1121,6 @@ $message-attachment-padding-horizontal: 8px; user-select: none; } -.module-message__author--with-tap-to-view-expired { - @include mixins.font-body-2-bold; - - height: 18px; - overflow-x: hidden; - overflow-y: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - @include mixins.light-theme { - color: variables.$color-gray-75; - } - @include mixins.dark-theme { - color: variables.$color-white; - } -} - .module-message__author_with_sticker { @include mixins.font-body-2-bold; @@ -1479,16 +1356,6 @@ $message-attachment-padding-horizontal: 8px; color: variables.$color-gray-25; } } -.module-message__metadata__date.module-message__metadata__date--incoming-with-tap-to-view-expired { - color: variables.$color-gray-75; - - @include mixins.dark-theme { - color: variables.$color-white-alpha-80; - } -} -.module-message__metadata__date.module-message__metadata__date--outgoing-with-tap-to-view-expired { - color: variables.$color-white-alpha-80; -} .module-message__metadata__date--with-sticker { @include mixins.light-theme { @@ -2115,6 +1982,34 @@ $message-attachment-padding-horizontal: 8px; } } +.module-message__tap-to-view__icon--ready { + @include mixins.color-svg-themed( + '../images/icons/v3/view_once/view_once-bold.svg', + variables.$color-gray-90, + variables.$color-gray-05 + ); +} +.module-message__tap-to-view__icon--outgoing { + @include mixins.color-svg( + '../images/icons/v3/view_once/view_once-dash-bold.svg', + variables.$color-white-alpha-80 + ); +} +.module-message__tap-to-view__icon--viewed { + @include mixins.color-svg-themed( + '../images/icons/v3/view_once/view_once-viewed-bold.svg', + variables.$color-gray-90, + variables.$color-gray-05 + ); +} +.module-message__tap-to-view__icon--not-available { + @include mixins.color-svg-themed( + '../images/icons/v3/view_once/view_once-slash-bold.svg', + variables.$color-gray-90, + variables.$color-gray-05 + ); +} + // Module: Expire Timer .module-expire-timer { @@ -2176,16 +2071,6 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', background-color: variables.$color-gray-25; } } -.module-expire-timer.module-expire-timer--incoming-with-tap-to-view-expired { - background-color: variables.$color-gray-75; - - @include mixins.dark-theme { - background-color: variables.$color-white-alpha-80; - } -} -.module-expire-timer.module-expire-timer--outgoing-with-tap-to-view-expired { - background-color: variables.$color-white-alpha-80; -} .module-expire-timer--with-sticker { @include mixins.light-theme { background-color: variables.$color-gray-60; @@ -2249,30 +2134,13 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', // Module: Embedded Contact .module-embedded-contact { - @include mixins.button-reset; + width: 100%; + padding-top: 4px; + padding-bottom: 4px; - & { - width: 100%; - padding: 5px; - - display: flex; - flex-direction: row; - align-items: center; - } - - @include mixins.keyboard-mode { - &:focus { - box-shadow: 0px 0px 0px 2px variables.$color-ultramarine; - } - } -} - -.module-embedded-contact--outgoing { - @include mixins.keyboard-mode { - &:focus { - box-shadow: 0px 0px 0px 2px variables.$color-white; - } - } + display: flex; + flex-direction: row; + align-items: center; } .module-embedded-contact--with-content-above { @@ -2283,15 +2151,51 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', padding-bottom: 4px; } -.module-embedded-contact__spinner-container { - padding-inline: 5px; +.module-embedded-contact__avatar-container { + height: 52px; + width: 52px; + border-radius: 26px; + + @include mixins.keyboard-mode { + &:focus { + box-shadow: 0px 0px 0px 2px variables.$color-ultramarine; + } + } +} + +.module-embedded-contact--outgoing .module-embedded-contact__avatar-container { + @include mixins.keyboard-mode { + &:focus { + box-shadow: 0px 0px 0px 2px variables.$color-white; + } + } +} + +.module-embedded-contact__avatar-container .AttachmentStatusIcon__container { + margin: 0; + height: 52px; + width: 52px; +} + +.module-embedded-contact__avatar-container + .AttachmentStatusIcon__circle-icon-container { + margin: 0; +} + +.module-embedded-contact__avatar-container { + @include mixins.button-reset; +} + +.module-embedded-contact__avatar-container + .AttachmentStatusIcon__circle-icon-container { + @include mixins.position-absolute-center; } .module-embedded-contact__text-container { flex-grow: 1; - margin-inline-start: 8px; + margin-inline-start: 12px; - max-width: calc(100% - 58px); + max-width: calc(100% - 64px); } .module-embedded-contact__contact-name { @@ -2357,7 +2261,35 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', } .module-contact-detail__avatar { - margin-bottom: 4px; + @include mixins.button-reset; + & { + cursor: default; + position: relative; + height: 80px; + width: 80px; + margin-bottom: 10px; + border-radius: 40px; + } + @include mixins.light-theme { + background-color: variables.$color-gray-05; + } + @include mixins.dark-theme { + background-color: variables.$color-gray-80; + } +} + +.module-contact-detail__avatar--clickable { + cursor: pointer; +} + +.module-contact-detail__avatar .AttachmentStatusIcon__container { + height: 80px; + width: 80px; + margin: 0; +} + +.module-contact-detail__avatar .AttachmentStatusIcon__circle-icon-container { + @include mixins.position-absolute-center; } .module-contact-detail__contact-name { @@ -2677,7 +2609,10 @@ button.ConversationDetails__action-button { width: 48px; height: 48px; - @include mixins.color-svg('../images/file.svg', variables.$color-gray-45); + @include mixins.color-svg( + '../images/generic-file.svg', + variables.$color-gray-45 + ); } .module-document-list-item__metadata { @@ -3459,7 +3394,7 @@ button.module-image__border-overlay:focus { .module-staged-generic-attachment__icon { margin-top: 30px; - background: url('../images/file-gradient.svg') no-repeat center; + background: url('../images/generic-file.svg') no-repeat center; height: 44px; width: 56px; margin-inline: 32px; diff --git a/stylesheets/components/AttachmentStatusIcon.scss b/stylesheets/components/AttachmentStatusIcon.scss new file mode 100644 index 0000000000..a39384aa7f --- /dev/null +++ b/stylesheets/components/AttachmentStatusIcon.scss @@ -0,0 +1,164 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +@use '../mixins'; +@use '../variables'; + +.AttachmentStatusIcon__container { + position: relative; + width: 36px; + height: 40px; + margin-top: 2px; + margin-bottom: 2px; + margin-inline-end: 12px; +} + +.AttachmentStatusIcon__circle-icon-container { + height: 36px; + width: 36px; + margin-top: 2px; + margin-bottom: 2px; + + border-radius: 18px; + position: relative; + + @include mixins.light-theme { + background-color: variables.$color-white-alpha-20; + &:hover { + background-color: variables.$color-white-alpha-30; + } + &:active { + background-color: variables.$color-white-alpha-40; + } + } + @include mixins.dark-theme { + background-color: variables.$color-white-alpha-20; + &:hover { + background-color: variables.$color-white-alpha-30; + } + &:active { + background-color: variables.$color-white-alpha-40; + } + } +} + +.AttachmentStatusIcon__circle-icon-container--incoming { + @include mixins.light-theme { + background-color: variables.$color-white-alpha-80; + &:hover { + background-color: variables.$color-white-alpha-60; + } + &:active { + background-color: variables.$color-white-alpha-40; + } + } + @include mixins.dark-theme { + background-color: variables.$color-white-alpha-20; + } +} + +.AttachmentStatusIcon__circle-icon-container--disabled { + @include mixins.light-theme { + background-color: variables.$color-white-alpha-10; + &:hover { + background-color: variables.$color-white-alpha-10; + } + &:active { + background-color: variables.$color-white-alpha-10; + } + } + + @include mixins.dark-theme { + background-color: variables.$color-white-alpha-10; + &:hover { + background-color: variables.$color-white-alpha-10; + } + &:active { + background-color: variables.$color-white-alpha-10; + } + } +} + +.AttachmentStatusIcon__circle-icon-container--incoming.AttachmentStatusIcon__circle-icon-container--disabled { + @include mixins.light-theme { + background-color: variables.$color-white-alpha-60; + &:hover { + background-color: variables.$color-white-alpha-60; + } + &:active { + background-color: variables.$color-white-alpha-60; + } + } + + @include mixins.dark-theme { + background-color: variables.$color-white-alpha-10; + &:hover { + background-color: variables.$color-white-alpha-10; + } + &:active { + background-color: variables.$color-white-alpha-10; + } + } +} + +.AttachmentStatusIcon__circle-icon { + @include mixins.position-absolute-center; + & { + height: 20px; + width: 20px; + } +} +.AttachmentStatusIcon__circle-icon--x { + @include mixins.color-svg-themed( + '../images/icons/v3/x/x-bold.svg', + variables.$color-white, + variables.$color-white-alpha-90 + ); +} +.AttachmentStatusIcon__circle-icon--arrow-down { + @include mixins.color-svg-themed( + '../images/icons/v3/arrow/arrow-down-bold.svg', + variables.$color-white, + variables.$color-white-alpha-90 + ); +} +.AttachmentStatusIcon__circle-icon--incoming { + @include mixins.light-theme { + background-color: variables.$color-gray-90; + } + @include mixins.dark-theme { + background-color: variables.$color-white-alpha-90; + } +} + +.AttachmentStatusIcon__progress-container { + .ProgressCircle .ProgressCircle__background { + @include mixins.light-theme { + stroke: none; + fill: none; + } + @include mixins.dark-theme { + stroke: none; + fill: none; + } + } + + .ProgressCircle .ProgressCircle__fill { + @include mixins.light-theme { + stroke: variables.$color-white; + } + @include mixins.dark-theme { + stroke: variables.$color-white-alpha-90; + } + } +} +.AttachmentStatusIcon__progress-container--incoming { + .ProgressCircle .ProgressCircle__fill { + @include mixins.light-theme { + stroke: variables.$color-gray-90; + } + @include mixins.dark-theme { + stroke: variables.$color-white-alpha-90; + } + } +} diff --git a/stylesheets/components/PlaybackButton.scss b/stylesheets/components/PlaybackButton.scss index 10b07f0393..262fa6514d 100644 --- a/stylesheets/components/PlaybackButton.scss +++ b/stylesheets/components/PlaybackButton.scss @@ -7,6 +7,7 @@ .PlaybackButton { @include mixins.button-reset; + position: relative; & { flex-shrink: 0; @@ -24,15 +25,20 @@ @mixin audio-icon($name, $icon, $color) { &.PlaybackButton--#{$name}::before { + @include mixins.position-absolute-center; @include mixins.color-svg('../images/icons/#{$icon}.svg', $color, false); + & { + height: 20px; + width: 20px; + } } } @mixin all-audio-icons($color) { @include audio-icon(play, v3/play/play-fill, $color); @include audio-icon(pause, v3/pause/pause-fill, $color); - @include audio-icon(download, v3/arrow/arrow-down, $color); - @include audio-icon(pending, v2/audio-spinner-arc-22, $color); + @include audio-icon(not-downloaded, v3/arrow/arrow-down, $color); + @include audio-icon(downloading, v3/x/x-bold, $color); } &--variant-message { @@ -43,6 +49,8 @@ &--variant-mini { &::before { -webkit-mask-size: 100% !important; + width: 8px !important; + height: 8px !important; } width: 14px; height: 14px; @@ -50,41 +58,104 @@ &--variant-draft { &::before { -webkit-mask-size: 100% !important; + width: 10px !important; + height: 10px !important; } width: 18px; height: 18px; } - &--pending { + &--computing { cursor: auto; } - - &--pending::before { - animation: rotate 1000ms linear infinite; + &__SpinnerV2-container { + @include mixins.position-absolute-center; + } + .ProgressCircle { + @include mixins.position-absolute-center; + .ProgressCircle__background { + stroke: none; + } + } + @include mixins.dark-theme { + .ProgressCircle .ProgressCircle__background { + stroke: none; + } } - @include mixins.light-theme { + .ProgressCircle .ProgressCircle__fill { + stroke: variables.$color-white; + } + .SpinnerV2 .SpinnerV2__Path { + stroke: variables.$color-white; + } + + @include all-audio-icons(variables.$color-gray-90); + &--context-incoming { &.PlaybackButton--variant-message { - background: variables.$color-white; + background: variables.$color-white-alpha-80; + &:hover { + background: variables.$color-white-alpha-60; + } + &:active { + background: variables.$color-white-alpha-40; + } + } + .ProgressCircle .ProgressCircle__fill { + stroke: variables.$color-gray-90; + } + .SpinnerV2 .SpinnerV2__Path { + stroke: variables.$color-gray-90; } } - @include all-audio-icons(variables.$color-gray-60); } @include mixins.dark-theme { + .ProgressCircle .ProgressCircle__fill { + stroke: variables.$color-white-alpha-90; + } + .SpinnerV2 .SpinnerV2__Path { + stroke: variables.$color-white-alpha-90; + } + + @include all-audio-icons(variables.$color-white-alpha-90); + &--context-incoming { &.PlaybackButton--variant-message { - background: variables.$color-gray-60; + background: variables.$color-white-alpha-20; + &:hover { + background: variables.$color-white-alpha-30; + } + &:active { + background: variables.$color-white-alpha-40; + } + } + .ProgressCircle .ProgressCircle__fill { + stroke: variables.$color-white-alpha-90; + } + .SpinnerV2 .SpinnerV2__Path { + stroke: variables.$color-white-alpha-90; } } - @include all-audio-icons(variables.$color-gray-15); } &--context-outgoing { &.PlaybackButton--variant-message { background: variables.$color-white-alpha-20; + &:hover { + background: variables.$color-white-alpha-30; + } + &:active { + background: variables.$color-white-alpha-40; + } } @include all-audio-icons(variables.$color-white); } + + @include mixins.dark-theme { + &--context-outgoing { + @include all-audio-icons(variables.$color-white-alpha-90); + } + } } diff --git a/stylesheets/components/Preferences.scss b/stylesheets/components/Preferences.scss index 00e77af5b3..81cea06022 100644 --- a/stylesheets/components/Preferences.scss +++ b/stylesheets/components/Preferences.scss @@ -107,6 +107,10 @@ &--privacy { @include preferences-icon('../images/icons/v3/lock/lock.svg'); } + + &--data-usage { + @include preferences-icon('../images/icons/v3/data/data.svg'); + } } &__settings-pane { @@ -230,6 +234,16 @@ padding-inline: 24px; } + &__option-name { + @include mixins.font-body-1; + @include mixins.light-theme { + color: variables.$color-gray-90; + } + @include mixins.dark-theme { + color: variables.$color-gray-05; + } + } + &__description { @include mixins.font-subtitle; @include mixins.light-theme { @@ -242,6 +256,10 @@ &--error { color: variables.$color-accent-red !important; } + + &--medium { + @include mixins.font-body-2; + } } &__select { diff --git a/stylesheets/components/TapToViewNotAvailableModal.scss b/stylesheets/components/TapToViewNotAvailableModal.scss new file mode 100644 index 0000000000..50c3fd70e5 --- /dev/null +++ b/stylesheets/components/TapToViewNotAvailableModal.scss @@ -0,0 +1,18 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +@use '../mixins'; +@use '../variables'; + +.TapToViewNotAvailableModal__width-container { + max-width: 440px; +} + +.TapToViewNotAvailableModal__body { + padding-block: 16px 0; + padding-inline: 16px; +} + +.TapToViewNotAvailableModal .module-Button { + padding-inline: 24px; +} diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 3d14145fd0..e1bcc29327 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -26,6 +26,7 @@ @use 'components/App.scss'; @use 'components/AttachmentDetailPill.scss'; @use 'components/AttachmentNotAvailableModal.scss'; +@use 'components/AttachmentStatusIcon.scss'; @use 'components/AudioCapture.scss'; @use 'components/AutoSizeInput.scss'; @use 'components/Avatar.scss'; @@ -179,6 +180,7 @@ @use 'components/StoryViewsNRepliesModal.scss'; @use 'components/SystemMessage.scss'; @use 'components/Tabs.scss'; +@use 'components/TapToViewNotAvailableModal.scss'; @use 'components/TextAttachment.scss'; @use 'components/TimelineDateHeader.scss'; @use 'components/TimelineFloatingHeader.scss'; diff --git a/ts/components/EditHistoryMessagesModal.tsx b/ts/components/EditHistoryMessagesModal.tsx index 4bffc659dc..9e75e6a606 100644 --- a/ts/components/EditHistoryMessagesModal.tsx +++ b/ts/components/EditHistoryMessagesModal.tsx @@ -67,6 +67,7 @@ const MESSAGE_DEFAULT_PROPS = { showExpiredOutgoingTapToViewToast: shouldNeverBeCalled, showLightboxForViewOnceMedia: shouldNeverBeCalled, showMediaNoLongerAvailableToast: shouldNeverBeCalled, + showTapToViewNotAvailableModal: shouldNeverBeCalled, startConversation: shouldNeverBeCalled, textDirection: TextDirection.Default, viewStory: shouldNeverBeCalled, @@ -128,8 +129,12 @@ export function EditHistoryMessagesModal({ isEditedMessage isSpoilerExpanded={revealedSpoilersById[currentMessageId] || {}} key={currentMessage.timestamp} - kickOffAttachmentDownload={kickOffAttachmentDownload} - cancelAttachmentDownload={cancelAttachmentDownload} + kickOffAttachmentDownload={() => + kickOffAttachmentDownload({ messageId: currentMessage.id }) + } + cancelAttachmentDownload={() => + cancelAttachmentDownload({ messageId: currentMessage.id }) + } messageExpanded={(messageId, displayLimit) => { const update = { ...displayLimitById, @@ -192,8 +197,12 @@ export function EditHistoryMessagesModal({ getPreferredBadge={getPreferredBadge} i18n={i18n} isSpoilerExpanded={revealedSpoilersById[syntheticId] || {}} - kickOffAttachmentDownload={kickOffAttachmentDownload} - cancelAttachmentDownload={cancelAttachmentDownload} + kickOffAttachmentDownload={() => + kickOffAttachmentDownload({ messageId: currentMessage.id }) + } + cancelAttachmentDownload={() => + cancelAttachmentDownload({ messageId: currentMessage.id }) + } messageExpanded={(messageId, displayLimit) => { const update = { ...displayLimitById, diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx index 8687487422..45bb2dc9e4 100644 --- a/ts/components/GlobalModalContainer.tsx +++ b/ts/components/GlobalModalContainer.tsx @@ -23,6 +23,10 @@ import { WhatsNewModal } from './WhatsNewModal'; import { MediaPermissionsModal } from './MediaPermissionsModal'; import type { StartCallData } from './ConfirmLeaveCallModal'; import type { AttachmentNotAvailableModalType } from './AttachmentNotAvailableModal'; +import { + TapToViewNotAvailableModal, + type DataPropsType as TapToViewNotAvailablePropsType, +} from './TapToViewNotAvailableModal'; // NOTE: All types should be required for this component so that the smart // component gives you type errors when adding/removing props. @@ -117,6 +121,9 @@ export type PropsType = { | SafetyNumberChangedBlockingDataType | undefined; renderSendAnywayDialog: () => JSX.Element; + // TapToViewNotAvailableModal + tapToViewNotAvailableModalProps: TapToViewNotAvailablePropsType | undefined; + hideTapToViewNotAvailableModal: () => void; // UserNotFoundModal hideUserNotFoundModal: () => unknown; userNotFoundModalState: UserNotFoundModalStateType | undefined; @@ -201,6 +208,9 @@ export function GlobalModalContainer({ hasSafetyNumberChangeModal, safetyNumberChangedBlockingData, renderSendAnywayDialog, + // TapToViewNotAvailableModal + tapToViewNotAvailableModalProps, + hideTapToViewNotAvailableModal, // UserNotFoundModal hideUserNotFoundModal, userNotFoundModalState, @@ -364,5 +374,15 @@ export function GlobalModalContainer({ return renderAttachmentNotAvailableModal(); } + if (tapToViewNotAvailableModalProps) { + return ( + + ); + } + return null; } diff --git a/ts/components/MiniPlayer.tsx b/ts/components/MiniPlayer.tsx index 49ad6c0423..ee25deda2e 100644 --- a/ts/components/MiniPlayer.tsx +++ b/ts/components/MiniPlayer.tsx @@ -64,7 +64,7 @@ export function MiniPlayer({ }, [state, onPause, onPlay]); let label: string | undefined; - let mod: 'play' | 'pause' | 'pending'; + let mod: 'play' | 'pause' | 'computing'; switch (state) { case PlayerState.playing: label = i18n('icu:MessageAudio--pause'); @@ -76,7 +76,7 @@ export function MiniPlayer({ break; case PlayerState.loading: label = i18n('icu:MessageAudio--pending'); - mod = 'pending'; + mod = 'computing'; break; default: throw new TypeError(`Missing case ${state}`); diff --git a/ts/components/PlaybackButton.stories.tsx b/ts/components/PlaybackButton.stories.tsx index 2ae8bffea4..a7e072ee29 100644 --- a/ts/components/PlaybackButton.stories.tsx +++ b/ts/components/PlaybackButton.stories.tsx @@ -1,12 +1,14 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; +import React, { useContext } from 'react'; import type { CSSProperties } from 'react'; import { action } from '@storybook/addon-actions'; import type { Meta } from '@storybook/react'; import type { ButtonProps } from './PlaybackButton'; import { PlaybackButton } from './PlaybackButton'; +import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; +import { ThemeType } from '../types/Util'; export default { title: 'components/PlaybackButton', @@ -20,6 +22,9 @@ const rowStyles: CSSProperties = { }; export function Default(): JSX.Element { + const theme = useContext(StorybookThemeContext); + const themeIncomingColor = theme === ThemeType.light ? '#e9e9e9' : '#3b3b3b'; + return ( <> {(['message', 'draft', 'mini'] as const).map(variant => ( @@ -28,10 +33,19 @@ export function Default(): JSX.Element {
- {(['play', 'download', 'pending', 'pause'] as const).map(mod => ( + {( + [ + 'play', + 'pause', + 'not-downloaded', + 'downloading', + 'computing', + ] as const + ).map(mod => ( void; @@ -27,7 +30,22 @@ export type ButtonProps = { /** Handles animations, key events, and stopping event propagation */ export const PlaybackButton = React.forwardRef( function ButtonInner(props, ref) { - const { mod, label, variant, onClick, context, visible = true } = props; + const { + context, + downloadFraction, + label, + mod, + onClick, + variant, + visible = true, + } = props; + let size = 36; + if (variant === 'mini') { + size = 14; + } else if (variant === 'draft') { + size = 18; + } + const reducedMotion = useReducedMotion(); // eslint-disable-next-line react-hooks/exhaustive-deps -- FIXME const [animProps] = useSpring( @@ -64,6 +82,32 @@ export const PlaybackButton = React.forwardRef( [onClick] ); + let content: JSX.Element | null = null; + const strokeWidth = variant === 'message' ? 2 : 1; + if (mod === 'downloading' && downloadFraction) { + content = ( + + ); + } else if ( + mod === 'computing' || + (mod === 'downloading' && !downloadFraction) + ) { + content = ( +
+ +
+ ); + } + const buttonComponent = ( ); if (variant === 'message') { diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx index c0d1120f03..45692d8e9f 100644 --- a/ts/components/Preferences.stories.tsx +++ b/ts/components/Preferences.stories.tsx @@ -50,6 +50,12 @@ export default { args: { i18n, + autoDownloadAttachment: { + photos: true, + videos: false, + audio: false, + documents: false, + }, availableCameras: [ { deviceId: @@ -133,6 +139,7 @@ export default { makeSyncRequest: action('makeSyncRequest'), onAudioNotificationsChange: action('onAudioNotificationsChange'), onAutoConvertEmojiChange: action('onAutoConvertEmojiChange'), + onAutoDownloadAttachmentChange: action('onAutoDownloadAttachmentChange'), onAutoDownloadUpdateChange: action('onAutoDownloadUpdateChange'), onAutoLaunchChange: action('onAutoLaunchChange'), onCallNotificationsChange: action('onCallNotificationsChange'), diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx index 2953db6afc..81525afd13 100644 --- a/ts/components/Preferences.tsx +++ b/ts/components/Preferences.tsx @@ -17,6 +17,7 @@ import * as LocaleMatcher from '@formatjs/intl-localematcher'; import type { MediaDeviceSettings } from '../types/Calling'; import type { + AutoDownloadAttachmentType, NotificationSettingType, SentMediaQualitySettingType, ZoomFactorType, @@ -73,6 +74,7 @@ type SelectChangeHandlerType = (value: T) => unknown; export type PropsDataType = { // Settings + autoDownloadAttachment: AutoDownloadAttachmentType; blockedCount: number; customColors: Record; defaultConversationColor: DefaultConversationColorType; @@ -162,6 +164,9 @@ type PropsFunctionType = { // Change handlers onAudioNotificationsChange: CheckboxChangeHandlerType; onAutoConvertEmojiChange: CheckboxChangeHandlerType; + onAutoDownloadAttachmentChange: ( + setting: AutoDownloadAttachmentType + ) => unknown; onAutoDownloadUpdateChange: CheckboxChangeHandlerType; onAutoLaunchChange: CheckboxChangeHandlerType; onCallNotificationsChange: CheckboxChangeHandlerType; @@ -209,6 +214,7 @@ enum Page { Calls = 'Calls', Notifications = 'Notifications', Privacy = 'Privacy', + DataUsage = 'DataUsage', // Sub pages ChatColor = 'ChatColor', @@ -245,6 +251,7 @@ const DEFAULT_ZOOM_FACTORS = [ export function Preferences({ addCustomColor, + autoDownloadAttachment, availableCameras, availableLocales, availableMicrophones, @@ -295,6 +302,7 @@ export function Preferences({ notificationContent, onAudioNotificationsChange, onAutoConvertEmojiChange, + onAutoDownloadAttachmentChange, onAutoDownloadUpdateChange, onAutoLaunchChange, onCallNotificationsChange, @@ -884,25 +892,6 @@ export function Preferences({ name="autoConvertEmoji" onChange={onAutoConvertEmojiChange} /> - - } - /> {isSyncSupported && ( @@ -1404,6 +1393,111 @@ export function Preferences({ ) : null} ); + } else if (page === Page.DataUsage) { + settings = ( + <> +
+
+ {i18n('icu:Preferences__button--data-usage')} +
+
+ + + onAutoDownloadAttachmentChange({ + ...autoDownloadAttachment, + photos: newValue, + }) + } + /> + + onAutoDownloadAttachmentChange({ + ...autoDownloadAttachment, + videos: newValue, + }) + } + /> + + onAutoDownloadAttachmentChange({ + ...autoDownloadAttachment, + audio: newValue, + }) + } + /> + + onAutoDownloadAttachmentChange({ + ...autoDownloadAttachment, + documents: newValue, + }) + } + /> +
+
+ {i18n('icu:Preferences__media-auto-download__description')} +
+
+
+ + +
+ {i18n('icu:Preferences__sent-media-quality')} +
+
+ {i18n('icu:Preferences__sent-media-quality__description')} +
+ + } + right={ +