diff --git a/addons/mail/i18n/mail.pot b/addons/mail/i18n/mail.pot index 48f154e8068..8065f167c37 100644 --- a/addons/mail/i18n/mail.pot +++ b/addons/mail/i18n/mail.pot @@ -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 diff --git a/addons/mail/static/src/core/web/messaging_menu.xml b/addons/mail/static/src/core/web/messaging_menu.xml index 0ea22574f0e..43785e157c2 100644 --- a/addons/mail/static/src/core/web/messaging_menu.xml +++ b/addons/mail/static/src/core/web/messaging_menu.xml @@ -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" > - - Install - + + Install + + diff --git a/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js b/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js index fc6c3c6972a..84b9b1d975c 100644 --- a/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js +++ b/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js @@ -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"); diff --git a/addons/web/static/src/core/browser/browser.js b/addons/web/static/src/core/browser/browser.js index 1d5ed6bd621..c7729b61504 100644 --- a/addons/web/static/src/core/browser/browser.js +++ b/addons/web/static/src/core/browser/browser.js @@ -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, diff --git a/addons/web/static/src/core/install_prompt/install_prompt_service.js b/addons/web/static/src/core/install_prompt/install_prompt_service.js index af5de4a4b29..b74f052cc89 100644 --- a/addons/web/static/src/core/install_prompt/install_prompt_service.js +++ b/addons/web/static/src/core/install_prompt/install_prompt_service.js @@ -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() {