In iOS theme, join attachment bubble with caption bubble

This commit is contained in:
Scott Nonnenberg 2018-04-06 14:51:52 -07:00
parent 3a76c3c86e
commit ae043bf239
No known key found for this signature in database
GPG key ID: 5F82280C35134661
9 changed files with 246 additions and 97 deletions

View file

@ -277,10 +277,12 @@
<span class='profileName'>{{ profileName }} </span> <span class='profileName'>{{ profileName }} </span>
{{ /profileName }} {{ /profileName }}
</div> </div>
<div class='attachments'></div> <div class='inner-bubble {{ innerBubbleClasses }}'>
<p class='content' dir='auto'> <div class='attachments'></div>
{{ #message }}<span class='body'>{{ message }}</span>{{ /message }} <p class='content' dir='auto'>
</p> {{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
</p>
</div>
<div class='meta'> <div class='meta'>
<span class='timestamp' data-timestamp={{ timestamp }}></span> <span class='timestamp' data-timestamp={{ timestamp }}></span>
<span class='status hide'></span> <span class='status hide'></span>

View file

@ -69,7 +69,7 @@
]; ];
Whisper.AttachmentView = Backbone.View.extend({ Whisper.AttachmentView = Backbone.View.extend({
tagName: 'span', tagName: 'div',
className() { className() {
if (this.isImage()) { if (this.isImage()) {
return 'attachment'; return 'attachment';

View file

@ -355,6 +355,24 @@
this.timerView.setElement(this.$('.timer')); this.timerView.setElement(this.$('.timer'));
this.timerView.update(); this.timerView.update();
}, },
isImageWithoutCaption: function() {
var attachments = this.model.get('attachments');
var body = this.model.get('body');
if (!attachments || attachments.length === 0) {
return false;
}
if (body && body.trim()) {
return false;
}
var first = attachments[0];
if (first.contentType.startsWith('image/') && first.contentType !== 'image/tiff') {
return true;
}
return false;
},
render: function() { render: function() {
var contact = this.model.isIncoming() ? this.model.getContact() : null; var contact = this.model.isIncoming() ? this.model.getContact() : null;
this.$el.html( this.$el.html(
@ -364,6 +382,7 @@
sender: (contact && contact.getTitle()) || '', sender: (contact && contact.getTitle()) || '',
avatar: (contact && contact.getAvatar()), avatar: (contact && contact.getAvatar()),
profileName: (contact && contact.getProfileName()), profileName: (contact && contact.getProfileName()),
innerBubbleClasses: this.isImageWithoutCaption() ? '' : 'with-tail',
}, this.render_partials()) }, this.render_partials())
); );
this.timeStampView.setElement(this.$('.timestamp')); this.timeStampView.setElement(this.$('.timestamp'));

View file

@ -451,6 +451,7 @@ span.status {
} }
.body { .body {
margin-top: 0.5em;
white-space: pre-wrap; white-space: pre-wrap;
a { a {
@ -591,6 +592,7 @@ span.status {
position: relative; position: relative;
padding: 5px; padding: 5px;
padding-right: 10px; padding-right: 10px;
padding-bottom: 0px;
cursor: pointer; cursor: pointer;

View file

@ -109,11 +109,33 @@ $ios-border-color: rgba(0,0,0,0.1);
.attachments .bubbled { .attachments .bubbled {
border-radius: 15px; border-radius: 15px;
margin-bottom: 0.25em;
padding: 10px; padding: 10px;
padding-top: 0px;
padding-bottom: 5px;
position: relative; position: relative;
}
.inner-bubble {
border-radius: 15px;
margin-bottom: 5px;
.body {
margin-top: 0;
display: inline-block;
padding: 10px;
position: relative;
word-break: break-word;
}
.attachments img {
border-radius: 15px;
}
}
.inner-bubble.with-tail {
position: relative;
&:before, &:after { &:before, &:after {
content: ''; content: '';
@ -135,51 +157,27 @@ $ios-border-color: rgba(0,0,0,0.1);
bottom: -3px; bottom: -3px;
background: #eee; background: #eee;
} }
}
.bubble {
.content {
margin-bottom: 5px;
.body {
display: inline-block;
padding: 10px;
position: relative;
word-break: break-word;
&:before, &:after {
content: '';
display: block;
border-radius: 20px;
position: absolute;
width: 10px;
}
&:before {
right: -1px;
bottom: -3px;
height: 10px;
border-radius: 20px;
background: $blue;
}
&:after {
height: 11px;
right: -6px;
bottom: -3px;
background: #eee;
}
}
}
.content, .attachments img {
border-radius: 15px;
}
.attachments img { .attachments img {
background-color: white; border-bottom-left-radius: 0px;
} border-bottom-right-radius: 0px;
.meta {
clear: both;
} }
} }
.incoming .bubbled { .meta {
clear: both;
}
.outgoing .inner-bubble.with-tail {
background-color: $blue;
max-width: 100%;
&, .body, a {
@include invert-text-color;
}
float: right;
}
.incoming .inner-bubble.with-tail {
background-color: white; background-color: white;
color: black; color: black;
float: left; float: left;
@ -194,31 +192,6 @@ $ios-border-color: rgba(0,0,0,0.1);
} }
} }
.incoming .content {
background-color: white;
color: black;
float: left;
.body {
&:before {
left: -1px;
background-color: white;
}
&:after {
left: -6px;
}
}
}
.outgoing {
.content, .attachments .bubbled {
background-color: $blue;
max-width: 100%;
&, .body, a {
@include invert-text-color;
}
float: right;
}
}
.outgoing .attachments .fileView .icon { .outgoing .attachments .fileView .icon {
@include color-svg('../images/file.svg', white); @include color-svg('../images/file.svg', white);
&.audio { &.audio {
@ -236,7 +209,6 @@ $ios-border-color: rgba(0,0,0,0.1);
a { a {
border-radius: 15px; border-radius: 15px;
} }
margin-bottom: 1px;
} }
.hourglass { .hourglass {
@include hourglass(#999); @include hourglass(#999);

View file

@ -206,14 +206,22 @@
<script type='text/x-tmpl-mustache' id='message'> <script type='text/x-tmpl-mustache' id='message'>
{{> avatar }} {{> avatar }}
<div class='bubble {{ avatar.color }}'> <div class='bubble {{ avatar.color }}'>
<div class='sender' dir='auto'>{{ sender }}</div> <div class='sender' dir='auto'>
<div class='attachments'></div> {{ sender }}
<p class='content' dir='auto'> {{ #profileName }}
{{ #message }}<span class='body'>{{ message }}</span>{{ /message }} <span class='profileName'>{{ profileName }} </span>
</p> {{ /profileName }}
</div>
<div class='inner-bubble {{ innerBubbleClasses }}'>
<div class='attachments'></div>
<p class='content' dir='auto'>
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
</p>
</div>
<div class='meta'> <div class='meta'>
<span class='timestamp' data-timestamp={{ timestamp }}></span> <span class='timestamp' data-timestamp={{ timestamp }}></span>
<span class='status hide'></span> <span class='status hide'></span>
<span class='timer'></span>
</div> </div>
</div> </div>
</script> </script>

View file

@ -32,10 +32,12 @@ window.Whisper.View.Templates = {
<span class='profileName'>{{ profileName }} </span> <span class='profileName'>{{ profileName }} </span>
{{ /profileName }} {{ /profileName }}
</div> </div>
<div class='attachments'></div> <div class='inner-bubble {{ innerBubbleClasses }}'>
<p class='content' dir='auto'> <div class='attachments'></div>
{{ #message }}<span class='body'>{{ message }}</span>{{ /message }} <p class='content' dir='auto'>
</p> {{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
</p>
</div>
<div class='meta'> <div class='meta'>
<span class='timestamp' data-timestamp={{ timestamp }}></span> <span class='timestamp' data-timestamp={{ timestamp }}></span>
<span class='status hide'></span> <span class='status hide'></span>

View file

@ -1,5 +1,5 @@
Placeholder: Placeholder component:
```jsx ```jsx
<util.ConversationContext theme={util.theme}> <util.ConversationContext theme={util.theme}>
@ -7,9 +7,36 @@ Placeholder:
</util.ConversationContext> </util.ConversationContext>
``` ```
## With an attachment ## MessageView (Backbone)
### Image ### Plain messages
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
body: 'How are you doing this fine day?',
sent_at: Date.now() - 18000,
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550100',
type: 'incoming',
}));
const View = Whisper.MessageView;
<util.ConversationContext theme={util.theme}>
<util.BackboneWrapper
View={View}
options={{ model: incoming }}
/>
<util.BackboneWrapper
View={View}
options={{ model: outgoing }}
/>
</util.ConversationContext>
```
### With an attachment
#### Image
```jsx ```jsx
const outgoing = new Whisper.Message({ const outgoing = new Whisper.Message({
@ -39,12 +66,41 @@ const View = Whisper.MessageView;
</util.ConversationContext> </util.ConversationContext>
``` ```
### Video #### Image, no caption
```jsx ```jsx
const outgoing = new Whisper.Message({ const outgoing = new Whisper.Message({
type: 'outgoing', type: 'outgoing',
body: "Beatiful, isn't it?", sent_at: Date.now() - 18000000,
attachments: [{
data: util.gif,
fileName: 'pi.gif',
contentType: 'image/gif',
}],
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550100',
type: 'incoming',
}));
const View = Whisper.MessageView;
<util.ConversationContext theme={util.theme}>
<util.BackboneWrapper
View={View}
options={{ model: incoming }}
/>
<util.BackboneWrapper
View={View}
options={{ model: outgoing }}
/>
</util.ConversationContext>
```
#### Video
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
body: "Beautiful, isn't it?",
sent_at: Date.now() - 10000, sent_at: Date.now() - 10000,
attachments: [{ attachments: [{
data: util.mp4, data: util.mp4,
@ -69,7 +125,36 @@ const View = Whisper.MessageView;
</util.ConversationContext> </util.ConversationContext>
``` ```
### Audio #### Video, no caption
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
sent_at: Date.now() - 10000,
attachments: [{
data: util.mp4,
fileName: 'freezing_bubble.mp4',
contentType: 'video/mp4',
}],
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550100',
type: 'incoming',
}));
const View = Whisper.MessageView;
<util.ConversationContext theme={util.theme}>
<util.BackboneWrapper
View={View}
options={{ model: incoming }}
/>
<util.BackboneWrapper
View={View}
options={{ model: outgoing }}
/>
</util.ConversationContext>
```
#### Audio
```jsx ```jsx
const outgoing = new Whisper.Message({ const outgoing = new Whisper.Message({
@ -99,12 +184,40 @@ const View = Whisper.MessageView;
</util.ConversationContext> </util.ConversationContext>
``` ```
### Voice message #### Audio, no caption
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
sent_at: Date.now() - 15000,
attachments: [{
data: util.mp3,
fileName: 'agnus_dei.mp3',
contentType: 'audio/mp3',
}],
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550100',
type: 'incoming',
}));
const View = Whisper.MessageView;
<util.ConversationContext theme={util.theme}>
<util.BackboneWrapper
View={View}
options={{ model: incoming }}
/>
<util.BackboneWrapper
View={View}
options={{ model: outgoing }}
/>
</util.ConversationContext>
```
#### Voice message
```jsx ```jsx
const outgoing = new Whisper.Message({ const outgoing = new Whisper.Message({
type: 'outgoing', type: 'outgoing',
body: 'This is a nice song',
sent_at: Date.now() - 15000, sent_at: Date.now() - 15000,
attachments: [{ attachments: [{
flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE, flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
@ -130,7 +243,7 @@ const View = Whisper.MessageView;
</util.ConversationContext> </util.ConversationContext>
``` ```
### Other file type #### Other file type
```jsx ```jsx
const outgoing = new Whisper.Message({ const outgoing = new Whisper.Message({
@ -159,3 +272,32 @@ const View = Whisper.MessageView;
/> />
</util.ConversationContext> </util.ConversationContext>
``` ```
#### Other file type, no caption
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
sent_at: Date.now() - 15000,
attachments: [{
data: util.txt,
fileName: 'lorum_ipsum.txt',
contentType: 'text/plain',
}],
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550100',
type: 'incoming',
}));
const View = Whisper.MessageView;
<util.ConversationContext theme={util.theme}>
<util.BackboneWrapper
View={View}
options={{ model: incoming }}
/>
<util.BackboneWrapper
View={View}
options={{ model: outgoing }}
/>
</util.ConversationContext>
```

View file

@ -4,7 +4,7 @@ import React from 'react';
/** /**
* A placeholder Message component for now, giving the structure of a plain message with * A placeholder Message component for now, giving the structure of a plain message with
* none of the dynamic functionality. This page will be used to build up our corpus of * none of the dynamic functionality. This page will be used to build up our corpus of
* permutations before start moving all message functionality to React. * permutations before we start moving all message functionality to React.
*/ */
export class Message extends React.Component<{}, {}> { export class Message extends React.Component<{}, {}> {
public render() { public render() {
@ -13,12 +13,14 @@ export class Message extends React.Component<{}, {}> {
<span className="avatar" /> <span className="avatar" />
<div className="bubble"> <div className="bubble">
<div className="sender" dir="auto" /> <div className="sender" dir="auto" />
<div className="attachments" /> <div className="inner-bubble">
<p className="content" dir="auto"> <div className="attachments" />
<span className="body"> <p className="content" dir="auto">
Hi there. How are you doing? Feeling pretty good? Awesome. <span className="body">
</span> Hi there. How are you doing? Feeling pretty good? Awesome.
</p> </span>
</p>
</div>
<div className="meta"> <div className="meta">
<span <span
className="timestamp" className="timestamp"