Show about info from contact profiles
This commit is contained in:
parent
c8b551edab
commit
258bd55dd2
13 changed files with 140 additions and 76 deletions
|
@ -201,21 +201,6 @@ a {
|
|||
.container {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.members .contact {
|
||||
@include light-theme {
|
||||
border-bottom: 1px solid $color-gray-05;
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@include dark-theme {
|
||||
border-bottom: 1px solid $color-gray-75;
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.banner {
|
||||
|
|
|
@ -2077,6 +2077,32 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
}
|
||||
}
|
||||
|
||||
.module-about {
|
||||
&__container {
|
||||
margin-bottom: 8px;
|
||||
max-width: 248px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
@include font-body-1;
|
||||
@include light-theme {
|
||||
color: $color-gray-60;
|
||||
}
|
||||
@include dark-theme {
|
||||
color: $color-gray-25;
|
||||
}
|
||||
max-width: 400px;
|
||||
|
||||
img.emoji {
|
||||
height: 1em;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
vertical-align: middle;
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Module: Embedded Contact
|
||||
|
||||
.module-embedded-contact {
|
||||
|
@ -2689,6 +2715,7 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
.module-contact-list-item__text {
|
||||
margin-left: 8px;
|
||||
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
|
@ -2696,7 +2723,13 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
}
|
||||
|
||||
.module-contact-list-item__text__name {
|
||||
@include font-body-2-bold;
|
||||
@include font-body-1-bold;
|
||||
@include light-theme {
|
||||
color: $color-gray-90;
|
||||
}
|
||||
@include dark-theme {
|
||||
color: $color-gray-05;
|
||||
}
|
||||
}
|
||||
|
||||
.module-contact-list-item__text__profile-name {
|
||||
|
@ -2731,7 +2764,7 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
text-align: right;
|
||||
height: 100%;
|
||||
|
||||
@include font-body-2-bold;
|
||||
@include font-body-2;
|
||||
}
|
||||
|
||||
// Module: In Contacts Icon
|
||||
|
@ -10299,7 +10332,7 @@ $contact-modal-padding: 18px;
|
|||
.module-contact-modal {
|
||||
@include font-body-2;
|
||||
|
||||
min-width: 250px;
|
||||
min-width: 280px;
|
||||
padding: $contact-modal-padding;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
@ -10336,7 +10369,7 @@ $contact-modal-padding: 18px;
|
|||
}
|
||||
|
||||
.module-contact-modal__name {
|
||||
@include font-body-1-bold;
|
||||
@include font-title-2;
|
||||
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ storiesOf('Components/ContactListItem', module)
|
|||
title="Someone 🔥 Somewhere"
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
isVerified
|
||||
profileName="🔥Flames🔥"
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
|
@ -39,6 +38,7 @@ storiesOf('Components/ContactListItem', module)
|
|||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
about="👍 Free to chat"
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
/>
|
||||
|
@ -48,26 +48,13 @@ storiesOf('Components/ContactListItem', module)
|
|||
name="Another ❄️ Yes"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="❄️Ice❄️"
|
||||
about="🙏 Be kind"
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
.add('With name and profile, verified', () => {
|
||||
return (
|
||||
<ContactListItem
|
||||
i18n={i18n}
|
||||
title="Someone 🔥 Somewhere"
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
isVerified
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.add('With name and profile, admin', () => {
|
||||
return (
|
||||
<ContactListItem
|
||||
|
@ -77,7 +64,7 @@ storiesOf('Components/ContactListItem', module)
|
|||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
isVerified
|
||||
about="👍 Free to chat"
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
/>
|
||||
|
@ -90,6 +77,7 @@ storiesOf('Components/ContactListItem', module)
|
|||
isAdmin
|
||||
title="(202) 555-0011"
|
||||
phoneNumber="(202) 555-0011"
|
||||
about="👍 Free to chat"
|
||||
avatarPath={gifUrl}
|
||||
onClick={onClick}
|
||||
/>
|
||||
|
@ -104,6 +92,7 @@ storiesOf('Components/ContactListItem', module)
|
|||
color="teal"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
about="👍 Free to chat"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
|
@ -115,39 +104,28 @@ storiesOf('Components/ContactListItem', module)
|
|||
phoneNumber="(202) 555-0011"
|
||||
title="🔥Flames🔥"
|
||||
profileName="🔥Flames🔥"
|
||||
about="👍 Free to chat"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.add('Verified, profile, no name, no avatar', () => {
|
||||
.add('No name, no profile, no avatar, no about', () => {
|
||||
return (
|
||||
<ContactListItem
|
||||
i18n={i18n}
|
||||
phoneNumber="(202) 555-0011"
|
||||
title="🔥Flames🔥"
|
||||
profileName="🔥Flames🔥"
|
||||
isVerified
|
||||
title="(202) 555-0011"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.add('No name, no profile, no avatar', () => {
|
||||
return (
|
||||
<ContactListItem
|
||||
i18n={i18n}
|
||||
phoneNumber="(202) 555-0011"
|
||||
title="(202) 555-0011"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.add('Verified, no name, no profile, no avatar', () => {
|
||||
return (
|
||||
<ContactListItem
|
||||
i18n={i18n}
|
||||
title="(202) 555-0011"
|
||||
about="👍 Free to chat"
|
||||
phoneNumber="(202) 555-0011"
|
||||
isVerified
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { About } from './conversation/About';
|
||||
import { Avatar } from './Avatar';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
import { InContactsIcon } from './InContactsIcon';
|
||||
|
@ -12,12 +13,12 @@ import { LocalizerType } from '../types/Util';
|
|||
import { ColorType } from '../types/Colors';
|
||||
|
||||
type Props = {
|
||||
about?: string;
|
||||
avatarPath?: string;
|
||||
color?: ColorType;
|
||||
i18n: LocalizerType;
|
||||
isAdmin?: boolean;
|
||||
isMe?: boolean;
|
||||
isVerified?: boolean;
|
||||
name?: string;
|
||||
onClick?: () => void;
|
||||
phoneNumber?: string;
|
||||
|
@ -53,24 +54,11 @@ export class ContactListItem extends React.Component<Props> {
|
|||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const {
|
||||
i18n,
|
||||
isAdmin,
|
||||
isMe,
|
||||
isVerified,
|
||||
name,
|
||||
onClick,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
title,
|
||||
} = this.props;
|
||||
const { about, i18n, isAdmin, isMe, name, onClick, title } = this.props;
|
||||
|
||||
const displayName = isMe ? i18n('you') : title;
|
||||
const shouldShowIcon = Boolean(name);
|
||||
|
||||
const showNumber = Boolean(isMe || name || profileName);
|
||||
const showVerified = !isMe && isVerified;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
|
@ -93,12 +81,7 @@ export class ContactListItem extends React.Component<Props> {
|
|||
) : null}
|
||||
</div>
|
||||
<div className="module-contact-list-item__text__additional-data">
|
||||
{showVerified ? (
|
||||
<div className="module-contact-list-item__text__verified-icon" />
|
||||
) : null}
|
||||
{showVerified ? ` ${i18n('verified')}` : null}
|
||||
{showVerified && showNumber ? ' ∙ ' : null}
|
||||
{showNumber ? phoneNumber : null}
|
||||
<About text={about} />
|
||||
</div>
|
||||
</div>
|
||||
{isAdmin ? (
|
||||
|
|
22
ts/components/conversation/About.tsx
Normal file
22
ts/components/conversation/About.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Emojify } from './Emojify';
|
||||
|
||||
export type PropsType = {
|
||||
text?: string;
|
||||
};
|
||||
|
||||
export const About = ({ text }: PropsType): JSX.Element | null => {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="module-about__text" dir="auto">
|
||||
<Emojify text={text || ''} />
|
||||
</span>
|
||||
);
|
||||
};
|
|
@ -24,6 +24,7 @@ const defaultContact: ConversationType = {
|
|||
title: 'Pauline Oliveros',
|
||||
type: 'direct',
|
||||
phoneNumber: '(333) 444-5515',
|
||||
about: '👍 Free to chat',
|
||||
};
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, { ReactPortal } from 'react';
|
|||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { About } from './About';
|
||||
import { Avatar } from '../Avatar';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
|
@ -110,6 +111,9 @@ export const ContactModal = ({
|
|||
title={contact.title}
|
||||
/>
|
||||
<div className="module-contact-modal__name">{contact.title}</div>
|
||||
<div className="module-about__container">
|
||||
<About text={contact.about} />
|
||||
</div>
|
||||
{contact.phoneNumber && (
|
||||
<div className="module-contact-modal__profile-and-number">
|
||||
{contact.phoneNumber}
|
||||
|
|
|
@ -11,6 +11,7 @@ import enMessages from '../../../_locales/en/messages.json';
|
|||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const getAbout = () => text('about', '👍 Free to chat');
|
||||
const getTitle = () => text('name', 'Cayce Bollard');
|
||||
const getName = () => text('name', 'Cayce Bollard');
|
||||
const getProfileName = () => text('profileName', 'Cayce Bollard (profile)');
|
||||
|
@ -23,6 +24,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={getTitle()}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
@ -39,6 +41,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={getTitle()}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
@ -55,6 +58,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={getTitle()}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
@ -71,6 +75,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={getTitle()}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
@ -87,6 +92,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={text('title', 'Cayce Bollard (profile)')}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
@ -103,6 +109,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
|||
return (
|
||||
<div style={{ width: '480px' }}>
|
||||
<ConversationHero
|
||||
about={getAbout()}
|
||||
i18n={i18n}
|
||||
title={text('title', '+1 (646) 327-2700')}
|
||||
avatarPath={getAvatarPath()}
|
||||
|
|
|
@ -5,11 +5,13 @@ import * as React from 'react';
|
|||
import { take } from 'lodash';
|
||||
import { Avatar, Props as AvatarProps } from '../Avatar';
|
||||
import { ContactName } from './ContactName';
|
||||
import { About } from './About';
|
||||
import { Emojify } from './Emojify';
|
||||
import { Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type Props = {
|
||||
about?: string;
|
||||
i18n: LocalizerType;
|
||||
isMe?: boolean;
|
||||
sharedGroupNames?: Array<string>;
|
||||
|
@ -111,6 +113,7 @@ const renderMembershipRow = ({
|
|||
|
||||
export const ConversationHero = ({
|
||||
i18n,
|
||||
about,
|
||||
avatarPath,
|
||||
color,
|
||||
conversationType,
|
||||
|
@ -188,6 +191,11 @@ export const ConversationHero = ({
|
|||
/>
|
||||
)}
|
||||
</h1>
|
||||
{about && (
|
||||
<div className="module-about__container">
|
||||
<About text={about} />
|
||||
</div>
|
||||
)}
|
||||
{!isMe ? (
|
||||
<div className="module-conversation-hero__with">
|
||||
{membersCount === 1
|
||||
|
|
2
ts/model-types.d.ts
vendored
2
ts/model-types.d.ts
vendored
|
@ -201,6 +201,8 @@ export type ConversationAttributesType = {
|
|||
e164?: string;
|
||||
|
||||
// Private other fields
|
||||
about?: string;
|
||||
aboutEmoji?: string;
|
||||
profileFamilyName?: string;
|
||||
profileKey?: string;
|
||||
profileName?: string;
|
||||
|
|
|
@ -1173,6 +1173,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
uuid: this.get('uuid'),
|
||||
e164: this.get('e164'),
|
||||
|
||||
about: this.getAboutText(),
|
||||
acceptedMessageRequest: this.getAccepted(),
|
||||
activeAt: this.get('active_at')!,
|
||||
areWePending: Boolean(
|
||||
|
@ -1884,6 +1885,17 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
return !this.get('profileSharing');
|
||||
}
|
||||
|
||||
getAboutText(): string | undefined {
|
||||
if (!this.get('about')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return window.i18n('message--getNotificationText--text-with-emoji', {
|
||||
text: this.get('about'),
|
||||
emoji: this.get('aboutEmoji'),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this conversation should be considered "accepted" in terms
|
||||
* of message requests
|
||||
|
@ -3755,6 +3767,30 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
});
|
||||
}
|
||||
|
||||
if (profile.about) {
|
||||
const key = this.get('profileKey');
|
||||
if (key) {
|
||||
const keyBuffer = base64ToArrayBuffer(key);
|
||||
const decrypted = await window.textsecure.crypto.decryptProfile(
|
||||
base64ToArrayBuffer(profile.about),
|
||||
keyBuffer
|
||||
);
|
||||
this.set('about', stringFromBytes(decrypted));
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.aboutEmoji) {
|
||||
const key = this.get('profileKey');
|
||||
if (key) {
|
||||
const keyBuffer = base64ToArrayBuffer(key);
|
||||
const decrypted = await window.textsecure.crypto.decryptProfile(
|
||||
base64ToArrayBuffer(profile.aboutEmoji),
|
||||
keyBuffer
|
||||
);
|
||||
this.set('aboutEmoji', stringFromBytes(decrypted));
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.capabilities) {
|
||||
c.set({ capabilities: profile.capabilities });
|
||||
}
|
||||
|
@ -3900,6 +3936,8 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
`Setting sealedSender to UNKNOWN for conversation ${this.idForLogging()}`
|
||||
);
|
||||
this.set({
|
||||
about: undefined,
|
||||
aboutEmoji: undefined,
|
||||
profileAvatar: undefined,
|
||||
profileKey,
|
||||
profileKeyVersion: undefined,
|
||||
|
@ -3943,6 +3981,8 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
}
|
||||
|
||||
this.set({
|
||||
about: undefined,
|
||||
aboutEmoji: undefined,
|
||||
profileKey: undefined,
|
||||
profileKeyVersion: undefined,
|
||||
profileKeyCredential: null,
|
||||
|
|
|
@ -51,6 +51,7 @@ export type ConversationType = {
|
|||
name?: string;
|
||||
firstName?: string;
|
||||
profileName?: string;
|
||||
about?: string;
|
||||
avatarPath?: string;
|
||||
areWeAdmin?: boolean;
|
||||
areWePending?: boolean;
|
||||
|
|
|
@ -14745,7 +14745,7 @@
|
|||
"rule": "React-useRef",
|
||||
"path": "ts/components/conversation/ContactModal.js",
|
||||
"line": " const overlayRef = react_1.default.useRef(null);",
|
||||
"lineNumber": 17,
|
||||
"lineNumber": 18,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-11-09T17:48:12.173Z"
|
||||
},
|
||||
|
@ -14753,7 +14753,7 @@
|
|||
"rule": "React-useRef",
|
||||
"path": "ts/components/conversation/ContactModal.js",
|
||||
"line": " const closeButtonRef = react_1.default.useRef(null);",
|
||||
"lineNumber": 18,
|
||||
"lineNumber": 19,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-11-10T21:27:04.909Z"
|
||||
},
|
||||
|
@ -14779,7 +14779,7 @@
|
|||
"rule": "React-useRef",
|
||||
"path": "ts/components/conversation/ConversationHero.js",
|
||||
"line": " const firstRenderRef = React.useRef(true);",
|
||||
"lineNumber": 80,
|
||||
"lineNumber": 81,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-10-26T19:12:24.410Z",
|
||||
"reasonDetail": "Doesn't refer to a DOM element."
|
||||
|
@ -14788,7 +14788,7 @@
|
|||
"rule": "React-useRef",
|
||||
"path": "ts/components/conversation/ConversationHero.tsx",
|
||||
"line": " const firstRenderRef = React.useRef(true);",
|
||||
"lineNumber": 127,
|
||||
"lineNumber": 130,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-10-26T19:12:24.410Z",
|
||||
"reasonDetail": "Doesn't refer to a DOM element."
|
||||
|
@ -15300,4 +15300,4 @@
|
|||
"updated": "2021-01-08T15:46:32.143Z",
|
||||
"reasonDetail": "Doesn't manipulate the DOM. This is just a function."
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
Loading…
Add table
Reference in a new issue