Methods

(static) applyOverhangUserDropdown(showDropdown) → {void}

Show the logged-in user in the overhang dropdown, or clear it to hide the dropdown. The name comes from the global user (the same value every post-login screen used to set imperatively), so a view only declares WHETHER the dropdown shows via shouldShowUserDropdown.

Parameters:
NameTypeDescription
showDropdownboolean
Returns:
Type: 
void

(static) cancelDeepLinkResolve() → {void}

Abort an in-flight deep-link/cast resolve (the metadata fetch that runs BEFORE navigation): unobserve + stop + drop the task, clear its args, hide the spinner. Safe to call when none is pending. Reused by the back arbiter (a back during the resolve aborts the cast) and resetRouter (sign-out tears the resolve down with the router).

Returns:
Type: 
void

(static) init() → {void}

Returns:
Type: 
void

(static) initRouter() → {void}

Initialize the router (outlet + routes + observers) WITHOUT navigating. Idempotent: a no-op if a router already exists on the scene. Called lazily by routerNavigate (main thread) so the very first navigation — pre-login OR Home — brings the router up. Re-callable after resetRouter() (sign-out → re-login): sgRouter.initialize creates a fresh router when none exists on the scene.

The router hosts the FULL app — the pre-login flow (/server, /users, /login) as well as the post-login content/playback routes. The pre-login routes carry no canActivate guards (the redirect target /login is one of them); the post-login routes carry the AuthManager guard. Pre-login back transitions are coordinator-driven (loginRouter), not router-history-driven, because cold start can enter directly at /users with no /server in history.

Returns:
Type: 
void

(static) isRouterNavigating() → {boolean}

Back-arbiter helper: is the router mid-navigation right now? Reads the public routerState field DIRECTLY (no observer). The routerState observer coalesces rapid writes and reliably drops the terminal NavigationEnd (proven on device), so a mirrored flag wedges true; a field READ never coalesces, so the field always holds the true latest state. A navigation runs through NavigationStart → … → NavigationEnd; any non-terminal type means a nav is in flight, a terminal type (or no router/state yet) means idle.

Returns:
Type: 
boolean

(static) navigateChainStep(routes, index, clearLoginSpinnerOnEndopt) → {void}

Navigate routes[index], then chain to the next step once it settles (sgRouter's navigateTo promise resolves at NavigationEnd, so each .then() defers until the previous route is fully mounted). The final step takes remote focus. Context carries routes/index (BrightScript closures can't capture locals). clearLoginSpinnerOnEnd: threaded through to the FINAL step's navigateThenFocus so the login replay (replayRoutedDeepLink) keeps the blocking spinner up across the whole chain and clears it only once the last route mounts. The runtime-cast caller (replayDeepLinkReplacingPlayer) leaves it false — no login spinner is up there.

Parameters:
NameTypeAttributesDefaultDescription
routesobject
indexinteger
clearLoginSpinnerOnEndboolean<optional>
false
Returns:
Type: 
void

(static) navigateThenFocus(routePath, contextopt, clearSpinneropt) → {void}

Navigate + take focus on settle + surface a rejected nav — the shared tail centralizing the navigate/focus/catch trio that was hand-copied across routerNavigate, navigateChainStep, and the settle drain (folds in finding A4: a rejected nav must re-assert focus, never strand the remote). routePath is dynamic: a string path for most calls, or a named-route AA ({ name, ... }) from routeForItem on the deep-link container path. sgRouter.navigateTo accepts both. clearSpinner: set by the login paths only. A blocking login spinner (started by the login coordinator on the OUTGOING pre-login view) is kept up across this async nav and cleared HERE, once the destination view has mounted (navigateTo resolves at NavigationEnd). Clearing it synchronously before the nav settles re-shows the outgoing pre-login view for a frame — the #677 login flash. Mirrors the ItemDetails -> player pattern (spinner stays up across the nav, the destination clears it).

Parameters:
NameTypeAttributesDefaultDescription
routePathdynamic
contextobject<optional>
{}
clearSpinnerboolean<optional>
false
Returns:
Type: 
void

(static) onActiveRoutedViewChanged() → {void}

