components_settings_settings.bs
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/globals.bs"
import "pkg:/source/utils/misc.bs"
' post device profile
import "pkg:/source/utils/deviceCapabilities.bs"
sub init()
m.log = log.Logger("Settings")
m.top.overhangTitle = tr("Settings")
m.top.optionsAvailable = false
m.userLocation = []
m.themeColorsChanged = false
versionLabel = m.top.findNode("versionLabel")
versionLabel.text = "v" + m.global.app.version
m.settingsMenu = m.top.findNode("settingsMenu")
m.settingDetail = m.top.findNode("settingDetail")
m.settingDesc = m.top.findNode("settingDesc")
m.path = m.top.findNode("path")
m.top.findNode("rightPanelBackground").blendColor = m.global.constants.colorBackgroundSecondary
m.boolSetting = m.top.findNode("boolSetting")
m.integerSetting = m.top.findNode("integerSetting")
m.radioSetting = m.top.findNode("radioSetting")
m.hexSetting = m.top.findNode("hexSetting")
m.integerSetting.observeField("submit", "onKeyGridSubmit")
m.integerSetting.observeField("escape", "onKeyGridEscape")
m.hexSetting.observeField("submit", "onHexKeyGridSubmit")
m.hexSetting.observeField("escape", "onHexKeyGridEscape")
m.settingsMenu.setFocus(true)
m.settingsMenu.observeField("itemFocused", "settingFocused")
m.settingsMenu.observeField("itemSelected", "settingSelected")
m.boolSetting.observeField("checkedItem", "boolSettingChanged")
m.radioSetting.observeField("checkedItem", "radioSettingChanged")
m.postTask = createObject("roSGNode", "PostTask")
' Load Configuration Tree
m.configTree = GetConfigTree()
LoadMenu({ children: m.configTree })
end sub
sub onKeyGridSubmit()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
newValue = m.integerSetting.text
' Update node field - observer handles registry persistence
user.settings.Save(selectedSetting.settingName, newValue)
m.settingsMenu.setFocus(true)
end sub
sub onKeyGridEscape()
if m.integerSetting.escape = "left" or m.integerSetting.escape = "back"
m.settingsMenu.setFocus(true)
end if
end sub
sub onHexKeyGridSubmit()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
newValue = UCase(m.hexSetting.text)
' Validate hex color input
if not isValidHexColor(newValue)
m.global.sceneManager.callFunc("userMessage", tr("Invalid Color"), tr("Please enter exactly 6 hex characters (0-9, A-F)."))
return
end if
' Track if theme color changed (requires UI refresh on exit)
if selectedSetting.settingName.left(12) = "uiThemeColor"
m.themeColorsChanged = true
end if
' Update node field - observer handles registry persistence
user.settings.Save(selectedSetting.settingName, newValue)
m.settingsMenu.setFocus(true)
end sub
sub onHexKeyGridEscape()
if m.hexSetting.escape = "left" or m.hexSetting.escape = "back"
m.settingsMenu.setFocus(true)
end if
end sub
sub LoadMenu(configSection)
if not isValid(configSection.children)
' Load parent menu
m.userLocation.pop()
configSection = m.userLocation.peek()
else
if m.userLocation.Count() > 0 then m.userLocation.peek().selectedIndex = m.settingsMenu.itemFocused
m.userLocation.push(configSection)
end if
result = CreateObject("roSGNode", "ContentNode")
for each item in configSection.children
listItem = result.CreateChild("ContentNode")
listItem.title = tr(item.title)
listItem.Description = tr(item.description)
listItem.id = item.id
end for
m.settingsMenu.content = result
if isValid(configSection.selectedIndex) and configSection.selectedIndex > -1
m.settingsMenu.jumpToItem = configSection.selectedIndex
end if
' Set Path display
m.path.text = ""
for each level in m.userLocation
if isValid(level.title)
if m.path.text = ""
m.path.text = tr(level.title)
else
m.path.text += " / " + tr(level.title)
end if
end if
end for
end sub
sub settingFocused()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
m.settingDesc.text = tr(selectedSetting.Description)
' Hide Settings
m.boolSetting.visible = false
m.integerSetting.visible = false
m.radioSetting.visible = false
m.hexSetting.visible = false
userSettings = m.global.user.settings
if not isValid(selectedSetting.type)
return
else if selectedSetting.type = "bool"
m.boolSetting.visible = true
if userSettings[selectedSetting.settingName] = true
m.boolSetting.checkedItem = 1
else
m.boolSetting.checkedItem = 0
end if
else if selectedSetting.type = "integer"
integerValue = userSettings[selectedSetting.settingName].ToStr()
if isValid(integerValue)
m.integerSetting.text = integerValue
end if
m.integerSetting.visible = true
else if LCase(selectedSetting.type) = "radio"
selectedValue = userSettings[selectedSetting.settingName]
radioContent = CreateObject("roSGNode", "ContentNode")
itemIndex = 0
for each item in m.userLocation.peek().children[m.settingsMenu.itemFocused].options
listItem = radioContent.CreateChild("ContentNode")
listItem.title = tr(item.title)
listItem.id = item.id
if selectedValue = item.id
m.radioSetting.checkedItem = itemIndex
end if
itemIndex++
end for
m.radioSetting.content = radioContent
m.radioSetting.visible = true
else if selectedSetting.type = "text"
' Text input type (used for hex color codes)
textValue = userSettings[selectedSetting.settingName]
if isValid(textValue)
m.hexSetting.text = textValue
else
m.hexSetting.text = ""
end if
m.hexSetting.visible = true
else
m.log.warn("Unknown setting type", selectedSetting.type)
end if
end sub
sub settingSelected()
selectedItem = m.userLocation.peek().children[m.settingsMenu.itemFocused]
if isValid(selectedItem.type) ' Show setting
if selectedItem.type = "bool"
m.boolSetting.setFocus(true)
else if selectedItem.type = "integer"
m.integerSetting.setFocus(true)
else if selectedItem.type = "radio"
m.radioSetting.setFocus(true)
else if selectedItem.type = "text"
m.hexSetting.setFocus(true)
end if
else if isValid(selectedItem.children) and selectedItem.children.Count() > 0 ' Show sub menu
LoadMenu(selectedItem)
m.settingsMenu.setFocus(true)
else
return
end if
m.settingDesc.text = m.settingsMenu.content.GetChild(m.settingsMenu.itemFocused).Description
end sub
sub boolSettingChanged()
if not isValid(m.boolSetting.focusedChild) then return
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
if m.boolSetting.checkedItem
' Update node field - observer handles registry persistence
user.settings.Save(selectedSetting.settingName, "true")
' Special handling for globalRememberMe - set active_user in global registry
if selectedSetting.settingName = "globalRememberMe"
set_setting("active_user", m.global.user.id)
end if
else
' Update node field - observer handles registry persistence
user.settings.Save(selectedSetting.settingName, "false")
' Special handling for globalRememberMe - remove active_user from global registry
if selectedSetting.settingName = "globalRememberMe"
unset_setting("active_user")
end if
end if
end sub
sub radioSettingChanged()
if not isValid(m.radioSetting.focusedChild) then return
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
newValue = m.radioSetting.content.getChild(m.radioSetting.checkedItem).id
' Update node field - observer handles registry persistence
user.settings.Save(selectedSetting.settingName, newValue)
end sub
' JRScreen hook that gets ran when the screen is shown.
sub OnScreenShown()
' Clear backdrop on settings screens
m.global.sceneManager.callFunc("setBackgroundImage", "")
overhang = m.top.getScene().findNode("overhang")
if not isValid(overhang) then return
overhang.isLogoVisible = true
overhang.currentUser = m.global.user.name
end sub
' JRScreen hook that gets ran as needed.
' Assumes settings were changed and they affect the device profile.
' Posts a new device profile to the server using the task thread.
sub OnScreenHidden()
' Post device profile updates
m.postTask.arrayData = getDeviceCapabilities()
m.postTask.apiUrl = "/Sessions/Capabilities/Full"
m.postTask.control = "RUN"
m.postTask.observeField("responseCode", "postFinished")
end sub
' Triggered by m.postTask after completing a post.
' Empty the task data when finished.
sub postFinished()
m.postTask.unobserveField("responseCode")
m.postTask.callFunc("empty")
end sub
' Returns true if any of the data entry forms are in focus
function isFormInFocus() as boolean
if isValid(m.settingDetail.focusedChild) or m.radioSetting.hasFocus() or m.boolSetting.hasFocus() or m.integerSetting.hasFocus() or m.hexSetting.hasFocus()
return true
end if
return false
end function
' Exit settings screen, refreshing theme colors only if they changed
sub exitSettingsAndReloadHome()
if m.themeColorsChanged
' Apply theme color overrides and refresh UI
applyThemeColorOverrides(m.global.user.settings)
m.global.sceneManager.callFunc("refreshThemeColors")
' Clear all scenes and reload home with fresh theme colors
m.global.sceneManager.callFunc("reloadHome")
else
' No theme changes - just pop the settings screen
m.global.sceneManager.callFunc("popScene")
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if (key = "back" or key = "left") and isValid(m.settingsMenu.focusedChild) and m.userLocation.Count() > 1
LoadMenu({})
return true
else if (key = "back" or key = "left") and isFormInFocus()
m.settingsMenu.setFocus(true)
return true
else if key = "back" and isValid(m.settingsMenu.focusedChild) and m.userLocation.Count() = 1
' Exiting Settings - apply theme colors and reload home screen
exitSettingsAndReloadHome()
return true
end if
if key = "options"
exitSettingsAndReloadHome()
return true
end if
if key = "right"
settingSelected()
end if
if key = "up" and isValid(m.settingsMenu.focusedChild) and m.settingsMenu.itemFocused = 0
m.settingsMenu.jumpToItem = m.settingsMenu.content.getChildCount() - 1
return true
end if
if key = "down" and isValid(m.settingsMenu.focusedChild)
if m.settingsMenu.itemFocused = m.settingsMenu.content.getChildCount() - 1
m.settingsMenu.jumpToItem = 0
return true
end if
end if
return false
end function