Redesign Search page with voice search (#593)

This commit is contained in:
candry7731 2022-09-05 01:50:13 -05:00 committed by GitHub
parent b348f3c96a
commit 621e53e647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 599 additions and 181 deletions

View File

@ -25,7 +25,17 @@ sub init()
m.itemGrid.observeField("itemFocused", "onItemFocused") m.itemGrid.observeField("itemFocused", "onItemFocused")
m.itemGrid.observeField("itemSelected", "onItemSelected") m.itemGrid.observeField("itemSelected", "onItemSelected")
m.itemGrid.observeField("AlphaSelected", "onItemAlphaSelected") m.itemGrid.observeField("alphaSelected", "onItemalphaSelected")
'Voice filter setup
m.voiceBox = m.top.findNode("voiceBox")
m.voiceBox.voiceEnabled = true
m.voiceBox.active = true
m.voiceBox.observeField("text", "onvoiceFilter")
'set voice help text
m.voiceBox.hintText = tr("Use voice remote to search")
'backdrop
m.newBackdrop.observeField("loadStatus", "newBGLoaded") m.newBackdrop.observeField("loadStatus", "newBGLoaded")
'Background Image Queued for loading 'Background Image Queued for loading
@ -45,19 +55,35 @@ sub init()
m.spinner = m.top.findNode("spinner") m.spinner = m.top.findNode("spinner")
m.spinner.visible = true m.spinner.visible = true
m.Alpha = m.top.findNode("AlphaMenu") m.Alpha = m.top.findNode("AlphaMenu")
m.AlphaSelected = m.top.findNode("AlphaSelected") m.AlphaSelected = m.top.findNode("AlphaSelected")
'Get reset folder setting 'Get reset folder setting
m.resetGrid = get_user_setting("itemgrid.reset") = "true" m.resetGrid = get_user_setting("itemgrid.reset") = "true"
'Check if device has voice remote
devinfo = CreateObject("roDeviceInfo")
m.deviFeature = devinfo.HasFeature("voice_remote")
m.micButton = m.top.findNode("micButton")
m.micButtonText = m.top.findNode("micButtonText")
'Hide voice search if device does not have voice remote
if m.deviFeature = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
end sub end sub
' '
'Load initial set of Data 'Load initial set of Data
sub loadInitialItems() sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder" if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
m.top.HomeLibraryItem = m.top.parentItem.Id m.top.HomeLibraryItem = m.top.parentItem.Id
end if end if
if m.top.parentItem.backdropUrl <> invalid if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl) SetBackground(m.top.parentItem.backdropUrl)
end if end if
@ -66,6 +92,14 @@ sub loadInitialItems()
if m.top.parentItem.collectionType = "livetv" if m.top.parentItem.collectionType = "livetv"
' Translate between app and server nomenclature ' Translate between app and server nomenclature
viewSetting = get_user_setting("display.livetv.landing") viewSetting = get_user_setting("display.livetv.landing")
'Move mic to be visiable on TV Guide screen
if m.deviFeature = true
m.micButton.translation = "[1540, 92]"
m.micButtonText.visible = true
m.micButtonText.translation = "[1600,130]"
m.micButtonText.font.size = 22
m.micButtonText.text = tr("Search")
end if
if viewSetting = "guide" if viewSetting = "guide"
m.view = "tvGuide" m.view = "tvGuide"
else else
@ -112,24 +146,31 @@ sub loadInitialItems()
end if end if
updateTitle() updateTitle()
m.loadItemsTask.nameStartsWith = m.top.AlphaSelected m.loadItemsTask.nameStartsWith = m.top.alphaSelected
m.loadItemsTask.searchTerm = m.voiceBox.text
m.emptyText.visible = false m.emptyText.visible = false
m.loadItemsTask.sortField = m.sortField m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0 m.loadItemsTask.startIndex = 0
' Load Item Types ' Load Item Types
if m.top.parentItem.collectionType = "movies" if getCollectionType() = "movies"
m.loadItemsTask.itemType = "Movie" m.loadItemsTask.itemType = "Movie"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "tvshows" else if getCollectionType() = "tvshows"
m.loadItemsTask.itemType = "Series" m.loadItemsTask.itemType = "Series"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "music" else if getCollectionType() = "music"
' Default Settings ' Default Settings
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]" if m.voiceBox.text <> ""
m.itemGrid.itemSpacing = "[ 0, 20]" m.loadItemsTask.recursive = true
else
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]"
end if
m.loadItemsTask.itemType = "MusicArtist,MusicAlbum" m.loadItemsTask.itemType = "MusicArtist,MusicAlbum"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
@ -143,19 +184,21 @@ sub loadInitialItems()
m.loadItemsTask.recursive = true m.loadItemsTask.recursive = true
end if end if
else if m.top.parentItem.collectionType = "livetv" else if m.top.parentItem.collectionType = "livetv"
m.loadItemsTask.itemType = "LiveTV" m.loadItemsTask.itemType = "TvChannel"
m.loadItemsTask.itemId = " "
' For LiveTV, we want to "Fit" the item images, not zoom ' For LiveTV, we want to "Fit" the item images, not zoom
m.top.imageDisplayMode = "scaleToFit" m.top.imageDisplayMode = "scaleToFit"
if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv" if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv"
showTvGuide() showTvGuide()
end if end if
else if m.top.parentItem.collectionType = "CollectionFolder" or m.top.parentItem.type = "CollectionFolder" or m.top.parentItem.collectionType = "boxsets" or m.top.parentItem.Type = "Boxset" or m.top.parentItem.Type = "Folder" or m.top.parentItem.Type = "Channel" else if m.top.parentItem.collectionType = "CollectionFolder" or m.top.parentItem.type = "CollectionFolder" or m.top.parentItem.collectionType = "boxsets" or m.top.parentItem.Type = "Boxset" or m.top.parentItem.Type = "Boxsets" or m.top.parentItem.Type = "Folder" or m.top.parentItem.Type = "Channel"
' Non-recursive, to not show subfolder contents if m.voiceBox.text <> ""
m.loadItemsTask.recursive = false m.loadItemsTask.recursive = true
else if m.top.parentItem.Type = "Channel" else
m.top.imageDisplayMode = "scaleToFit" ' non recursive for collections (folders, boxsets, photo albums, etc)
m.loadItemsTask.recursive = false
end if
else if m.top.parentItem.json.type = "Studio" else if m.top.parentItem.json.type = "Studio"
m.loadItemsTask.itemId = m.top.parentItem.parentFolder m.loadItemsTask.itemId = m.top.parentItem.parentFolder
m.loadItemsTask.itemType = "Series,Movie" m.loadItemsTask.itemType = "Series,Movie"
@ -491,13 +534,33 @@ sub onItemSelected()
m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected) m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected)
end sub end sub
sub onItemAlphaSelected() sub onItemalphaSelected()
m.loadedRows = 0 if m.top.alphaSelected <> ""
m.loadedItems = 0 m.loadedRows = 0
m.data = CreateObject("roSGNode", "ContentNode") m.loadedItems = 0
m.itemGrid.content = m.data m.data = CreateObject("roSGNode", "ContentNode")
m.spinner.visible = true m.itemGrid.content = m.data
loadInitialItems() m.loadItemsTask.searchTerm = ""
m.VoiceBox.text = ""
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
m.spinner.visible = true
loadInitialItems()
end if
end sub
sub onvoiceFilter()
if m.VoiceBox.text <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = m.voiceBox.text
m.loadItemsTask.recursive = true
m.spinner.visible = true
loadInitialItems()
end if
end sub end sub
@ -598,6 +661,7 @@ sub optionsClosed()
if m.tvGuide <> invalid if m.tvGuide <> invalid
m.tvGuide.lastFocus.setFocus(true) m.tvGuide.lastFocus.setFocus(true)
end if end if
end sub end sub
sub showTVGuide() sub showTVGuide()
@ -608,6 +672,7 @@ sub showTVGuide()
m.tvGuide.observeField("focusedChannel", "onChannelFocused") m.tvGuide.observeField("focusedChannel", "onChannelFocused")
end if end if
m.tvGuide.filter = m.filter m.tvGuide.filter = m.filter
m.tvGuide.searchTerm = m.voiceBox.text
m.top.appendChild(m.tvGuide) m.top.appendChild(m.tvGuide)
m.tvGuide.lastFocus.setFocus(true) m.tvGuide.lastFocus.setFocus(true)
end sub end sub
@ -629,6 +694,12 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false if not press then return false
topGrp = m.top.findNode("itemGrid") topGrp = m.top.findNode("itemGrid")
searchGrp = m.top.findNode("voiceBox")
if key = "left" and searchGrp.isinFocusChain()
topGrp.setFocus(true)
searchGrp.setFocus(false)
end if
if key = "options" if key = "options"
if m.options.visible = true if m.options.visible = true
m.options.visible = false m.options.visible = false
@ -655,9 +726,13 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.visible = false m.options.visible = false
optionsClosed() optionsClosed()
return true return true
else
m.global.sceneManager.callfunc("popScene")
m.loadItemsTask.control = "stop"
return true
end if end if
else if key = "play" or key = "OK" else if key = "play" or key = "OK"
markupGrid = m.top.getChild(2) markupGrid = m.top.findNode("itemGrid")
itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused) itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused)
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode") if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
@ -673,9 +748,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "left" and topGrp.isinFocusChain() else if key = "left" and topGrp.isinFocusChain()
m.top.alphaActive = true m.top.alphaActive = true
topGrp.setFocus(false) topGrp.setFocus(false)
alpha = m.Alpha.getChild(0).findNode("Alphamenu") alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true) alpha.setFocus(true)
return true return true
else if key = "right" and m.Alpha.isinFocusChain() else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false m.top.alphaActive = false
m.Alpha.setFocus(false) m.Alpha.setFocus(false)
@ -690,6 +766,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if end if
end if end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""
m.top.alphaSelected = ""
m.loadItemsTask.filter = "All"
m.filter = "All"
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
return true
end if
return false return false
end function end function
@ -699,9 +789,11 @@ sub updateTitle()
else if m.filter = "Favorites" else if m.filter = "Favorites"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)") m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)")
end if end if
if m.voiceBox.text <> ""
if m.top.AlphaSelected <> "" m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.searchTerm + ")"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Filtered)") end if
if m.top.alphaSelected <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.nameStartsWith + ")"
end if end if
if m.options.view = "Networks" or m.view = "Networks" if m.options.view = "Networks" or m.view = "Networks"

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="ItemGrid" extends="JFGroup"> <component name="ItemGrid" extends="JFGroup">
<children> <children>
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x262626ff" translation = "[25, 75]" />
<poster id="backdrop" <poster id="backdrop"
loadDisplayMode="scaleToFill" loadDisplayMode="scaleToFill"
width="1920" width="1920"
@ -23,10 +24,12 @@
vertFocusAnimationStyle = "fixed" vertFocusAnimationStyle = "fixed"
itemSize = "[ 290, 425 ]" itemSize = "[ 290, 425 ]"
itemSpacing = "[ 0, 45 ]" itemSpacing = "[ 0, 45 ]"
drawFocusFeedback = "false" /> drawFocusFeedback = "false" />
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1920" horizAlign="center" vertAlign="center" height="64" visible="false" /> <Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" /> <ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[920, 540]" /> <Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear" > <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 = "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" /> <FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />

