Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 0204724

Browse files
authored
Rework how the onboarding notifications task works (#12839)
* Rework how the onboarding notifications task works Signed-off-by: Michael Telatynski <[email protected]> * Add test Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent dd20741 commit 0204724

File tree

6 files changed

+85
-17
lines changed

6 files changed

+85
-17
lines changed

src/Notifier.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
IRoomTimelineData,
3030
M_LOCATION,
3131
EventType,
32+
TypedEventEmitter,
3233
} from "matrix-js-sdk/src/matrix";
3334
import { logger } from "matrix-js-sdk/src/logger";
3435
import { PermissionChanged as PermissionChangedEvent } from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
@@ -103,7 +104,15 @@ const msgTypeHandlers: Record<string, (event: MatrixEvent) => string | null> = {
103104
},
104105
};
105106

106-
class NotifierClass {
107+
export const enum NotifierEvent {
108+
NotificationHiddenChange = "notification_hidden_change",
109+
}
110+
111+
interface EmittedEvents {
112+
[NotifierEvent.NotificationHiddenChange]: (hidden: boolean) => void;
113+
}
114+
115+
class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents> {
107116
private notifsByRoom: Record<string, Notification[]> = {};
108117

109118
// A list of event IDs that we've received but need to wait until
@@ -357,6 +366,7 @@ class NotifierClass {
357366
if (persistent && global.localStorage) {
358367
global.localStorage.setItem("notifications_hidden", String(hidden));
359368
}
369+
this.emit(NotifierEvent.NotificationHiddenChange, hidden);
360370
}
361371

362372
public shouldShowPrompt(): boolean {

src/hooks/useUserOnboardingContext.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import { logger } from "matrix-js-sdk/src/logger";
1818
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
1919
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2020

21-
import { Notifier } from "../Notifier";
21+
import { Notifier, NotifierEvent } from "../Notifier";
2222
import DMRoomMap from "../utils/DMRoomMap";
2323
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
24+
import { useSettingValue } from "./useSettings";
25+
import { useEventEmitter } from "./useEventEmitter";
2426

2527
export interface UserOnboardingContext {
2628
hasAvatar: boolean;
2729
hasDevices: boolean;
2830
hasDmRooms: boolean;
29-
hasNotificationsEnabled: boolean;
31+
showNotificationsPrompt: boolean;
3032
}
3133

3234
const USER_ONBOARDING_CONTEXT_INTERVAL = 5000;
@@ -82,6 +84,18 @@ function useUserOnboardingContextValue<T>(defaultValue: T, callback: (cli: Matri
8284
return value;
8385
}
8486

87+
function useShowNotificationsPrompt(): boolean {
88+
const [value, setValue] = useState<boolean>(Notifier.shouldShowPrompt());
89+
useEventEmitter(Notifier, NotifierEvent.NotificationHiddenChange, () => {
90+
setValue(Notifier.shouldShowPrompt());
91+
});
92+
const setting = useSettingValue("notificationsEnabled");
93+
useEffect(() => {
94+
setValue(Notifier.shouldShowPrompt());
95+
}, [setting]);
96+
return value;
97+
}
98+
8599
export function useUserOnboardingContext(): UserOnboardingContext {
86100
const hasAvatar = useUserOnboardingContextValue(false, async (cli) => {
87101
const profile = await cli.getProfileInfo(cli.getUserId()!);
@@ -96,12 +110,10 @@ export function useUserOnboardingContext(): UserOnboardingContext {
96110
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
97111
return Boolean(Object.keys(dmRooms).length);
98112
});
99-
const hasNotificationsEnabled = useUserOnboardingContextValue(false, async () => {
100-
return Notifier.isPossible();
101-
});
113+
const showNotificationsPrompt = useShowNotificationsPrompt();
102114

103115
return useMemo(
104-
() => ({ hasAvatar, hasDevices, hasDmRooms, hasNotificationsEnabled }),
105-
[hasAvatar, hasDevices, hasDmRooms, hasNotificationsEnabled],
116+
() => ({ hasAvatar, hasDevices, hasDmRooms, showNotificationsPrompt }),
117+
[hasAvatar, hasDevices, hasDmRooms, showNotificationsPrompt],
106118
);
107119
}

src/hooks/useUserOnboardingTasks.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,18 @@ const tasks: UserOnboardingTask[] = [
136136
id: "permission-notifications",
137137
title: _t("onboarding|enable_notifications"),
138138
description: _t("onboarding|enable_notifications_description"),
139-
completed: (ctx: UserOnboardingContext) => ctx.hasNotificationsEnabled,
139+
completed: (ctx: UserOnboardingContext) => !ctx.showNotificationsPrompt,
140140
action: {
141141
label: _t("onboarding|enable_notifications_action"),
142142
onClick: (ev: ButtonEvent) => {
143143
PosthogTrackers.trackInteraction("WebUserOnboardingTaskEnableNotifications", ev);
144-
Notifier.setEnabled(true);
144+
defaultDispatcher.dispatch({
145+
action: Action.ViewUserSettings,
146+
initialTabId: UserTab.Notifications,
147+
});
148+
Notifier.setPromptHidden(true);
145149
},
146-
hideOnComplete: true,
150+
hideOnComplete: !Notifier.isPossible(),
147151
},
148152
},
149153
];

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,8 +1652,8 @@
16521652
"download_brand_desktop": "Download %(brand)s Desktop",
16531653
"download_f_droid": "Get it on F-Droid",
16541654
"download_google_play": "Get it on Google Play",
1655-
"enable_notifications": "Turn on notifications",
1656-
"enable_notifications_action": "Enable notifications",
1655+
"enable_notifications": "Turn on desktop notifications",
1656+
"enable_notifications_action": "Open settings",
16571657
"enable_notifications_description": "Don’t miss a reply or important message",
16581658
"explore_rooms": "Explore Public Rooms",
16591659
"find_community_members": "Find and invite your community members",

src/toasts/DesktopNotificationsToast.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import GenericToast from "../components/views/toasts/GenericToast";
2020
import ToastStore from "../stores/ToastStore";
2121
import { MatrixClientPeg } from "../MatrixClientPeg";
2222
import { getLocalNotificationAccountDataEventType } from "../utils/notifications";
23+
import SettingsStore from "../settings/SettingsStore";
24+
import { SettingLevel } from "../settings/SettingLevel";
2325

24-
const onAccept = (): void => {
25-
Notifier.setEnabled(true);
26+
const onAccept = async (): Promise<void> => {
27+
await SettingsStore.setValue("notificationsEnabled", null, SettingLevel.DEVICE, true);
2628
const cli = MatrixClientPeg.safeGet();
2729
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId!);
2830
cli.setAccountData(eventType, {

test/hooks/useUserOnboardingTasks-test.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import React from "react";
1718
import { renderHook } from "@testing-library/react-hooks";
19+
import { waitFor } from "@testing-library/react";
1820

1921
import { useUserOnboardingTasks } from "../../src/hooks/useUserOnboardingTasks";
22+
import { useUserOnboardingContext } from "../../src/hooks/useUserOnboardingContext";
23+
import { stubClient } from "../test-utils";
24+
import MatrixClientContext from "../../src/contexts/MatrixClientContext";
25+
import DMRoomMap from "../../src/utils/DMRoomMap";
26+
import PlatformPeg from "../../src/PlatformPeg";
2027

2128
describe("useUserOnboardingTasks", () => {
2229
it.each([
@@ -25,15 +32,15 @@ describe("useUserOnboardingTasks", () => {
2532
hasAvatar: false,
2633
hasDevices: false,
2734
hasDmRooms: false,
28-
hasNotificationsEnabled: false,
35+
showNotificationsPrompt: false,
2936
},
3037
},
3138
{
3239
context: {
3340
hasAvatar: true,
3441
hasDevices: false,
3542
hasDmRooms: false,
36-
hasNotificationsEnabled: true,
43+
showNotificationsPrompt: true,
3744
},
3845
},
3946
])("sequence should stay static", async ({ context }) => {
@@ -46,4 +53,37 @@ describe("useUserOnboardingTasks", () => {
4653
expect(result.current[3].id).toBe("setup-profile");
4754
expect(result.current[4].id).toBe("permission-notifications");
4855
});
56+
57+
it("should mark desktop notifications task completed on click", async () => {
58+
jest.spyOn(PlatformPeg, "get").mockReturnValue({
59+
supportsNotifications: jest.fn().mockReturnValue(true),
60+
maySendNotifications: jest.fn().mockReturnValue(false),
61+
} as any);
62+
63+
const cli = stubClient();
64+
cli.pushRules = {
65+
global: {
66+
override: [
67+
{
68+
rule_id: ".m.rule.master",
69+
enabled: false,
70+
actions: [],
71+
default: true,
72+
},
73+
],
74+
},
75+
};
76+
DMRoomMap.makeShared(cli);
77+
const context = renderHook(() => useUserOnboardingContext(), {
78+
wrapper: (props) => {
79+
return <MatrixClientContext.Provider value={cli}>{props.children}</MatrixClientContext.Provider>;
80+
},
81+
});
82+
const { result, rerender } = renderHook(() => useUserOnboardingTasks(context.result.current));
83+
expect(result.current[4].id).toBe("permission-notifications");
84+
await waitFor(() => expect(result.current[4].completed).toBe(false));
85+
result.current[4].action!.onClick!({ type: "click" } as any);
86+
rerender();
87+
await waitFor(() => expect(result.current[4].completed).toBe(true));
88+
});
4989
});

0 commit comments

Comments
 (0)