Files
tavolo/web/src/components/notify.rs
T
woggioni 7d67eccca2 Notify when another player plays a card
Add an opt-in bell to the game HUD: the first click asks for the
browser's notification permission, and the choice is remembered per
device. A play by any other seat then raises a desktop notification
with the player's name, the card and the next player, while the page
is in the background and the viewer is actually seated in the game.

Detect the newest play across the game families: scopone and scopa
report it in last_move, while briscola and briscola chiamata only
expose the trick, which mid-trick or once completed ends with the
card just played. The first snapshot after every (re)connection is a
resync baseline, so reconnects and the initial load never replay old
moves; so are the viewer's own plays and the server's auto-plays.

Deliver through the service worker registration (installed PWAs on
Android reject page-context notifications), falling back to a page
notification when no worker is active, under a per-game tag that
replaces the previous move. sw.js now focuses the game window when a
notification is clicked.
2026-10-15 10:34:14 +00:00

81 lines
2.8 KiB
Rust

//! HUD bell for desktop notifications about the other players' moves.
use std::cell::Cell;
use std::rc::Rc;
use sycamore::prelude::*;
use wasm_bindgen_futures::spawn_local;
use crate::notify::{self, Permission};
/// Notification state shared by the game page and its table views: the
/// opt-in flag and the browser permission, both page-scoped so a
/// permission prompt that resolves after a table re-render still lands
/// somewhere safe. `alive` mirrors the game page's unmount flag.
#[derive(Clone)]
pub struct NotifyCtx {
pub enabled: Signal<bool>,
pub permission: Signal<Permission>,
pub alive: Rc<Cell<bool>>,
}
/// The caption for the current (enabled, permission) state, and whether
/// the button is a dead end (the user must change browser settings).
fn face(enabled: bool, permission: Permission) -> (&'static str, bool) {
match (enabled, permission) {
(true, _) => ("🔔 Notifications on", false),
(false, Permission::Granted | Permission::Default) => ("🔔 Notify me", false),
(false, Permission::Denied | Permission::Unsupported) => {
("🔔 Notifications blocked", true)
}
}
}
/// The move-notification bell, rendered in the HUD of seated players.
///
/// The preference is global (all games) and persisted per device. The
/// permission prompt fires on the first click: browsers require a user
/// gesture, and asking on page load would be ignored.
pub fn notify_toggle(ctx: NotifyCtx) -> View {
let NotifyCtx { enabled, permission, alive } = ctx;
let on_click = move |_| {
if enabled.get() {
enabled.set(false);
notify::set_enabled(false);
return;
}
match permission.get() {
Permission::Default => {
let alive = alive.clone();
spawn_local(async move {
let granted = notify::request_permission().await;
if !alive.get() {
return;
}
permission.set(granted);
if granted == Permission::Granted {
enabled.set(true);
notify::set_enabled(true);
}
})
}
Permission::Granted => {
enabled.set(true);
notify::set_enabled(true);
}
Permission::Denied | Permission::Unsupported => {}
}
};
view! {
button(
class="btn btn-outline-light",
disabled=move || face(enabled.get(), permission.get()).1,
title="Desktop notification when another player plays a card, \
while this tab is in the background",
on:click=on_click,
) {
(move || face(enabled.get(), permission.get()).0)
}
}
}