View File

@ -1,7 +1,7 @@
sub init() sub init()
m.buttons = m.top.findNode("buttons") m.buttons = m.top.findNode("buttons")
m.buttons.buttons = [tr("TAB_VIEW"), tr("TAB_SORT"), tr("TAB_FILTER")] m.buttons.buttons = [tr("View"), tr("Sort"), tr("Filter")]
m.buttons.selectedIndex = 1 m.buttons.selectedIndex = 1
m.buttons.setFocus(true) m.buttons.setFocus(true)

View File

@ -1,9 +1,15 @@
sub init() sub init()
m.top.functionName = "loadItems" m.top.functionName = "loadItems"
m.top.limit = 60
usersettingLimit = get_user_setting("itemgrid.Limit")
if usersettingLimit <> invalid
m.top.limit = usersettingLimit
end if
end sub end sub
sub loadItems() sub loadItems()
results = [] results = []
sort_field = m.top.sortField sort_field = m.top.sortField
@ -26,15 +32,30 @@ sub loadItems()
StudioIds: m.top.studioIds, StudioIds: m.top.studioIds,
genreIds: m.top.genreIds genreIds: m.top.genreIds
} }
' Handle special case when getting names starting with numeral ' Handle special case when getting names starting with numeral
if m.top.NameStartsWith <> "" if m.top.NameStartsWith <> ""
if m.top.NameStartsWith = "#" if m.top.NameStartsWith = "#"
params.NameLessThan = "A" if m.top.ItemType = "LiveTV" or m.top.ItemType = "TvChannel"
params.searchterm = "A"
params.append({ parentid: " " })
else
params.NameLessThan = "A"
end if
else else
params.NameStartsWith = m.top.nameStartsWith if m.top.ItemType = "LiveTV" or m.top.ItemType = "TvChannel"
params.searchterm = m.top.nameStartsWith
params.append({ parentid: " " })
else
params.NameStartsWith = m.top.nameStartsWith
end if
end if end if
end if end if
if m.top.searchTerm <> ""
params.searchTerm = m.top.searchTerm
end if
filter = m.top.filter filter = m.top.filter
if filter = "All" or filter = "all" if filter = "All" or filter = "all"
' do nothing ' do nothing
@ -71,7 +92,7 @@ sub loadItems()
tmp = CreateObject("roSGNode", "MovieData") tmp = CreateObject("roSGNode", "MovieData")
else if item.Type = "Series" else if item.Type = "Series"
tmp = CreateObject("roSGNode", "SeriesData") tmp = CreateObject("roSGNode", "SeriesData")
else if item.Type = "BoxSet" else if item.Type = "BoxSet" or item.Type = "ManualPlaylistsFolder"
tmp = CreateObject("roSGNode", "CollectionData") tmp = CreateObject("roSGNode", "CollectionData")
else if item.Type = "TvChannel" else if item.Type = "TvChannel"
tmp = CreateObject("roSGNode", "ChannelData") tmp = CreateObject("roSGNode", "ChannelData")

