diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4addb45638..cf5224601d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" diff --git a/images/profile-moved-dark.svg b/images/profile-moved-dark.svg new file mode 100644 index 0000000000..bbe3474438 --- /dev/null +++ b/images/profile-moved-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/profile-moved.svg b/images/profile-moved.svg new file mode 100644 index 0000000000..3c1feede3e --- /dev/null +++ b/images/profile-moved.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stylesheets/components/NavTabs.scss b/stylesheets/components/NavTabs.scss index 1dd6958ba7..5c6cacef42 100644 --- a/stylesheets/components/NavTabs.scss +++ b/stylesheets/components/NavTabs.scss @@ -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 { diff --git a/stylesheets/components/ProfileMovedModal.scss b/stylesheets/components/ProfileMovedModal.scss new file mode 100644 index 0000000000..234f8f8b10 --- /dev/null +++ b/stylesheets/components/ProfileMovedModal.scss @@ -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; +} diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 5ec954edc5..252ed25d79 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -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'; diff --git a/ts/background.ts b/ts/background.ts index 4eb7c72bb0..750a73de22 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -992,6 +992,13 @@ export async function startApp(): Promise { 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( diff --git a/ts/components/NavTabs.stories.tsx b/ts/components/NavTabs.stories.tsx index f03d961a6f..f9a929d6db 100644 --- a/ts/components/NavTabs.stories.tsx +++ b/ts/components/NavTabs.stories.tsx @@ -23,6 +23,7 @@ const createProps = ( me: getDefaultConversation(), navTabsCollapsed: Boolean(overrideProps.navTabsCollapsed), onChangeLocation: action('onChangeLocation'), + onDismissProfileMovedModal: action('onToggleNavTabsCollapse'), onToggleNavTabsCollapse: action('onToggleNavTabsCollapse'), renderCallsTab: () => Calls Tab goes here, renderChatsTab: () => Chats Tab goes here, @@ -39,6 +40,7 @@ const createProps = ( markedUnread: false, }, unreadStoriesCount: overrideProps.unreadStoriesCount ?? 0, + profileMovedModalNeeded: false, }); export default { diff --git a/ts/components/NavTabs.tsx b/ts/components/NavTabs.tsx index 3d11ba5d35..68bb18ce92 100644 --- a/ts/components/NavTabs.tsx +++ b/ts/components/NavTabs.tsx @@ -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 ? ( + { + setShowingProfileMovedModal(false); + handleSelectionChange(NavTab.Settings); + onDismissProfileMovedModal(); + }} + theme={theme} + /> + ) : undefined} { - handleSelectionChange(NavTab.Settings); + if (profileMovedModalNeeded) { + setShowingProfileMovedModal(true); + } else { + handleSelectionChange(NavTab.Settings); + } }} aria-label={i18n('icu:NavTabs__ItemLabel--Profile')} > diff --git a/ts/components/ProfileMovedModal.stories.tsx b/ts/components/ProfileMovedModal.stories.tsx new file mode 100644 index 0000000000..bab6f27e80 --- /dev/null +++ b/ts/components/ProfileMovedModal.stories.tsx @@ -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; + +const defaultProps: ComponentProps = { + i18n, + theme: ThemeType.light, + onClose: action('onClose'), +}; + +export function Default(): JSX.Element { + return ; +} diff --git a/ts/components/ProfileMovedModal.tsx b/ts/components/ProfileMovedModal.tsx new file mode 100644 index 0000000000..8a5cf66fb7 --- /dev/null +++ b/ts/components/ProfileMovedModal.tsx @@ -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 ( + + + + + + + + {i18n('icu:ProfileMovedModal__title')} + + + {i18n('icu:ProfileMovedModal__description')} + + + + {i18n('icu:ok')} + + + + ); +} diff --git a/ts/state/selectors/items.ts b/ts/state/selectors/items.ts index 575ab391cf..d50f0295d5 100644 --- a/ts/state/selectors/items.ts +++ b/ts/state/selectors/items.ts @@ -34,6 +34,12 @@ export const getAreWeASubscriber = createSelector( Boolean(areWeASubscriber) ); +export const getProfileMovedModalNeeded = createSelector( + getItems, + ({ needProfileMovedModal }: Readonly): boolean => + Boolean(needProfileMovedModal) +); + export const getUserAgent = createSelector( getItems, (state: ItemsStateType): string => state.userAgent as string diff --git a/ts/state/smart/NavTabs.tsx b/ts/state/smart/NavTabs.tsx index e086e1fb6a..46e96934c6 100644 --- a/ts/state/smart/NavTabs.tsx +++ b/ts/state/smart/NavTabs.tsx @@ -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} diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index 104a2cbfa5..0400a2909d 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -201,6 +201,7 @@ export type StorageAccessType = { }; serverAlerts: ServerAlertsType; needOrphanedAttachmentCheck: boolean; + needProfileMovedModal: boolean; notificationProfileOverride: NotificationProfileOverride | undefined; observedCapabilities: { deleteSync?: true;