components_data_SceneManager.bs

import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/translationKeys.bs"
import "pkg:/source/utils/misc.bs"
import "pkg:/source/utils/translate.bs"

sub init()
  m.log = new log.Logger("SceneManager")
  m.scene = m.top.getScene()
  m.overhang = m.scene.findNode("overhang")
end sub

' SceneManager is purely a shared SERVICE node — dialogs, toast/spinner, backdrop, theme, and
' the overhang user/clock passthroughs below. The router host (JRScene) owns navigation and its
' own overhang controller; there is no scene stack (pushScene / popScene / getActiveScene etc.).

' Signal that home screen should be reloaded (used after theme color changes)
' Main.bs observes reloadHomeRequested field and handles the actual reload
sub reloadHome()
  m.top.reloadHomeRequested = true
end sub

' Update username in overhang
sub updateUser()
  ' Passthrough to overhang
  if isValid(m.overhang) then m.overhang.currentUser = m.top.currentUser
end sub

' Reset time
sub resetTime()
  ' Passthrough to overhang
  m.overhang.callFunc("resetTime")
end sub

' Set the background image
' @param {string} uri - The image URI to display
' @param {boolean} isAnimated - Whether to animate the transition
' @param {boolean} forceBackdrop - Force show backdrop regardless of user setting (used for login splashscreen)
sub setBackgroundImage(uri as string, isAnimated = true as boolean, forceBackdrop = false as boolean)
  m.log.info("SceneManager.setBackgroundImage called", uri, isAnimated, forceBackdrop)
  ' Passthrough to scene via callFunc
  if isValid(m.scene)
    m.scene.callFunc("setBackgroundImage", uri, isAnimated, forceBackdrop)
  else
    m.log.error("SceneManager.setBackgroundImage: m.scene is invalid!")
  end if
end sub

' Display dialog to user with an OK button
sub userMessage(title as string, message as string)
  dialog = createObject("roSGNode", "StandardMessageDialog")
  dialog.title = title
  ' StandardMessageDialog.message is an array-of-strings field; assigning a
  ' bare string fails silently and renders an empty body.
  dialog.message = [message]
  dialog.buttons = [translate(translationKeys.ButtonOk)]
  dialog.observeField("buttonSelected", "dismissDialog")
  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub standardDialog(title, message)
  constants = m.global.constants

  dialog = createObject("roSGNode", "StandardDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: constants.colorBackgroundPrimary,
    DialogFocusColor: constants.colorPrimary,
    DialogFocusItemColor: constants.colorTextPrimary,
    DialogSecondaryTextColor: constants.colorTextSecondary,
    DialogSecondaryItemColor: constants.colorSecondary,
    DialogTextColor: constants.colorTextPrimary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "dismissDialog")
  dialog.title = title
  dialog.setField("contentData", message) ' setField: BSC cannot resolve custom fields on built-in dialog subtypes
  dialog.setField("buttons", [translate(translationKeys.ButtonOk)])

  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub radioDialog(title, message)
  constants = m.global.constants

  dialog = createObject("roSGNode", "RadioDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: constants.colorBackgroundPrimary,
    DialogFocusColor: constants.colorPrimary,
    DialogFocusItemColor: constants.colorTextPrimary,
    DialogSecondaryTextColor: constants.colorTextSecondary,
    DialogSecondaryItemColor: constants.colorSecondary,
    DialogTextColor: constants.colorTextPrimary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "dismissDialog")
  dialog.title = title
  dialog.setField("contentData", message) ' setField: BSC cannot resolve custom fields on built-in dialog subtypes
  dialog.setField("buttons", [translate(translationKeys.ButtonOk)])

  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub showConfirmationDialog(title, message, buttons)
  constants = m.global.constants
  m.top.isDataReturned = false
  m.top.returnData = invalid
  m.userselection = false

  dialog = createObject("roSGNode", "StandardMessageDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: constants.colorBackgroundPrimary,
    DialogFocusColor: constants.colorPrimary,
    DialogFocusItemColor: constants.colorTextPrimary,
    DialogSecondaryTextColor: constants.colorTextSecondary,
    DialogSecondaryItemColor: constants.colorSecondary,
    DialogTextColor: constants.colorTextPrimary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "optionSelected")
  dialog.observeField("wasClosed", "optionClosed")
  dialog.title = title
  if message = invalid
    dialog.message = invalid
  else if type(message) = "roArray"
    dialog.message = message
  else
    dialog.message = [message]
  end if
  dialog.buttons = buttons

  m.scene.dialog = dialog
end sub

' Return button the user selected
sub optionClosed()
  if m.userselection then return

  m.top.returnData = {
    indexSelected: -1,
    buttonSelected: ""
  }
  m.top.isDataReturned = true
end sub

' Return button the user selected
sub optionSelected()
  m.userselection = true
  m.top.returnData = {
    indexSelected: m.scene.dialog.buttonSelected,
    buttonSelected: m.scene.dialog.buttons[m.scene.dialog.buttonSelected]
  }
  m.top.isDataReturned = true

  dismissDialog()
end sub

' Close currently displayed dialog
sub dismissDialog()
  m.scene.dialog.close = true
end sub

' Returns bool indicating if dialog is currently displayed
function isDialogOpen() as boolean
  return isValid(m.scene.dialog)
end function

' Refresh theme colors on JRScene and overhang.
' Called after theme color settings change (settings exit, login, logout).
' Uses refreshThemeOnTree() to walk the overhang's node tree and update all
' themed elements (labels, buttons, icons) in place — no component recreation
' or per-component refresh functions needed.
sub refreshThemeColors()
  m.scene.backgroundColor = m.global.constants.colorBackgroundPrimary

  if isValid(m.overhang)
    refreshThemeOnTree(m.overhang)
  end if
end sub