View File

@ -12,6 +12,7 @@
<field id="nameStartsWith" type="string" value="" /> <field id="nameStartsWith" type="string" value="" />
<field id="recursive" type="boolean" value="true" /> <field id="recursive" type="boolean" value="true" />
<field id="filter" type="string" value="All" /> <field id="filter" type="string" value="All" />
<field id="searchTerm" type="string" value="" />
<field id="studioIds" type="string" value="" /> <field id="studioIds" type="string" value="" />
<field id="genreIds" type="string" value="" /> <field id="genreIds" type="string" value="" />
<field id="view" type="string" value="" /> <field id="view" type="string" value="" />

View File

@ -1,6 +1,7 @@
sub init() sub init()
m.title = m.top.findNode("title") m.title = m.top.findNode("title")
m.staticTitle = m.top.findNode("staticTitle") m.staticTitle = m.top.findNode("staticTitle")
m.series = m.top.findNode("Series")
m.poster = m.top.findNode("poster") m.poster = m.top.findNode("poster")
m.backdrop = m.top.findNode("backdrop") m.backdrop = m.top.findNode("backdrop")
@ -39,6 +40,8 @@ sub updateSize()
m.staticTitle.height = m.title.height m.staticTitle.height = m.title.height
m.staticTitle.translation = m.title.translation m.staticTitle.translation = m.title.translation
m.series.maxWidth = maxSize[0]
m.poster.width = int(maxSize[0]) - 4 m.poster.width = int(maxSize[0]) - 4
m.poster.height = int(maxSize[1]) - m.title.height 'Set poster height to available space m.poster.height = int(maxSize[1]) - m.title.height 'Set poster height to available space
@ -54,9 +57,14 @@ sub itemContentChanged() as void
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
m.series.text = itemData.json.Series
m.series.visible = true
else if itemData.json.lookup("Type") = "MusicAlbum" else if itemData.json.lookup("Type") = "MusicAlbum"
m.title.font = "font:SmallestSystemFont" m.title.font = "font:SmallestSystemFont"
m.staticTitle.font = "font:SmallestSystemFont" m.staticTitle.font = "font:SmallestSystemFont"
end if end if
m.staticTitle.text = m.title.text m.staticTitle.text = m.title.text
@ -64,7 +72,7 @@ sub itemContentChanged() as void
if get_user_setting("ui.tvshows.blurunwatched") = "true" if get_user_setting("ui.tvshows.blurunwatched") = "true"
if itemData.json.lookup("Type") = "Episode" if itemData.json.lookup("Type") = "Episode" and itemData.json.userdata <> invalid
if not itemData.json.userdata.played if not itemData.json.userdata.played
imageUrl = imageUrl + "&blur=15" imageUrl = imageUrl + "&blur=15"
end if end if
@ -82,6 +90,7 @@ sub focusChanged()
if m.top.itemHasFocus = true if m.top.itemHasFocus = true
m.title.repeatCount = -1 m.title.repeatCount = -1
m.series.repeatCount = -1
m.staticTitle.visible = false m.staticTitle.visible = false
m.title.visible = true m.title.visible = true
@ -94,6 +103,7 @@ sub focusChanged()
else else
m.title.repeatCount = 0 m.title.repeatCount = 0
m.series.repeatCount = 0
m.staticTitle.visible = true m.staticTitle.visible = true
m.title.visible = false m.title.visible = false
end if end if

View File

@ -2,6 +2,12 @@
<component name="ListPoster" extends="Group"> <component name="ListPoster" extends="Group">
<children> <children>
<Rectangle id="backdrop" /> <Rectangle id="backdrop" />
<ScrollingLabel id="Series"
horizAlign="center"
font="font:SmallSystemFont"
repeatCount="0"
visible="false"
/>
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit" /> <Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit" />
<ScrollingLabel id="title" <ScrollingLabel id="title"
horizAlign="center" horizAlign="center"

View File

