[FIX] web, *: show a dismiss button in the PWA notification
*: mail This commit fixes the behavior of the notification to install the PWA. The initial spec was not completely achieved, since the 'Dismiss' button, using the fa-close icon was not present. See task-3275275. Now, the button is present, and it can hide the notification and the user don't need to interact with the installation prompt to dismiss it. The fix also fixes the behavior of the notification when being used in an incognito tab. The installPrompt service assumed the PWA could be installed, since window.BeforeInstallPromptEvent is defined. But in an incognito scenario, the event was never actually triggered, since the feature is disabled by the browser. This could finally create a UX issue if the app had been installed, and the localStorage cleaned. The notification would appear, but wouldn't do anything when clicking on the Install button since the event is no longer triggered by the browser. Now, we rely on the trigger of the event exclusively, except on Safari, where we rely on the installationState and the standalone display-mode. Now, the service only set its state when the event has effectivelly been triggered and the native prompt can be called. Since the UI relies on the state from the service, the notification is no longer shown. Tests have been adapted, to dispatch a custom event, allowing the service to be initialized as if it was in a situation where the PWA can be installed effectively. A new test have been added to verify the interaction with the close icon. closes odoo/odoo#149961 Signed-off-by: Luca Vitali (luvi) <luvi@odoo.com>
This commit is contained in:
@@ -2800,6 +2800,14 @@ msgstr ""
|
||||
msgid "Discussions"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail
|
||||
#. odoo-javascript
|
||||
#: code:addons/mail/static/src/core/web/messaging_menu.xml:0
|
||||
#, python-format
|
||||
msgid "Dismiss"
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. module: mail
|
||||
#: model:ir.model.fields,field_description:mail.field_discuss_channel__display_name
|
||||
#: model:ir.model.fields,field_description:mail.field_discuss_channel_member__display_name
|
||||
|
||||
@@ -33,15 +33,17 @@
|
||||
displayName="installationRequest.displayName"
|
||||
iconSrc="installationRequest.iconSrc"
|
||||
onClick="installationRequest.onClick"
|
||||
onSwipeRight="hasTouch() ? { action: () => this.installPrompt.decline(), icon: 'fa-times-circle', bgColor: 'bg-warning' } : undefined"
|
||||
>
|
||||
<t t-set-slot="icon">
|
||||
<ImStatus persona="installationRequest.partner" className="'position-absolute top-100 start-100 translate-middle mt-n1 ms-n1'"/>
|
||||
</t>
|
||||
<t t-set-slot="requestContent">
|
||||
<a t-if="ui.isSmall" class="btn fa fa-cloud-download" />
|
||||
<a t-else="" class="btn btn-primary">
|
||||
Install
|
||||
</a>
|
||||
<t t-else="">
|
||||
<a class="btn btn-primary">Install</a>
|
||||
<span t-if="!hasTouch()" t-on-click.stop="() => this.installPrompt.decline()" class="text-dark bg-transparent fa fa-close opacity-50 opacity-100-hover" title="Dismiss"></span>
|
||||
</t>
|
||||
</t>
|
||||
</NotificationItem>
|
||||
</t>
|
||||
|
||||
@@ -146,6 +146,9 @@ QUnit.test("rendering with PWA installation request", async (assert) => {
|
||||
});
|
||||
|
||||
const { env } = await start();
|
||||
// This event must be triggered to initialize the installPrompt service properly
|
||||
// as if it was run by a browser supporting PWA (never triggered in a test otherwise).
|
||||
browser.dispatchEvent(new CustomEvent("beforeinstallprompt"));
|
||||
patchWithCleanup(env.services.installPrompt, {
|
||||
show() {
|
||||
assert.step("show prompt");
|
||||
@@ -157,10 +160,9 @@ QUnit.test("rendering with PWA installation request", async (assert) => {
|
||||
await contains(".o-mail-MessagingMenu-counter", { text: "1" });
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-NotificationItem");
|
||||
assert.ok(
|
||||
target
|
||||
.querySelector(".o-mail-NotificationItem img")
|
||||
.dataset.src.includes("/web/image?field=avatar_128&id=2&model=res.partner")
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o-mail-NotificationItem img[data-src*='/web/image?field=avatar_128&id=2&model=res.partner']"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-mail-NotificationItem-name").textContent,
|
||||
@@ -175,6 +177,46 @@ QUnit.test("rendering with PWA installation request", async (assert) => {
|
||||
assert.verifySteps(["show prompt"]);
|
||||
});
|
||||
|
||||
QUnit.test("installation of the PWA request can be dismissed", async (assert) => {
|
||||
patchWithCleanup(browser, {
|
||||
BeforeInstallPromptEvent: () => {},
|
||||
});
|
||||
patchWithCleanup(browser.localStorage, {
|
||||
getItem(key) {
|
||||
if (key === "pwa.installationState") {
|
||||
assert.step("getItem " + key);
|
||||
// in this test, installation has not yet proceeded
|
||||
return null;
|
||||
}
|
||||
return super.getItem(key);
|
||||
},
|
||||
setItem(key, value) {
|
||||
if (key === "pwa.installationState") {
|
||||
assert.step("installationState value: " + value);
|
||||
}
|
||||
return super.setItem(key, value);
|
||||
},
|
||||
});
|
||||
|
||||
const { env } = await start();
|
||||
// This event must be triggered to initialize the installPrompt service properly
|
||||
// as if it was run by a browser supporting PWA (never triggered in a test otherwise).
|
||||
browser.dispatchEvent(new CustomEvent("beforeinstallprompt"));
|
||||
patchWithCleanup(env.services.installPrompt, {
|
||||
show() {
|
||||
assert.step("show prompt should not be triggered");
|
||||
},
|
||||
});
|
||||
assert.verifySteps(["getItem pwa.installationState"]);
|
||||
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem .fa-close");
|
||||
assert.verifySteps(["installationState value: dismissed"]);
|
||||
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-NotificationItem", { count: 0 });
|
||||
});
|
||||
|
||||
QUnit.test("rendering with PWA installation request (dismissed)", async (assert) => {
|
||||
const target = getFixture();
|
||||
|
||||
@@ -193,6 +235,9 @@ QUnit.test("rendering with PWA installation request (dismissed)", async (assert)
|
||||
});
|
||||
|
||||
await start();
|
||||
// This event must be triggered to initialize the installPrompt service properly
|
||||
// as if it was run by a browser supporting PWA (never triggered in a test otherwise).
|
||||
browser.dispatchEvent(new CustomEvent("beforeinstallprompt"));
|
||||
assert.verifySteps(["getItem pwa.installationState"]);
|
||||
assert.containsNone(target, ".o-mail-MessagingMenu-counter");
|
||||
|
||||
@@ -205,12 +250,6 @@ QUnit.test("rendering with PWA installation request (already running as PWA)", a
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
BeforeInstallPromptEvent: () => {},
|
||||
matchMedia(media) {
|
||||
if (media === "(display-mode: standalone)") {
|
||||
return { matches: true };
|
||||
}
|
||||
return super.matchMedia(media);
|
||||
},
|
||||
});
|
||||
patchWithCleanup(browser.localStorage, {
|
||||
getItem(key) {
|
||||
@@ -224,6 +263,8 @@ QUnit.test("rendering with PWA installation request (already running as PWA)", a
|
||||
});
|
||||
|
||||
await start();
|
||||
// The 'beforeinstallprompt' event is not triggered here, since the
|
||||
// browser wouldn't trigger it when the app is already launched
|
||||
assert.verifySteps(["getItem pwa.installationState"]);
|
||||
assert.containsNone(target, ".o-mail-MessagingMenu-counter");
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ try {
|
||||
|
||||
export const browser = {
|
||||
addEventListener: window.addEventListener.bind(window),
|
||||
dispatchEvent: window.dispatchEvent.bind(window),
|
||||
AnalyserNode: window.AnalyserNode,
|
||||
Audio: window.Audio,
|
||||
AudioBufferSourceNode: window.AudioBufferSourceNode,
|
||||
|
||||
@@ -26,33 +26,35 @@ const installPromptService = {
|
||||
|
||||
// The PWA can only be installed if the app is not already launched (display-mode standalone)
|
||||
// For Apple devices, PWA are supported on any mobile version of Safari, or in desktop since version 17
|
||||
// On Safari devices, the check is also done on the display-mode and we rely on the installationState to
|
||||
// decide whether we must show the prompt or not
|
||||
const canBeInstalled =
|
||||
!isDisplayStandalone() &&
|
||||
(browser.BeforeInstallPromptEvent !== undefined ||
|
||||
(isBrowserSafari() &&
|
||||
(isIOS() ||
|
||||
(isMacOS() &&
|
||||
browser.navigator.userAgent.match(/Version\/(\d+)/)[1] >= 17))));
|
||||
browser.BeforeInstallPromptEvent !== undefined ||
|
||||
(isBrowserSafari() &&
|
||||
!isDisplayStandalone() &&
|
||||
(isIOS() ||
|
||||
(isMacOS() && browser.navigator.userAgent.match(/Version\/(\d+)/)[1] >= 17)));
|
||||
|
||||
const installationState = browser.localStorage.getItem("pwa.installationState");
|
||||
|
||||
// It is possible that the browser still has the installationState stored in the localstorage once
|
||||
// the app has been uninstalled. This is why we don't use installationState directly in canBeInstalled
|
||||
state.canPromptToInstall = canBeInstalled && !installationState;
|
||||
|
||||
const isDeclined = installationState === "dismissed";
|
||||
|
||||
if (canBeInstalled && !isDeclined) {
|
||||
browser.addEventListener("beforeinstallprompt", (ev) => {
|
||||
// This event is only triggered by the browser when the native prompt to install can be shown
|
||||
// This excludes incognito tabs, as well as visiting the website while the app is installed
|
||||
ev.preventDefault();
|
||||
nativePrompt = ev;
|
||||
if (installationState === "accepted") {
|
||||
// If this event is triggered with the installationState stored, it means that the app has been
|
||||
// removed since its installation. The prompt can be displayed, and the installation state is reset.
|
||||
state.canPromptToInstall = true;
|
||||
browser.localStorage.removeItem("pwa.installationState");
|
||||
}
|
||||
ev.preventDefault();
|
||||
nativePrompt = ev;
|
||||
state.canPromptToInstall = true;
|
||||
});
|
||||
if (isBrowserSafari()) {
|
||||
// since those platforms don't rely on the beforeinstallprompt event, we handle it ourselves
|
||||
state.canPromptToInstall = installationState !== "accepted";
|
||||
}
|
||||
}
|
||||
|
||||
async function show() {
|
||||
|
||||
Reference in New Issue
Block a user