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 | 98 +++++++++++++++++++++++++--------- stylesheets/_conversation.scss | 13 ++++- stylesheets/_ios.scss | 22 ++++++-- stylesheets/android-dark.scss | 30 +++++++++-- stylesheets/manifest.css | 74 +++++++++++++++++++++---- 8 files changed, 208 insertions(+), 48 deletions(-) create mode 100644 images/voice.svg diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8c7202bdb..a61406db5 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 0b751bdc1..a6ea82e61 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 000000000..0ada9d145 --- /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 d183bbe0d..913c634e9 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; - - default: - if (this.view instanceof MediaView) { - return; - } - this.saveFile(); + if (this.isImage()) { + var view = new Whisper.LightboxView({ model: this }); + view.render(); + view.$el.appendTo(this.el); + view.$el.trigger('show'); + + } 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 e53a4dd0a..1308290df 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 413a4815f..4d192ef9d 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 9942c5600..51fff994a 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 6dcc0b6a3..0e31b59a3 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%;