Merge master into update-alpha-menu

This commit is contained in:
Charles Ewert 2023-12-17 19:30:14 -05:00
commit bf26780a30
362 changed files with 3855 additions and 1539 deletions

View File

@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
with:
days-before-issue-stale: -1
days-before-issue-close: -1

View File

@ -6,8 +6,6 @@ concurrency:
on:
push:
branches:
- unstable
pull_request_target:
jobs:

View File

@ -2,11 +2,10 @@ name: build-dev
on:
pull_request:
branches-ignore:
- master
push:
branches:
- unstable
- master
- "*.*.z"
jobs:
dev:
@ -23,7 +22,7 @@ jobs:
run: npm run ropm
- name: Build app
run: npm run build
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
with:
name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/build/staging

View File

@ -3,7 +3,7 @@ name: build-docs
on:
push:
branches:
- unstable
- master
jobs:
docs:

View File

@ -1,64 +1,15 @@
# Builds the production version of the app
name: build-prod
on:
pull_request:
branches:
- master
push:
branches:
- master
- "*.*.z"
jobs:
version-check:
runs-on: ubuntu-latest
steps:
- name: Checkout master (the latest release)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: master
- name: Install jq to parse json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save old package.json version
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save old manifest version
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
- name: Save old Makefile version
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Checkout PR branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Save new package.json version
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: package.json version must be updated
if: env.oldPackVersion == env.newPackVersion
run: exit 1
- name: Find and save new major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save new manifest version
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Manifest version must be updated
if: env.oldManVersion == env.newManVersion
run: exit 1
- name: Save new Makefile version
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Makefile version must be updated
if: env.oldMakeVersion == env.newMakeVersion
run: exit 1
- name: All new versions must match
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
run: exit 1
prod:
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
@ -72,7 +23,7 @@ jobs:
run: npm run ropm
- name: Build app for production
run: npm run build-prod
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging

View File

@ -3,7 +3,7 @@ name: deploy-api-docs
on:
push:
branches: ["unstable"]
branches: ["master"]
paths: ["docs/**"] # only run if the docs are updated
# Allows you to run this workflow manually from the Actions tab
@ -32,7 +32,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Setup Pages
uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382 # v3
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
- name: Upload artifact
uses: actions/upload-pages-artifact@a753861a5debcf57bf8b404356158c8e1e33150c # v2
with:
@ -40,4 +40,4 @@ jobs:
path: "docs/api"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@9dbe3824824f8a1377b8e298bafde1a50ede43e5 # v2
uses: actions/deploy-pages@13b55b33dd8996121833dbc1db458c793a334630 # v3

View File

@ -2,7 +2,6 @@ name: lint
on:
pull_request:
jobs:
brightscript:
runs-on: ubuntu-latest
@ -79,4 +78,4 @@ jobs:
- name: Install roku package dependencies
run: npx ropm install
- name: Check markdown files for spelling errors
run: npm run lint-spelling
run: npm run lint-spelling

80
.github/workflows/release-prep.yml vendored Normal file
View File

@ -0,0 +1,80 @@
# All of the jobs in this workflow will only run if the PR that triggered it has a 'release-prep' label
name: release-prep
on:
pull_request:
types: [labeled, opened, reopened, synchronize]
jobs:
version-check:
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- name: DEBUG ${{ github.event.pull_request.base.ref }}
run: echo ${{ github.event.pull_request.base.ref }}
- name: Checkout the branch this PR wants to update
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Install jq to parse json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save old package.json version
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save old manifest version
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
- name: Save old Makefile version
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Checkout PR branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Save new package.json version
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: package.json version must be updated
if: env.oldPackVersion == env.newPackVersion
run: exit 1
- name: Find and save new major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save new manifest version
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Manifest version must be updated
if: env.oldManVersion == env.newManVersion
run: exit 1
- name: Save new Makefile version
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Makefile version must be updated
if: env.oldMakeVersion == env.newMakeVersion
run: exit 1
- name: All new versions must match
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
run: exit 1
build-prod:
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
with:
node-version: "lts/*"
cache: "npm"
- name: NPM install
run: npm ci
- name: Install roku module dependencies
run: npm run ropm
- name: Build app for production
run: npm run build-prod
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging
if-no-files-found: error

View File