@ -154,7 +154,7 @@ sub createDialogPallete()
DialogSecondaryTextColor: "0xf8f8f8ff", DialogSecondaryTextColor: "0xf8f8f8ff",
DialogSecondaryItemColor: "0xcc7ecc4D", DialogSecondaryItemColor: "0xcc7ecc4D",
DialogInputFieldColor: "0x80FF8080", DialogInputFieldColor: "0x80FF8080",
DialogKeyboardColor: "0x80FF804D", KeyboardDialogColor: "0x80FF804D",
DialogFootprintColor: "0x80FF804D" DialogFootprintColor: "0x80FF804D"
} }
end sub end sub

View File

@ -3,48 +3,25 @@ sub init()
m.top.horizAlignment = "center" m.top.horizAlignment = "center"
m.top.vertAlignment = "top" m.top.vertAlignment = "top"
m.top.visible = false m.top.visible = false
m.searchText = m.top.findNode("search_Key")
m.searchText.textEditBox.hintText = tr("Search")
m.searchText.keyGrid.keyDefinitionUri = "pkg:/components/data/CustomAddressKDF.json"
m.searchText.textEditBox.voiceEnabled = true
m.searchText.textEditBox.active = true
m.searchText.ObserveField("text", "searchMedias")
m.searchSelect = m.top.findNode("searchSelect")
'set lable text
m.label = m.top.findNode("text")
m.label.text = tr("Search now")
show_dialog()
end sub end sub
function onKeyEvent(key as string, press as boolean) as boolean sub searchMedias()
if not press then return false m.top.search_values = m.searchText.text
if m.top.search_values.len() > 1
if key = "OK" m.searchText.textEditBox.leadingEllipsis = true
' Make a Keyboard Dialog here else
show_dialog() m.searchText.textEditBox.leadingEllipsis = false
return true
end if end if
return false
end function
function onDialogButton()
d = m.top.getScene().dialog
button_text = d.buttons[d.buttonSelected]
if button_text = tr("Search")
m.top.search_value = d.text
dismiss_dialog()
return true
else if button_text = tr("Cancel")
dismiss_dialog()
return true
end if
return false
end function
sub show_dialog()
dialog = CreateObject("roSGNode", "KeyboardDialog")
dialog.title = tr("Search")
dialog.buttons = [tr("Search"), tr("Cancel")]
m.top.getScene().dialog = dialog
dialog.observeField("buttonselected", "onDialogButton")
end sub
sub dismiss_dialog()
m.top.getScene().dialog.close = true
end sub end sub

View File

@ -1,11 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="SearchBox" extends="LayoutGroup"> <component name="SearchBox" extends="LayoutGroup">
<children> <children>
<Label text="Search" /> <Label id = "text" text="" visible="false" />
<TextEditBox id="search-input" width="800" maxTextLength="75" /> <DynamicMiniKeyboard id="search_Key" />
</children> </children>
<interface> <interface>
<field id="search_value" type="string" alwaysNotify="true" /> <field id="search_values" type="string" alwaysNotify="true" />
</interface> </interface>
<script type="text/brightscript" uri="SearchBox.brs" /> <script type="text/brightscript" uri="SearchBox.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component> </component>

View File

@ -45,13 +45,18 @@ end function
sub show_dialog(configField) sub show_dialog(configField)
dialog = createObject("roSGNode", "KeyboardDialog") dialog = createObject("roSGNode", "StandardKeyboardDialog")
m.configField = configField m.configField = configField
dialog.title = "Enter the " + configField.label dialog.title = "Enter the " + configField.label
dialog.buttons = [tr("OK"), tr("Cancel")] dialog.buttons = [tr("OK"), tr("Cancel")]
m.greenPalette = createObject("roSGNode", "RSGPalette")
m.greenPalette.colors = {
DialogBackgroundColor: "#2A2B2A"
}
dialog.palette = m.greenPalette
if configField.type = "password" if configField.type = "password"
dialog.keyboard.textEditBox.secureMode = true dialog.textEditBox.secureMode = true
end if end if
if configField.value <> "" if configField.value <> ""

View File

@ -107,10 +107,13 @@ sub ScanForServersComplete(event)
end sub end sub
sub ShowKeyboard() sub ShowKeyboard()
dialog = createObject("roSGNode", "KeyboardDialog") dialog = createObject("roSGNode", "StandardKeyboardDialog")
dialog.title = tr("Enter the server name or ip address") dialog.title = tr("Enter the server name or ip address")
dialog.buttons = [tr("OK"), tr("Cancel")] dialog.buttons = [tr("OK"), tr("Cancel")]
dialog.text = m.serverUrlTextbox.text dialog.text = m.serverUrlTextbox.text
greenPalette = createObject("roSGNode", "RSGPalette")
greenPalette.colors = { DialogBackgroundColor: "#2A2B2A" }
dialog.palette = greenPalette
m.top.getscene().dialog = dialog m.top.getscene().dialog = dialog
m.dialog = dialog m.dialog = dialog

View File

@ -0,0 +1,96 @@
{
"keyboardWidthFHD": 374,
"keyboardHeightFHD": 409,
"keyboardWidthHD": 250,
"keyboardHeightHD": 273,
"sections": [
{
"grids": [
{
"rows": [
{
"keys": [
{ "label": "a" },
{ "label": "b" },
{ "label": "c" },
{ "label": "d" },
{ "label": "e" },
{ "label": "f" }
]
},
{
"keys": [
{ "label": "g" },
{ "label": "h" },
{ "label": "i" },
{ "label": "j" },
{ "label": "k" },
{ "label": "l" }
]
},
{
"keys": [
{ "label": "m" },
{ "label": "n" },
{ "label": "o" },
{ "label": "p" },
{ "label": "q" },
{ "label": "r" }
]
},
{
"keys": [
{ "label": "s" },
{ "label": "t" },
{ "label": "u" },
{ "label": "v" },
{ "label": "w" },
{ "label": "x" }
]
},
{
"keys": [
{ "label": "y" },
{ "label": "z" },
{ "label": "1" },
{ "label": "2" },
{ "label": "3" },
{ "label": "4" }
]
},
{
"keys": [
{ "label": "5" },
{ "label": "6" },
{ "label": "7" },
{ "label": "8" },
{ "label": "9" },
{ "label": "0" }
]
},
{
"keys": [
{
"icon": "theme:DKB_ClearKeyBitmap",
"focusIcon": "theme:DKB_ClearKeyFocusBitmap",
"strOut": "clear"
},
{
"icon": "theme:DKB_SpaceKeyBitmap",
"focusIcon": "theme:DKB_SpaceKeyFocusBitmap",
"strOut": "space"
},
{
"icon": "theme:DKB_DeleteKeyBitmap",
"focusIcon": "theme:DKB_DeleteKeyFocusBitmap",
"autoRepeat": 1,
"strOut": "backspace"
}
]
}
]
}
]
}
]
}

