Show your preferred badge in the left pane and avatar popup
This commit is contained in:
parent
f02b1ebce2
commit
7de340a104
|
@ -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<BadgeType>): boolean =>
|
||||||
|
'isVisible' in badge ? badge.isVisible : true;
|
|
@ -22,6 +22,7 @@ import * as log from '../logging/log';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { shouldBlurAvatar } from '../util/shouldBlurAvatar';
|
import { shouldBlurAvatar } from '../util/shouldBlurAvatar';
|
||||||
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
||||||
|
import { isBadgeVisible } from '../badges/isBadgeVisible';
|
||||||
import { BadgeImageTheme } from '../badges/BadgeImageTheme';
|
import { BadgeImageTheme } from '../badges/BadgeImageTheme';
|
||||||
|
|
||||||
export enum AvatarBlur {
|
export enum AvatarBlur {
|
||||||
|
@ -212,7 +213,7 @@ export const Avatar: FunctionComponent<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
let badgeNode: ReactNode;
|
let badgeNode: ReactNode;
|
||||||
if (badge && theme && !isMe) {
|
if (badge && theme && !noteToSelf && isBadgeVisible(badge)) {
|
||||||
const badgeSize = Math.ceil(size * 0.425);
|
const badgeSize = Math.ceil(size * 0.425);
|
||||||
const badgeTheme =
|
const badgeTheme =
|
||||||
theme === ThemeType.light ? BadgeImageTheme.Light : BadgeImageTheme.Dark;
|
theme === ThemeType.light ? BadgeImageTheme.Light : BadgeImageTheme.Dark;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import enMessages from '../../_locales/en/messages.json';
|
||||||
import type { PropsType } from './MainHeader';
|
import type { PropsType } from './MainHeader';
|
||||||
import { MainHeader } from './MainHeader';
|
import { MainHeader } from './MainHeader';
|
||||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||||
|
import { ThemeType } from '../types/Util';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
searchConversation: overrideProps.searchConversation,
|
searchConversation: overrideProps.searchConversation,
|
||||||
selectedConversation: undefined,
|
selectedConversation: undefined,
|
||||||
startSearchCounter: 0,
|
startSearchCounter: 0,
|
||||||
|
theme: ThemeType.light,
|
||||||
|
|
||||||
phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber),
|
phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber),
|
||||||
title: requiredText('title', overrideProps.title),
|
title: requiredText('title', overrideProps.title),
|
||||||
|
|
|
@ -8,10 +8,11 @@ import { createPortal } from 'react-dom';
|
||||||
import { showSettings } from '../shims/Whisper';
|
import { showSettings } from '../shims/Whisper';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
import { AvatarPopup } from './AvatarPopup';
|
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 { AvatarColorType } from '../types/Colors';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { LeftPaneSearchInput } from './LeftPaneSearchInput';
|
import { LeftPaneSearchInput } from './LeftPaneSearchInput';
|
||||||
|
import type { BadgeType } from '../badges/types';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
|
@ -29,7 +30,9 @@ export type PropsType = {
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
title: string;
|
title: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
|
badge?: BadgeType;
|
||||||
hasPendingUpdate: boolean;
|
hasPendingUpdate: boolean;
|
||||||
|
theme: ThemeType;
|
||||||
|
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
|
||||||
|
@ -191,6 +194,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
avatarPath,
|
avatarPath,
|
||||||
|
badge,
|
||||||
color,
|
color,
|
||||||
disabled,
|
disabled,
|
||||||
hasPendingUpdate,
|
hasPendingUpdate,
|
||||||
|
@ -203,6 +207,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
showArchivedConversations,
|
showArchivedConversations,
|
||||||
startComposing,
|
startComposing,
|
||||||
startUpdate,
|
startUpdate,
|
||||||
|
theme,
|
||||||
title,
|
title,
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -219,6 +224,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
acceptedMessageRequest
|
||||||
avatarPath={avatarPath}
|
avatarPath={avatarPath}
|
||||||
|
badge={badge}
|
||||||
className="module-main-header__avatar"
|
className="module-main-header__avatar"
|
||||||
color={color}
|
color={color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
@ -227,6 +233,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
name={name}
|
name={name}
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
|
theme={theme}
|
||||||
title={title}
|
title={title}
|
||||||
// `sharedGroupNames` makes no sense for yourself, but
|
// `sharedGroupNames` makes no sense for yourself, but
|
||||||
// `<Avatar>` needs it to determine blurring.
|
// `<Avatar>` needs it to determine blurring.
|
||||||
|
@ -247,6 +254,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
{({ ref, style }) => (
|
{({ ref, style }) => (
|
||||||
<AvatarPopup
|
<AvatarPopup
|
||||||
acceptedMessageRequest
|
acceptedMessageRequest
|
||||||
|
badge={badge}
|
||||||
innerRef={ref}
|
innerRef={ref}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
isMe
|
||||||
|
@ -256,6 +264,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
name={name}
|
name={name}
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
|
theme={theme}
|
||||||
title={title}
|
title={title}
|
||||||
avatarPath={avatarPath}
|
avatarPath={avatarPath}
|
||||||
size={28}
|
size={28}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { mapDispatchToProps } from '../actions';
|
||||||
import { MainHeader } from '../../components/MainHeader';
|
import { MainHeader } from '../../components/MainHeader';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
|
|
||||||
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import {
|
import {
|
||||||
getQuery,
|
getQuery,
|
||||||
getSearchConversation,
|
getSearchConversation,
|
||||||
|
@ -15,6 +16,7 @@ import {
|
||||||
import {
|
import {
|
||||||
getIntl,
|
getIntl,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
|
getTheme,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getUserUuid,
|
getUserUuid,
|
||||||
|
@ -22,6 +24,8 @@ import {
|
||||||
import { getMe, getSelectedConversation } from '../selectors/conversations';
|
import { getMe, getSelectedConversation } from '../selectors/conversations';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
const me = getMe(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
disabled: state.network.challengeStatus !== 'idle',
|
disabled: state.network.challengeStatus !== 'idle',
|
||||||
hasPendingUpdate: Boolean(state.updates.didSnooze),
|
hasPendingUpdate: Boolean(state.updates.didSnooze),
|
||||||
|
@ -33,7 +37,9 @@ const mapStateToProps = (state: StateType) => {
|
||||||
ourConversationId: getUserConversationId(state),
|
ourConversationId: getUserConversationId(state),
|
||||||
ourNumber: getUserNumber(state),
|
ourNumber: getUserNumber(state),
|
||||||
ourUuid: getUserUuid(state),
|
ourUuid: getUserUuid(state),
|
||||||
...getMe(state),
|
...me,
|
||||||
|
badge: getPreferredBadgeSelector(state)(me.badges),
|
||||||
|
theme: getTheme(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue