judu/ex-egopublic Fork
⑂ 97afc3b
Commits
⬇ Clone ▾
git clone https://git.rickub.com/judu/ex-ego.git
git clone ssh://git@rickub.com/judu/ex-ego.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat(timer): pastille sur l'icône (Badging API) tant qu'un timer ou une mesure existeUnverified

Julien Durillon committed 2026-08-08T13:38:12+02:00 Browse files
97afc3b parent: ea3b762
modified src/render.rs +3 -0
@@ -61,4 +61,7 @@ pub struct Shell {
6161 /// Mes équipes pour le sélecteur de la pill d'en-tête — vide si je n'en
6262 /// ai qu'une (pill inactive, pas de sheet).
6363 pub teams: Vec<TeamChoiceView>,
64+ /// Pastille sur l'icône de l'app (Badging API) tant qu'un timer ou une
65+ /// mesure existe — indépendant de l'affichage de la pill flottante.
66+ pub badge: bool,
6467 }
@@ -61,4 +61,7 @@ pub struct Shell {
61 /// Mes équipes pour le sélecteur de la pill d'en-tête — vide si je n'en61 /// Mes équipes pour le sélecteur de la pill d'en-tête — vide si je n'en
62 /// ai qu'une (pill inactive, pas de sheet).62 /// ai qu'une (pill inactive, pas de sheet).
63 pub teams: Vec<TeamChoiceView>,63 pub teams: Vec<TeamChoiceView>,
64+ /// Pastille sur l'icône de l'app (Badging API) tant qu'un timer ou une
65+ /// mesure existe — indépendant de l'affichage de la pill flottante.
66+ pub badge: bool,
64 }67 }
modified src/routes/mod.rs +4 -1
@@ -122,7 +122,9 @@ pub async fn shell(
122122 .await?
123123 .unwrap_or_default();
124124 let invite_count = invitations::count_for(&state.pool, ctx.user.id).await?;
125- let timer = timers::get(&state.pool, ctx.user.id).await?.map(|t| {
125+ let timer_row = timers::get(&state.pool, ctx.user.id).await?;
126+ let badge = timer_row.is_some();
127+ let timer = timer_row.map(|t| {
126128 let foreign = t.team_id != ctx.team_id;
127129 match t.measured_minutes() {
128130 Some(m) => TimerView {
@@ -165,6 +167,7 @@ pub async fn shell(
165167 },
166168 timer,
167169 show_start: false,
170+ badge,
168171 teams: {
169172 let mine = teams_db::teams_of(&state.pool, ctx.user.id).await?;
170173 if mine.len() > 1 {
@@ -122,7 +122,9 @@ pub async fn shell(
122 .await?122 .await?
123 .unwrap_or_default();123 .unwrap_or_default();
124 let invite_count = invitations::count_for(&state.pool, ctx.user.id).await?;124 let invite_count = invitations::count_for(&state.pool, ctx.user.id).await?;
125- let timer = timers::get(&state.pool, ctx.user.id).await?.map(|t| {125+ let timer_row = timers::get(&state.pool, ctx.user.id).await?;
126+ let badge = timer_row.is_some();
127+ let timer = timer_row.map(|t| {
126 let foreign = t.team_id != ctx.team_id;128 let foreign = t.team_id != ctx.team_id;
127 match t.measured_minutes() {129 match t.measured_minutes() {
128 Some(m) => TimerView {130 Some(m) => TimerView {
@@ -165,6 +167,7 @@ pub async fn shell(
165 },167 },
166 timer,168 timer,
167 show_start: false,169 show_start: false,
170+ badge,
168 teams: {171 teams: {
169 let mine = teams_db::teams_of(&state.pool, ctx.user.id).await?;172 let mine = teams_db::teams_of(&state.pool, ctx.user.id).await?;
170 if mine.len() > 1 {173 if mine.len() > 1 {
modified templates/app.html +14 -0
@@ -110,6 +110,20 @@
110110 </a>
111111 </nav>
112112 <script>
113+// Pastille sur l'icône de l'app tant qu'un timer ou une mesure existe.
114+// iOS exige la permission notifications : demandée une seule fois, dans le
115+// geste « Lancer le minuteur » (Safari refuse les demandes hors interaction).
116+(function () {
117+ if (!('setAppBadge' in navigator)) return;
118+ navigator.{% if shell.badge %}setAppBadge(1){% else %}clearAppBadge(){% endif %}.catch(function () {});
119+ var start = document.querySelector('form[action="/timer/demarrer"]');
120+ if (start && 'Notification' in window && Notification.permission === 'default') {
121+ start.addEventListener('submit', function (e) {
122+ e.preventDefault();
123+ Notification.requestPermission().catch(function () {}).finally(function () { start.submit(); });
124+ }, { once: true });
125+ }
126+})();
113127 // Amélioration progressive : le temps écoulé rendu par le serveur reste
114128 // juste sans JS, ceci le rafraîchit toutes les 10 s sans recharger.
115129 (function () {
@@ -110,6 +110,20 @@
110 </a>110 </a>
111 </nav>111 </nav>
112 <script>112 <script>
113+// Pastille sur l'icône de l'app tant qu'un timer ou une mesure existe.
114+// iOS exige la permission notifications : demandée une seule fois, dans le
115+// geste « Lancer le minuteur » (Safari refuse les demandes hors interaction).
116+(function () {
117+ if (!('setAppBadge' in navigator)) return;
118+ navigator.{% if shell.badge %}setAppBadge(1){% else %}clearAppBadge(){% endif %}.catch(function () {});
119+ var start = document.querySelector('form[action="/timer/demarrer"]');
120+ if (start && 'Notification' in window && Notification.permission === 'default') {
121+ start.addEventListener('submit', function (e) {
122+ e.preventDefault();
123+ Notification.requestPermission().catch(function () {}).finally(function () { start.submit(); });
124+ }, { once: true });
125+ }
126+})();
113 // Amélioration progressive : le temps écoulé rendu par le serveur reste127 // Amélioration progressive : le temps écoulé rendu par le serveur reste
114 // juste sans JS, ceci le rafraîchit toutes les 10 s sans recharger.128 // juste sans JS, ceci le rafraîchit toutes les 10 s sans recharger.
115 (function () {129 (function () {