View File

@ -11,6 +11,7 @@
<field id="type" type="string" value="Episode" /> <field id="type" type="string" value="Episode" />
<field id="json" type="assocarray" onChange="setFields" /> <field id="json" type="assocarray" onChange="setFields" />
<field id="selectedAudioStreamIndex" type="integer" /> <field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface> </interface>
<script type="text/brightscript" uri="TVEpisodeData.brs" /> <script type="text/brightscript" uri="TVEpisodeData.brs" />
</component> </component>

View File

@ -6,15 +6,41 @@ sub loadChannels()
results = [] results = []
sort_field = m.top.sortField
if m.top.sortAscending = true
sort_order = "Ascending"
else
sort_order = "Descending"
end if
params = { params = {
includeItemTypes: "LiveTvChannel",
SortBy: sort_field,
SortOrder: sort_order,
recursive: m.top.recursive,
UserId: get_setting("active_user") UserId: get_setting("active_user")
} }
' Handle special case when getting names starting with numeral
if m.top.NameStartsWith <> ""
if m.top.NameStartsWith = "#"
params.searchterm = "A"
else
params.searchterm = m.top.nameStartsWith
end if
end if
'Append voice search when there is text
if m.top.searchTerm <> ""
params.searchTerm = m.top.searchTerm
end if
if m.top.filter = "Favorites" if m.top.filter = "Favorites"
params.append({ isFavorite: true }) params.append({ isFavorite: true })
end if end if
url = "LiveTv/Channels" url = Substitute("Users/{0}/Items/", get_setting("active_user"))
resp = APIRequest(url, params) resp = APIRequest(url, params)
data = getJson(resp) data = getJson(resp)
@ -39,7 +65,5 @@ sub loadChannels()
results.push(channel) results.push(channel)
end if end if
end for end for
m.top.channels = results m.top.channels = results
end sub end sub

View File

@ -5,7 +5,11 @@
<field id="limit" type="integer" value="" /> <field id="limit" type="integer" value="" />
<field id="startIndex" type="integer" value="0" /> <field id="startIndex" type="integer" value="0" />
<field id="filter" type="string" value="All" /> <field id="filter" type="string" value="All" />
<field id="searchTerm" type="string" value="" />
<field id="sortField" type="string" value="SortName" />
<field id="sortAscending" type="boolean" value="true" />
<field id="nameStartsWith" type="string" value="" />
<field id="recursive" type="boolean" value="true" />
<!-- Total records available from server--> <!-- Total records available from server-->
<field id="channels" type="array" /> <field id="channels" type="array" />
</interface> </interface>

View File