@ -27,7 +27,7 @@ jobs:
if: env.BRANCH_NAME == 'master'
run: npm run build-prod
- name: Use Java 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
with:
distribution: "temurin"
java-version: "17"

View File

@ -18,4 +18,4 @@
"docs/api/**": true
},
"brightscriptcomment.addExtraAtStartAndEnd": false
}
}

View File

@ -3,7 +3,7 @@
# If you want to get_images, you'll also need convert from ImageMagick
##########################################################################
VERSION := 1.6.6
VERSION := 2.0.0
## usage

View File

@ -4,7 +4,7 @@
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]">
<Poster id="backdrop" width="290" height="425" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
<Poster id="itemPoster" width="290" height="425" loadDisplayMode="scaleToZoom">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[201, 0]">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" opacity=".99" translation="[201, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>

View File

@ -83,7 +83,7 @@ end sub
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
startLoadingSpinner()
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
m.top.HomeLibraryItem = m.top.parentItem.Id
@ -227,7 +227,7 @@ sub loadInitialItems()
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.spinner.visible = true
startLoadingSpinner(false)
m.loadItemsTask.control = "RUN"
SetUpOptions()
end sub
@ -444,6 +444,7 @@ sub ItemDataLoaded(msg)
if itemData = invalid
m.Loading = false
stopLoadingSpinner()
return
end if
@ -462,7 +463,7 @@ sub ItemDataLoaded(msg)
m.genreList.setFocus(true)
m.loading = false
m.spinner.visible = false
stopLoadingSpinner()
return
end if
@ -493,6 +494,7 @@ sub ItemDataLoaded(msg)
end if
m.spinner.visible = false
stopLoadingSpinner()
end sub
'
@ -565,8 +567,9 @@ end sub
'
'Load next set of items
sub loadMoreData()
m.spinner.visible = true
if m.Loading = true then return
startLoadingSpinner(false)
m.Loading = true
m.loadItemsTask.startIndex = m.loadedItems
m.loadItemsTask.observeField("content", "ItemDataLoaded")
@ -854,7 +857,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""

View File

@ -20,7 +20,6 @@
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id="fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id="fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />

View File

@ -80,8 +80,6 @@ sub init()
'set inital counts for overhang before content is loaded.
m.loadItemsTask.totalRecordCount = 0
m.spinner.visible = true
'Get reset folder setting
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
end sub
@ -108,7 +106,7 @@ end sub
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
startLoadingSpinner(false)
if m.top.parentItem.json.Type = "CollectionFolder"
m.top.HomeLibraryItem = m.top.parentItem.Id
@ -215,7 +213,6 @@ sub loadInitialItems()
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.spinner.visible = true
m.loadItemsTask.control = "RUN"
m.getFiltersTask.observeField("filters", "FilterDataLoaded")
@ -431,7 +428,7 @@ sub ItemDataLoaded(msg)
m.genreList.setFocus(true)
m.loading = false
m.spinner.visible = false
stopLoadingSpinner()
' Return focus to options menu if it was opened while library was loading
if m.options.visible
m.options.setFocus(true)
@ -487,7 +484,7 @@ sub ItemDataLoaded(msg)
m.emptyText.visible = true
end if
m.spinner.visible = false
stopLoadingSpinner()
' Return focus to options menu if it was opened while library was loading
if m.options.visible
m.options.setFocus(true)
@ -692,8 +689,9 @@ end sub
'
'Load next set of items
sub loadMoreData()
m.spinner.visible = true
if m.Loading = true then return
startLoadingSpinner(false)
m.Loading = true
m.loadItemsTask.startIndex = m.loadedItems
m.loadItemsTask.observeField("content", "ItemDataLoaded")
@ -941,7 +939,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""

View File

@ -37,7 +37,6 @@
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id="fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 1.00 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id="fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 1.00, 0.00 ]" fieldToInterp="backdrop.opacity" />

View File

