diff --git a/app/assets/images/gear.svg b/app/assets/images/gear.svg new file mode 100644 index 000000000..197da4fd9 --- /dev/null +++ b/app/assets/images/gear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/install-edge.svg b/app/assets/images/install-edge.svg new file mode 100644 index 000000000..0f1ec118a --- /dev/null +++ b/app/assets/images/install-edge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/install.svg b/app/assets/images/install.svg new file mode 100644 index 000000000..82608c765 --- /dev/null +++ b/app/assets/images/install.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/lifebuoy.svg b/app/assets/images/lifebuoy.svg new file mode 100644 index 000000000..b26ca7307 --- /dev/null +++ b/app/assets/images/lifebuoy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/sliders.svg b/app/assets/images/sliders.svg new file mode 100644 index 000000000..d84a39c1b --- /dev/null +++ b/app/assets/images/sliders.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/switch.svg b/app/assets/images/switch.svg new file mode 100644 index 000000000..2dc1f1232 --- /dev/null +++ b/app/assets/images/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/icons.css b/app/assets/stylesheets/icons.css index 7b6b2f73a..2167c28bc 100644 --- a/app/assets/stylesheets/icons.css +++ b/app/assets/stylesheets/icons.css @@ -41,10 +41,15 @@ .icon--crown { --svg: url("crown.svg "); } .icon--email { --svg: url("email.svg "); } .icon--everyone { --svg: url("everyone.svg "); } + .icon--gear { --svg: url("gear.svg "); } .icon--filter { --svg: url("filter.svg "); } .icon--globe { --svg: url("globe.svg "); } .icon--golden-ticket { --svg: url("golden-ticket.svg "); } .icon--home { --svg: url("home.svg "); } + .icon--install { --svg: url("install.svg "); } + .icon--install { --svg: url("install.svg "); } + .icon--install-edge { --svg: url("install-edge.svg "); } + .icon--lifebuoy { --svg: url("lifebuoy.svg "); } .icon--lock { --svg: url("lock.svg "); } .icon--logout { --svg: url("logout.svg "); } .icon--menu { --svg: url("menu.svg "); } @@ -72,6 +77,8 @@ .icon--search { --svg: url("search.svg "); } .icon--settings { --svg: url("settings.svg "); } .icon--share { --svg: url("share.svg "); } + .icon--sliders { --svg: url("sliders.svg "); } + .icon--switch { --svg: url("switch.svg "); } .icon--tag { --svg: url("tag.svg "); } .icon--thumb-up { --svg: url("thumb-up.svg "); } .icon--trash { --svg: url("trash.svg "); } diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index e8659a268..7aac80d0b 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -44,5 +44,53 @@ display: none; } } + + + /* Help + /* ------------------------------------------------------------------------ */ + .notifications-help { + h2 { + font-size: var(--text-medium); + margin: 0; + } + + .icon { + --icon-size: 1.2em; + + vertical-align: text-top; + } + + ol { + margin-block: var(--block-space-half) var(--block-space); + + &:last-of-type { + margin-block-end: var(--block-space-half); + } + } + } + + .notifications-help__explainer { + padding: var(--block-space); + } + + .notifications__on-message { + display: none; + + .notifications--on & { + display: block; + } + } + + .notifications__off-message { + display: block; + + .notifications--on & { + display: none; + } + } + + .notifications__status { + --panel-padding: var(--block-space); + } } diff --git a/app/assets/stylesheets/pwa.css b/app/assets/stylesheets/pwa.css new file mode 100644 index 000000000..4f30941b0 --- /dev/null +++ b/app/assets/stylesheets/pwa.css @@ -0,0 +1,14 @@ +/* PWA install */ +.pwa__instructions { + @media (display-mode: standalone) { + display: none; + } +} + +.pwa__installer { + display: none; + + .pwa--can-install & { + display: block; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 45cfd7d56..4661c391e 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -219,6 +219,18 @@ [hidden] { display: none !important; } [contents] { display: contents; } + .hide-in-pwa { + @media (display-mode: standalone) { + display: none; + } + } + + .hide-in-browser { + @media (display-mode: browser) { + display: none; + } + } + .hide-focus-ring { --focus-ring-size: 0; } diff --git a/app/javascript/controllers/notifications_controller.js b/app/javascript/controllers/notifications_controller.js index eff1f888d..11820ba86 100644 --- a/app/javascript/controllers/notifications_controller.js +++ b/app/javascript/controllers/notifications_controller.js @@ -1,14 +1,24 @@ import { Controller } from "@hotwired/stimulus" import { post } from "@rails/request.js" -import { pageIsTurboPreview } from "helpers/turbo_helpers" -import { onNextEventLoopTick } from "helpers/timing_helpers" export default class extends Controller { + static classes = [ "enabled" ] + static targets = [ "subscribeButton", "explainer" ] static values = { subscriptionsUrl: String } - - async connect() { - const state = await this.getCurrentState() - this.updateUI(state) + async connect() { + if (this.#allowed && Notification.permission == "default") { + this.subscribeButtonTarget.hidden = false + this.explainerTarget.hidden = true + } + + if (this.#allowed && Notification.permission == "granted") { + const registration = await this.#getServiceWorkerRegistration() + const subscription = await registration?.pushManager?.getSubscription() + + if (registration && subscription) { + this.element.classList.add(this.enabledClass) + } + } } async attemptToSubscribe() { @@ -20,8 +30,6 @@ export default class extends Controller { case "granted": { this.#subscribe(registration); break } case "default": { this.#requestPermissionAndSubscribe(registration) } } - } else { - console.log("Should display not-allowed notice") } } @@ -31,9 +39,6 @@ export default class extends Controller { const existingSubscription = await registration?.pushManager?.getSubscription() return Notification.permission == "granted" && registration && existingSubscription - } else { - console.log("Not alllowed") - return false } } @@ -59,7 +64,12 @@ export default class extends Controller { async #syncPushSubscription(subscription) { const response = await post(this.subscriptionsUrlValue, { body: this.#extractJsonPayloadAsString(subscription), responseKind: "turbo-stream" }) - if (!response.ok) subscription.unsubscribe() + if (response.ok) { + this.element.classList.add(this.enabledClass) + this.subscribeButtonTarget.hidden = true + } else { + subscription.unsubscribe() + } } async #requestPermissionAndSubscribe(registration) { @@ -91,35 +101,4 @@ export default class extends Controller { return outputArray } - - updateUI(state) { - console.log(state) - } - - async getCurrentState() { - if (!this.#allowed) { - return { status: 'not-supported' } - } - - const registration = await this.#getServiceWorkerRegistration() - const subscription = await registration?.pushManager?.getSubscription() - - return { - permission: Notification.permission, - hasRegistration: !!registration, - hasSubscription: !!subscription, - status: this.#determineStatus(Notification.permission, registration, subscription) - } - } - - #determineStatus(permission, registration, subscription) { - if (permission === "denied") return "permission-denied" - if (permission === "default") return "permission-not-requested" - if (permission === "granted") { - if (!registration) return "service-worker-missing" - if (!subscription) return "subscription-missing" - return "enabled" - } - return "unknown" - } } diff --git a/app/models/application_platform.rb b/app/models/application_platform.rb index c8c24a50b..500f4adc6 100644 --- a/app/models/application_platform.rb +++ b/app/models/application_platform.rb @@ -15,6 +15,14 @@ class ApplicationPlatform < PlatformAgent user_agent.browser.match? /Chrome/ end + def edge? + user_agent.browser.match? /Edg/ + end + + def firefox? + user_agent.browser.match? /Firefox|FxiOS/ + end + def safari? user_agent.browser.match? /Safari/ end @@ -26,4 +34,21 @@ class ApplicationPlatform < PlatformAgent def desktop? !mobile? end + + def windows? + operating_system == "Windows" + end + + def operating_system + case user_agent.platform + when /Android/ then "Android" + when /iPad/ then "iPad" + when /iPhone/ then "iPhone" + when /Macintosh/ then "macOS" + when /Windows/ then "Windows" + when /CrOS/ then "ChromeOS" + else + os =~ /Linux/ ? "Linux" : os + end + end end diff --git a/app/views/notifications/settings/_browser.html.erb b/app/views/notifications/settings/_browser.html.erb new file mode 100644 index 000000000..4662c558b --- /dev/null +++ b/app/views/notifications/settings/_browser.html.erb @@ -0,0 +1,109 @@ +<% unless (platform.safari? || platform.chrome?) && platform.ios? %> +
Ensure notifications are enabled for <%= root_url %> in your web browser settings.
+ <% end %> +