@ -1,5 +1,4 @@
sub init() sub init()
m.EPGLaunchCompleteSignaled = false m.EPGLaunchCompleteSignaled = false
m.scheduleGrid = m.top.findNode("scheduleGrid") m.scheduleGrid = m.top.findNode("scheduleGrid")
m.detailsPane = m.top.findNode("detailsPane") m.detailsPane = m.top.findNode("detailsPane")
@ -7,7 +6,6 @@ sub init()
m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected") m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected")
m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected") m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected")
m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected") m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected")
m.gridStartDate = CreateObject("roDateTime") m.gridStartDate = CreateObject("roDateTime")
m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800 m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800
m.gridEndDate = createObject("roDateTime") m.gridEndDate = createObject("roDateTime")
@ -28,10 +26,11 @@ sub init()
m.top.lastFocus = m.scheduleGrid m.top.lastFocus = m.scheduleGrid
m.channelIndex = {} m.channelIndex = {}
m.spinner = m.top.findNode("spinner")
end sub end sub
sub channelFilterSet() sub channelFilterSet()
print "Channel Filter set"
m.scheduleGrid.jumpToChannel = 0 m.scheduleGrid.jumpToChannel = 0
if m.top.filter <> invalid and m.LoadChannelsTask.filter <> m.top.filter if m.top.filter <> invalid and m.LoadChannelsTask.filter <> m.top.filter
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop" if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
@ -42,6 +41,19 @@ sub channelFilterSet()
end sub end sub
'Voice Search set
sub channelsearchTermSet()
m.scheduleGrid.jumpToChannel = 0
if m.top.searchTerm <> invalid and m.LoadChannelsTask.searchTerm <> m.top.searchTerm
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
m.LoadChannelsTask.searchTerm = m.top.searchTerm
m.spinner.visible = true
m.LoadChannelsTask.control = "RUN"
end if
end sub
' Initial list of channels loaded ' Initial list of channels loaded
sub onChannelsLoaded() sub onChannelsLoaded()
gridData = createObject("roSGNode", "ContentNode") gridData = createObject("roSGNode", "ContentNode")
@ -49,32 +61,36 @@ sub onChannelsLoaded()
counter = 0 counter = 0
channelIdList = "" channelIdList = ""
for each item in m.LoadChannelsTask.channels 'if search returns channels
gridData.appendChild(item) if m.LoadChannelsTask.channels.count() > 0
m.channelIndex[item.Id] = counter for each item in m.LoadChannelsTask.channels
counter = counter + 1 gridData.appendChild(item)
channelIdList = channelIdList + item.Id + "," m.channelIndex[item.Id] = counter
end for counter = counter + 1
channelIdList = channelIdList + item.Id + ","
end for
m.scheduleGrid.content = gridData
m.scheduleGrid.content = gridData m.LoadScheduleTask = createObject("roSGNode", "LoadScheduleTask")
m.LoadScheduleTask.observeField("schedule", "onScheduleLoaded")
m.LoadScheduleTask = createObject("roSGNode", "LoadScheduleTask") m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString()
m.LoadScheduleTask.observeField("schedule", "onScheduleLoaded") m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString() m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask")
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString() m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded")
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask") m.scheduleGrid.setFocus(true)
m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded") if m.EPGLaunchCompleteSignaled = false
m.top.signalBeacon("EPGLaunchComplete") ' Required Roku Performance monitoring
m.EPGLaunchCompleteSignaled = true
end if
m.LoadChannelsTask.channels = []
m.scheduleGrid.setFocus(true)
if m.EPGLaunchCompleteSignaled = false
m.top.signalBeacon("EPGLaunchComplete") ' Required Roku Performance monitoring
m.EPGLaunchCompleteSignaled = true
end if end if
m.LoadChannelsTask.channels = []
end sub end sub
' When LoadScheduleTask completes (initial or more data) and we have a schedule to display ' When LoadScheduleTask completes (initial or more data) and we have a schedule to display
@ -102,6 +118,7 @@ sub onScheduleLoaded()
m.scheduleGrid.showLoadingDataFeedback = false m.scheduleGrid.showLoadingDataFeedback = false
m.scheduleGrid.setFocus(true) m.scheduleGrid.setFocus(true)
m.LoadScheduleTask.schedule = [] m.LoadScheduleTask.schedule = []
m.spinner.visible = false
end sub end sub
sub onProgramFocused() sub onProgramFocused()
@ -118,7 +135,9 @@ sub onProgramFocused()
m.top.focusedChannel = channel m.top.focusedChannel = channel
' Exit if Channels not yet loaded ' Exit if Channels not yet loaded
if channel = invalid or channel.getChildCount() = 0 if channel = invalid or channel.getChildCount() = 0
m.detailsPane.programDetails = invalid m.detailsPane.programDetails = invalid
return return
end if end if
@ -195,6 +214,22 @@ sub onWatchChannelSelected()
m.top.watchChannel = m.detailsPane.channel m.top.watchChannel = m.detailsPane.channel
end sub end sub
' As user scrolls grid, check if more data requries to be loaded
sub onGridScrolled()
' If we're within 12 hours of end of grid, load next 24hrs of data
if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds()
' Ensure the task is not already (still) running,
if m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.control = "RUN"
end if
end if
end sub
' Handle user selecting "Record Channel" from Program Details ' Handle user selecting "Record Channel" from Program Details
sub onRecordChannelSelected() sub onRecordChannelSelected()
if m.detailsPane.recordSelectedChannel = false then return if m.detailsPane.recordSelectedChannel = false then return
@ -242,27 +277,18 @@ sub onRecordOperationDone()
end if end if
end sub end sub
' As user scrolls grid, check if more data requries to be loaded
sub onGridScrolled()
' If we're within 12 hours of end of grid, load next 24hrs of data
if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds()
' Ensure the task is not already (still) running,
if m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.control = "RUN"
end if
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false if not press then return false
detailsGrp = m.top.findNode("detailsPane")
gridGrp = m.top.findNode("scheduleGrid")
if key = "back" and m.detailsPane.isInFocusChain() if key = "back" and detailsGrp.isInFocusChain()
focusProgramDetails(false) focusProgramDetails(false)
detailsGrp.setFocus(false)
gridGrp.setFocus(true)
return true
else if key = "back"
m.global.sceneManager.callFunc("popScene")
return true return true
end if end if

View File

@ -12,6 +12,7 @@
programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000" programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000"
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555" showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
/> />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" > <Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" >
<Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" /> <Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" />
</Animation> </Animation>
@ -20,6 +21,7 @@
<field id="watchChannel" type="node" alwaysNotify="false" /> <field id="watchChannel" type="node" alwaysNotify="false" />
<field id="focusedChannel" type="node" alwaysNotify="false" /> <field id="focusedChannel" type="node" alwaysNotify="false" />
<field id="filter" type="string" value="All" onChange="channelFilterSet" /> <field id="filter" type="string" value="All" onChange="channelFilterSet" />
<field id="searchTerm" type="string" value="" onChange="channelsearchTermSet" />
</interface> </interface>
<script type="text/brightscript" uri="schedule.brs" /> <script type="text/brightscript" uri="schedule.brs" />
</component> </component>

View File

@ -46,8 +46,9 @@ sub itemContentChanged()
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
' Set default video source ' Set default video source
m.top.selectedVideoStreamId = itemData.MediaSources[0].id if itemData.MediaSources <> invalid
m.top.selectedVideoStreamId = itemData.MediaSources[0].id
end if
' Find first Audio Stream and set that as default ' Find first Audio Stream and set that as default
SetDefaultAudioTrack(itemData) SetDefaultAudioTrack(itemData)

View File

@ -408,7 +408,7 @@ sub onMetaDataLoaded()
if data <> invalid and data.count() > 0 if data <> invalid and data.count() > 0
' Use metadata to load backdrop image ' Use metadata to load backdrop image
if isvalid(data?.json?.ArtistItems?[0].id) if isvalid(data?.json?.ArtistItems?[0]?.id)
m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id
m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded") m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded")
m.LoadBackdropImageTask.control = "RUN" m.LoadBackdropImageTask.control = "RUN"

View File