@ -79,8 +79,6 @@ sub init()
'set inital counts for overhang before content is loaded.
m.loadItemsTask.totalRecordCount = 0
m.spinner.visible = true
'Get reset folder setting
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
end sub
@ -107,7 +105,7 @@ end sub
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
startLoadingSpinner(false)
if LCase(m.top.parentItem.json.Type) = "collectionfolder"
m.top.HomeLibraryItem = m.top.parentItem.Id
@ -205,7 +203,6 @@ sub loadInitialItems()
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.spinner.visible = true
m.loadItemsTask.control = "RUN"
SetUpOptions()
end sub
@ -334,6 +331,7 @@ sub ItemDataLoaded(msg)
print "itemData=", itemData
if itemData = invalid
m.Loading = false
stopLoadingSpinner()
return
end if
@ -352,7 +350,7 @@ sub ItemDataLoaded(msg)
m.loadedRows = m.loadedItems / m.genreList.numColumns
m.loading = false
m.spinner.visible = false
stopLoadingSpinner()
return
end if
@ -381,8 +379,7 @@ sub ItemDataLoaded(msg)
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
m.emptyText.visible = true
end if
m.spinner.visible = false
stopLoadingSpinner()
end sub
'
@ -563,8 +560,9 @@ end sub
'
'Load next set of items
sub loadMoreData()
m.spinner.visible = true
if m.Loading = true then return
startLoadingSpinner(false)
m.Loading = true
m.loadItemsTask.startIndex = m.loadedItems
m.loadItemsTask.observeField("content", "ItemDataLoaded")
@ -813,7 +811,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""

View File

@ -22,7 +22,6 @@
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id="fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 1.00 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id="fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 1.00, 0.00 ]" fieldToInterp="backdrop.opacity" />

View File

@ -3,6 +3,27 @@ import "pkg:/source/utils/misc.bs"
sub init()
m.top.backgroundColor = "#262626" '"#101010"
m.top.backgroundURI = ""
m.spinner = m.top.findNode("spinner")
end sub
' Triggered when the isLoading boolean component field is changed
sub isLoadingChanged()
m.spinner.visible = m.top.isLoading
end sub
' Triggered when the disableRemote boolean component field is changed
sub disableRemoteChanged()
if m.top.disableRemote
dialog = createObject("roSGNode", "ProgressDialog")
dialog.id = "invisibiledialog"
dialog.visible = false
dialog.opacity = 0
m.top.dialog = dialog
else
if isValid(m.top.dialog)
m.top.dialog.close = true
end if
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean

View File

@ -3,8 +3,11 @@
<children>
<Group id="content" />
<JFOverhang id="overhang" />
<Spinner id="spinner" translation="[897, 477]" visible="false" />
</children>
<interface>
<field id="disableRemote" type="boolean" value="false" onchange="disableRemoteChanged" />
<field id="isLoading" type="boolean" value="false" onchange="isLoadingChanged" />
<field id="exit" type="boolean" alwaysNotify="true" />
</interface>
</component>

View File

@ -2,5 +2,5 @@ sub init()
m.top.poster.uri = "pkg:/images/spinner.png"
m.top.control = "start"
m.top.clockwise = true
m.top.spinInterval = 3
m.top.spinInterval = 1
end sub

View File

@ -5,7 +5,7 @@ sub init()
m.top.id = "OKDialog"
m.top.height = 900
m.top.title = "What's New?"
m.top.title = m.global.app.version + " - " + tr("What's New?")
m.top.buttons = [tr("OK")]
dialogStyles = {
@ -21,7 +21,7 @@ sub init()
}
}
whatsNewList = ParseJSON(ReadAsciiFile("pkg:/source/static/whatsNew.json"))
whatsNewList = ParseJSON(ReadAsciiFile("pkg:/source/static/whatsNew/" + m.global.app.version.ToStr().trim() + ".json"))
for each item in whatsNewList
textLine = m.content.CreateChild("StdDlgMultiStyleTextItem")

View File

@ -5,7 +5,6 @@ sub init()
m.log = log.Logger("SetServerScreen")
m.top.setFocus(true)
m.spinner = m.top.findNode("spinner")
m.serverPicker = m.top.findNode("serverPicker")
m.serverUrlTextbox = m.top.findNode("serverUrlTextbox")
m.serverUrlContainer = m.top.findNode("serverUrlContainer")
@ -76,7 +75,7 @@ sub ScanForServers()
'run the task
m.ssdpScanner.observeField("content", "ScanForServersComplete")
m.ssdpScanner.control = "RUN"
m.spinner.visible = true
startLoadingSpinner(false)
end sub
sub ScanForServersComplete(event)
@ -109,7 +108,7 @@ sub ScanForServersComplete(event)
end if
m.serverPicker.content = items
m.spinner.visible = false
stopLoadingSpinner()
'if we have at least one server, focus on the server picker
if m.servers.Count() > 0

