Skip to content

✨ add UserAttentionService to gate background pollers on window visibility#4406

Merged
guiyanakuang merged 1 commit into
mainfrom
feat/issue-4405-user-attention-service
May 15, 2026
Merged

✨ add UserAttentionService to gate background pollers on window visibility#4406
guiyanakuang merged 1 commit into
mainfrom
feat/issue-4405-user-attention-service

Conversation

@guiyanakuang
Copy link
Copy Markdown
Member

Closes #4405

Summary

  • New UserAttentionService (commonMain) exposes per-surface visibility as StateFlow<Boolean> — main window and search window. Comes with two helpers: attentionOn(vararg AttentionSurface) to combine surfaces, and CoroutineScope.launchWhileAttentive(...) to drive polling loops gated on attention.
  • Desktop binds DesktopUserAttentionService (wraps DesktopAppWindowManager); headless binds HeadlessUserAttentionService (permanently false).
  • DesktopNetworkProfileService is the first migration: the 60s Windows firewall scan now only runs while the main window is visible. On window-show the helper fires runDetection() immediately, so the user sees fresh diagnosis the moment they open the window — not after waiting a poll interval.

Design notes

  • Opt-in per service. SyncPollingManager / CleanScheduler are not migrated — they need to run regardless of UI state. The abstraction is here for the pollers that only matter when a user can see the result.
  • Per-surface granularity. Future detectors can subscribe to MAIN_WINDOW only, SEARCH_WINDOW only, or both via attentionOn(MAIN_WINDOW, SEARCH_WINDOW). Network detection only renders warnings in the main window so it gates on MAIN_WINDOW.
  • Bubble window is not exposed. No consumer needs it; add when one does.
  • WindowInfo.show is the app's logical show state, so minimized windows still count as visible. If we later decide minimized should suspend polling, change the mapping in DesktopUserAttentionService only — consumers don't need to change.
  • Headless wins a small efficiency for free: the always-false flows mean runDetection() is never invoked in headless mode, without explicit if (headless) checks scattered through services.
  • runOnResume = true is the default so a user opening the main window sees a fresh diagnosis immediately, not after a 60s wait.

Files

  • app/src/commonMain/kotlin/com/crosspaste/app/UserAttentionService.kt — new
  • app/src/desktopMain/kotlin/com/crosspaste/app/DesktopUserAttentionService.kt — new
  • app/src/desktopMain/kotlin/com/crosspaste/headless/HeadlessUserAttentionService.kt — new
  • app/src/desktopMain/kotlin/com/crosspaste/DesktopUiModule.kt — bind UserAttentionService to desktop impl
  • app/src/desktopMain/kotlin/com/crosspaste/headless/HeadlessModule.kt — bind to headless impl
  • app/src/desktopMain/kotlin/com/crosspaste/DesktopNetworkModule.kt — pass new dep into DesktopNetworkProfileService
  • app/src/desktopMain/kotlin/com/crosspaste/net/DesktopNetworkProfileService.kt — migrate to launchWhileAttentive

Test plan

  • ./gradlew app:desktopTest — all desktop tests pass.
  • Manual on Windows: open main window → confirm firewall check fires once immediately, then every 60s; close window → confirm no further runDetection log lines until window reopens.
  • Manual on Windows headless install: confirm no Windows firewall COM calls happen at all (no "queryProfile" / "queryMDnsAllowed" log lines).
  • Visually confirm that on first launch (main window auto-shows), the diagnosis & menu-bar warning entry populate within ~3s of startup as before.

…ility

Introduces an abstract UserAttentionService in commonMain that exposes
per-surface visibility (main / search window) as StateFlows, plus two
helpers:

- attentionOn(vararg AttentionSurface) — OR-combines the requested
  surfaces into a single Flow<Boolean>.
- CoroutineScope.launchWhileAttentive(attention, interval, runOnResume)
  — drives a polling loop only while attention is true; cancels cleanly
  on flip-false; re-fires immediately on resume so the user sees fresh
  state the moment they open the relevant window.

Desktop binds DesktopUserAttentionService backed by
DesktopAppWindowManager's mainWindowInfo / searchWindowInfo. Headless
binds HeadlessUserAttentionService whose flows are permanently false,
which naturally suppresses gated pollers in headless installs without
explicit checks.

DesktopNetworkProfileService is the first migration: its 60s Windows
firewall scan now only runs while the main window is visible (the only
surface that renders the warning dialog and the menu-bar warning
entry). The 3s startup delay is preserved.

Polling services that must keep running regardless of UI (sync,
cleanup) intentionally do not subscribe — the abstraction is opt-in.
@guiyanakuang guiyanakuang merged commit 8e634c7 into main May 15, 2026
5 checks passed
@guiyanakuang guiyanakuang deleted the feat/issue-4405-user-attention-service branch May 15, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background pollers waste work when no user-facing surface is visible

1 participant