@ -1,3 +1,60 @@
sub init() sub init()
m.top.optionsAvailable = false m.top.optionsAvailable = false
m.searchSpinner = m.top.findnode("searchSpinner")
m.searchSelect = m.top.findnode("searchSelect")
m.searchTask = CreateObject("roSGNode", "SearchTask")
'set label text
m.searchHelpText = m.top.findNode("SearchHelpText")
m.searchHelpText.text = tr("You can search for Titles, People, Live TV Channels and more")
end sub end sub
sub searchMedias()
query = m.top.searchAlpha
'if user deletes the search string hide the spinner
if query.len() = 0
m.searchSpinner.visible = false
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
end if
m.searchTask.observeField("results", "loadResults")
m.searchTask.query = query
m.top.overhangTitle = tr("Search") + ": " + query
m.searchTask.control = "RUN"
end sub
sub loadResults()
m.searchTask.unobserveField("results")
m.searchSpinner.visible = false
m.searchSelect.itemdata = m.searchTask.results
m.searchSelect.query = m.top.SearchAlpha
m.searchHelpText.visible = false
m.searchAlphabox = m.top.findnode("searchResults")
m.searchAlphabox.translation = "[470, 85]"
end sub
function onKeyEvent(key as string, press as boolean) as boolean
m.searchAlphabox = m.top.findNode("search_Key")
if m.searchAlphabox.textEditBox.hasFocus()
m.searchAlphabox.textEditBox.translation = "[0, -150]"
else
m.searchAlphabox.textEditBox.translation = "[0, 0]"
end if
if key = "left" and m.searchSelect.isinFocusChain() and (m.searchSelect.currFocusColumn = -1 or m.searchSelect.currFocusColumn = 0)
m.searchAlphabox.setFocus(true)
return true
else if key = "right"
m.searchSelect.setFocus(true)
return true
end if
return false
end function

View File

@ -1,13 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="SearchResults" extends="JFGroup"> <component name="searchResults" extends="JFGroup">
<children> <children>
<SearchBox id="SearchBox" visible="true" translation="[960, 145]" /> <Rectangle width="1920" height="1080" color="#000000" opacity="0.75" />
<SearchRow id="SearchSelect" visible="false" /> <LayoutGroup layoutDirection="horiz" id="SearchAlphabox" translation="[70, 120]">
<SearchBox id="SearchBox" visible="true" focusable="true"/>
</LayoutGroup>
<LayoutGroup layoutDirection="vert" id="searchResults" translation="[470, 150]" >
<Label id = "SearchHelpText" text=""/>
<SearchRow id="searchSelect" visible="true" focusable="true"/>
</LayoutGroup>
<OptionsSlider id="options" /> <OptionsSlider id="options" />
<Spinner id = "searchSpinner" visible="false" translation="[1050, 500]"/>
</children> </children>
<interface> <interface>
<field id="query" type="string" alwaysNotify="true" /> <field id="query" type="string" alwaysNotify="true" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="searchAlpha" type="string" alias="SearchBox.search_values" alwaysNotify="true" onChange="searchMedias" />
</interface> </interface>
<script type="text/brightscript" uri="SearchResults.brs" /> <script type="text/brightscript" uri="SearchResults.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component> </component>

View File

@ -11,27 +11,28 @@ sub init()
' TODO - Define a failed to load image background ' TODO - Define a failed to load image background
' m.top.failedBitmapURI ' m.top.failedBitmapURI
m.top.setFocus(true)
end sub end sub
sub updateSize() sub updateSize()
' In search results, rowSize only dictates how many are on screen at once ' In search results, rowSize only dictates how many are on screen at once
m.top.rowSize = 5 m.top.rowSize = 3
dimensions = m.top.getScene().currentDesignResolution dimensions = m.top.getScene().currentDesignResolution
border = 75 border = 50
m.top.translation = [border, border + 115] m.top.translation = [border, border + 115]
textHeight = 80 textHeight = 80
itemWidth = (dimensions["width"] - border * 2) / m.top.rowSize itemWidth = (dimensions["width"] - border) / 6
itemHeight = itemWidth * 1.5 + textHeight itemHeight = itemWidth + (textHeight / 2.3)
m.top.itemSize = [dimensions["width"] - border * 2, itemHeight] m.top.itemSize = [1350, itemHeight] ' this is used for setting the row size
m.top.itemSpacing = [0, 50] m.top.itemSpacing = [0, 105]
m.top.rowItemSize = [itemWidth, itemHeight] m.top.rowItemSize = [itemWidth, itemHeight]
m.top.rowItemSpacing = [0, 0] m.top.rowItemSpacing = [0, 0]
m.top.numRows = 2
m.top.translation = "[12,18]"
end sub end sub
function getData() function getData()
@ -45,16 +46,17 @@ function getData()
' todo - Or get the old data? I can't remember... ' todo - Or get the old data? I can't remember...
data = CreateObject("roSGNode", "ContentNode") data = CreateObject("roSGNode", "ContentNode")
' Do this to keep the ordering, AssociateArrays have no order ' Do this to keep the ordering, AssociateArrays have no order
type_array = ["Movie", "Series", "TvChannel", "Episode", "AlbumArtist", "Album", "Audio", "Person"] type_array = ["Movie", "Series", "TvChannel", "Episode", "MusicArtist", "MusicAlbum", "Audio", "Person", "PlaylistsFolder"]
content_types = { content_types = {
"TvChannel": { "label": "Channels", "count": 0 }, "TvChannel": { "label": "Channels", "count": 0 },
"Movie": { "label": "Movies", "count": 0 }, "Movie": { "label": "Movies", "count": 0 },
"Series": { "label": "Shows", "count": 0 }, "Series": { "label": "Shows", "count": 0 },
"Episode": { "label": "Episodes", "count": 0 }, "Episode": { "label": "Episodes", "count": 0 },
"AlbumArtist": { "label": "Artists", "count": 0 }, "MusicArtist": { "label": "Artists", "count": 0 },
"Album": { "label": "Albums", "count": 0 }, "MusicAlbum": { "label": "Albums", "count": 0 },
"Audio": { "label": "Songs", "count": 0 }, "Audio": { "label": "Songs", "count": 0 },
"Person": { "label": "People", "count": 0 } "Person": { "label": "People", "count": 0 },
"PlaylistsFolder": { "label": "Playlist", "count": 0 }
} }
for each item in itemData.searchHints for each item in itemData.searchHints
@ -84,3 +86,4 @@ sub addRow(data, title, type_filter)
end if end if
end for end for
end sub end sub

View File

@ -4,7 +4,12 @@
<field id="rowSize" type="int" /> <field id="rowSize" type="int" />
<field id="itemData" type="assocarray" onChange="getData" /> <field id="itemData" type="assocarray" onChange="getData" />
<field id="query" type="string" /> <field id="query" type="string" />
<field id="itemSelected" type="int" /> <field id="itemSelected" type="node" alwaysNotify="true" />
</interface> </interface>
<script type="text/brightscript" uri="SearchRow.brs" /> <script type="text/brightscript" uri="SearchRow.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component> </component>

