Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions web_pwa_customize/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ Web Pwa Customize
This module allows to configure data for Progressive Web App: Short
name, Background color, Theme color and Icon.

It also covers iOS Safari's "Add to Home Screen", which reads the
``apple-touch-icon`` link tag rather than the manifest - without this,
an icon configured here would only ever show up on Chrome, Edge and
Android.

**Table of contents**

.. contents::
Expand All @@ -46,6 +51,13 @@ Use Cases / Context
The existing definitions in the old web_pwa_oca of 16.0 (not existing or
customizable in core) are maintained.

The settings this module adds live in the same "Progressive Web App"
block as core's own ``web.web_app_name`` field, which core hides behind
developer mode (``base.group_no_one``). That restriction is dropped here
so the settings are actually reachable - General Settings itself already
requires admin access, so this doesn't expose anything that wasn't
already admin-only.

Configuration
=============

Expand Down Expand Up @@ -78,6 +90,9 @@ Contributors
- Víctor Martínez
- Pedro M. Baeza

- `LadyHwesta <https://github.com/LadyHwesta>`__ (apple-touch-icon
support, settings visibility)

Maintainers
-----------

Expand Down
7 changes: 5 additions & 2 deletions web_pwa_customize/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
"name": "Web Pwa Customize",
"author": "Tecnativa, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web",
"version": "19.0.1.0.0",
"version": "19.0.1.1.0",
"depends": ["web"],
"license": "AGPL-3",
"category": "Website",
"installable": True,
"maintainers": ["victoralmau"],
"data": ["views/res_config_settings_views.xml"],
"data": [
"views/res_config_settings_views.xml",
"views/webclient_templates.xml",
],
}
59 changes: 59 additions & 0 deletions web_pwa_customize/controllers/webmanifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@

from odoo.addons.web.controllers import webmanifest

# iOS Safari's "Add to Home Screen" reads a single apple-touch-icon link
# tag, not the PWA manifest's icons array, so it needs one fixed image
# rather than a list of sizes. None of the sizes this module generates
# (128 up to 512) is the platform's own recommended 180x180, so 192x192 -
# the closest larger one - is tried first, then whatever else exists.
APPLE_TOUCH_ICON_SIZES = [
"192x192",
"256x256",
"152x152",
"144x144",
"128x128",
"512x512",
]


class WebManifest(webmanifest.WebManifest):
def _get_pwa_manifest_icons(self, pwa_icon):
Expand Down Expand Up @@ -42,6 +56,51 @@ def _get_pwa_manifest_icons(self, pwa_icon):
]
return icons

def _get_apple_touch_icon_attachment(self):
"""The best-fitting configured icon for iOS's apple-touch-icon, or
None if no custom icon has been uploaded at all."""
pwa_icon = (
request.env["ir.attachment"]
.sudo()
.search([("url", "like", "/web_pwa_customize/icon.")])
)
if not pwa_icon:
return None
if pwa_icon.mimetype.startswith("image/svg"):
return pwa_icon
sized_icons = (
request.env["ir.attachment"]
.sudo()
.search(
[
("url", "like", "/web_pwa_customize/icon"),
("url", "not like", "/web_pwa_customize/icon."),
]
)
)
by_url = {icon.url: icon for icon in sized_icons}
for size in APPLE_TOUCH_ICON_SIZES:
icon = by_url.get(f"/web_pwa_customize/icon{size}.png")
if icon:
return icon
return pwa_icon

@http.route(
"/web_pwa_customize/apple_touch_icon",
type="http",
auth="public",
readonly=True,
)
def apple_touch_icon(self):
"""iOS Safari's "Add to Home Screen" reads the apple-touch-icon
link tag, not the PWA manifest - redirect it to whichever
configured icon fits best, falling back to Odoo's own artwork if
nothing has been configured."""
icon = self._get_apple_touch_icon_attachment()
if not icon:
return request.redirect("/web/static/img/odoo-icon-ios.png")
return request.redirect(icon.url)

