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() {