The router mounted (or switched to) a different content view. Re-point the overhang controller: tear down the binding to the previous view, bind the new one. (Lifted from SceneManager's register/unregister pair — same contract.)

Returns:
Type: 
void

(static) onBackgroundImageUriChanged() → {void}

Returns:
Type: 
void

(static) onDeepLinkResolved() → {void}

Returns:
Type: 
void

(static) onIsLoadingChanged() → {void}

Triggered when the isLoading boolean component field is changed

Returns:
Type: 
void

(static) onKeyEvent(key, press) → {boolean}

Parameters:
NameTypeDescription
keystring
pressboolean
Returns:
Type: 
boolean

(static) onLoadingTextChanged() → {void}

Returns:
Type: 
void

(static) onOverhangTabSelected() → {void}

Proxy overhang tab selection back to the active routed view's selectedTabId.

Returns:
Type: 
void

(static) onPhotoLaunchRequested() → {void}

A photo launcher (quickplay.photo / QueueManager slideshow output) signalled a photo-viewer launch. Carry the launch AA through as route context — PhotoDetails reads itemsNode/itemsArray + slideshow flags + start index from it on mount.

Returns:
Type: 
void

(static) onPlaybackLaunchRequested() → {void}

QueueManager.playQueue signalled a video playback launch. The queue is already populated, so navigate to the play route — PlayerHostView mounts the player for the current queue item. :type/:id give the route a deep-link identity; the queue is the source of truth for what actually plays.

Returns:
Type: 
void

(static) onTestToast() → {void}

DEBUG: Triggered when testToast field is set from the BrightScript console. Format: "type|message" where type is "error", "success", "warning", or "info". Falls back to "error" type if no pipe delimiter is found.

Usage from BrightScript console (port 8085): m.top.getScene().testToast = "error|Something went wrong" m.top.getScene().testToast = "success|Item saved" m.top.getScene().testToast = "info|Loading filters..." m.top.getScene().testToast = "Just a message"

Returns:
Type: 
void

(static) refreshBackdropSetting() → {void}

Re-evaluate and apply the backdrop visibility setting Useful when settings change at runtime (e.g., from Settings screen)

Returns:
Type: 
void

(static) registerOverhangData(group) → {void}


Overhang controller. Drives the shared overhang from the router-active view's overhang fields, preserving the tabs-before-title ordering (prevents a title→tab render flash) and the bidirectional selectedTabId write-back (overhang tab tap → view.selectedTabId).

Parameters:
NameTypeDescription
groupobject
Returns:
Type: 
void

(static) reloadRoutedHome() → {void}

Theme/locale change: force a fresh Home render through the router. Called from main.bs's reloadHomeRequested handler (replaces the old clearScenes + createAndShowHomeGroup). Navigating to "/" from Settings (the active routed view) is a component change, so clearStackOnResolve rebuilds Home from scratch — it picks up the new theme constants / reloaded translations.

Returns:
Type: 
void

(static) replayDeepLinkReplacingPlayer(targetRoute) → {void}

A deep link arrived while a media player is active. Two steps:

  1. tear the player down synchronously (stop + report to the server) so nothing keeps decoding;
  2. navigate Home, then to the new content, via the reliable navigateTo PROMISE chain (navigateChainStep). Navigating "/" tears the player host down (Home is clearStackOnResolve, so the stack — player host included — is cleared and Home re-mounts) and resolves at its NavigationEnd; the chain then mounts the target, which auto-launches. Back lands Player → Details → Home, the intended deep-link shape, with no stale prior item left behind.

We do NOT pop via goBack + a routerState-observer settle wait here (the old ADR-0020 primitive): goBack returns a bare Boolean (no promise to chain), and the routerState observer coalesces and reliably drops the terminal NavigationEnd, so the settle drain never fired and the cast stranded (proven on device). navigateChainStep rides navigateTo's promise — which resolves reliably via the router's internal chain, NOT the coalescing field observer — exactly as post-login deep-link replay already does.

Parameters:
NameTypeDescription
targetRoutestring
Returns:
Type: 
void

Replay a (deferred) deep link after login by navigating a SEQUENCE of routes, each step waiting for the previous to settle. Called from the main-thread replay helper (source/replayRoute.bs) via callFunc; routes is an ordered array of path strings. Used both for the no-deep-link case (["/"], a plain Home nav) and the deep-link case (["/", "/details/:type/:id", "/details/:type/:id/play"]) so back lands Player -> Details -> Home (decision #3). The queue for a /play step is populated by the stash producer (replayRoute / the deep-link handlers) BEFORE this runs; PlayerHostView reads it on mount.

Parameters:
NameTypeDescription
routesobject
Returns:
Type: 
void

(static) resetRouter() → {void}

Sign-out / change-user / change-server: tear down the whole routed stack before main.bs re-enters the routed pre-login flow (loginRouter). Without this the routed views would linger and leak. initRouter() re-initializes a fresh router on the next successful login.

Returns:
Type: 
void

VALIDATE a deep-link id (a metadata fetch) BEFORE navigating, so an invalid id never disturbs the active session — it just toasts. args = { itemId, action, isPlayback, homeFirst, replacePlayer, detailsRoute } from source/replayRoute.bs. homeFirst (post-login/cold): land on Home first so an invalid id toasts on Home (good UX). On a valid id, onDeepLinkResolved navigates (replacing an active player for a playback cast).

Parameters:
NameTypeDescription
argsobject
Returns:
Type: 
void

(static) routerGoBack() → {void}

Render-thread sgRouter.goBack() wrapper for main.bs, which runs on the main thread and can't call the sgrouter namespace directly (it resolves the router via m.top's scene). Used after a delete confirmation to leave the now-deleted routed detail.

Returns:
Type: 
void

(static) routerNavigate(routePath, contextopt, clearSpinneropt) → {void}

Navigate the router to a route, bringing the router up first if needed, then hand the mounted view remote focus. Called from main.bs / loginRouter (main thread) to drive BOTH the pre-login flow (/server, /users, /login) and the transition to Home ("/"). context is forwarded as route context (e.g. a prefilled username for /login).

Parameters:
NameTypeAttributesDefaultDescription
routePathstring
contextobject<optional>
{}
clearSpinnerboolean<optional>
false
Returns:
Type: 
void

(static) setBackgroundImage(uri, isAnimatedopt, forceBackdropopt) → {void}

Set the background image with animation control

Parameters:
NameTypeAttributesDefaultDescription
uristring

The image URI to display

isAnimatedboolean<optional>
true

Whether to animate the transition

forceBackdropboolean<optional>
false

Force show backdrop regardless of user setting (used for login splashscreen)

Returns:
Type: 
void

(static) showExitConfirmation() → {void}

Confirm app exit. Reuses SceneManager's confirmation dialog + the existing isPendingExitConfirmation contract that main.bs's isDataReturned handler reads to set m.scene.exit on confirm — unchanged from the old popScene stack<=1 branch.

Returns:
Type: 
void

(static) showToast(message, toastTypeopt) → {void}

showToast: Display a transient toast notification.

Parameters:
NameTypeAttributesDefaultDescription
messagestring

The message to display

toastTypestring<optional>
"error"

"error", "success", or "info"

Returns:
Type: 
void

(static) stopDeepLinkResolveTask() → {void}

Release the in-flight deep-link/cast resolve task: drop its observer, stop it, clear the ref. Safe to call when none is pending. Centralizes the unobserve->stop->invalid sequence so every teardown path (resolveDeepLink replace, cancelDeepLinkResolve, onDeepLinkResolved) stays consistent — a stop without a preceding unobserve can leave a stale observer firing.

Returns:
Type: 
void

(static) teardownRoutedViews() → {void}

Run beforeViewClose (→ onScreenHidden + onDestroy) on every mounted routed view — the active one in viewTarget and every suspended keepAlive view in keepAliveViewTarget — before the router is destroyed under them. The target Groups live inside the outlet and survive destroy(); only their children (the views) are torn down here. beforeViewClose is the same permanent-close entry sgRouter drives on a normal close, so re-running onScreenHidden on an already-suspended keepAlive view matches the existing contract (suspend already fired it once; it must be idempotent).

Returns:
Type: 
void

(static) unregisterOverhangData(group) → {void}

Parameters:
NameTypeDescription
groupobject
Returns:
Type: 
void

(static) updateOverhangIcons(msg) → {void}

Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void
Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void

(static) updateOverhangTabs(msg) → {void}

Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void

(static) updateOverhangTitle(msg) → {void}

Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void

(static) updateOverhangUserDropdown(msg) → {void}

Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void

(static) updateOverhangVisible(msg) → {void}

Parameters:
NameTypeDescription
msgdynamic
Returns:
Type: 
void