View File

@ -15,7 +15,6 @@
</LayoutGroup>
<!--background for server picker-->
<Rectangle color="0x00000020" width="1620" height="400">
<Spinner id="spinner" translation="[717, 136]" />
<MarkupList id="serverPicker" translation="[50, 20]" itemComponentName="JFServer" itemSpacing="[0, 10]" itemSize="[1520, 100]" numRows="3" vertFocusAnimationStyle="floatingFocus" />
</Rectangle>

View File

@ -1,4 +1,5 @@
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/misc.bs"
sub init()
m.log = log.Logger("SceneManager")
@ -120,7 +121,7 @@ sub popScene()
' Exit app if the stack is empty after removing group
m.scene.exit = true
end if
stopLoadingSpinner()
end sub

View File

@ -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.

View File

@ -2,8 +2,12 @@
<component name="Home" extends="JFScreen">
<children>
<Poster id="backdrop" loadDisplayMode="scaleToZoom" width="1920" height="1200" />
<HomeRows id="homeRows" />
<HomeRows id="homeRows" focusBitmapBlendColor="0xFFFFFF00" />
<OptionsSlider id="options" />
<Animation id="fadeInFocusBitmap" delay="1" duration=".2" repeat="false" easeFunction="inQuad">
<ColorFieldInterpolator id="fadeInFocusBitmapInterpolator" key="[0.0, 1.0]" keyValue="[0xFFFFFF00, 0xFFFFFFFF]" fieldToInterp="homeRows.focusBitmapBlendColor" />
</Animation>
</children>
<interface>
<field id="selectedItem" alias="homeRows.selectedItem" />

View File

@ -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

View File

@ -2,6 +2,7 @@
<component name="HomeRow" extends="ContentNode">
<interface>
<field id="imageWidth" type="integer" value="464" />
<field id="cursorSize" type="array" value="[464, 331]" />
<field id="usePoster" type="bool" value="false" />
</interface>
</component>

View File

