From d07b8e82b2f47aa633b24d14a77ac3c14e0b7548 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 23 Jul 2020 18:35:32 -0700 Subject: [PATCH] First-class profile name rendering --- _locales/en/messages.json | 4 + js/models/conversations.js | 38 ++--- js/models/messages.js | 22 +-- js/views/contact_list_view.js | 11 +- js/views/conversation_view.js | 12 +- patches/react-tooltip-lite+1.12.0.patch | 60 ++++++++ stylesheets/_mixins.scss | 12 ++ stylesheets/_modules.scss | 127 +++++++++++++++-- ts/ConversationController.ts | 7 +- ts/components/Avatar.tsx | 7 +- ts/components/AvatarPopup.tsx | 11 +- ts/components/CallManager.stories.tsx | 6 +- ts/components/CallScreen.stories.tsx | 6 +- ts/components/CallScreen.tsx | 6 +- ts/components/CompositionArea.tsx | 8 +- ts/components/ContactListItem.md | 110 --------------- ts/components/ContactListItem.stories.tsx | 133 ++++++++++++++++++ ts/components/ContactListItem.tsx | 34 +++-- ts/components/ConversationListItem.tsx | 8 +- ts/components/InContactsIcon.stories.tsx | 16 +++ ts/components/InContactsIcon.tsx | 31 ++++ ts/components/IncomingCallBar.stories.tsx | 10 +- ts/components/IncomingCallBar.tsx | 10 +- ts/components/MainHeader.tsx | 8 +- ts/components/MessageSearchResult.tsx | 11 +- .../SafetyNumberChangeDialog.stories.tsx | 60 ++++++-- ts/components/SafetyNumberChangeDialog.tsx | 84 ++++++----- ts/components/SafetyNumberViewer.stories.tsx | 60 +++++++- ts/components/SafetyNumberViewer.tsx | 30 ++-- ts/components/SearchResults.stories.tsx | 12 ++ ts/components/StartNewConversation.tsx | 2 +- ts/components/conversation/ContactName.md | 21 --- .../conversation/ContactName.stories.tsx | 47 +++++++ ts/components/conversation/ContactName.tsx | 15 +- .../ConversationHeader.stories.tsx | 49 +++++-- .../conversation/ConversationHeader.tsx | 63 +++++---- .../conversation/ConversationHero.stories.tsx | 77 +++++++++- .../conversation/ConversationHero.tsx | 16 ++- .../GroupNotification.stories.tsx | 36 +++++ .../conversation/GroupNotification.tsx | 7 +- .../conversation/Message.stories.tsx | 4 +- ts/components/conversation/Message.tsx | 22 ++- ts/components/conversation/MessageDetail.tsx | 16 ++- .../MessageRequestActions.stories.tsx | 4 +- .../conversation/MessageRequestActions.tsx | 7 +- .../MessageRequestActionsConfirmation.tsx | 9 +- ts/components/conversation/Quote.tsx | 6 +- ts/components/conversation/ReactionViewer.tsx | 4 + .../conversation/SafetyNumberNotification.md | 12 +- .../conversation/SafetyNumberNotification.tsx | 5 +- .../conversation/TimerNotification.md | 6 + .../conversation/TimerNotification.tsx | 6 +- ts/components/conversation/TypingBubble.tsx | 5 +- .../conversation/UnsupportedMessage.tsx | 5 +- .../conversation/VerificationNotification.tsx | 5 +- ts/components/conversation/_contactUtil.tsx | 4 +- ts/services/calling.ts | 7 +- ts/state/ducks/calling.ts | 12 +- ts/state/ducks/conversations.ts | 8 +- ts/state/selectors/conversations.ts | 37 +---- ts/test/state/selectors/conversations_test.ts | 4 +- ts/util/lint/exceptions.json | 16 +-- ts/util/safetyNumber.ts | 7 + 63 files changed, 1044 insertions(+), 454 deletions(-) create mode 100644 patches/react-tooltip-lite+1.12.0.patch delete mode 100644 ts/components/ContactListItem.md create mode 100644 ts/components/ContactListItem.stories.tsx create mode 100644 ts/components/InContactsIcon.stories.tsx create mode 100644 ts/components/InContactsIcon.tsx delete mode 100644 ts/components/conversation/ContactName.md create mode 100644 ts/components/conversation/ContactName.stories.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f93661e3f9a6..86276befaca5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -715,6 +715,10 @@ "message": "Typing animation for this conversation", "description": "Used as the 'title' attibute for the typing animation" }, + "contactInAddressBook": { + "message": "This person is in your contacts.", + "description": "Description of icon denoting that contact is from your address book" + }, "contactAvatarAlt": { "message": "Avatar for contact $name$", "description": "Used in the alt tag for the image avatar of a contact", diff --git a/js/models/conversations.js b/js/models/conversations.js index 6d929e8e4735..b864154982e5 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -467,6 +467,7 @@ uuid: this.get('uuid'), e164: this.get('e164'), + isAccepted: this.getAccepted(), isArchived: this.get('isArchived'), isBlocked: this.isBlocked(), isVerified: this.isVerified(), @@ -477,7 +478,7 @@ isMe: this.isMe(), typingContact: typingContact ? typingContact.format() : null, lastUpdated: this.get('timestamp'), - name: this.getName(), + name: this.get('name'), profileName: this.getProfileName(), timestamp, inboxPosition, @@ -589,10 +590,9 @@ const receiptSpecs = readMessages.map(m => ({ senderE164: m.get('source'), senderUuid: m.get('sourceUuid'), - senderId: ConversationController.get({ + senderId: ConversationController.ensureContactIds({ e164: m.get('source'), uuid: m.get('sourceUuid'), - lowTrust: true, }), timestamp: m.get('sent_at'), hasErrors: m.hasErrors(), @@ -2651,16 +2651,14 @@ }); }, - getName() { - if (this.isPrivate()) { - return this.get('name'); - } - return this.get('name') || i18n('unknownGroup'); - }, - getTitle() { if (this.isPrivate()) { - return this.get('name') || this.getNumber() || i18n('unknownContact'); + return ( + this.get('name') || + this.getProfileName() || + this.getNumber() || + i18n('unknownContact') + ); } return this.get('name') || i18n('unknownGroup'); }, @@ -2675,24 +2673,6 @@ return null; }, - getDisplayName() { - if (!this.isPrivate()) { - return this.getTitle(); - } - - const name = this.get('name'); - if (name) { - return name; - } - - const profileName = this.get('profileName'); - if (profileName) { - return `${this.getNumber()} ~${profileName}`; - } - - return this.getNumber(); - }, - getNumber() { if (!this.isPrivate()) { return ''; diff --git a/js/models/messages.js b/js/models/messages.js index abc776f92c0a..2ba12c69b7ef 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -594,6 +594,7 @@ status: this.getMessagePropStatus(), contact: this.getPropsForEmbeddedContact(), canReply: this.canReply(), + authorTitle: contact.title, authorColor, authorName: contact.name, authorProfileName: contact.profileName, @@ -781,7 +782,8 @@ ourRegionCode: regionCode, }); const authorProfileName = contact ? contact.getProfileName() : null; - const authorName = contact ? contact.getName() : null; + const authorName = contact ? contact.get('name') : null; + const authorTitle = contact ? contact.getTitle() : null; const isFromMe = contact ? contact.isMe() : false; const firstAttachment = quote.attachments && quote.attachments[0]; @@ -795,6 +797,7 @@ authorId: author, authorPhoneNumber, authorProfileName, + authorTitle, authorName, authorColor, referencedMessageNotFound, @@ -891,7 +894,7 @@ if (fromContact.isMe()) { messages.push(i18n('youUpdatedTheGroup')); } else { - messages.push(i18n('updatedTheGroup', fromContact.getDisplayName())); + messages.push(i18n('updatedTheGroup', fromContact.getTitle())); } if (groupUpdate.joined && groupUpdate.joined.length) { @@ -906,9 +909,7 @@ messages.push( i18n( 'multipleJoinedTheGroup', - _.map(joinedWithoutMe, contact => - contact.getDisplayName() - ).join(', ') + _.map(joinedWithoutMe, contact => contact.getTitle()).join(', ') ) ); @@ -924,7 +925,7 @@ messages.push(i18n('youJoinedTheGroup')); } else { messages.push( - i18n('joinedTheGroup', joinedContacts[0].getDisplayName()) + i18n('joinedTheGroup', joinedContacts[0].getTitle()) ); } } @@ -1018,7 +1019,7 @@ if (!conversation) { return number; } - return conversation.getDisplayName(); + return conversation.getTitle(); }, onDestroy() { this.cleanup(); @@ -2346,11 +2347,11 @@ if ( // Avatar added - !existingAvatar || + (!existingAvatar && avatarAttachment) || // Avatar changed (existingAvatar && existingAvatar.hash !== hash) || // Avatar removed - avatarAttachment === null + (existingAvatar && !avatarAttachment) ) { // Removes existing avatar from disk if (existingAvatar && existingAvatar.path) { @@ -2396,10 +2397,11 @@ conversation.set({ addedBy: message.getContactId() }); } } else if (dataMessage.group.type === GROUP_TYPES.QUIT) { - const sender = ConversationController.ensureContactIds({ + const senderId = ConversationController.ensureContactIds({ e164: source, uuid: sourceUuid, }); + const sender = ConversationController.get(senderId); const inGroup = Boolean( sender && (conversation.get('members') || []).includes(sender.id) diff --git a/js/views/contact_list_view.js b/js/views/contact_list_view.js index 2558b997efc5..9bf97bf30246 100644 --- a/js/views/contact_list_view.js +++ b/js/views/contact_list_view.js @@ -26,21 +26,12 @@ this.contactView = null; } - const isMe = this.model.isMe(); - this.contactView = new Whisper.ReactWrapperView({ className: 'contact-wrapper', Component: window.Signal.Components.ContactListItem, props: { - isMe, - color: this.model.getColor(), - avatarPath: this.model.getAvatarPath(), - phoneNumber: this.model.getNumber(), - name: this.model.getName(), - profileName: this.model.getProfileName(), - verified: this.model.isVerified(), + ...this.model.cachedProps, onClick: this.showIdentity.bind(this), - disabled: this.loading, }, }); this.$el.append(this.contactView.el); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 904d50ecb231..aaeefb399d27 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -360,18 +360,8 @@ : null; return { - id: this.model.id, - name: this.model.getName(), - phoneNumber: this.model.getNumber(), - profileName: this.model.getProfileName(), - color: this.model.getColor(), - avatarPath: this.model.getAvatarPath(), + ...this.model.cachedProps, - isAccepted: this.model.getAccepted(), - isVerified: this.model.isVerified(), - isMe: this.model.isMe(), - isGroup: !this.model.isPrivate(), - isArchived: this.model.get('isArchived'), leftGroup: this.model.get('left'), expirationSettingName, diff --git a/patches/react-tooltip-lite+1.12.0.patch b/patches/react-tooltip-lite+1.12.0.patch new file mode 100644 index 000000000000..502a73534bfa --- /dev/null +++ b/patches/react-tooltip-lite+1.12.0.patch @@ -0,0 +1,60 @@ +diff --git a/node_modules/react-tooltip-lite/dist/index.js b/node_modules/react-tooltip-lite/dist/index.js +index 32ce07d..6461913 100644 +--- a/node_modules/react-tooltip-lite/dist/index.js ++++ b/node_modules/react-tooltip-lite/dist/index.js +@@ -80,7 +80,7 @@ function (_React$Component) { + + _this.state = { + showTip: false, +- hasHover: false, ++ hasHover: 0, + ignoreShow: false, + hasBeenShown: false + }; +@@ -232,7 +232,7 @@ function (_React$Component) { + var _this3 = this; + + this.setState({ +- hasHover: false ++ hasHover: 0 + }); + + if (this.state.showTip) { +@@ -250,7 +250,7 @@ function (_React$Component) { + value: function startHover() { + if (!this.state.ignoreShow) { + this.setState({ +- hasHover: true ++ hasHover: (this.state.hasHover || 0) + 1, + }); + clearTimeout(this.hoverTimeout); + this.hoverTimeout = setTimeout(this.checkHover, this.props.hoverDelay); +@@ -260,7 +260,7 @@ function (_React$Component) { + key: "endHover", + value: function endHover() { + this.setState({ +- hasHover: false ++ hasHover: Math.max((this.state.hasHover || 0) - 1, 0), + }); + clearTimeout(this.hoverTimeout); + this.hoverTimeout = setTimeout(this.checkHover, this.props.mouseOutDelay || this.props.hoverDelay); +@@ -268,7 +268,7 @@ function (_React$Component) { + }, { + key: "checkHover", + value: function checkHover() { +- this.state.hasHover ? this.showTip() : this.hideTip(); ++ this.state.hasHover > 0 ? this.showTip() : this.hideTip(); + } + }, { + key: "render", +@@ -330,7 +330,9 @@ function (_React$Component) { + props[eventToggle] = this.toggleTip; // only use hover if they don't have a toggle event + } else if (useHover && !isControlledByProps) { + props.onMouseEnter = this.startHover; +- props.onMouseLeave = tipContentHover || mouseOutDelay ? this.endHover : this.hideTip; ++ props.onMouseLeave = this.endHover; ++ props.onFocus = this.startHover; ++ props.onBlur = this.endHover; + props.onTouchStart = this.targetTouchStart; + props.onTouchEnd = this.targetTouchEnd; + diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss index 9cf904e5b8bc..aa7b395def20 100644 --- a/stylesheets/_mixins.scss +++ b/stylesheets/_mixins.scss @@ -152,6 +152,18 @@ @content; } } + +@mixin dark-mouse-mode() { + .dark-theme.mouse-mode & { + @content; + } +} +@mixin ios-mouse-mode() { + .ios-theme.mouse-mode & { + @content; + } +} + @mixin dark-ios-keyboard-mode() { .dark-theme.ios-theme.keyboard-mode & { @content; diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 6bb5b880accb..0ad0c2015577 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -36,12 +36,6 @@ } } -// Module: Contact Name - -.module-contact-name__profile-name { - font-style: italic; -} - // Module: Message // Note: this does the same thing as module-timeline__message-container but @@ -2389,6 +2383,10 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', } } +.module-safety-number__bold-name { + font-weight: bold; +} + .module-message-calling { &--audio { text-align: center; @@ -2612,18 +2610,40 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', cursor: inherit; - padding-top: 8px; - padding-bottom: 8px; + padding: 8px; + width: 100%; + display: flex; flex-direction: row; align-items: center; @include light-theme { color: $color-gray-60; + + @include mouse-mode { + &:hover { + background-color: $color-gray-02; + } + } + @include keyboard-mode { + &:focus { + background-color: $color-gray-02; + } + } } @include dark-theme { color: $color-gray-15; } + @include dark-mouse-mode { + &:hover { + background-color: $color-gray-80; + } + } + @include dark-keyboard-mode { + &:focus { + background-color: $color-gray-80; + } + } } .module-contact-list-item--with-click-handler { @@ -2665,6 +2685,61 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', } } +// Module: In Contacts Icon + +.module-in-contacts-icon__icon { + display: inline-block; + height: 15px; + width: 15px; + + margin-bottom: 2px; + vertical-align: middle; + + @include light-theme { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $color-gray-60 + ); + } + @include dark-theme { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $color-gray-25 + ); + } + + @include keyboard-mode { + &:focus { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $ultramarine-ui-light + ); + } + } +} + +.module-in-contacts-icon__tooltip { + .react-tooltip-lite { + color: $color-white; + background-color: $ultramarine-ui-light; + } + + .react-tooltip-lite-arrow { + border-color: $ultramarine-ui-light; + } + + @include dark-theme { + .react-tooltip-lite { + color: $color-white; + background-color: $ultramarine-ui-light; + } + + .react-tooltip-lite-arrow { + border-color: $ultramarine-ui-light; + } + } +} + // Module: Conversation Header .module-conversation-header { @@ -2771,6 +2846,37 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', } } +.module-conversation-header__contacts-icon { + display: inline-block; + height: 15px; + width: 15px; + + margin-bottom: 3px; + vertical-align: middle; + + @include light-theme { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $color-gray-60 + ); + } + @include dark-theme { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $color-gray-25 + ); + } + + @include keyboard-mode { + &:focus { + @include color-svg( + '../images/icons/v2/profile-circle-outline-24.svg', + $ultramarine-ui-light + ); + } + } +} + .module-conversation-header__title__profile-name { @include font-body-1-bold-italic; } @@ -4380,7 +4486,10 @@ button.module-image__border-overlay:focus { left: 0; right: 0; bottom: 0; - z-index: 2; + z-index: 3; + + // This allows click-through to the overlay button behind it + pointer-events: none; color: $color-white; diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 3ca18dcb5097..b8728948cf47 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -228,11 +228,10 @@ export class ConversationController { } /** * Given a UUID and/or an E164, resolves to a string representing the local - * database id of the given contact. It may create new contacts, and it may merge - * contacts. + * database id of the given contact. In high trust mode, it may create new contacts, + * and it may merge contacts. * - * lowTrust = uuid/e164 pairing came from source like GroupV1 member list - * highTrust = uuid/e164 pairing came from source like CDS + * highTrust = uuid/e164 pairing came from CDS, the server, or your own device */ // tslint:disable-next-line cyclomatic-complexity max-func-body-length ensureContactIds({ diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 9c657f88b2d2..b5bf6e0b7c65 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -10,6 +10,7 @@ export type Props = { conversationType: 'group' | 'direct'; noteToSelf?: boolean; + title: string; name?: string; phoneNumber?: string; profileName?: string; @@ -63,17 +64,13 @@ export class Avatar extends React.Component { } public renderImage() { - const { avatarPath, i18n, name, phoneNumber, profileName } = this.props; + const { avatarPath, i18n, title } = this.props; const { imageBroken } = this.state; if (!avatarPath || imageBroken) { return null; } - const title = `${name || phoneNumber}${ - !name && profileName ? ` ~${profileName}` : '' - }`; - return ( { const focusRef = React.useRef(null); const { i18n, + name, profileName, phoneNumber, + title, onViewPreferences, onViewArchive, style, } = props; - const hasProfileName = !isEmpty(profileName); + const shouldShowNumber = Boolean(name || profileName); // Note: mechanisms to dismiss this view are all in its host, MainHeader @@ -41,10 +42,8 @@ export const AvatarPopup = (props: Props) => {
-
- {hasProfileName ? profileName : phoneNumber} -
- {hasProfileName ? ( +
{title}
+ {shouldShowNumber ? (
{phoneNumber}
diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 10ff1f90bdc9..21bd88996ab0 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -14,11 +14,13 @@ import { action } from '@storybook/addon-actions'; const i18n = setupI18n('en', enMessages); const callDetails = { - avatarPath: undefined, callId: 0, - contactColor: 'ultramarine' as ColorType, isIncoming: true, isVideoCall: true, + + avatarPath: undefined, + color: 'ultramarine' as ColorType, + title: 'Rick Sanchez', name: 'Rick Sanchez', phoneNumber: '3051234567', profileName: 'Rick Sanchez', diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index aeaf8e3b4012..3c34b6b7edb9 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -15,11 +15,13 @@ import { action } from '@storybook/addon-actions'; const i18n = setupI18n('en', enMessages); const callDetails = { - avatarPath: undefined, callId: 0, - contactColor: 'ultramarine' as ColorType, isIncoming: true, isVideoCall: true, + + avatarPath: undefined, + color: 'ultramarine' as ColorType, + title: 'Rick Sanchez', name: 'Rick Sanchez', phoneNumber: '3051234567', profileName: 'Rick Sanchez', diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 56460d3cf62e..d5bb54b564dc 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -275,22 +275,24 @@ export class CallScreen extends React.Component { const { i18n } = this.props; const { avatarPath, - contactColor, + color, name, phoneNumber, profileName, + title, } = callDetails; return (
diff --git a/ts/components/CompositionArea.tsx b/ts/components/CompositionArea.tsx index 9688926252da..a1f5fa029b86 100644 --- a/ts/components/CompositionArea.tsx +++ b/ts/components/CompositionArea.tsx @@ -114,18 +114,19 @@ export const CompositionArea = ({ showPickerHint, clearShowPickerHint, // Message Requests - messageRequestsEnabled, acceptedMessageRequest, conversationType, isBlocked, + messageRequestsEnabled, name, onAccept, onBlock, onBlockAndDelete, - onUnblock, onDelete, - profileName, + onUnblock, phoneNumber, + profileName, + title, }: Props) => { const [disabled, setDisabled] = React.useState(false); const [showMic, setShowMic] = React.useState(!startingText); @@ -333,6 +334,7 @@ export const CompositionArea = ({ name={name} profileName={profileName} phoneNumber={phoneNumber} + title={title} /> ); } diff --git a/ts/components/ContactListItem.md b/ts/components/ContactListItem.md deleted file mode 100644 index 254c2d660241..000000000000 --- a/ts/components/ContactListItem.md +++ /dev/null @@ -1,110 +0,0 @@ -#### It's me! - -```jsx - console.log('onClick')} -/> -``` - -#### With name and profile - -Note the proper spacing between these two. - -```jsx -
- console.log('onClick')} - /> - console.log('onClick')} - /> -
-``` - -#### With name and profile, verified - -```jsx - console.log('onClick')} -/> -``` - -#### With name and profile, no avatar - -```jsx - console.log('onClick')} -/> -``` - -#### Profile, no name, no avatar - -```jsx - console.log('onClick')} -/> -``` - -#### Verified, profile, no name, no avatar - -```jsx - console.log('onClick')} -/> -``` - -#### No name, no profile, no avatar - -```jsx - console.log('onClick')} -/> -``` - -#### Verified, no name, no profile, no avatar - -```jsx - console.log('onClick')} -/> -``` diff --git a/ts/components/ContactListItem.stories.tsx b/ts/components/ContactListItem.stories.tsx new file mode 100644 index 000000000000..f13a81bab797 --- /dev/null +++ b/ts/components/ContactListItem.stories.tsx @@ -0,0 +1,133 @@ +import * as React from 'react'; + +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { gifUrl } from '../storybook/Fixtures'; + +// @ts-ignore +import { setup as setupI18n } from '../../js/modules/i18n'; +// @ts-ignore +import enMessages from '../../\_locales/en/messages.json'; + +import { ContactListItem } from './ContactListItem'; + +const i18n = setupI18n('en', enMessages); +const onClick = action('onClick'); + +storiesOf('Components/ContactListItem', module) + .add("It's me!", () => { + return ( + + ); + }) + .add('With name and profile (note vertical spacing)', () => { + return ( +
+ + +
+ ); + }) + .add('With name and profile, verified', () => { + return ( + + ); + }) + .add('With name and profile, no avatar', () => { + return ( + + ); + }) + .add('Profile, no name, no avatar', () => { + return ( + + ); + }) + .add('Verified, profile, no name, no avatar', () => { + return ( + + ); + }) + .add('No name, no profile, no avatar', () => { + return ( + + ); + }) + .add('Verified, no name, no profile, no avatar', () => { + return ( + + ); + }) + .add('No name, no profile, no number', () => { + return ( + + ); + }); diff --git a/ts/components/ContactListItem.tsx b/ts/components/ContactListItem.tsx index adb66bd9b083..0438cffa0cdb 100644 --- a/ts/components/ContactListItem.tsx +++ b/ts/components/ContactListItem.tsx @@ -3,15 +3,17 @@ import classNames from 'classnames'; import { Avatar } from './Avatar'; import { Emojify } from './conversation/Emojify'; +import { InContactsIcon } from './InContactsIcon'; import { ColorType, LocalizerType } from '../types/Util'; interface Props { - phoneNumber: string; + title: string; + phoneNumber?: string; isMe?: boolean; name?: string; - color: ColorType; - verified: boolean; + color?: ColorType; + isVerified?: boolean; profileName?: string; avatarPath?: string; i18n: LocalizerType; @@ -27,6 +29,7 @@ export class ContactListItem extends React.Component { name, phoneNumber, profileName, + title, } = this.props; return ( @@ -38,6 +41,7 @@ export class ContactListItem extends React.Component { name={name} phoneNumber={phoneNumber} profileName={profileName} + title={title} size={52} /> ); @@ -51,21 +55,15 @@ export class ContactListItem extends React.Component { isMe, phoneNumber, profileName, - verified, + title, + isVerified, } = this.props; - const title = name ? name : phoneNumber; const displayName = isMe ? i18n('you') : title; + const shouldShowIcon = Boolean(name); - const profileElement = - !isMe && profileName && !name ? ( - - ~ - - ) : null; - - const showNumber = isMe || name; - const showVerified = !isMe && verified; + const showNumber = Boolean(isMe || name || profileName); + const showVerified = !isMe && isVerified; return ( - - ))} + ) : null} +
+ + + ); + })}
)}
- {safetyNumberChanged - ? i18n('changedRightAfterVerify', [name, name]) - : i18n('yourSafetyNumberWith', [name])} +
{safetyNumber || getPlaceholder()}
- {i18n('verifyHelp', [name])} +
{isVerified ? ( ) : ( )} - {verifiedStatus} +