From 3ea5c0435b5f2124a8da4cb3df0c6b820a0d8bf8 Mon Sep 17 00:00:00 2001 From: lilia Date: Thu, 11 May 2017 16:45:26 -0700 Subject: [PATCH] Update attachment style Add names and sizes for all attachments except images, and (as with arbitrary attachments), clicking on the text will open a save dialog. In the absence of a filename, choose something that makes sense. Display different icons for different media types, including distinct icons for voice notes and audio files. In iOS theme, audio, video, voice, and files are all encapsulated in bubbles. Closes #804 Closes #842 Closes #836 // FREEBIE --- _locales/en/messages.json | 12 +++++ background.html | 6 ++- images/voice.svg | 1 + js/views/attachment_view.js | 96 +++++++++++++++++++++++++--------- stylesheets/_conversation.scss | 13 ++++- stylesheets/_ios.scss | 22 ++++++-- stylesheets/android-dark.scss | 30 +++++++++-- stylesheets/manifest.css | 74 ++++++++++++++++++++++---- 8 files changed, 207 insertions(+), 47 deletions(-) create mode 100644 images/voice.svg diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8c7202bdb0..a61406db51 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -52,6 +52,18 @@ "message": "Unsupported attachment type. Click to save.", "description": "Displayed for incoming unsupported attachment" }, + "clickToSave": { + "message": "Click to save", + "description": "Hover text for attachment filenames" + }, + "unnamedFile": { + "message": "Unnamed File", + "description": "Hover text for attachment filenames" + }, + "voiceMessage": { + "message": "Voice Message", + "description": "Name for a voice message attachment" + }, "unsupportedFileType": { "message": "Unsupported file type", "description": "Displayed for outgoing unsupported attachment" diff --git a/background.html b/background.html index 0b751bdc19..a6ea82e617 100644 --- a/background.html +++ b/background.html @@ -152,9 +152,11 @@ diff --git a/images/voice.svg b/images/voice.svg new file mode 100644 index 0000000000..0ada9d1456 --- /dev/null +++ b/images/voice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/js/views/attachment_view.js b/js/views/attachment_view.js index d183bbe0dc..913c634e94 100644 --- a/js/views/attachment_view.js +++ b/js/views/attachment_view.js @@ -62,12 +62,16 @@ Whisper.AttachmentView = Backbone.View.extend({ tagName: 'span', - className: 'attachment', + className: function() { + if (this.isImage()) { + return 'attachment'; + } else { + return 'attachment bubbled'; + } + }, initialize: function(options) { this.blob = new Blob([this.model.data], {type: this.model.contentType}); - var parts = this.model.contentType.split('/'); - this.contentType = parts[0]; if (options.timestamp) { this.timestamp = options.timestamp; } @@ -82,21 +86,59 @@ } }, onclick: function(e) { - switch (this.contentType) { - case 'image': - var view = new Whisper.LightboxView({ model: this }); - view.render(); - view.$el.appendTo(this.el); - view.$el.trigger('show'); - break; + if (this.isImage()) { + var view = new Whisper.LightboxView({ model: this }); + view.render(); + view.$el.appendTo(this.el); + view.$el.trigger('show'); - default: - if (this.view instanceof MediaView) { - return; - } - this.saveFile(); + } else { + this.saveFile(); } }, + isVoiceMessage: function() { + if (this.model.flags & textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE) { + return true; + } + + // Support for android legacy voice messages + if (this.isAudio() && this.model.fileName === null) { + return true; + } + }, + isAudio: function() { + return this.model.contentType.startsWith('audio/'); + }, + isVideo: function() { + return this.model.contentType.startsWith('video/'); + }, + isImage: function() { + return this.model.contentType.startsWith('image/'); + }, + mediaType: function() { + if (this.isVoiceMessage()) { + return 'voice'; + } else if (this.isAudio()) { + return 'audio'; + } else if (this.isVideo()) { + return 'video'; + } else if (this.isImage()) { + return 'image'; + } + }, + displayName: function() { + if (this.model.fileName) { + return this.model.fileName; + } + if (this.isVoiceMessage()) { + return i18n('voiceMessage'); + } + if (this.isAudio() || this.isVideo()) { + return i18n('mediaMssage'); + } + + return i18n('unnamedFile'); + }, suggestedName: function() { if (this.model.fileName) { return this.model.fileName; @@ -131,15 +173,21 @@ } }, render: function() { + if (!this.isImage()) { + this.renderFileView(); + } var View; - switch(this.contentType) { - case 'image': View = ImageView; break; - case 'video': View = VideoView; break; - case 'audio': View = AudioView; break; + if (this.isImage()) { + View = ImageView; + } else if (this.isAudio()) { + View = AudioView; + } else if (this.isVideo()) { + View = VideoView; } if (!View || _.contains(UnsupportedFileTypes, this.model.contentType)) { - return this.renderFileView(); + this.update(); + return this; } if (!this.objectUrl) { @@ -157,20 +205,20 @@ onTimeout: function() { // Image or media element failed to load. Fall back to FileView. this.stopListening(this.view); - this.renderFileView(); + this.update(); }, renderFileView: function() { this.view = new FileView({ model: { - fileName: this.suggestedName(), + mediaType: this.mediaType(), + fileName: this.displayName(), fileSize: window.filesize(this.model.size), - altText: i18n('unsupportedAttachment') + altText: i18n('clickToSave') } }); this.view.$el.appendTo(this.$el.empty()); this.view.render(); - this.update(); return this; }, update: function() { diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index e53a4dd0a0..1308290df5 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -492,6 +492,7 @@ li.entry .error-icon-container { .icon { margin-left: -0.5em; + margin-right: 0.5em; display: inline-block; vertical-align: middle; &:before { @@ -499,7 +500,17 @@ li.entry .error-icon-container { display: inline-block; width: $button-height * 2; height: $button-height * 2; - @include color-svg('/images/file.svg', black); + @include color-svg('/images/file.svg', $grey_d); + } + + &.audio:before { + @include color-svg('/images/audio.svg', $grey_d); + } + &.video:before { + @include color-svg('/images/video.svg', $grey_d); + } + &.voice:before { + @include color-svg('/images/voice.svg', $grey_d); } } } diff --git a/stylesheets/_ios.scss b/stylesheets/_ios.scss index 413a4815f8..4d192ef9d7 100644 --- a/stylesheets/_ios.scss +++ b/stylesheets/_ios.scss @@ -87,11 +87,12 @@ $ios-border-color: rgba(0,0,0,0.1); } - .message-list .attachments .fileView { + .message-list .attachments .bubbled { border-radius: 15px; margin-bottom: 0.25em; padding: 10px; + position: relative; &:before, &:after { @@ -158,7 +159,7 @@ $ios-border-color: rgba(0,0,0,0.1); } } - .message-list .incoming .attachment .fileView { + .message-list .incoming .bubbled { background-color: #e6e5ea; color: black; float: left; @@ -187,7 +188,7 @@ $ios-border-color: rgba(0,0,0,0.1); } } .outgoing { - .content, .attachments .fileView { + .content, .attachments .bubbled { background-color: $blue; &, .body, a { @include invert-text-color; @@ -196,8 +197,19 @@ $ios-border-color: rgba(0,0,0,0.1); } } - .outgoing .attachments .fileView .icon::before { - @include color-svg('/images/file.svg', white); + .outgoing .attachments .fileView .icon { + &::before { + @include color-svg('/images/file.svg', white); + } + &.audio:before { + @include color-svg('/images/audio.svg', white); + } + &.video:before { + @include color-svg('/images/video.svg', white); + } + &.voice:before { + @include color-svg('/images/voice.svg', white); + } } .attachment { diff --git a/stylesheets/android-dark.scss b/stylesheets/android-dark.scss index 9942c56009..51fff994ad 100644 --- a/stylesheets/android-dark.scss +++ b/stylesheets/android-dark.scss @@ -149,12 +149,34 @@ $text-dark: #CCCCCC; } } - .incoming .bubble .fileView .icon::before { - @include color-svg('/images/file.svg', white); + .incoming .bubble .fileView .icon{ + &::before { + @include color-svg('/images/file.svg', white); + } + &.audio:before { + @include color-svg('/images/audio.svg', white); + } + &.video:before { + @include color-svg('/images/video.svg', white); + } + &.voice:before { + @include color-svg('/images/voice.svg', white); + } } - .outgoing .bubble .fileView .icon::before { - @include color-svg('/images/file.svg', #CCCCCC); + .outgoing .bubble .fileView .icon { + &::before { + @include color-svg('/images/file.svg', #CCCCCC); + } + &.audio:before { + @include color-svg('/images/audio.svg', #CCCCCC); + } + &.video:before { + @include color-svg('/images/video.svg', #CCCCCC); + } + &.voice:before { + @include color-svg('/images/voice.svg', #CCCCCC); + } } button.clock { diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index 6dcc0b6a3a..0e31b59a3c 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -1352,6 +1352,7 @@ li.entry .error-icon-container { .message-container .attachments .fileView .icon, .message-list .attachments .fileView .icon { margin-left: -0.5em; + margin-right: 0.5em; display: inline-block; vertical-align: middle; } .message-container .attachments .fileView .icon:before, @@ -1362,7 +1363,22 @@ li.entry .error-icon-container { height: 48px; -webkit-mask: url("/images/file.svg") no-repeat center; -webkit-mask-size: 100%; - background-color: black; } + background-color: #454545; } + .message-container .attachments .fileView .icon.audio:before, + .message-list .attachments .fileView .icon.audio:before { + -webkit-mask: url("/images/audio.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #454545; } + .message-container .attachments .fileView .icon.video:before, + .message-list .attachments .fileView .icon.video:before { + -webkit-mask: url("/images/video.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #454545; } + .message-container .attachments .fileView .icon.voice:before, + .message-list .attachments .fileView .icon.voice:before { + -webkit-mask: url("/images/voice.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #454545; } .message-container .outgoing .avatar, .message-list .outgoing .avatar { display: none; } @@ -1545,24 +1561,24 @@ li.entry .error-icon-container { .ios .error-message.content, .ios .control .content { padding: 10px; } -.ios .message-list .attachments .fileView { +.ios .message-list .attachments .bubbled { border-radius: 15px; margin-bottom: 0.25em; padding: 10px; position: relative; } - .ios .message-list .attachments .fileView:before, .ios .message-list .attachments .fileView:after { + .ios .message-list .attachments .bubbled:before, .ios .message-list .attachments .bubbled:after { content: ''; display: block; border-radius: 20px; position: absolute; width: 10px; } - .ios .message-list .attachments .fileView:before { + .ios .message-list .attachments .bubbled:before { right: -1px; bottom: -3px; height: 10px; border-radius: 20px; background: #2090ea; } - .ios .message-list .attachments .fileView:after { + .ios .message-list .attachments .bubbled:after { height: 11px; right: -6px; bottom: -3px; @@ -1597,14 +1613,14 @@ li.entry .error-icon-container { border: 1px solid rgba(0, 0, 0, 0.1); } .ios .bubble .meta { clear: both; } -.ios .message-list .incoming .attachment .fileView { +.ios .message-list .incoming .bubbled { background-color: #e6e5ea; color: black; float: left; } - .ios .message-list .incoming .attachment .fileView:before { + .ios .message-list .incoming .bubbled:before { left: -1px; background-color: #e6e5ea; } - .ios .message-list .incoming .attachment .fileView:after { + .ios .message-list .incoming .bubbled:after { left: -6px; } .ios .incoming .content { background-color: #e6e5ea; @@ -1615,18 +1631,30 @@ li.entry .error-icon-container { background-color: #e6e5ea; } .ios .incoming .content .body:after { left: -6px; } -.ios .outgoing .content, .ios .outgoing .attachments .fileView { +.ios .outgoing .content, .ios .outgoing .attachments .bubbled { background-color: #2090ea; float: right; } - .ios .outgoing .content, .ios .outgoing .content .body, .ios .outgoing .content a, .ios .outgoing .attachments .fileView, .ios .outgoing .attachments .fileView .body, .ios .outgoing .attachments .fileView a { + .ios .outgoing .content, .ios .outgoing .content .body, .ios .outgoing .content a, .ios .outgoing .attachments .bubbled, .ios .outgoing .attachments .bubbled .body, .ios .outgoing .attachments .bubbled a { color: white; } - .ios .outgoing .content::selection, .ios .outgoing .content .body::selection, .ios .outgoing .content a::selection, .ios .outgoing .attachments .fileView::selection, .ios .outgoing .attachments .fileView .body::selection, .ios .outgoing .attachments .fileView a::selection { + .ios .outgoing .content::selection, .ios .outgoing .content .body::selection, .ios .outgoing .content a::selection, .ios .outgoing .attachments .bubbled::selection, .ios .outgoing .attachments .bubbled .body::selection, .ios .outgoing .attachments .bubbled a::selection { background: white; color: #454545; } .ios .outgoing .attachments .fileView .icon::before { -webkit-mask: url("/images/file.svg") no-repeat center; -webkit-mask-size: 100%; background-color: white; } +.ios .outgoing .attachments .fileView .icon.audio:before { + -webkit-mask: url("/images/audio.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } +.ios .outgoing .attachments .fileView .icon.video:before { + -webkit-mask: url("/images/video.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } +.ios .outgoing .attachments .fileView .icon.voice:before { + -webkit-mask: url("/images/voice.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } .ios .attachment { margin-bottom: 1px; } .ios .attachment a { @@ -2010,10 +2038,34 @@ li.entry .error-icon-container { -webkit-mask: url("/images/file.svg") no-repeat center; -webkit-mask-size: 100%; background-color: white; } + .android-dark .incoming .bubble .fileView .icon.audio:before { + -webkit-mask: url("/images/audio.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } + .android-dark .incoming .bubble .fileView .icon.video:before { + -webkit-mask: url("/images/video.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } + .android-dark .incoming .bubble .fileView .icon.voice:before { + -webkit-mask: url("/images/voice.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: white; } .android-dark .outgoing .bubble .fileView .icon::before { -webkit-mask: url("/images/file.svg") no-repeat center; -webkit-mask-size: 100%; background-color: #CCCCCC; } + .android-dark .outgoing .bubble .fileView .icon.audio:before { + -webkit-mask: url("/images/audio.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #CCCCCC; } + .android-dark .outgoing .bubble .fileView .icon.video:before { + -webkit-mask: url("/images/video.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #CCCCCC; } + .android-dark .outgoing .bubble .fileView .icon.voice:before { + -webkit-mask: url("/images/voice.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #CCCCCC; } .android-dark button.clock { -webkit-mask: url("/images/clock.svg") no-repeat center; -webkit-mask-size: 100%;