diff options
| author | Oliver <oliver@mohlin.dev> | 2024-12-10 14:27:16 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-12-17 12:15:39 +0100 |
| commit | 07c93b1d518e7e072a54889d3ca74d507dc742ee (patch) | |
| tree | 17a6516c8027463bf945ed43e154f7ef28c6e51e | |
| parent | 662eae9872baf4dddc08e668e1fbacd40111a36e (diff) | |
| download | mullvadvpn-07c93b1d518e7e072a54889d3ca74d507dc742ee.tar.xz mullvadvpn-07c93b1d518e7e072a54889d3ca74d507dc742ee.zip | |
Show notification instead of dialog when new version installed
12 files changed, 128 insertions, 85 deletions
diff --git a/desktop/packages/mullvad-vpn/src/main/account.ts b/desktop/packages/mullvad-vpn/src/main/account.ts index 024e4a7208..7fc6b48d47 100644 --- a/desktop/packages/mullvad-vpn/src/main/account.ts +++ b/desktop/packages/mullvad-vpn/src/main/account.ts @@ -13,7 +13,7 @@ import { AccountExpiredNotificationProvider, CloseToAccountExpiryNotificationProvider, SystemNotificationCategory, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { Scheduler } from '../shared/scheduler'; import AccountDataCache from './account-data-cache'; import { DaemonRpc } from './daemon-rpc'; diff --git a/desktop/packages/mullvad-vpn/src/main/index.ts b/desktop/packages/mullvad-vpn/src/main/index.ts index ea2f12199c..efbd5e1654 100644 --- a/desktop/packages/mullvad-vpn/src/main/index.ts +++ b/desktop/packages/mullvad-vpn/src/main/index.ts @@ -363,6 +363,7 @@ class ApplicationMain this.daemonRpc.disconnect(); } + this.settings.gui.changelogDisplayedForVersion = this.version.currentVersion.gui; for (const logger of [log, this.rendererLog]) { try { logger?.disposeDisposableOutputs(); diff --git a/desktop/packages/mullvad-vpn/src/main/notification-controller.ts b/desktop/packages/mullvad-vpn/src/main/notification-controller.ts index 925bff0812..664f102709 100644 --- a/desktop/packages/mullvad-vpn/src/main/notification-controller.ts +++ b/desktop/packages/mullvad-vpn/src/main/notification-controller.ts @@ -16,7 +16,7 @@ import { SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { Scheduler } from '../shared/scheduler'; const THROTTLE_DELAY = 500; diff --git a/desktop/packages/mullvad-vpn/src/main/version.ts b/desktop/packages/mullvad-vpn/src/main/version.ts index 1bab7728b4..676391e150 100644 --- a/desktop/packages/mullvad-vpn/src/main/version.ts +++ b/desktop/packages/mullvad-vpn/src/main/version.ts @@ -8,7 +8,7 @@ import { SystemNotificationCategory, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { DaemonRpc } from './daemon-rpc'; import { IpcMainEventChannel } from './ipc-event-channel'; import { NotificationSender } from './notification-controller'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx index dac62db192..6aac11c9d5 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx @@ -1,9 +1,7 @@ import { useCallback, useState } from 'react'; -import { useSelector } from 'react-redux'; import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; -import { NewDeviceNotificationProvider } from '../../shared/notifications/new-device'; import { BlockWhenDisconnectedNotificationProvider, CloseToAccountExpiryNotificationProvider, @@ -13,18 +11,22 @@ import { InAppNotificationProvider, InAppNotificationTroubleshootInfo, InconsistentVersionNotificationProvider, + NewVersionNotificationProvider, ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../../shared/notifications/notification'; +} from '../../shared/notifications'; +import { NewDeviceNotificationProvider } from '../../shared/notifications/new-device'; import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import { transitions, useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { RoutePath } from '../lib/routes'; import accountActions from '../redux/account/actions'; -import { IReduxState } from '../redux/store'; +import { IReduxState, useSelector } from '../redux/store'; +import { Colors } from '../tokens'; import * as AppButton from './AppButton'; +import { Link } from './common/text'; import { ModalAlert, ModalAlertType, ModalMessage, ModalMessageList } from './Modal'; import { NotificationActions, @@ -59,6 +61,18 @@ export default function NotificationArea(props: IProps) { const { hideNewDeviceBanner } = useActions(accountActions); + const { setDisplayedChangelog } = useAppContext(); + + const currentVersion = useSelector((state) => state.version.current); + const displayedForVersion = useSelector( + (state) => state.settings.guiSettings.changelogDisplayedForVersion, + ); + const changelog = useSelector((state) => state.userInterface.changelog); + + const close = useCallback(() => { + setDisplayedChangelog(); + }, [setDisplayedChangelog]); + const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), new ReconnectingNotificationProvider(tunnelState), @@ -84,6 +98,12 @@ export default function NotificationArea(props: IProps) { deviceName: account.deviceName ?? '', close: hideNewDeviceBanner, }), + new NewVersionNotificationProvider({ + currentVersion, + displayedForVersion, + changelog, + close, + }), new UpdateAvailableNotificationProvider(version), ); @@ -106,7 +126,16 @@ export default function NotificationArea(props: IProps) { {notification.title} </NotificationTitle> <NotificationSubtitle data-testid="notificationSubTitle"> - {formatHtml(notification.subtitle ?? '')} + {notification.subtitleAction?.type === 'navigate' ? ( + <Link + variant="labelTiny" + color={Colors.white60} + {...notification.subtitleAction.link}> + {formatHtml(notification.subtitle ?? '')} + </Link> + ) : ( + formatHtml(notification.subtitle ?? '') + )} </NotificationSubtitle> </NotificationContent> {notification.action && <NotificationActionWrapper action={notification.action} />} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx index 924f65ff99..12f9fbef6e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx @@ -6,6 +6,7 @@ import { messages } from '../../shared/gettext'; import { InAppNotificationIndicatorType } from '../../shared/notifications/notification'; import { useEffectEvent, useLastDefinedValue, useStyledRef } from '../lib/utility-hooks'; import * as AppButton from './AppButton'; +import { IconButton } from './common/molecules'; import { tinyText } from './common-styles'; import ImageView from './ImageView'; @@ -81,12 +82,14 @@ export function NotificationTroubleshootDialogAction(props: NotificationActionPr export function NotificationCloseAction(props: NotificationActionProps) { return ( - <NotificationActionButton + <IconButton aria-describedby={NOTIFICATION_AREA_ID} aria-label={messages.pgettext('accessibility', 'Close notification')} - onClick={props.onClick}> - <NotificationActionButtonInner source="icon-close" width={16} tintColor={colors.white60} /> - </NotificationActionButton> + onClick={props.onClick} + icon="icon-close" + size="small" + variant="secondary" + /> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx index 15f760941f..2bc05a5f04 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx @@ -2,20 +2,17 @@ import { useCallback } from 'react'; import { strings } from '../../config.json'; import { messages } from '../../shared/gettext'; -import { getDownloadUrl } from '../../shared/version'; import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; -import { Colors } from '../tokens'; -import { RedButton } from './AppButton'; import * as Cell from './cell'; +import { Button } from './common/molecules/Button'; import { TitleBig } from './common/text'; import { BackAction } from './KeyboardNavigation'; import { ButtonStack, Footer, - LabelStack, Layout, SettingsContainer, SettingsContent, @@ -86,7 +83,7 @@ export default function Support() { <SettingsGroup> <SupportButton /> - <AppVersionButton /> + <AppInfoButton /> </SettingsGroup> {window.env.development && ( @@ -204,59 +201,16 @@ function ApiAccessMethodsButton() { ); } -function AppVersionButton() { +function AppInfoButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.appInfo), [history]); const appVersion = useSelector((state) => state.version.current); - const consistentVersion = useSelector((state) => state.version.consistent); - const upToDateVersion = useSelector((state) => (state.version.suggestedUpgrade ? false : true)); - const suggestedIsBeta = useSelector((state) => state.version.suggestedIsBeta ?? false); - const isOffline = useSelector((state) => state.connection.isBlocked); - - const { openUrl } = useAppContext(); - const openDownloadLink = useCallback( - () => openUrl(getDownloadUrl(suggestedIsBeta)), - [openUrl, suggestedIsBeta], - ); - - let alertIcon; - let footer; - if (!consistentVersion || !upToDateVersion) { - const inconsistentVersionMessage = messages.pgettext( - 'settings-view', - 'App is out of sync. Please quit and restart.', - ); - - const updateAvailableMessage = messages.pgettext( - 'settings-view', - 'Update available. Install the latest app version to stay up to date.', - ); - - const message = !consistentVersion ? inconsistentVersionMessage : updateAvailableMessage; - - alertIcon = <Cell.UntintedIcon source="icon-alert" width={18} tintColor={Colors.red} />; - footer = ( - <Cell.CellFooter> - <Cell.CellFooterText>{message}</Cell.CellFooterText> - </Cell.CellFooter> - ); - } return ( - <> - <Cell.CellNavigationButton - disabled={isOffline} - onClick={openDownloadLink} - icon={{ - source: 'icon-extLink', - 'aria-label': messages.pgettext('accessibility', 'Opens externally'), - }}> - <LabelStack> - {alertIcon} - <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label> - </LabelStack> - <Cell.SubText>{appVersion}</Cell.SubText> - </Cell.CellNavigationButton> - {footer} - </> + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label>{messages.pgettext('settings-view', 'App info')}</Cell.Label> + <Cell.SubText>{appVersion}</Cell.SubText> + </Cell.CellNavigationButton> ); } @@ -287,10 +241,10 @@ function QuitButton() { const tunnelState = useSelector((state) => state.connection.status); return ( - <RedButton onClick={quit}> + <Button variant="destructive" onClick={quit}> {tunnelState.state === 'disconnected' ? messages.gettext('Quit') : messages.gettext('Disconnect & quit')} - </RedButton> + </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx index c91fbbdb20..b9ce4a6a59 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { smallText } from './common-styles'; -import { MultiButtonCompatibleProps } from './MultiButton'; export enum SmallButtonColor { blue, @@ -84,6 +83,11 @@ const StyledTextOffset = styled.span<{ $width: number }>((props) => ({ flex: `0 1 ${props.$width}px`, })); +export interface MultiButtonCompatibleProps { + className?: string; + textOffset?: number; +} + interface SmallButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'color'>, MultiButtonCompatibleProps { diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts new file mode 100644 index 0000000000..615ce42250 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts @@ -0,0 +1,14 @@ +export * from './account-expired'; +export * from './close-to-account-expiry'; +export * from './block-when-disconnected'; +export * from './connected'; +export * from './connecting'; +export * from './disconnected'; +export * from './daemon-disconnected'; +export * from './error'; +export * from './inconsistent-version'; +export * from './new-version'; +export * from './notification'; +export * from './reconnecting'; +export * from './unsupported-version'; +export * from './update-available'; diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/new-version.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/new-version.ts new file mode 100644 index 0000000000..991266adbe --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/new-version.ts @@ -0,0 +1,44 @@ +import { RoutePath } from '../../renderer/lib/routes'; +import { messages } from '../gettext'; +import { IChangelog } from '../ipc-types'; +import { InAppNotification, InAppNotificationProvider } from './notification'; + +interface NewVersionNotificationContext { + currentVersion: string; + displayedForVersion: string; + changelog: IChangelog; + close: () => void; +} + +export class NewVersionNotificationProvider implements InAppNotificationProvider { + public constructor(private context: NewVersionNotificationContext) {} + + public mayDisplay = () => { + return ( + this.context.displayedForVersion !== this.context.currentVersion && + this.context.changelog.length > 0 + ); + }; + + public getInAppNotification(): InAppNotification { + const title = messages.pgettext('in-app-notifications', 'NEW VERSION INSTALLED'); + const subtitle = messages.pgettext('in-app-notifications', "Click here to see what's new."); + return { + indicator: 'success', + action: { type: 'close', close: this.context.close }, + title, + subtitle, + subtitleAction: { + type: 'navigate', + link: { + to: RoutePath.changelog, + onClick: this.context.close, + 'aria-label': messages.pgettext( + 'accessibility', + 'New version installed, click here to see the changelog', + ), + }, + }, + }; + } +} diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts index 87166aab4d..96754811a6 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts @@ -1,3 +1,5 @@ +import { LinkProps } from '../../renderer/components/common/text'; + export type NotificationAction = { type: 'open-url'; url: string; @@ -25,6 +27,10 @@ export type InAppNotificationAction = | { type: 'close'; close: () => void; + } + | { + type: 'navigate'; + link: Pick<LinkProps, 'to' | 'onClick' | 'aria-label'>; }; export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error'; @@ -59,9 +65,10 @@ export interface SystemNotification { export interface InAppNotification { indicator?: InAppNotificationIndicatorType; + action?: InAppNotificationAction; title: string; subtitle?: string; - action?: InAppNotificationAction; + subtitleAction?: InAppNotificationAction; } export interface SystemNotificationProvider extends NotificationProvider { @@ -71,16 +78,3 @@ export interface SystemNotificationProvider extends NotificationProvider { export interface InAppNotificationProvider extends NotificationProvider { getInAppNotification(): InAppNotification | undefined; } - -export * from './account-expired'; -export * from './close-to-account-expiry'; -export * from './block-when-disconnected'; -export * from './connected'; -export * from './connecting'; -export * from './disconnected'; -export * from './daemon-disconnected'; -export * from './error'; -export * from './inconsistent-version'; -export * from './reconnecting'; -export * from './unsupported-version'; -export * from './update-available'; diff --git a/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts b/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts index d05e967efc..50acda1393 100644 --- a/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts +++ b/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts @@ -9,7 +9,7 @@ import { FirewallPolicyErrorType } from '../../src/shared/daemon-rpc-types'; import { UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../../src/shared/notifications/notification'; +} from '../../src/shared/notifications'; function createController() { return new NotificationController({ |