@http.route(
"/web/manifest.webmanifest",
type="http",
Expand Down
2 changes: 2 additions & 0 deletions web_pwa_customize/readme/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
The existing definitions in the old web_pwa_oca of 16.0 (not existing or customizable
in core) are maintained.

The settings this module adds live in the same "Progressive Web App" block as core's own `web.web_app_name` field, which core hides behind developer mode (`base.group_no_one`). That restriction is dropped here so the settings are actually reachable - General Settings itself already requires admin access, so this doesn't expose anything that wasn't already admin-only.
1 change: 1 addition & 0 deletions web_pwa_customize/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- [Tecnativa](https://www.tecnativa.com):
- Víctor Martínez
- Pedro M. Baeza
- [LadyHwesta](https://github.com/LadyHwesta) (apple-touch-icon support, settings visibility)
2 changes: 2 additions & 0 deletions web_pwa_customize/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
This module allows to configure data for Progressive Web App: Short name, Background color, Theme color and Icon.

It also covers iOS Safari's "Add to Home Screen", which reads the `apple-touch-icon` link tag rather than the manifest - without this, an icon configured here would only ever show up on Chrome, Edge and Android.
12 changes: 12 additions & 0 deletions web_pwa_customize/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ <h1>Web Pwa Customize</h1>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/19.0/web_pwa_customize"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-19-0/web-19-0-web_pwa_customize"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=19.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows to configure data for Progressive Web App: Short
name, Background color, Theme color and Icon.</p>
<p>It also covers iOS Safari’s “Add to Home Screen”, which reads the
<tt class="docutils literal"><span class="pre">apple-touch-icon</span></tt> link tag rather than the manifest - without this,
an icon configured here would only ever show up on Chrome, Edge and
Android.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
Expand All @@ -395,6 +399,12 @@ <h1>Web Pwa Customize</h1>
<h2><a class="toc-backref" href="#toc-entry-1">Use Cases / Context</a></h2>
<p>The existing definitions in the old web_pwa_oca of 16.0 (not existing or
customizable in core) are maintained.</p>
<p>The settings this module adds live in the same “Progressive Web App”
block as core’s own <tt class="docutils literal">web.web_app_name</tt> field, which core hides behind
developer mode (<tt class="docutils literal">base.group_no_one</tt>). That restriction is dropped here
so the settings are actually reachable - General Settings itself already
requires admin access, so this doesn’t expose anything that wasn’t
already admin-only.</p>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-2">Configuration</a></h2>
Expand Down Expand Up @@ -425,6 +435,8 @@ <h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<li>Pedro M. Baeza</li>
</ul>
</li>
<li><a class="reference external" href="https://github.com/LadyHwesta">LadyHwesta</a> (apple-touch-icon
support, settings visibility)</li>
</ul>
</div>
<div class="section" id="maintainers">
Expand Down
70 changes: 70 additions & 0 deletions web_pwa_customize/tests/test_web_pwa_customize.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,73 @@ def test_default_get_colors(self):
)
self.assertEqual(res.get("pwa_background_color"), "#714B67")
self.assertEqual(res.get("pwa_theme_color"), "#714B67")

def test_apple_touch_icon_falls_back_without_custom_icon(self):
"""iOS Safari reads apple-touch-icon, not the manifest - with no
icon configured it should fall back to Odoo's own artwork."""
self.env["res.config.settings"].create({"pwa_icon": False}).execute()
response = self.url_open(
"/web_pwa_customize/apple_touch_icon", allow_redirects=False
)
self.assertEqual(response.status_code, 303)
# Werkzeug renders this Location either as a bare path or as an
# absolute URL depending on the environment (both are valid per
# RFC 7231) - assert on the path, not on which form it took.
self.assertTrue(
response.headers["Location"].endswith("/web/static/img/odoo-icon-ios.png")
)

def test_apple_touch_icon_redirects_to_configured_png(self):
img = Image.new("RGB", (512, 512), color="red")
img_byte_arr = io.BytesIO()
img.save(img_byte_arr, format="PNG")
icon_base64 = base64.b64encode(img_byte_arr.getvalue())
self.env["res.config.settings"].create({"pwa_icon": icon_base64}).execute()
response = self.url_open(
"/web_pwa_customize/apple_touch_icon", allow_redirects=False
)
self.assertEqual(response.status_code, 303)
self.assertTrue(
response.headers["Location"].endswith("/web_pwa_customize/icon192x192.png")
)

def test_apple_touch_icon_redirects_to_configured_svg(self):
svg_content = (
'<svg width="100" height="100">'
'<circle cx="50" cy="50" r="40" stroke="green" '
'stroke-width="4" fill="yellow" />'
"</svg>"
)
svg_base64 = base64.b64encode(svg_content.encode("utf-8"))
self.env["res.config.settings"].create({"pwa_icon": svg_base64}).execute()
response = self.url_open(
"/web_pwa_customize/apple_touch_icon", allow_redirects=False
)
self.assertEqual(response.status_code, 303)
self.assertTrue(
response.headers["Location"].endswith("/web_pwa_customize/icon.svg")
)

def test_apple_touch_icon_link_uses_our_route(self):
self.authenticate("admin", "admin")
page = self.url_open("/odoo")
self.assertIn(
'<link rel="apple-touch-icon" href="/web_pwa_customize/apple_touch_icon"/>',
page.text,
)

def test_pwa_settings_visible_without_developer_mode(self):
"""The whole block - short name, colors, icon, and core's own
web_app_name - should be visible to a plain admin, not just one
with developer mode's base.group_no_one enabled."""
admin = self.env.ref("base.user_admin")
self.assertNotIn(
self.env.ref("base.group_no_one"),
admin.group_ids,
"test assumption broken: admin already has group_no_one",
)
arch = self.env["res.config.settings"].with_user(admin).get_view()["arch"]
self.assertIn('id="pwa_settings"', arch)
self.assertIn('name="web_app_name"', arch)
self.assertIn('name="pwa_short_name"', arch)
self.assertIn('name="pwa_icon"', arch)
68 changes: 40 additions & 28 deletions web_pwa_customize/views/res_config_settings_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,46 @@
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form" />
<field name="arch" type="xml">
<block id="pwa_settings" position="inside">
<setting>
<field name="pwa_short_name" placeholder="Odoo" />
</setting>
<setting string="Colors">
<div class="w-50 row">
<span class="d-block w-75 py-2">Background Color</span>
<field
name="pwa_background_color"
class="d-block w-25 p-0 m-0"
widget="color"
/>
</div>
<div class="w-50 row mt-1">
<span class="d-block w-75 py-2">Theme Color</span>
<field
name="pwa_theme_color"
class="d-block w-25 p-0 m-0"
widget="color"
/>
</div>
</setting>
<setting string="Icon">
<div class="w-50 row">
<field name="pwa_icon" widget="image" class="oe_avatar" />
</div>
</setting>
</block>
<data>
<!-- This block (including core's own pre-existing
web_app_name field) is normally hidden unless
developer mode is on - drop that restriction so the
settings this module adds are actually reachable.
General Settings itself already requires admin
access, so this doesn't expose anything that wasn't
already admin-only. -->
<xpath expr="//block[@id='pwa_settings']" position="attributes">
<attribute name="groups" />
</xpath>
<block id="pwa_settings" position="inside">
<setting>
<field name="pwa_short_name" placeholder="Odoo" />
</setting>
<setting string="Colors">
<div class="w-50 row">
<span class="d-block w-75 py-2">Background Color</span>
<field
name="pwa_background_color"
class="d-block w-25 p-0 m-0"
widget="color"
/>
</div>
<div class="w-50 row mt-1">
<span class="d-block w-75 py-2">Theme Color</span>
<field
name="pwa_theme_color"
class="d-block w-25 p-0 m-0"
widget="color"
/>
</div>
</setting>
<setting string="Icon">
<div class="w-50 row">
<field name="pwa_icon" widget="image" class="oe_avatar" />
</div>
</setting>
</block>
</data>
</field>
</record>
</odoo>
13 changes: 13 additions & 0 deletions web_pwa_customize/views/webclient_templates.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 Tecnativa - Víctor Martínez
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<template
id="webclient_bootstrap_apple_touch_icon"
inherit_id="web.webclient_bootstrap"
>
<xpath expr="//link[@rel='apple-touch-icon']" position="attributes">
<attribute name="href">/web_pwa_customize/apple_touch_icon</attribute>
</xpath>
</template>
</odoo>
Loading