@ -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.homeSectionIndexes = {
count: 0
}
m.top.content = CreateObject("roSGNode", "ContentNode")
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,239 @@ 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
' Add sections in order based on user settings
' calculate expected row count by processing homesections
for i = 0 to 6
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
sectionLoaded = addHomeSection(content, sizeArray, sectionName)
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 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 = 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")
m.top.rowItemSize = sizeArray
m.top.content = content
' Start the timer for creating the content rows before we set the cursor size
m.loadingTimer.control = "start"
end sub
' Removes a home section from the home rows
sub removeHomeSection(sectionType as string)
sectionName = LCase(sectionType)
' 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 = []
removedSectionIndex = m.homeSectionIndexes[sectionName]
processUserSections()
end sub
if not isValid(removedSectionIndex) then return
' 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(" ", "")
for each section in m.homeSectionIndexes
if m.homeSectionIndexes[section] > removedSectionIndex
m.homeSectionIndexes[section]--
sectionIndex = 0
indexLatestMediaSection = 0
for i = 0 to 6
settingSectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
if settingSectionName = "latestmedia"
indexLatestMediaSection = i
end if
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)
m.top.content.removeChildIndex(removedSectionIndex)
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(sectionIndexToRemove)
setRowItemSize()
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
' 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
' 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++
' We don't refresh library data, so if section already exists, exit
if sectionExists(sectionName)
return
end if
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 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 +310,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
sectionName = tr("Favorites")
rowIndex = m.homeSectionIndexes.favorites
if itemData.count() < 1
removeHomeSection("favorites")
return
else
' remake row using the new data
row = CreateObject("roSGNode", "HomeRow")
row.title = tr("Favorites")
for each item in itemData
usePoster = true
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
usePoster = false
end if
item.usePoster = usePoster
item.imageWidth = row.imageWidth
row.appendChild(item)
end for
' replace the old row
m.top.content.replaceChild(row, rowIndex)
end if
end sub
sub updateContinueWatchingItems()
itemData = m.LoadContinueWatchingTask.content
m.LoadContinueWatchingTask.unobserveField("content")
m.LoadContinueWatchingTask.content = []
if itemData = invalid then return
if itemData.count() < 1
removeHomeSection("resume")
if not isValidAndNotEmpty(itemData)
removeHomeSection(sectionName)
return
end if
' 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
usePoster = true
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
usePoster = false
end if
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 = []
sectionName = tr("Continue Watching")
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 = 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 +483,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
metadata = {
"title": lib.name,
"contentType": lib.json.CollectionType
}
loadLatest.metadata = metadata
loadLatest.observeField("content", "updateLatestItems")
loadLatest.control = "RUN"
end if
for each item in itemData
item.usePoster = row.usePoster
item.imageWidth = row.imageWidth
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, 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
imagesize = homeRowItemSizes.WIDE_POSTER
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
for each item in itemData
item.usePoster = row.usePoster
item.imageWidth = row.imageWidth
row.appendChild(item)
end for
' remake row using new data
row = CreateObject("roSGNode", "HomeRow")
row.title = sectionName
row.imageWidth = imagesize[0]
row.cursorSize = imagesize
row.usePoster = true
rowIndex = m.homeSectionIndexes[sectionName]
' Replace the old row
if isValid(rowIndex)
updateSizeArray(itemSize, rowIndex, "replace")
m.top.content.replaceChild(row, rowIndex)
return
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)
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
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])
' 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
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

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/misc.bs"
sub init()
m.EPGLaunchCompleteSignaled = false
m.scheduleGrid = m.top.findNode("scheduleGrid")
@ -26,8 +28,6 @@ sub init()
m.top.lastFocus = m.scheduleGrid
m.channelIndex = {}
m.spinner = m.top.findNode("spinner")
end sub
sub channelFilterSet()
@ -48,14 +48,14 @@ sub channelsearchTermSet()
if LCase(m.top.searchTerm) = LCase(tr("all")) or m.LoadChannelsTask.searchTerm = LCase(tr("all"))
m.top.searchTerm = " "
m.LoadChannelsTask.searchTerm = " "
m.spinner.visible = true
startLoadingSpinner()
m.LoadChannelsTask.control = "RUN"
'filter if the searterm is not invalid
else if m.top.searchTerm <> invalid and LCase(m.LoadChannelsTask.searchTerm) <> LCase(m.top.searchTerm)
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
m.LoadChannelsTask.searchTerm = m.top.searchTerm
m.spinner.visible = true
startLoadingSpinner()
m.LoadChannelsTask.control = "RUN"
end if
@ -125,7 +125,7 @@ sub onScheduleLoaded()
m.scheduleGrid.showLoadingDataFeedback = false
m.scheduleGrid.setFocus(true)
m.LoadScheduleTask.schedule = []
m.spinner.visible = false
stopLoadingSpinner()
end sub
sub onProgramFocused()

View File

@ -12,7 +12,6 @@
programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000"
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
/>
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad">
<Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" />
</Animation>

View File

@ -1,8 +1,11 @@
import "pkg:/source/utils/misc.bs"
sub init()
m.top.optionsAvailable = false
end sub
sub itemContentChanged()
stopLoadingSpinner()
m.top.findNode("UserRow").ItemContent = m.top.itemContent
redraw()
end sub

View File

@ -21,8 +21,6 @@ sub init()
m.buttonGrp.setFocus(true)
m.top.lastFocus = m.buttonGrp
m.spinner = m.top.findNode("spinner")
m.top.observeField("itemContent", "itemContentChanged")
end sub
@ -150,7 +148,7 @@ sub itemContentChanged()
SetUpVideoOptions(itemData.mediaSources)
SetUpAudioOptions(itemData.mediaStreams)
m.buttonGrp.visible = true
m.spinner.visible = false
stopLoadingSpinner()
end sub

View File

@ -40,7 +40,6 @@
</LayoutGroup>
</LayoutGroup>
<MovieOptions id="movieOptions" visible="false" />
<Spinner id="spinner" translation="[900, 450]" visible="true" />
<!-- "Cast and Crew" row -->
<extrasSlider id="movieExtras" />

View File

@ -12,8 +12,6 @@ sub init()
m.songListRect = m.top.FindNode("songListRect")
m.songList.observeField("doneLoading", "onDoneLoading")
m.spinner = m.top.findNode("spinner")
m.spinner.visible = true
m.dscr = m.top.findNode("overview")
createDialogPallete()
@ -99,8 +97,6 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if m.spinner.visible then return false
if key = "options"
if m.dscr.isTextEllipsized
createFullDscrDlg()
@ -169,9 +165,5 @@ end sub
sub onDoneLoading()
m.songList.unobservefield("doneLoading")
m.spinner.visible = false
end sub
sub OnScreenHidden()
m.spinner.visible = false
stopLoadingSpinner()
end sub