View File

@ -0,0 +1,9 @@
sub init()
m.top.functionName = "search"
end sub
sub search()
if m.top.query <> invalid and m.top.query <> ""
m.top.results = searchMedia(m.top.query)
end if
end sub

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="SearchTask" extends="Task">
<interface>
<field id="query" type="string" />
<field id="results" type="assocarray" />
</interface>
<script type="text/brightscript" uri="SearchTask.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component>

BIN
images/icons/mic_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -552,6 +552,21 @@
<translation>Go to episode</translation> <translation>Go to episode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment> <extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message> </message>
<message>
<source>Use voice remote to search</source>
<translation>Use voice remote to search</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>You can search for Titles, People, Live TV Channels and more</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message> <message>
<source>%1 of %2</source> <source>%1 of %2</source>
<translation>%1 of %2</translation> <translation>%1 of %2</translation>

View File

@ -250,8 +250,29 @@ sub Main (args as dynamic) as void
' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist ] ' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist ]
if node.type = "Series" if node.type = "Series"
group = CreateSeriesDetailsGroup(node) group = CreateSeriesDetailsGroup(node)
else else if node.type = "Movie"
group = CreateMovieDetailsGroup(node) group = CreateMovieDetailsGroup(node)
else if node.type = "MusicArtist"
group = CreateArtistView(node.json)
else if node.type = "MusicAlbum"
group = CreateAlbumView(node.json)
else if node.type = "Audio"
group = CreateAudioPlayerGroup([node.json])
else if node.type = "Person"
group = CreatePersonView(node)
else if node.type = "TvChannel"
group = CreateVideoPlayerGroup(node.id)
sceneManager.callFunc("pushScene", group)
else if node.type = "Episode"
group = CreateVideoPlayerGroup(node.id)
sceneManager.callFunc("pushScene", group)
else if node.type = "Audio"
selectedIndex = msg.getData()
screenContent = msg.getRoSGNode()
group = CreateAudioPlayerGroup([screenContent.albumData.items[node.id]])
else
' TODO - switch on more node types
message_dialog("This type is not yet supported: " + node.type + ".")
end if end if
else if isNodeEvent(msg, "buttonSelected") else if isNodeEvent(msg, "buttonSelected")
' If a button is selected, we have some determining to do ' If a button is selected, we have some determining to do
@ -334,8 +355,8 @@ sub Main (args as dynamic) as void
end if end if
group = CreateSearchPage() group = CreateSearchPage()
sceneManager.callFunc("pushScene", group) sceneManager.callFunc("pushScene", group)
group.findNode("SearchBox").findNode("search-input").setFocus(true) group.findNode("SearchBox").findNode("search_Key").setFocus(true)
group.findNode("SearchBox").findNode("search-input").active = true group.findNode("SearchBox").findNode("search_Key").active = true
else if button.id = "change_server" else if button.id = "change_server"
unset_setting("server") unset_setting("server")
unset_setting("port") unset_setting("port")

View File

@ -446,12 +446,8 @@ end function
function CreateSearchPage() function CreateSearchPage()
' Search + Results Page ' Search + Results Page
group = CreateObject("roSGNode", "SearchResults") group = CreateObject("roSGNode", "searchResults")
options = group.findNode("searchSelect")
search = group.findNode("SearchBox")
search.observeField("search_value", m.port)
options = group.findNode("SearchSelect")
options.observeField("itemSelected", m.port) options.observeField("itemSelected", m.port)
return group return group

View File

@ -34,34 +34,40 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
end function end function
' Search across all libraries ' Search across all libraries
function SearchMedia(query as string) function searchMedia(query as string)
' This appears to be done differently on the web now ' This appears to be done differently on the web now
' For each potential type, a separate query is done: ' For each potential type, a separate query is done:
' varying item types, and artists, and people ' varying item types, and artists, and people
resp = APIRequest(Substitute("Users/{0}/Items", get_setting("active_user")), {
"searchTerm": query,
"IncludePeople": true,
"IncludeMedia": true,
"IncludeShows": true,
"IncludeGenres": false,
"IncludeStudios": false,
"IncludeArtists": false,
"IncludeItemTypes": "TvChannel,Movie,BoxSet,Series,Episode,Video",
"EnableTotalRecordCount": false,
"ImageTypeLimit": 1,
"Recursive": true
})
data = getJson(resp) if query <> ""
results = [] resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), {
for each item in data.Items "searchTerm": query,
tmp = CreateObject("roSGNode", "SearchData") "IncludePeople": true,
tmp.image = PosterImage(item.id) "IncludeMedia": true,
tmp.json = item "IncludeShows": true,
results.push(tmp) "IncludeGenres": true,
end for "IncludeStudios": true,
data.SearchHints = results "IncludeArtists": true,
return data "IncludeItemTypes": "LiveTvChannel,Movie,BoxSet,Series,Episode,Video,Person,Audio,MusicAlbum,MusicArtist,Playlist",
"EnableTotalRecordCount": false,
"ImageTypeLimit": 1,
"Recursive": true,
"limit": 100
})
data = getJson(resp)
results = []
for each item in data.SearchHints
tmp = CreateObject("roSGNode", "SearchData")
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
data.SearchHints = results
return data
end if
return []
end function end function
' MetaData about an item ' MetaData about an item
@ -94,7 +100,7 @@ function ItemMetaData(id as string)
tmp.image = PosterImage(data.id, imgParams) tmp.image = PosterImage(data.id, imgParams)
tmp.json = data tmp.json = data
return tmp return tmp
else if data.type = "BoxSet" else if data.type = "BoxSet" or data.type = "Playlist"
tmp = CreateObject("roSGNode", "CollectionData") tmp = CreateObject("roSGNode", "CollectionData")
tmp.image = PosterImage(data.id, imgParams) tmp.image = PosterImage(data.id, imgParams)
tmp.json = data tmp.json = data