Settings Tab: Educate user about change to bottom-left profile icon

This commit is contained in:
Scott Nonnenberg
2025-06-07 10:26:09 +10:00
committed by GitHub
parent 974c29fd41
commit 54d4c2240e
13 changed files with 190 additions and 6 deletions

View File

@@ -5272,6 +5272,15 @@
"messageformat": "Tap on \"Donate to Signal\" and subscribe",
"description": "In the instructions for becoming a sustainer. Third instruction."
},
"icu:ProfileMovedModal__title": {
"messageformat": "Your profile has moved",
"description": "Bolded text on modal that explains the move of Profile Editor into the new Settings Tab"
},
"icu:ProfileMovedModal__description": {
"messageformat": "Open the Settings tab to view and edit your profile.",
"description": "Description text on modal that explains the move of Profile Editor into the new Settings Tab"
},
"icu:BackupImportScreen__title": {
"messageformat": "Syncing messages",
"description": "Title of backup import screen"

View File

@@ -0,0 +1 @@
<svg width="200" height="150" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#settings-moved-dark__a)"><g filter="url(#settings-moved-dark__b)"><path d="M56 0h128c8.837 0 16 7.163 16 16v101H72c-8.837 0-16-7.163-16-16V0Z" fill="color(display-p3 .4531 .4887 .6451)"/></g><path d="M63 82c0-5.523 4.477-10 10-10h28c5.523 0 10 4.477 10 10v18c0 5.523-4.477 10-10 10H73c-5.523 0-10-4.477-10-10V82Z" fill="color(display-p3 .5608 .5922 .7294)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M85.836 82.284c-.55 0-1.036.36-1.196.887l-.398 1.31a.417.417 0 0 1-.19.24l-1.016.586a.417.417 0 0 1-.303.045l-1.334-.31a1.25 1.25 0 0 0-1.365.592L78.87 87.65a1.25 1.25 0 0 0 .17 1.48l.936 1a.417.417 0 0 1 .112.284v1.172c0 .106-.04.208-.112.285l-.936 1a1.25 1.25 0 0 0-.17 1.48l1.163 2.015c.276.477.83.717 1.367.592l1.333-.31a.417.417 0 0 1 .303.045l1.016.586c.091.053.16.139.19.24l.398 1.31c.16.527.646.887 1.196.887h2.328c.55 0 1.036-.36 1.196-.887l.398-1.31a.417.417 0 0 1 .19-.24l1.016-.586a.416.416 0 0 1 .302-.045l1.334.31a1.25 1.25 0 0 0 1.366-.592l1.164-2.016a1.25 1.25 0 0 0-.17-1.479l-.936-1a.417.417 0 0 1-.112-.285v-1.172c0-.106.04-.208.112-.285l.936-1a1.25 1.25 0 0 0 .17-1.48l-1.164-2.015a1.25 1.25 0 0 0-1.366-.592l-1.334.31a.416.416 0 0 1-.303-.045l-1.015-.586a.417.417 0 0 1-.19-.24l-.398-1.31a1.25 1.25 0 0 0-1.196-.887h-2.328ZM83.666 91a3.333 3.333 0 1 1 6.667 0 3.333 3.333 0 0 1-6.666 0Z" fill="color(display-p3 .9569 .9608 .9725)"/><path d="M118 0v116h-1V0h1Z" fill="color(display-p3 .5255 .5569 .702)"/><path d="M57 101c0 8.284 6.716 15 15 15h128v1H72l-.413-.005c-8.509-.216-15.367-7.074-15.582-15.582L56 101V0h1v101Z" fill="color(display-p3 .5255 .5569 .702)"/></g><defs><clipPath id="settings-moved-dark__a"><rect width="200" height="150" rx="16" fill="#fff"/></clipPath><filter id="settings-moved-dark__b" x="24" y="-24" width="208" height="181" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dy="8"/><feGaussianBlur stdDeviation="16"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_291_3997"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_291_3997" result="shape"/></filter></defs></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

1
images/profile-moved.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="200" height="150" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#settings-moved__a)"><g filter="url(#settings-moved__b)"><path d="M56 0h128c8.837 0 16 7.163 16 16v101H72c-8.837 0-16-7.163-16-16V0Z" fill="color(display-p3 .8902 .9098 .9961)"/></g><path d="M63 82c0-5.523 4.477-10 10-10h28c5.523 0 10 4.477 10 10v18c0 5.523-4.477 10-10 10H73c-5.523 0-10-4.477-10-10V82Z" fill="color(display-p3 .7843 .8039 .9569)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M85.836 82.284c-.55 0-1.036.36-1.196.887l-.398 1.31a.417.417 0 0 1-.19.24l-1.016.586a.417.417 0 0 1-.303.045l-1.334-.31a1.25 1.25 0 0 0-1.365.592L78.87 87.65a1.25 1.25 0 0 0 .17 1.48l.936 1a.417.417 0 0 1 .112.284v1.172c0 .106-.04.208-.112.285l-.936 1a1.25 1.25 0 0 0-.17 1.48l1.163 2.015c.276.477.83.717 1.367.592l1.333-.31a.417.417 0 0 1 .303.045l1.016.586c.091.053.16.139.19.24l.398 1.31c.16.527.646.887 1.196.887h2.328c.55 0 1.036-.36 1.196-.887l.398-1.31a.417.417 0 0 1 .19-.24l1.016-.586a.416.416 0 0 1 .302-.045l1.334.31a1.25 1.25 0 0 0 1.366-.592l1.164-2.016a1.25 1.25 0 0 0-.17-1.479l-.936-1a.417.417 0 0 1-.112-.285v-1.172c0-.106.04-.208.112-.285l.936-1a1.25 1.25 0 0 0 .17-1.48l-1.164-2.015a1.25 1.25 0 0 0-1.366-.592l-1.334.31a.416.416 0 0 1-.303-.045l-1.015-.586a.417.417 0 0 1-.19-.24l-.398-1.31a1.25 1.25 0 0 0-1.196-.887h-2.328ZM83.666 91a3.333 3.333 0 1 1 6.667 0 3.333 3.333 0 0 1-6.666 0Z" fill="color(display-p3 .0068 .0431 .6732)"/><path d="M118 0v116h-1V0h1Z" fill="color(display-p3 .7137 .7373 .9333)"/><path d="M57 101c0 8.284 6.716 15 15 15h128v1H72l-.413-.005c-8.509-.216-15.367-7.074-15.582-15.582L56 101V0h1v101Z" fill="color(display-p3 .7137 .7373 .9333)"/></g><defs><clipPath id="settings-moved__a"><rect width="200" height="150" rx="16" fill="#fff"/></clipPath><filter id="settings-moved__b" x="24" y="-24" width="208" height="181" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dy="8"/><feGaussianBlur stdDeviation="16"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_281_3958"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_281_3958" result="shape"/></filter></defs></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -25,6 +25,7 @@ $NavTabs__ProfileAvatar__size: 28px;
width: variables.$NavTabs__width;
height: 100%;
padding-top: var(--title-bar-drag-area-height);
padding-bottom: 8px;
@include mixins.light-theme {
background-color: variables.$color-gray-04;
border-inline-end: 1px solid variables.$color-black-alpha-16;
@@ -240,7 +241,6 @@ $NavTabs__ProfileAvatar__size: 28px;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 8px;
}
.NavTabs__TabPanel {

View File

@@ -0,0 +1,37 @@
// Copyright 2014 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
@use '../mixins';
@use '../variables';
.ProfileMovedModal {
width: 300px;
text-align: center;
}
.ProfileMovedModal__image {
margin-top: 8px;
margin-bottom: 16px;
}
.ProfileMovedModal__title {
@include mixins.font-body-1-bold;
margin-bottom: 4px;
}
.ProfileMovedModal__description {
margin-bottom: 20px;
margin-inline-start: 16px;
margin-inline-end: 16px;
@include mixins.light-theme {
color: variables.$color-black-alpha-50;
}
@include mixins.dark-theme {
color: variables.$color-white-alpha-50;
}
}
.ProfileMovedModal__button {
min-width: 236px;
}

View File

@@ -144,6 +144,7 @@
@use 'components/PlaybackRateButton.scss';
@use 'components/Preferences.scss';
@use 'components/ProfileEditor.scss';
@use 'components/ProfileMovedModal.scss';
@use 'components/ProfileNameWarningModal.scss';
@use 'components/ProgressBar.scss';
@use 'components/ProgressCircle.scss';

View File

@@ -992,6 +992,13 @@ export async function startApp(): Promise<void> {
if (window.isBeforeVersion(lastVersion, 'v7.56.0-beta.1')) {
await window.storage.remove('backupMediaDownloadIdle');
}
if (
window.isBeforeVersion(lastVersion, 'v7.57.0') &&
window.storage.get('needProfileMovedModal') === undefined
) {
await window.storage.put('needProfileMovedModal', true);
}
}
setAppLoadingScreenMessage(

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { Key, ReactNode } from 'react';
import React from 'react';
import React, { useState } from 'react';
import { Tabs, TabList, Tab, TabPanel } from 'react-aria-components';
import classNames from 'classnames';
import { Avatar, AvatarSize } from './Avatar';
@@ -16,6 +16,7 @@ import { Theme } from '../util/theme';
import type { UnreadStats } from '../util/countUnreadStats';
import { Page } from './Preferences';
import { EditState } from './ProfileEditor';
import { ProfileMovedModal } from './ProfileMovedModal';
type NavTabsItemBadgesProps = Readonly<{
i18n: LocalizerType;
@@ -199,7 +200,9 @@ export type NavTabsProps = Readonly<{
me: ConversationType;
navTabsCollapsed: boolean;
onChangeLocation: (location: Location) => void;
onDismissProfileMovedModal: () => void;
onToggleNavTabsCollapse: (collapsed: boolean) => void;
profileMovedModalNeeded: boolean;
renderCallsTab: () => ReactNode;
renderChatsTab: () => ReactNode;
renderStoriesTab: () => ReactNode;
@@ -221,7 +224,9 @@ export function NavTabs({
me,
navTabsCollapsed,
onChangeLocation,
onDismissProfileMovedModal,
onToggleNavTabsCollapse,
profileMovedModalNeeded,
renderCallsTab,
renderChatsTab,
renderStoriesTab,
@@ -234,6 +239,9 @@ export function NavTabs({
unreadConversationsStats,
unreadStoriesCount,
}: NavTabsProps): JSX.Element {
const [showingProfileMovedModal, setShowingProfileMovedModal] =
useState(false);
function handleSelectionChange(key: Key) {
const tab = key as NavTab;
if (tab === NavTab.Settings) {
@@ -258,6 +266,17 @@ export function NavTabs({
selectedKey={selectedNavTab}
onSelectionChange={handleSelectionChange}
>
{showingProfileMovedModal ? (
<ProfileMovedModal
i18n={i18n}
onClose={() => {
setShowingProfileMovedModal(false);
handleSelectionChange(NavTab.Settings);
onDismissProfileMovedModal();
}}
theme={theme}
/>
) : undefined}
<nav
data-supertab
className={classNames('NavTabs', {
@@ -329,7 +348,11 @@ export function NavTabs({
type="button"
className="NavTabs__Item NavTabs__Item--Profile"
onClick={() => {
handleSelectionChange(NavTab.Settings);
if (profileMovedModalNeeded) {
setShowingProfileMovedModal(true);
} else {
handleSelectionChange(NavTab.Settings);
}
}}
aria-label={i18n('icu:NavTabs__ItemLabel--Profile')}
>

View File

@@ -0,0 +1,29 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { ComponentProps } from 'react';
import type { Meta } from '@storybook/react';
import { ProfileMovedModal } from './ProfileMovedModal';
import { ThemeType } from '../types/Util';
import type { PropsType } from './ProfileMovedModal';
const { i18n } = window.SignalContext;
export default {
title: 'Components/ProfileMovedModal',
} satisfies Meta<PropsType>;
const defaultProps: ComponentProps<typeof ProfileMovedModal> = {
i18n,
theme: ThemeType.light,
onClose: action('onClose'),
};
export function Default(): JSX.Element {
return <ProfileMovedModal {...defaultProps} />;
}

View File

@@ -0,0 +1,53 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { Modal } from './Modal';
import { Button } from './Button';
import { ThemeType } from '../types/Util';
import type { LocalizerType } from '../types/Util';
export type PropsType = Readonly<{
i18n: LocalizerType;
onClose: () => unknown;
theme: ThemeType;
}>;
export function ProfileMovedModal({
i18n,
onClose,
theme,
}: PropsType): JSX.Element {
const imagePath =
theme === ThemeType.dark
? 'images/profile-moved-dark.svg'
: 'images/profile-moved.svg';
return (
<Modal
modalName="ProfileMovedModal"
moduleClassName="ProfileMovedModal"
i18n={i18n}
onClose={onClose}
>
<div className="ProfileMovedModal__contents">
<div className="ProfileMovedModal__main">
<div className="ProfileMovedModal__image">
<img src={imagePath} height="150" width="200" alt="" />
</div>
<div className="ProfileMovedModal__title">
{i18n('icu:ProfileMovedModal__title')}
</div>
<div className="ProfileMovedModal__description">
{i18n('icu:ProfileMovedModal__description')}
</div>
</div>
<Button className="ProfileMovedModal__button" onClick={onClose}>
{i18n('icu:ok')}
</Button>
</div>
</Modal>
);
}

View File

@@ -34,6 +34,12 @@ export const getAreWeASubscriber = createSelector(
Boolean(areWeASubscriber)
);
export const getProfileMovedModalNeeded = createSelector(
getItems,
({ needProfileMovedModal }: Readonly<ItemsStateType>): boolean =>
Boolean(needProfileMovedModal)
);
export const getUserAgent = createSelector(
getItems,
(state: ItemsStateType): string => state.userAgent as string

View File

@@ -15,13 +15,17 @@ import {
getHasAnyFailedStorySends,
getStoriesNotificationCount,
} from '../selectors/stories';
import { getStoriesEnabled } from '../selectors/items';
import {
getProfileMovedModalNeeded,
getStoriesEnabled,
} from '../selectors/items';
import { getSelectedNavTab } from '../selectors/nav';
import type { Location } from '../ducks/nav';
import { useNavActions } from '../ducks/nav';
import { getHasPendingUpdate } from '../selectors/updates';
import { getCallHistoryUnreadCount } from '../selectors/callHistory';
import { Environment } from '../../environment';
import { useItemsActions } from '../ducks/items';
export type SmartNavTabsProps = Readonly<{
navTabsCollapsed: boolean;
@@ -42,7 +46,6 @@ export const SmartNavTabs = memo(function SmartNavTabs({
}: SmartNavTabsProps): JSX.Element {
const i18n = useSelector(getIntl);
const selectedNavTab = useSelector(getSelectedNavTab);
const { changeLocation } = useNavActions();
const me = useSelector(getMe);
const badge = useSelector(getPreferredBadgeSelector)(me.badges);
const theme = useSelector(getTheme);
@@ -52,10 +55,21 @@ export const SmartNavTabs = memo(function SmartNavTabs({
const unreadCallsCount = useSelector(getCallHistoryUnreadCount);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const profileMovedModalNeeded = useSelector(getProfileMovedModalNeeded);
const isNightly = useSelector(getIsNightly);
const { changeLocation } = useNavActions();
const { putItem } = useItemsActions();
const shouldShowProfileIcon =
useSelector(getIsNightly) ||
profileMovedModalNeeded ||
isNightly ||
window.SignalContext.getEnvironment() !== Environment.PackagedApp;
const onDismissProfileMovedModal = useCallback(() => {
putItem('needProfileMovedModal', false);
}, [putItem]);
const onChangeLocation = useCallback(
(location: Location) => {
// For some reason react-aria will call this more often than the tab
@@ -77,6 +91,8 @@ export const SmartNavTabs = memo(function SmartNavTabs({
navTabsCollapsed={navTabsCollapsed}
onChangeLocation={onChangeLocation}
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
profileMovedModalNeeded={profileMovedModalNeeded}
onDismissProfileMovedModal={onDismissProfileMovedModal}
renderCallsTab={renderCallsTab}
renderChatsTab={renderChatsTab}
renderStoriesTab={renderStoriesTab}

View File

@@ -201,6 +201,7 @@ export type StorageAccessType = {
};
serverAlerts: ServerAlertsType;
needOrphanedAttachmentCheck: boolean;
needProfileMovedModal: boolean;
notificationProfileOverride: NotificationProfileOverride | undefined;
observedCapabilities: {
deleteSync?: true;