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%;