PWA widgets dashboard, lock screen, screensaver with active category
- Dashboard (/dashboard): configurable widget system with 6 widget types (current_tasks, category_time, today_progress, next_task, motivace, calendar_mini) stored in localStorage widget_config - Lock screen (/lockscreen): fullscreen with clock, active group badge, up to 4 current tasks, gradient from group color, swipe/tap to unlock - InactivityMonitor: auto-redirect to lockscreen after configurable timeout (only in PWA standalone/fullscreen mode) - Settings: widget toggle switches, inactivity timeout slider, lockscreen preview - manifest.json: added display_override ["fullscreen","standalone"] + orientation portrait Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
apps/tasks/components/InactivityMonitor.tsx
Normal file
54
apps/tasks/components/InactivityMonitor.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
|
||||
export default function InactivityMonitor() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// Only activate in PWA standalone mode
|
||||
const isStandalone =
|
||||
window.matchMedia("(display-mode: standalone)").matches ||
|
||||
window.matchMedia("(display-mode: fullscreen)").matches ||
|
||||
(window.navigator as unknown as { standalone?: boolean }).standalone === true;
|
||||
if (!isStandalone) return;
|
||||
|
||||
// Skip on lockscreen/login pages
|
||||
if (pathname?.startsWith("/lockscreen") || pathname?.startsWith("/login") || pathname?.startsWith("/register")) return;
|
||||
|
||||
// Read timeout from localStorage
|
||||
let timeoutMinutes = 5;
|
||||
try {
|
||||
const stored = localStorage.getItem("widget_config");
|
||||
if (stored) {
|
||||
const cfg = JSON.parse(stored);
|
||||
if (cfg.inactivityTimeout > 0) timeoutMinutes = cfg.inactivityTimeout;
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
const timeoutMs = timeoutMinutes * 60 * 1000;
|
||||
|
||||
function resetTimer() {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => {
|
||||
router.push("/lockscreen");
|
||||
}, timeoutMs);
|
||||
}
|
||||
|
||||
const events = ["mousedown", "mousemove", "keydown", "touchstart", "scroll", "click"];
|
||||
events.forEach(e => window.addEventListener(e, resetTimer, { passive: true }));
|
||||
resetTimer();
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
events.forEach(e => window.removeEventListener(e, resetTimer));
|
||||
};
|
||||
}, [pathname, router]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user