Support for iOS theme

This commit is contained in:
Scott Nonnenberg 2018-04-12 12:21:37 -07:00
parent 644bc9e6fb
commit 087dd0f758
No known key found for this signature in database
GPG key ID: 5F82280C35134661
7 changed files with 226 additions and 19 deletions

View file

@ -428,6 +428,24 @@
"selectAContact": {
"message": "Select a contact or group to start chatting."
},
"replyingToYourself": {
"message": "Replying to Yourself",
"description": "Shown in iOS theme when you quote yourself"
},
"replyingToYou": {
"message": "Replying to You",
"description": "Shown in iOS theme when someone else quotes a message from you"
},
"replyingTo": {
"message": "Replying to $name`$",
"description": "Shown in iOS theme when you or someone quotes to a message which is not from you",
"placeholders": {
"name": {
"content": "$1",
"example": "John"
}
}
},
"audio": {
"message": "Audio",
"description": "Shown in a quotation of a message containing an audio attachment if no text was originally provided with that attachment"

View file

@ -400,29 +400,30 @@
});
}
const OUR_NUMBER = textsecure.storage.user.getNumber();
const { author } = quote;
const contact = ConversationController.get(author);
const authorTitle = contact ? contact.getTitle() : author;
const authorProfileName = contact ? contact.getProfileName() : null;
const authorColor = contact ? contact.getColor() : 'grey';
const isFromMe = contact ? contact.id === OUR_NUMBER : false;
const isIncoming = this.model.isIncoming();
const quoterContact = this.model.getContact();
const quoterAuthorColor = quoterContact ? quoterContact.getColor() : null;
const props = {
authorTitle,
authorProfileName,
attachments: quote.attachments && quote.attachments.map(processAttachment),
authorColor,
authorProfileName,
authorTitle,
isFromMe,
isIncoming,
quoterAuthorColor,
openQuotedMessage: () => {
onClick: () => {
const { quotedMessage } = this.model;
if (quotedMessage) {
this.trigger('scroll-to-message', { id: quotedMessage.id });
}
},
text: quote.text,
attachments: quote.attachments && quote.attachments.map(processAttachment),
};
if (!this.replyView) {

View file

@ -379,11 +379,7 @@ li.entry .error-icon-container {
display: none;
}
.message-list .outgoing .bubble .quote {
margin-top: $android-bubble-quote-padding - $android-bubble-padding-vertical;
}
.private .message-list .incoming .bubble .quote {
.message-list .outgoing .bubble .quote, .private .message-list .incoming .bubble .quote {
margin-top: $android-bubble-quote-padding - $android-bubble-padding-vertical;
}
@ -479,7 +475,7 @@ span.status {
margin-bottom: 0.5em;
// Accent color border:
border-left-width: 3;
border-left-width: 3px;
border-left-style: solid;
.primary {
@ -489,6 +485,12 @@ span.status {
padding-top: 6px;
padding-bottom: 6px;
// Will turn on in the iOS theme. This extra element is necessary because the iOS
// theme requires text that isn't used at all in the Android Theme
.ios-label {
display: none;
}
.author {
font-weight: bold;
margin-bottom: 0.3em;

View file

@ -106,6 +106,130 @@ $ios-border-color: rgba(0,0,0,0.1);
padding: 10px;
}
.message-list {
.quote {
border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
// Not ideal, but necessary to override the specificity of the android theme color
// classes used in conversations.scss
background-color: white !important;
border: 1px solid lightgray !important;
border-bottom: none !important;
margin-top: 0px;
margin-bottom: 0px;
margin-left: 0px;
margin-right: 0px;
.primary {
padding: 10px;
.text,
.filename-label,
.type-label {
border-left: 2px solid lightgray;
padding: 5px;
padding-left: 7px;
// Without this smaller bottom padding, text beyond four lines still shows up!
padding-bottom: 2px;
color: black;
}
.author {
display: none;
}
.ios-label {
display: block;
color: lightgray;
font-size: smaller;
margin-bottom: 3px;
}
}
.icon-container {
height: 61px;
width: 61px;
.circle-background {
left: 12px;
right: 12px;
top: 12px;
bottom: 12px;
background-color: $blue !important;
}
.icon {
left: 18px;
right: 18px;
top: 18px;
bottom: 18px;
background-color: white !important;
}
.inner {
padding: 12px;
}
}
.from-me {
.primary {
.text,
.filename-label,
.type-label {
border-left: 2px solid $blue;
}
}
}
}
.incoming {
.bubble {
.quote {
background-color: lightgray !important;
border-left: none;
.ios-label {
color: white;
}
.primary {
.text,
.filename-label,
.type-label {
border-left: 2px solid white;
}
}
}
}
}
.bubble {
.quote.from-me {
.primary {
.text,
.filename-label,
.type-label {
border-left: 2px solid $blue;
}
}
}
}
.outgoing .bubble .quote,
.private .message-list .incoming .bubble .quote {
margin-top: 0px;
}
.outgoing .bubble .quote .icon-container .circle-background {
background-color: lightgray !important;
}
}
.attachments .bubbled {
border-radius: 15px;

View file

@ -34,6 +34,39 @@ const View = Whisper.MessageView;
</util.ConversationContext>
```
#### Replies to you or yourself
```jsx
const outgoing = new Whisper.Message({
type: 'outgoing',
body: 'About six',
sent_at: Date.now() - 18000000,
quote: {
text: 'How many ferrets do you have?',
author: util.ourNumber,
id: Date.now() - 1000,
},
});
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
source: '+12025550011',
type: 'incoming',
quote: Object.assign({}, outgoing.attributes.quote, {
author: util.ourNumber,
}),
}));
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>
```
#### In a group conversation
```jsx

View file

@ -11,9 +11,9 @@ interface Props {
authorProfileName?: string;
authorTitle: string;
i18n: (key: string, values?: Array<string>) => string;
isFromMe: string;
isIncoming: boolean;
openQuotedMessage?: () => void;
quoterAuthorColor?: string;
onClick?: () => void;
text: string;
}
@ -68,10 +68,10 @@ export class Quote extends React.Component<Props, {}> {
}
public renderIcon(icon: string) {
const { authorColor, isIncoming, quoterAuthorColor } = this.props;
const { authorColor, isIncoming } = this.props;
const backgroundColor = isIncoming ? 'white' : authorColor;
const iconColor = isIncoming ? quoterAuthorColor : 'white';
const iconColor = isIncoming ? authorColor : 'white';
return (
<div className="icon-container">
@ -138,12 +138,28 @@ export class Quote extends React.Component<Props, {}> {
return <div className="filename-label">{fileName}</div>;
}
public renderIOSLabel() {
const { i18n, isIncoming, isFromMe, authorTitle, authorProfileName } = this.props;
const profileString = authorProfileName ? ` ~${authorProfileName}` : '';
const authorName = `${authorTitle}${profileString}`;
const label = isFromMe
? isIncoming
? i18n('replyingToYou')
: i18n('replyingToYourself')
: i18n('replyingTo', [authorName]);
return <div className='ios-label'>{label}</div>;
}
public render() {
const {
authorTitle,
authorProfileName,
authorColor,
openQuotedMessage,
onClick,
isFromMe,
} = this.props;
if (!validateQuote(this.props)) {
@ -155,8 +171,13 @@ export class Quote extends React.Component<Props, {}> {
: null;
return (
<div onClick={openQuotedMessage} className={classnames(authorColor, 'quote')} >
<div onClick={onClick} className={classnames(
authorColor,
'quote',
isFromMe ? 'from-me' : null
)} >
<div className="primary">
{this.renderIOSLabel()}
<div className={classnames(authorColor, 'author')}>
{authorTitle}{' '}{authorProfileElement}
</div>

View file

@ -141,9 +141,17 @@ const CONTACTS = COLORS.map((color, index) => {
return parent.ConversationController.dangerouslyCreateAndAdd(contact);
});
const me = parent.ConversationController.dangerouslyCreateAndAdd({
id: ourNumber,
name: 'Me!',
type: 'private',
color: 'light_blue',
});
export {
COLORS,
CONTACTS,
me,
};
parent.textsecure.storage.user.getNumber = () => ourNumber;