feat(ao-vivo): avisa bateria baixa e mostra o ultimo sinal do celular

- aviso no painel quando a bateria do celular chega a 15% e a 5% sem
  carregador (uma vez cada; volta a valer depois de carregar)
- selo do celular fica vermelho com bateria baixa
- celular offline mostra ha quanto tempo foi o ultimo sinal e com quanta
  bateria; abaixo de 10% sugere que a bateria acabou

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 02:24:33 -03:00
co-authored by Claude Opus 5.5
parent 69018c8b36
commit f45e05ab81
4 changed files with 70 additions and 14 deletions
+28 -7
View File
@@ -5,15 +5,19 @@
// Cartão do aparelho com o que o próprio app informa (IP, modelo, Android, versão, Wi-Fi, bateria).
const s = $derived(estado.app?.estado ?? {});
const ap = $derived(s.aparelho ?? null);
const bateria = $derived.by(() => {
const b = s.bateria;
const n = typeof b === 'number' ? b : b?.nivel;
return typeof n === 'number' ? Math.round(n <= 1 ? n * 100 : n) : null;
const bateria = $derived(estado.bateria.nivel);
const carregando = $derived(estado.bateria.carregando);
// Tempo desde o último sinal, em texto curto ("há 3 min").
const semSinal = $derived.by(() => {
if (!estado.ultimoSinalEm) return '';
const seg = Math.max(0, Math.round((estado.agora - estado.ultimoSinalEm) / 1000));
if (seg < 60) return `há ${seg} s`;
if (seg < 3600) return `há ${Math.round(seg / 60)} min`;
if (seg < 86400) return `há ${Math.round(seg / 3600)} h`;
return `há ${Math.round(seg / 86400)} dia(s)`;
});
const carregando = $derived(typeof s.bateria === 'object' && !!s.bateria?.carregando);
const IconeBateria = $derived(carregando ? BatteryCharging : bateria === null ? BatteryMedium : bateria < 20 ? BatteryLow : bateria < 70 ? BatteryMedium : BatteryFull);
const ip = $derived(ap?.ip || estado.app?.celular_ip || '—');
const visto = $derived(estado.app?.idade ?? null);
</script>
<div class="celular" class:offline={!estado.celularOnline}>
@@ -25,7 +29,7 @@
{#if estado.celularOnline}
{[ap?.android && 'Android ' + ap.android, ap?.app && 'app ' + ap.app].filter(Boolean).join(' · ') || 'online'}
{:else}
offline{visto !== null ? ` · visto há ${Math.round(visto)} s` : ''}
offline{semSinal ? ' · último sinal ' + semSinal : ''}
{/if}
</span>
</div>
@@ -70,6 +74,12 @@
</div>
</dl>
{:else}
{#if bateria !== null && estado.ultimoSinalEm}
<p class="ultimo" class:baixa={bateria <= 15 && !carregando}>
<IconeBateria size={18} /> Último sinal com {bateria}%{carregando ? ', carregando' : ''}.
{#if bateria <= 10 && !carregando}Provavelmente a bateria acabou: ligue o carregador e abra o app.{/if}
</p>
{/if}
<p class="ajuda">Abra o app Carinha no celular e confira se ele aponta para este servidor (toque longo › Mudar o servidor).</p>
{/if}
</div>
@@ -170,6 +180,17 @@
rect.cheia {
fill: var(--texto);
}
.ultimo {
display: flex;
align-items: center;
gap: var(--e-2);
flex-wrap: wrap;
font-size: var(--t-sm);
font-weight: 500;
}
.ultimo.baixa {
color: var(--perigo);
}
.ajuda {
font-size: var(--t-sm);
color: var(--texto-3);