View File

@ -20,7 +20,6 @@
</LayoutGroup>
</LayoutGroup>
</LayoutGroup>
<Spinner id="spinner" translation="[920, 540]" visible="false" />
</children>
<interface>
<field id="pageContent" type="node" onChange="pageContentChanged" />

View File

@ -76,6 +76,7 @@ sub OnScreenShown()
m.overhang.isVisible = false
m.overhang.opacity = "1"
end if
stopLoadingSpinner()
end sub
sub OnScreenHidden()

View File

@ -447,6 +447,7 @@ sub loadButtons()
end sub
sub onAudioStreamLoaded()
stopLoadingSpinner()
data = m.LoadAudioStreamTask.content[0]
m.LoadAudioStreamTask.unobserveField("content")
if data <> invalid and data.count() > 0

View File

@ -11,8 +11,6 @@ sub init()
m.songListRect = m.top.FindNode("songListRect")
m.songList.observeField("doneLoading", "onDoneLoading")
m.spinner = m.top.findNode("spinner")
m.spinner.visible = true
m.dscr = m.top.findNode("overview")
createDialogPallete()
@ -98,8 +96,6 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if m.spinner.visible then return false
if key = "options"
if m.dscr.isTextEllipsized
createFullDscrDlg()
@ -160,9 +156,5 @@ end sub
sub onDoneLoading()
m.songList.unobservefield("doneLoading")
m.spinner.visible = false
end sub
sub OnScreenHidden()
m.spinner.visible = false
stopLoadingSpinner()
end sub

View File

@ -19,7 +19,6 @@
</LayoutGroup>
</LayoutGroup>
</LayoutGroup>
<Spinner id="spinner" translation="[920, 540]" visible="false" />
</children>
<interface>
<field id="pageContent" type="node" onChange="pageContentChanged" />

View File

@ -41,6 +41,7 @@ sub itemContentChanged()
end sub
sub onPhotoLoaded()
stopLoadingSpinner()
if m.LoadLibrariesTask.results <> invalid
photo = m.top.findNode("photo")
photo.uri = m.LoadLibrariesTask.results

View File

@ -6,7 +6,6 @@ import "pkg:/source/utils/deviceCapabilities.bs"
sub init()
m.top.optionsAvailable = false
m.searchSpinner = m.top.findnode("searchSpinner")
m.searchSelect = m.top.findnode("searchSelect")
m.searchTask = CreateObject("roSGNode", "SearchTask")
@ -20,12 +19,12 @@ sub searchMedias()
query = m.top.searchAlpha
'if user deletes the search string hide the spinner
if query.len() = 0
m.searchSpinner.visible = false
stopLoadingSpinner()
end if
'if search task is running and user selectes another letter stop the search and load the next letter
m.searchTask.control = "stop"
if query <> invalid and query <> ""
m.searchSpinner.visible = true
startLoadingSpinner(false)
end if
m.searchTask.observeField("results", "loadResults")
m.searchTask.query = query
@ -37,7 +36,7 @@ end sub
sub loadResults()
m.searchTask.unobserveField("results")
m.searchSpinner.visible = false
stopLoadingSpinner()
m.searchSelect.itemdata = m.searchTask.results
m.searchSelect.query = m.top.SearchAlpha
m.searchHelpText.visible = false

View File

@ -10,7 +10,6 @@
<SearchRow id="searchSelect" visible="true" focusable="true" />
</LayoutGroup>
<OptionsSlider id="options" />
<Spinner id="searchSpinner" visible="false" translation="[1050, 500]" />
</children>
<interface>
<field id="query" type="string" alwaysNotify="true" />

View File

@ -297,6 +297,8 @@ sub onVideoContentLoaded()
videoContent = m.LoadMetaDataTask.content
m.LoadMetaDataTask.content = []
stopLoadingSpinner()
' If we have nothing to play, return to previous screen
if not isValid(videoContent)
showPlaybackErrorDialog(tr("There was an error retrieving the data for this item from the server."))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More