diff --git a/.vscode/settings.json b/.vscode/settings.json index a005ef427..746c772b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,4 +18,4 @@ "docs/api/**": true }, "brightscriptcomment.addExtraAtStartAndEnd": false -} +} \ No newline at end of file diff --git a/components/home/Home.bs b/components/home/Home.bs index 5c1f39f35..33b3c8606 100644 --- a/components/home/Home.bs +++ b/components/home/Home.bs @@ -9,6 +9,10 @@ sub init() m.top.optionsAvailable = true m.postTask = createObject("roSGNode", "PostTask") + m.homeRows = m.top.findNode("homeRows") + + m.fadeInFocusBitmap = m.top.findNode("fadeInFocusBitmap") + if m.global.session.user.settings["ui.home.splashBackground"] = true m.backdrop = m.top.findNode("backdrop") m.backdrop.uri = buildURL("/Branding/Splashscreen?format=jpg&foregroundLayer=0.15&fillWidth=1280&width=1280&fillHeight=720&height=720&tag=splash") @@ -16,11 +20,14 @@ sub init() end sub sub refresh() - m.top.findNode("homeRows").callFunc("updateHomeRows") + m.homeRows.focusBitmapBlendColor = "0xFFFFFFFF" + m.homeRows.callFunc("updateHomeRows") end sub sub loadLibraries() - m.top.findNode("homeRows").callFunc("loadLibraries") + m.homeRows.focusBitmapBlendColor = "0xFFFFFF00" + m.homeRows.callFunc("loadLibraries") + m.fadeInFocusBitmap.control = "start" end sub ' JFScreen hook that gets ran as needed. diff --git a/components/home/Home.xml b/components/home/Home.xml index 9ce89fcf5..4e06e0013 100644 --- a/components/home/Home.xml +++ b/components/home/Home.xml @@ -2,8 +2,12 @@ - + + + + + diff --git a/components/home/HomeItem.bs b/components/home/HomeItem.bs index f83be55ad..882f6b5f5 100644 --- a/components/home/HomeItem.bs +++ b/components/home/HomeItem.bs @@ -28,6 +28,7 @@ end sub sub itemContentChanged() + m.unplayedCount.visible = false itemData = m.top.itemContent if itemData = invalid then return @@ -65,6 +66,10 @@ sub itemContentChanged() ' Format the Data based on the type of Home Data if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel" + m.itemText.font.size = 35 + m.itemText.height = 64 + m.itemText.horizAlign = "center" + m.itemText.vertAlign = "bottom" m.itemText.text = itemData.name m.itemPoster.uri = itemData.widePosterURL return diff --git a/components/home/HomeRow.xml b/components/home/HomeRow.xml index c846e3f20..8d1db2cc4 100644 --- a/components/home/HomeRow.xml +++ b/components/home/HomeRow.xml @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/components/home/HomeRows.bs b/components/home/HomeRows.bs index 100a41b06..b888da392 100644 --- a/components/home/HomeRows.bs +++ b/components/home/HomeRows.bs @@ -1,4 +1,7 @@ import "pkg:/source/utils/misc.bs" +import "pkg:/source/constants/HomeRowItemSizes.bs" + +const LOADING_WAIT_TIME = 2 sub init() m.top.itemComponentName = "HomeItem" @@ -10,11 +13,14 @@ sub init() m.top.showRowLabel = [true] m.top.rowLabelOffset = [0, 20] - m.top.showRowCounter = [true] + ' Hide the row counter to prevent flicker. We'll show it once loading timer fires + m.top.showRowCounter = [false] + + m.top.content = CreateObject("roSGNode", "ContentNode") - m.homeSectionIndexes = { - count: 0 - } + m.loadingTimer = createObject("roSGNode", "Timer") + m.loadingTimer.duration = LOADING_WAIT_TIME + m.loadingTimer.observeField("fire", "loadingTimerComplete") updateSize() @@ -26,7 +32,7 @@ sub init() m.LoadLibrariesTask = createObject("roSGNode", "LoadItemsTask") m.LoadLibrariesTask.observeField("content", "onLibrariesLoaded") - ' set up tesk nodes for other rows + ' set up task nodes for other rows m.LoadContinueWatchingTask = createObject("roSGNode", "LoadItemsTask") m.LoadContinueWatchingTask.itemsToLoad = "continue" @@ -57,139 +63,241 @@ sub updateSize() ' spacing between items in a row m.top.rowItemSpacing = [20, 0] + ' Default size to wide poster, the most used size + m.top.rowItemSize = homeRowItemSizes.WIDE_POSTER + m.top.visible = true end sub -sub onLibrariesLoaded() - ' save data for other functions - m.libraryData = m.LoadLibrariesTask.content - m.LoadLibrariesTask.unobserveField("content") - m.LoadLibrariesTask.content = [] +' processUserSections: Loop through user's chosen home section settings and generate the content for each row +' +sub processUserSections() + m.expectedRowCount = 1 ' the favorites row is hardcoded to always show atm + m.processedRowCount = 0 - content = CreateObject("roSGNode", "ContentNode") - sizeArray = [] - loadedSections = 0 + ' calculate expected row count by processing homesections + for i = 0 to 6 + sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) + if sectionName = "latestmedia" + ' expect 1 row per filtered media library + m.filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) + for each latestLibrary in m.filteredLatest + if latestLibrary.collectionType <> "boxsets" and latestLibrary.collectionType <> "livetv" and latestLibrary.json.CollectionType <> "Program" + m.expectedRowCount++ + end if + end for + else if sectionName <> "none" + m.expectedRowCount++ + end if + end for - ' Add sections in order based on user settings + ' Add home sections in order based on user settings + loadedSections = 0 for i = 0 to 6 sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) - sectionLoaded = addHomeSection(content, sizeArray, sectionName) + sectionLoaded = false + if sectionName <> "none" + sectionLoaded = addHomeSection(sectionName) + end if ' Count how many sections with data are loaded if sectionLoaded then loadedSections++ ' If 2 sections with data are loaded or we're at the end of the web client section data, consider the home view loaded - if loadedSections = 2 or i = 6 - if not m.global.app_loaded + if not m.global.app_loaded + if loadedSections = 2 or i = 6 m.top.signalBeacon("AppLaunchComplete") ' Roku Performance monitoring m.global.app_loaded = true end if end if end for - ' Favorites isn't an option on Web settings, so we must manually add it for now - addHomeSection(content, sizeArray, "favorites") + ' Favorites isn't an option in Web settings, so we manually add it to the end for now + addHomeSection("favorites") + + ' Start the timer for creating the content rows before we set the cursor size + m.loadingTimer.control = "start" +end sub + +' onLibrariesLoaded: Handler when LoadLibrariesTask returns data +' +sub onLibrariesLoaded() + ' save data for other functions + m.libraryData = m.LoadLibrariesTask.content + m.LoadLibrariesTask.unobserveField("content") + m.LoadLibrariesTask.content = [] - m.top.rowItemSize = sizeArray - m.top.content = content + processUserSections() end sub -' Removes a home section from the home rows -sub removeHomeSection(sectionType as string) - sectionName = LCase(sectionType) +' getOriginalSectionIndex: Gets the index of a section from user settings and adds count of currently known latest media sections +' +' @param {string} sectionName - Name of section we're looking up +' +' @return {integer} indicating index of section taking latest media sections into account +function getOriginalSectionIndex(sectionName as string) as integer + searchSectionName = LCase(sectionName).Replace(" ", "") - removedSectionIndex = m.homeSectionIndexes[sectionName] + sectionIndex = 0 + indexLatestMediaSection = 0 - if not isValid(removedSectionIndex) then return + for i = 0 to 6 + settingSectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) + if settingSectionName = "latestmedia" + indexLatestMediaSection = i + end if - for each section in m.homeSectionIndexes - if m.homeSectionIndexes[section] > removedSectionIndex - m.homeSectionIndexes[section]-- + if settingSectionName = searchSectionName + sectionIndex = i end if end for - m.homeSectionIndexes.Delete(sectionName) + ' If the latest media section is before the section we're searching for, then we need to account for how many latest media rows there are + addLatestMediaSectionCount = (indexLatestMediaSection < sectionIndex) + + if addLatestMediaSectionCount + for i = sectionIndex to m.top.content.getChildCount() - 1 + sectionToTest = m.top.content.getChild(i) + if LCase(Left(sectionToTest.title, 6)) = "latest" + sectionIndex++ + end if + end for + end if + + return sectionIndex +end function + +' removeHomeSection: Removes a home section from the home rows +' +' @param {string} sectionToRemove - Title property of section we're removing +sub removeHomeSection(sectionTitleToRemove as string) + if not isValid(sectionTitleToRemove) then return + + sectionTitle = LCase(sectionTitleToRemove).Replace(" ", "") + if not sectionExists(sectionTitle) then return + + sectionIndexToRemove = getSectionIndex(sectionTitle) - m.top.content.removeChildIndex(removedSectionIndex) + m.top.content.removeChildIndex(sectionIndexToRemove) + setRowItemSize() +end sub + +' setRowItemSize: Loops through all home sections and sets the correct item sizes per row +' +sub setRowItemSize() + if not isValid(m.top.content) then return + + homeSections = m.top.content.getChildren(-1, 0) + newSizeArray = CreateObject("roArray", homeSections.count(), false) + + for i = 0 to homeSections.count() - 1 + newSizeArray[i] = isValid(homeSections[i].cursorSize) ? homeSections[i].cursorSize : homeRowItemSizes.WIDE_POSTER + end for + m.top.rowItemSize = newSizeArray + + ' If we have processed the expected number of content rows, stop the loading timer and run the complete function + if m.expectedRowCount = m.processedRowCount + m.loadingTimer.control = "stop" + loadingTimerComplete() + end if +end sub + +' loadingTimerComplete: Event handler for when loading wait time has expired +' +sub loadingTimerComplete() + if not m.top.showRowCounter[0] + ' Show the row counter to prevent flicker + m.top.showRowCounter = [true] + end if end sub -' Adds a new home section to the home rows. -' Returns a boolean indicating whether the section was handled. -function addHomeSection(content as dynamic, sizeArray as dynamic, sectionName as string) as boolean +' addHomeSection: Adds a new home section to the home rows. +' +' @param {string} sectionType - Type of section to add +' @return {boolean} indicating if the section was handled +function addHomeSection(sectionType as string) as boolean ' Poster size library items - if sectionName = "livetv" - createLiveTVRow(content, sizeArray) + if sectionType = "livetv" + createLiveTVRow() return true end if ' Poster size library items - if sectionName = "smalllibrarytiles" - createLibraryRow(content, sizeArray) + if sectionType = "smalllibrarytiles" + createLibraryRow() return true end if ' Continue Watching items - if sectionName = "resume" - createContinueWatchingRow(content, sizeArray) + if sectionType = "resume" + createContinueWatchingRow() return true end if ' Next Up items - if sectionName = "nextup" - createNextUpRow(content, sizeArray) + if sectionType = "nextup" + createNextUpRow() return true end if ' Latest items in each library - if sectionName = "latestmedia" - createLatestInRows(content, sizeArray) + if sectionType = "latestmedia" + createLatestInRows() return true end if ' Favorite Items - if sectionName = "favorites" - createFavoritesRow(content, sizeArray) + if sectionType = "favorites" + createFavoritesRow() return true end if + ' This section type isn't supported. + ' Count it as processed since we aren't going to do anything else with it + m.processedRowCount++ return false end function -' Create a row displaying the user's libraries -sub createLibraryRow(content as dynamic, sizeArray as dynamic) +' createLibraryRow: Creates a row displaying the user's libraries +' +sub createLibraryRow() + m.processedRowCount++ ' Ensure we have data if not isValidAndNotEmpty(m.libraryData) then return - mediaRow = content.CreateChild("HomeRow") - mediaRow.title = tr("My Media") + sectionName = tr("My Media") - m.homeSectionIndexes.AddReplace("library", m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - - sizeArray.push([464, 331]) + row = CreateObject("roSGNode", "HomeRow") + row.title = sectionName + row.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + row.cursorSize = homeRowItemSizes.WIDE_POSTER filteredMedia = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.MyMediaExcludes) for each item in filteredMedia - mediaRow.appendChild(item) + row.appendChild(item) end for + + ' Row already exists, replace it with new content + if sectionExists(sectionName) + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() + return + end if + + ' Row does not exist, insert it into the home view + m.top.content.insertChild(row, getOriginalSectionIndex("smalllibrarytiles")) + setRowItemSize() end sub -' Create a row displaying latest items in each of the user's libraries -sub createLatestInRows(content as dynamic, sizeArray as dynamic) +' createLatestInRows: Creates a row displaying latest items in each of the user's libraries +' +sub createLatestInRows() ' Ensure we have data if not isValidAndNotEmpty(m.libraryData) then return ' create a "Latest In" row for each library - filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) - for each lib in filteredLatest + for each lib in m.filteredLatest if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program" - latestInRow = content.CreateChild("HomeRow") - latestInRow.title = tr("Latest in") + " " + lib.name + " >" - - m.homeSectionIndexes.AddReplace("latestin" + LCase(lib.name).Replace(" ", ""), m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - sizeArray.Push([464, 331]) - loadLatest = createObject("roSGNode", "LoadItemsTask") loadLatest.itemsToLoad = "latest" loadLatest.itemId = lib.id @@ -204,149 +312,168 @@ sub createLatestInRows(content as dynamic, sizeArray as dynamic) end for end sub -' Create a row displaying the live tv now on section -sub createLiveTVRow(content as dynamic, sizeArray as dynamic) - contentRow = content.CreateChild("HomeRow") - contentRow.title = tr("On Now") - m.homeSectionIndexes.AddReplace("livetv", m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - sizeArray.push([464, 331]) +' sectionExists: Checks if passed section exists in home row content +' +' @param {string} sectionTitle - Title of section we're checking for +' +' @return {boolean} indicating if the section currently exists in the home row content +function sectionExists(sectionTitle as string) as boolean + if not isValid(sectionTitle) then return false + if not isValid(m.top.content) then return false + searchSectionTitle = LCase(sectionTitle).Replace(" ", "") + + homeSections = m.top.content.getChildren(-1, 0) + + for each section in homeSections + if LCase(section.title).Replace(" ", "") = searchSectionTitle + return true + end if + end for + + return false +end function + +' getSectionIndex: Returns index of requested section in home row content +' +' @param {string} sectionTitle - Title of section we're checking for +' +' @return {integer} indicating index of request section +function getSectionIndex(sectionTitle as string) as integer + if not isValid(sectionTitle) then return false + if not isValid(m.top.content) then return false + + searchSectionTitle = LCase(sectionTitle).Replace(" ", "") + + homeSections = m.top.content.getChildren(-1, 0) + + sectionIndex = homeSections.count() + i = 0 + + for each section in homeSections + if LCase(section.title).Replace(" ", "") = searchSectionTitle + sectionIndex = i + exit for + end if + i++ + end for + + return sectionIndex +end function + +' createLiveTVRow: Creates a row displaying the live tv now on section +' +sub createLiveTVRow() m.LoadOnNowTask.observeField("content", "updateOnNowItems") m.LoadOnNowTask.control = "RUN" end sub -' Create a row displaying items the user can continue watching -sub createContinueWatchingRow(content as dynamic, sizeArray as dynamic) - continueWatchingRow = content.CreateChild("HomeRow") - continueWatchingRow.title = tr("Continue Watching") - m.homeSectionIndexes.AddReplace("resume", m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - sizeArray.push([464, 331]) - +' createContinueWatchingRow: Creates a row displaying items the user can continue watching +' +sub createContinueWatchingRow() ' Load the Continue Watching Data m.LoadContinueWatchingTask.observeField("content", "updateContinueWatchingItems") m.LoadContinueWatchingTask.control = "RUN" end sub -' Create a row displaying next episodes up to watch -sub createNextUpRow(content as dynamic, sizeArray as dynamic) - nextUpRow = content.CreateChild("HomeRow") - nextUpRow.title = tr("Next Up >") - m.homeSectionIndexes.AddReplace("nextup", m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - sizeArray.push([464, 331]) +' createNextUpRow: Creates a row displaying next episodes up to watch +' +sub createNextUpRow() + sectionName = tr("Next Up") + ">" + + if not sectionExists(sectionName) + nextUpRow = m.top.content.CreateChild("HomeRow") + nextUpRow.title = sectionName + nextUpRow.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + nextUpRow.cursorSize = homeRowItemSizes.WIDE_POSTER + end if ' Load the Next Up Data m.LoadNextUpTask.observeField("content", "updateNextUpItems") m.LoadNextUpTask.control = "RUN" end sub -' Create a row displaying items from the user's favorites list -sub createFavoritesRow(content as dynamic, sizeArray as dynamic) - favoritesRow = content.CreateChild("HomeRow") - favoritesRow.title = tr("Favorites") - sizeArray.Push([464, 331]) - - m.homeSectionIndexes.AddReplace("favorites", m.homeSectionIndexes.count) - m.homeSectionIndexes.count++ - +' createFavoritesRow: Creates a row displaying items from the user's favorites list +' +sub createFavoritesRow() ' Load the Favorites Data m.LoadFavoritesTask.observeField("content", "updateFavoritesItems") m.LoadFavoritesTask.control = "RUN" end sub -' Update home row data +' updateHomeRows: Update function exposed to outside components +' sub updateHomeRows() - ' If resume section exists, reload row's data - if m.homeSectionIndexes.doesExist("resume") - m.LoadContinueWatchingTask.observeField("content", "updateContinueWatchingItems") - m.LoadContinueWatchingTask.control = "RUN" - end if - - ' If next up section exists, reload row's data - if m.homeSectionIndexes.doesExist("nextup") - m.LoadNextUpTask.observeField("content", "updateNextUpItems") - m.LoadNextUpTask.control = "RUN" - end if - - ' If favorites section exists, reload row's data - if m.homeSectionIndexes.doesExist("favorites") - m.LoadFavoritesTask.observeField("content", "updateFavoritesItems") - m.LoadFavoritesTask.control = "RUN" - end if - - ' If live tv's on now section exists, reload row's data - if m.homeSectionIndexes.doesExist("livetv") - m.LoadOnNowTask.observeField("content", "updateOnNowItems") - m.LoadOnNowTask.control = "RUN" - end if - - ' If latest in library section exists, reload row's data - hasLatestHomeSection = false - - for each section in m.homeSectionIndexes - if LCase(Left(section, 6)) = "latest" - hasLatestHomeSection = true - exit for - end if - end for - - if hasLatestHomeSection - updateLatestInRows() - end if + ' Hide the row counter to prevent flicker. We'll show it once loading timer fires + m.top.showRowCounter = [false] + processUserSections() end sub +' updateFavoritesItems: Processes LoadFavoritesTask content. Removes, Creates, or Updates favorites row as needed +' sub updateFavoritesItems() + m.processedRowCount++ itemData = m.LoadFavoritesTask.content m.LoadFavoritesTask.unobserveField("content") m.LoadFavoritesTask.content = [] - if itemData = invalid then return - - rowIndex = m.homeSectionIndexes.favorites + sectionName = tr("Favorites") - if itemData.count() < 1 - removeHomeSection("favorites") + if not isValidAndNotEmpty(itemData) + removeHomeSection(sectionName) return - else - ' remake row using the new data - row = CreateObject("roSGNode", "HomeRow") - row.title = tr("Favorites") + end if - for each item in itemData - usePoster = true + ' remake row using the new data + row = CreateObject("roSGNode", "HomeRow") + row.title = sectionName + row.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + row.cursorSize = homeRowItemSizes.WIDE_POSTER - if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist" - usePoster = false - end if + for each item in itemData + usePoster = true - item.usePoster = usePoster - item.imageWidth = row.imageWidth - row.appendChild(item) - end for + if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist" + usePoster = false + end if - ' replace the old row - m.top.content.replaceChild(row, rowIndex) + item.usePoster = usePoster + item.imageWidth = row.imageWidth + row.appendChild(item) + end for + if sectionExists(sectionName) + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() + return end if + + m.top.content.insertChild(row, getSectionIndex(sectionName)) + setRowItemSize() end sub +' updateContinueWatchingItems: Processes LoadContinueWatchingTask content. Removes, Creates, or Updates continue watching row as needed +' sub updateContinueWatchingItems() + m.processedRowCount++ itemData = m.LoadContinueWatchingTask.content m.LoadContinueWatchingTask.unobserveField("content") m.LoadContinueWatchingTask.content = [] - if itemData = invalid then return + sectionName = tr("Continue Watching") - if itemData.count() < 1 - removeHomeSection("resume") + if not isValidAndNotEmpty(itemData) + removeHomeSection(sectionName) return end if + sectionName = tr("Continue Watching") + ' remake row using the new data row = CreateObject("roSGNode", "HomeRow") - row.title = tr("Continue Watching") + row.title = sectionName + row.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + row.cursorSize = homeRowItemSizes.WIDE_POSTER for each item in itemData if isValid(item.json) and isValid(item.json.UserData) and isValid(item.json.UserData.PlayedPercentage) @@ -358,198 +485,161 @@ sub updateContinueWatchingItems() row.appendChild(item) end for - ' replace the old row - m.top.content.replaceChild(row, m.homeSectionIndexes.resume) + ' Row already exists, replace it with new content + if sectionExists(sectionName) + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() + return + end if + + ' Row does not exist, insert it into the home view + m.top.content.insertChild(row, getOriginalSectionIndex("resume")) + setRowItemSize() end sub +' updateNextUpItems: Processes LoadNextUpTask content. Removes, Creates, or Updates next up row as needed +' sub updateNextUpItems() + m.processedRowCount++ itemData = m.LoadNextUpTask.content m.LoadNextUpTask.unobserveField("content") m.LoadNextUpTask.content = [] + m.LoadNextUpTask.control = "STOP" - if itemData = invalid then return + sectionName = tr("Next Up") + " >" - if itemData.count() < 1 - removeHomeSection("nextup") + if not isValidAndNotEmpty(itemData) + removeHomeSection(sectionName) return - else - ' remake row using the new data - row = CreateObject("roSGNode", "HomeRow") - row.title = tr("Next Up") + " >" - for each item in itemData - item.usePoster = row.usePoster - item.imageWidth = row.imageWidth - row.appendChild(item) - end for - - ' replace the old row - m.top.content.replaceChild(row, m.homeSectionIndexes.nextup) end if -end sub -' Iterate over user's libraries and update data for each Latest In section -sub updateLatestInRows() - ' Ensure we have data - if not isValidAndNotEmpty(m.libraryData) then return + ' remake row using the new data + row = CreateObject("roSGNode", "HomeRow") + row.title = tr("Next Up") + " >" + row.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + row.cursorSize = homeRowItemSizes.WIDE_POSTER - ' Load new data for each library - filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) - for each lib in filteredLatest - if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program" - loadLatest = createObject("roSGNode", "LoadItemsTask") - loadLatest.itemsToLoad = "latest" - loadLatest.itemId = lib.id + for each item in itemData + item.usePoster = row.usePoster + item.imageWidth = row.imageWidth + row.appendChild(item) + end for - metadata = { - "title": lib.name, - "contentType": lib.json.CollectionType - } + ' Row already exists, replace it with new content + if sectionExists(sectionName) + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() + return + end if - loadLatest.metadata = metadata - loadLatest.observeField("content", "updateLatestItems") - loadLatest.control = "RUN" - end if - end for + ' Row does not exist, insert it into the home view + m.top.content.insertChild(row, getSectionIndex(sectionName)) + setRowItemSize() end sub +' updateLatestItems: Processes LoadItemsTask content. Removes, Creates, or Updates latest in {library} row as needed +' +' @param {dynamic} msg - LoadItemsTask sub updateLatestItems(msg) + m.processedRowCount++ itemData = msg.GetData() node = msg.getRoSGNode() node.unobserveField("content") node.content = [] - if itemData = invalid then return + sectionName = tr("Latest in") + " " + node.metadata.title + " >" - sectionName = "latestin" + LCase(node.metadata.title).Replace(" ", "") - - if itemData.count() < 1 + if not isValidAndNotEmpty(itemData) removeHomeSection(sectionName) return - else - ' remake row using new data - row = CreateObject("roSGNode", "HomeRow") - row.title = tr("Latest in") + " " + node.metadata.title + " >" - row.usePoster = true - ' Handle specific types with different item widths - if node.metadata.contentType = "movies" - row.imageWidth = 180 - itemSize = [188, 331] - else if node.metadata.contentType = "music" - row.imageWidth = 261 - itemSize = [261, 331] - else - row.imageWidth = 464 - itemSize = [464, 331] - end if - - for each item in itemData - item.usePoster = row.usePoster - item.imageWidth = row.imageWidth - row.appendChild(item) - end for + end if - rowIndex = m.homeSectionIndexes[sectionName] + imagesize = homeRowItemSizes.WIDE_POSTER - ' Replace the old row - if isValid(rowIndex) - updateSizeArray(itemSize, rowIndex, "replace") - m.top.content.replaceChild(row, rowIndex) - return + if isValid(node.metadata.contentType) + if LCase(node.metadata.contentType) = "movies" + imagesize = homeRowItemSizes.MOVIE_POSTER + else if LCase(node.metadata.contentType) = "music" + imagesize = homeRowItemSizes.MUSIC_ALBUM end if + end if - ' Determine highest index of a Lastest In section so we can append the new section after it - highestLatestHomeSectionIndex = 0 - - for each section in m.homeSectionIndexes - if LCase(Left(section, 6)) = "latest" - if m.homeSectionIndexes[section] > highestLatestHomeSectionIndex - highestLatestHomeSectionIndex = m.homeSectionIndexes[section] - end if - end if - end for - - ' We have data for a section that doesn't currently exist - rowIndex = highestLatestHomeSectionIndex + 1 - - ' Advance all the indexes greater than or equal than our new row - for each section in m.homeSectionIndexes - if m.homeSectionIndexes[section] >= rowIndex - m.homeSectionIndexes[section]++ - end if - end for - - m.homeSectionIndexes.AddReplace(sectionName, rowIndex) + ' remake row using new data + row = CreateObject("roSGNode", "HomeRow") + row.title = sectionName + row.imageWidth = imagesize[0] + row.cursorSize = imagesize + row.usePoster = true - m.top.content.insertChild(row, rowIndex) - updateSizeArray(itemSize, rowIndex) + for each item in itemData + item.usePoster = row.usePoster + item.imageWidth = row.imageWidth + row.appendChild(item) + end for + if sectionExists(sectionName) + ' Row already exists, replace it with new content + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() return end if + + m.top.content.insertChild(row, getOriginalSectionIndex("latestmedia")) + setRowItemSize() end sub +' updateOnNowItems: Processes LoadOnNowTask content. Removes, Creates, or Updates latest in on now row as needed +' sub updateOnNowItems() + m.processedRowCount++ itemData = m.LoadOnNowTask.content m.LoadOnNowTask.unobserveField("content") m.LoadOnNowTask.content = [] - if itemData = invalid then return + sectionName = tr("On Now") - if itemData.count() < 1 - removeHomeSection("livetv") + if not isValidAndNotEmpty(itemData) + removeHomeSection(sectionName) return - else - ' remake row using the new data - row = CreateObject("roSGNode", "HomeRow") - row.title = tr("On Now") - itemSize = [464, 331] - row.imageWidth = 464 - for each item in itemData - row.usePoster = false - if (not isValid(item.thumbnailURL) or item.thumbnailURL = "") and isValid(item.json) and isValid(item.json.imageURL) - item.thumbnailURL = item.json.imageURL - row.usePoster = true - row.imageWidth = 180 - itemSize = [188, 331] - end if - item.usePoster = row.usePoster - item.imageWidth = row.imageWidth - row.appendChild(item) - end for - - ' replace the old row - updateSizeArray(itemSize, m.homeSectionIndexes.livetv, "replace") - m.top.content.replaceChild(row, m.homeSectionIndexes.livetv) - end if -end sub -sub updateSizeArray(rowItemSize, rowIndex = invalid, action = "insert") - sizeArray = m.top.rowItemSize - ' append by default - if rowIndex = invalid - rowIndex = sizeArray.count() - end if + ' remake row using the new data + row = CreateObject("roSGNode", "HomeRow") + row.title = tr("On Now") + row.imageWidth = homeRowItemSizes.WIDE_POSTER[0] + row.cursorSize = homeRowItemSizes.WIDE_POSTER - newSizeArray = [] - for i = 0 to sizeArray.count() - if rowIndex = i - if action = "replace" - newSizeArray.Push(rowItemSize) - else if action = "insert" - newSizeArray.Push(rowItemSize) - if isValid(sizeArray[i]) - newSizeArray.Push(sizeArray[i]) - end if - end if - else if isValid(sizeArray[i]) - newSizeArray.Push(sizeArray[i]) + for each item in itemData + row.usePoster = false + + if (not isValid(item.thumbnailURL) or item.thumbnailURL = "") and isValid(item.json) and isValid(item.json.imageURL) + item.thumbnailURL = item.json.imageURL + row.usePoster = true + row.imageWidth = homeRowItemSizes.MOVIE_POSTER[0] + row.cursorSize = homeRowItemSizes.MOVIE_POSTER end if + + item.usePoster = row.usePoster + item.imageWidth = row.imageWidth + row.appendChild(item) end for - m.top.rowItemSize = newSizeArray + + ' Row already exists, replace it with new content + if sectionExists(sectionName) + m.top.content.replaceChild(row, getSectionIndex(sectionName)) + setRowItemSize() + return + end if + + ' Row does not exist, insert it into the home view + m.top.content.insertChild(row, getOriginalSectionIndex("livetv")) + setRowItemSize() end sub sub itemSelected() + m.selectedRowItem = m.top.rowItemSelected + m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1]) 'Prevent the selected item event from double firing diff --git a/locale/en_US/translations.ts b/locale/en_US/translations.ts index eccdff59e..1fe478489 100644 --- a/locale/en_US/translations.ts +++ b/locale/en_US/translations.ts @@ -1231,5 +1231,15 @@ No Chapter Data Found Message shown in OSD when no chapter data is returned by the API + + Use Web Client's Home Section Arrangement + Use Web Client's Home Section Arrangement + User Setting - Setting title + + + Make the arrangement of the Roku home view sections match the web client's home screen. Jellyfin will need to be closed and reopened for change to take effect. + Make the arrangement of the Roku home view sections match the web client's home screen. Jellyfin will need to be closed and reopened for change to take effect. + User Setting - Setting description + \ No newline at end of file diff --git a/settings/settings.json b/settings/settings.json index 497202f62..9c33a6b01 100644 --- a/settings/settings.json +++ b/settings/settings.json @@ -231,6 +231,13 @@ "settingName": "ui.home.splashBackground", "type": "bool", "default": "false" + }, + { + "title": "Use Web Client's Home Section Arrangement", + "description": "Make the arrangement of the Roku home view sections match the web client's home screen. Jellyfin will need to be closed and reopened for change to take effect.", + "settingName": "ui.home.useWebSectionArrangement", + "type": "bool", + "default": "true" } ] }, diff --git a/source/constants/HomeRowItemSizes.bs b/source/constants/HomeRowItemSizes.bs new file mode 100644 index 000000000..9ee0690a3 --- /dev/null +++ b/source/constants/HomeRowItemSizes.bs @@ -0,0 +1,7 @@ +' @fileoverview Constants for rowItemSize on the home view + +namespace homeRowItemSizes + const WIDE_POSTER = [464, 331] + const MOVIE_POSTER = [180, 331] + const MUSIC_ALBUM = [261, 331] +end namespace diff --git a/source/migrations.bs b/source/migrations.bs index de0ab838d..1d4979110 100644 --- a/source/migrations.bs +++ b/source/migrations.bs @@ -79,16 +79,18 @@ sub runRegistryUserMigrations() m.wasMigrated = true print `Running Registry Migration for ${CLIENT_VERSION_REQUIRING_BASE_MIGRATION} for userid: ${section}` + ' If this is an existing user, set the useWebSectionArrangement setting to false + ' This way the home view for upgrading users is not changed without them opting in + if not hasUserVersion + print "useWebSectionArrangement set to false" + registry_write("ui.home.useWebSectionArrangement", "false", section) + end if + ' no longer saving password to registry registry_delete("password", section) ' av1 playback no longer hidden behind user setting registry_delete("playback.av1", section) end if - - ' update lastRunVersion if needed - if hasUserVersion and lastRunVersion <> m.global.app.version - registry_write("LastRunVersion", m.global.app.version, section) - end if end if end for end sub diff --git a/source/utils/session.bs b/source/utils/session.bs index 9bb28bfad..a468a5605 100644 --- a/source/utils/session.bs +++ b/source/utils/session.bs @@ -156,8 +156,11 @@ namespace session ' grab lastRunVersion for this user lastRunVersion = get_user_setting("LastRunVersion") - if lastRunVersion <> invalid + if isValid(lastRunVersion) and lastRunVersion = m.global.app.version + ' Don't update the registry, only update the global session session.user.Update("LastRunVersion", lastRunVersion) + else + set_user_setting("LastRunVersion", m.global.app.version) end if ' update user session settings with values from registry @@ -220,6 +223,17 @@ namespace session unset_user_setting("display.livetv.landing") end if else + ' User has no custom prefs. Save default home section values. + session.user.SaveUserHomeSections({ + homesection0: "smalllibrarytiles", + homesection1: "resume", + homesection2: "nextup", + homesection3: "latestmedia", + homesection4: "livetv", + homesection5: "none", + homesection6: "none" + }) + unset_user_setting("display.livetv.landing") end if end sub @@ -230,6 +244,14 @@ namespace session userPreferences = customPrefs rowTypes = [] + useWebSectionArrangement = m.global.session.user.settings["ui.home.useWebSectionArrangement"] + + if isValid(useWebSectionArrangement) + if not useWebSectionArrangement + userPreferences.delete("homesection0") + end if + end if + ' If user has no section preferences, use default settings if not userPreferences.doesExist("homesection0") userPreferences = {