diff --git a/ts/badges/isBadgeVisible.ts b/ts/badges/isBadgeVisible.ts new file mode 100644 index 000000000..6903f6404 --- /dev/null +++ b/ts/badges/isBadgeVisible.ts @@ -0,0 +1,7 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { BadgeType } from './types'; + +export const isBadgeVisible = (badge: Readonly): boolean => + 'isVisible' in badge ? badge.isVisible : true; diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 7a9ed648e..c25f35bb4 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -22,6 +22,7 @@ import * as log from '../logging/log'; import { assert } from '../util/assert'; import { shouldBlurAvatar } from '../util/shouldBlurAvatar'; import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath'; +import { isBadgeVisible } from '../badges/isBadgeVisible'; import { BadgeImageTheme } from '../badges/BadgeImageTheme'; export enum AvatarBlur { @@ -212,7 +213,7 @@ export const Avatar: FunctionComponent = ({ } let badgeNode: ReactNode; - if (badge && theme && !isMe) { + if (badge && theme && !noteToSelf && isBadgeVisible(badge)) { const badgeSize = Math.ceil(size * 0.425); const badgeTheme = theme === ThemeType.light ? BadgeImageTheme.Light : BadgeImageTheme.Dark; diff --git a/ts/components/MainHeader.stories.tsx b/ts/components/MainHeader.stories.tsx index 71b94739a..d7407d579 100644 --- a/ts/components/MainHeader.stories.tsx +++ b/ts/components/MainHeader.stories.tsx @@ -11,6 +11,7 @@ import enMessages from '../../_locales/en/messages.json'; import type { PropsType } from './MainHeader'; import { MainHeader } from './MainHeader'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; +import { ThemeType } from '../types/Util'; const i18n = setupI18n('en', enMessages); @@ -26,6 +27,7 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ searchConversation: overrideProps.searchConversation, selectedConversation: undefined, startSearchCounter: 0, + theme: ThemeType.light, phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber), title: requiredText('title', overrideProps.title), diff --git a/ts/components/MainHeader.tsx b/ts/components/MainHeader.tsx index 6ca7fd105..fe1c7ffb1 100644 --- a/ts/components/MainHeader.tsx +++ b/ts/components/MainHeader.tsx @@ -8,10 +8,11 @@ import { createPortal } from 'react-dom'; import { showSettings } from '../shims/Whisper'; import { Avatar } from './Avatar'; import { AvatarPopup } from './AvatarPopup'; -import type { LocalizerType } from '../types/Util'; +import type { LocalizerType, ThemeType } from '../types/Util'; import type { AvatarColorType } from '../types/Colors'; import type { ConversationType } from '../state/ducks/conversations'; import { LeftPaneSearchInput } from './LeftPaneSearchInput'; +import type { BadgeType } from '../badges/types'; export type PropsType = { searchTerm: string; @@ -29,7 +30,9 @@ export type PropsType = { profileName?: string; title: string; avatarPath?: string; + badge?: BadgeType; hasPendingUpdate: boolean; + theme: ThemeType; i18n: LocalizerType; @@ -191,6 +194,7 @@ export class MainHeader extends React.Component { public render(): JSX.Element { const { avatarPath, + badge, color, disabled, hasPendingUpdate, @@ -203,6 +207,7 @@ export class MainHeader extends React.Component { showArchivedConversations, startComposing, startUpdate, + theme, title, toggleProfileEditor, } = this.props; @@ -219,6 +224,7 @@ export class MainHeader extends React.Component { { name={name} phoneNumber={phoneNumber} profileName={profileName} + theme={theme} title={title} // `sharedGroupNames` makes no sense for yourself, but // `` needs it to determine blurring. @@ -247,6 +254,7 @@ export class MainHeader extends React.Component { {({ ref, style }) => ( { name={name} phoneNumber={phoneNumber} profileName={profileName} + theme={theme} title={title} avatarPath={avatarPath} size={28} diff --git a/ts/state/smart/MainHeader.tsx b/ts/state/smart/MainHeader.tsx index aabffb220..0c8b1e47f 100644 --- a/ts/state/smart/MainHeader.tsx +++ b/ts/state/smart/MainHeader.tsx @@ -7,6 +7,7 @@ import { mapDispatchToProps } from '../actions'; import { MainHeader } from '../../components/MainHeader'; import type { StateType } from '../reducer'; +import { getPreferredBadgeSelector } from '../selectors/badges'; import { getQuery, getSearchConversation, @@ -15,6 +16,7 @@ import { import { getIntl, getRegionCode, + getTheme, getUserConversationId, getUserNumber, getUserUuid, @@ -22,6 +24,8 @@ import { import { getMe, getSelectedConversation } from '../selectors/conversations'; const mapStateToProps = (state: StateType) => { + const me = getMe(state); + return { disabled: state.network.challengeStatus !== 'idle', hasPendingUpdate: Boolean(state.updates.didSnooze), @@ -33,7 +37,9 @@ const mapStateToProps = (state: StateType) => { ourConversationId: getUserConversationId(state), ourNumber: getUserNumber(state), ourUuid: getUserUuid(state), - ...getMe(state), + ...me, + badge: getPreferredBadgeSelector(state)(me.badges), + theme: getTheme(state), i18n: getIntl(state), }; }; diff --git a/ts/test-both/badges/isBadgeVisible_test.ts b/ts/test-both/badges/isBadgeVisible_test.ts new file mode 100644 index 000000000..869f6725e --- /dev/null +++ b/ts/test-both/badges/isBadgeVisible_test.ts @@ -0,0 +1,31 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import type { BadgeType } from '../../badges/types'; + +import { isBadgeVisible } from '../../badges/isBadgeVisible'; +import { BadgeCategory } from '../../badges/BadgeCategory'; + +describe('isBadgeVisible', () => { + const fakeBadge = (isVisible?: boolean): BadgeType => ({ + category: BadgeCategory.Donor, + descriptionTemplate: 'test', + id: 'TEST', + images: [], + name: 'test', + ...(typeof isVisible === 'boolean' ? { expiresAt: 123, isVisible } : {}), + }); + + it("returns true if the visibility is unspecified (someone else's badge)", () => { + assert.isTrue(isBadgeVisible(fakeBadge())); + }); + + it('returns false if not visible', () => { + assert.isFalse(isBadgeVisible(fakeBadge(false))); + }); + + it('returns true if visible', () => { + assert.isTrue(isBadgeVisible(fakeBadge(true))); + }); +});