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("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")
'Background Image Queued for loading
@ -45,19 +55,35 @@ sub init()
m.spinner = m.top.findNode("spinner")
m.spinner.visible = true
m.Alpha = m.top.findNode("AlphaMenu")
m.AlphaSelected = m.top.findNode("AlphaSelected")
'Get reset folder setting
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
'
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
m.top.HomeLibraryItem = m.top.parentItem.Id
end if
if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl)
end if
@ -66,6 +92,14 @@ sub loadInitialItems()
if m.top.parentItem.collectionType = "livetv"
' Translate between app and server nomenclature
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"
m.view = "tvGuide"
else
@ -112,24 +146,31 @@ sub loadInitialItems()
end if
updateTitle()
m.loadItemsTask.nameStartsWith = m.top.AlphaSelected
m.loadItemsTask.nameStartsWith = m.top.alphaSelected
m.loadItemsTask.searchTerm = m.voiceBox.text
m.emptyText.visible = false
m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0
' Load Item Types
if m.top.parentItem.collectionType = "movies"
if getCollectionType() = "movies"
m.loadItemsTask.itemType = "Movie"
m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "tvshows"
else if getCollectionType() = "tvshows"
m.loadItemsTask.itemType = "Series"
m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "music"
else if getCollectionType() = "music"
' Default Settings
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]"
m.itemGrid.itemSpacing = "[ 0, 20]"
if m.voiceBox.text <> ""
m.loadItemsTask.recursive = true
else
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]"
end if
m.loadItemsTask.itemType = "MusicArtist,MusicAlbum"
m.loadItemsTask.itemId = m.top.parentItem.Id
@ -143,19 +184,21 @@ sub loadInitialItems()
m.loadItemsTask.recursive = true
end if
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
m.top.imageDisplayMode = "scaleToFit"
if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv"
showTvGuide()
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"
' Non-recursive, to not show subfolder contents
m.loadItemsTask.recursive = false
else if m.top.parentItem.Type = "Channel"
m.top.imageDisplayMode = "scaleToFit"
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"
if m.voiceBox.text <> ""
m.loadItemsTask.recursive = true
else
' non recursive for collections (folders, boxsets, photo albums, etc)
m.loadItemsTask.recursive = false
end if
else if m.top.parentItem.json.type = "Studio"
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
m.loadItemsTask.itemType = "Series,Movie"
@ -491,13 +534,33 @@ sub onItemSelected()
m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected)
end sub
sub onItemAlphaSelected()
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.spinner.visible = true
loadInitialItems()
sub onItemalphaSelected()
if m.top.alphaSelected <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
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
@ -598,6 +661,7 @@ sub optionsClosed()
if m.tvGuide <> invalid
m.tvGuide.lastFocus.setFocus(true)
end if
end sub
sub showTVGuide()
@ -608,6 +672,7 @@ sub showTVGuide()
m.tvGuide.observeField("focusedChannel", "onChannelFocused")
end if
m.tvGuide.filter = m.filter
m.tvGuide.searchTerm = m.voiceBox.text
m.top.appendChild(m.tvGuide)
m.tvGuide.lastFocus.setFocus(true)
end sub
@ -629,6 +694,12 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
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 m.options.visible = true
m.options.visible = false
@ -655,9 +726,13 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.visible = false
optionsClosed()
return true
else
m.global.sceneManager.callfunc("popScene")
m.loadItemsTask.control = "stop"
return true
end if
else if key = "play" or key = "OK"
markupGrid = m.top.getChild(2)
markupGrid = m.top.findNode("itemGrid")
itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused)
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()
m.top.alphaActive = true
topGrp.setFocus(false)
alpha = m.Alpha.getChild(0).findNode("Alphamenu")
alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true)
return true
else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false
m.Alpha.setFocus(false)
@ -690,6 +766,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
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
end function
@ -699,9 +789,11 @@ sub updateTitle()
else if m.filter = "Favorites"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)")
end if
if m.top.AlphaSelected <> ""
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Filtered)")
if m.voiceBox.text <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.searchTerm + ")"
end if
if m.top.alphaSelected <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.nameStartsWith + ")"
end if
if m.options.view = "Networks" or m.view = "Networks"

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="ItemGrid" extends="JFGroup">
<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"
loadDisplayMode="scaleToFill"
width="1920"
@ -23,10 +24,12 @@
vertFocusAnimationStyle = "fixed"
itemSize = "[ 290, 425 ]"
itemSpacing = "[ 0, 45 ]"
drawFocusFeedback = "false" />
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1920" horizAlign="center" vertAlign="center" height="64" visible="false" />
drawFocusFeedback = "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" />
<Spinner id="spinner" translation="[920, 540]" />
<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

@ -1,7 +1,7 @@
sub init()
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.setFocus(true)

View File

@ -1,9 +1,15 @@
sub init()
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
sub loadItems()
results = []
sort_field = m.top.sortField
@ -26,15 +32,30 @@ sub loadItems()
StudioIds: m.top.studioIds,
genreIds: m.top.genreIds
}
' Handle special case when getting names starting with numeral
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
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
if m.top.searchTerm <> ""
params.searchTerm = m.top.searchTerm
end if
filter = m.top.filter
if filter = "All" or filter = "all"
' do nothing
@ -71,7 +92,7 @@ sub loadItems()
tmp = CreateObject("roSGNode", "MovieData")
else if item.Type = "Series"
tmp = CreateObject("roSGNode", "SeriesData")
else if item.Type = "BoxSet"
else if item.Type = "BoxSet" or item.Type = "ManualPlaylistsFolder"
tmp = CreateObject("roSGNode", "CollectionData")
else if item.Type = "TvChannel"
tmp = CreateObject("roSGNode", "ChannelData")

View File

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

View File

@ -1,6 +1,7 @@
sub init()
m.title = m.top.findNode("title")
m.staticTitle = m.top.findNode("staticTitle")
m.series = m.top.findNode("Series")
m.poster = m.top.findNode("poster")
m.backdrop = m.top.findNode("backdrop")
@ -39,6 +40,8 @@ sub updateSize()
m.staticTitle.height = m.title.height
m.staticTitle.translation = m.title.translation
m.series.maxWidth = maxSize[0]
m.poster.width = int(maxSize[0]) - 4
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
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"
m.title.font = "font:SmallestSystemFont"
m.staticTitle.font = "font:SmallestSystemFont"
end if
m.staticTitle.text = m.title.text
@ -64,7 +72,7 @@ sub itemContentChanged() as void
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
imageUrl = imageUrl + "&blur=15"
end if
@ -82,6 +90,7 @@ sub focusChanged()
if m.top.itemHasFocus = true
m.title.repeatCount = -1
m.series.repeatCount = -1
m.staticTitle.visible = false
m.title.visible = true
@ -94,6 +103,7 @@ sub focusChanged()
else
m.title.repeatCount = 0
m.series.repeatCount = 0
m.staticTitle.visible = true
m.title.visible = false
end if

View File

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

View File

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

View File

@ -3,48 +3,25 @@ sub init()
m.top.horizAlignment = "center"
m.top.vertAlignment = "top"
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
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "OK"
' Make a Keyboard Dialog here
show_dialog()
return true
sub searchMedias()
m.top.search_values = m.searchText.text
if m.top.search_values.len() > 1
m.searchText.textEditBox.leadingEllipsis = true
else
m.searchText.textEditBox.leadingEllipsis = false
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

View File

@ -1,11 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="SearchBox" extends="LayoutGroup">
<children>
<Label text="Search" />
<TextEditBox id="search-input" width="800" maxTextLength="75" />
<Label id = "text" text="" visible="false" />
<DynamicMiniKeyboard id="search_Key" />
</children>
<interface>
<field id="search_value" type="string" alwaysNotify="true" />
<field id="search_values" type="string" alwaysNotify="true" />
</interface>
<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>

View File

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

View File

@ -107,10 +107,13 @@ sub ScanForServersComplete(event)
end sub
sub ShowKeyboard()
dialog = createObject("roSGNode", "KeyboardDialog")
dialog = createObject("roSGNode", "StandardKeyboardDialog")
dialog.title = tr("Enter the server name or ip address")
dialog.buttons = [tr("OK"), tr("Cancel")]
dialog.text = m.serverUrlTextbox.text
greenPalette = createObject("roSGNode", "RSGPalette")
greenPalette.colors = { DialogBackgroundColor: "#2A2B2A" }
dialog.palette = greenPalette
m.top.getscene().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="json" type="assocarray" onChange="setFields" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface>
<script type="text/brightscript" uri="TVEpisodeData.brs" />
</component>

View File

@ -6,15 +6,41 @@ sub loadChannels()
results = []
sort_field = m.top.sortField
if m.top.sortAscending = true
sort_order = "Ascending"
else
sort_order = "Descending"
end if
params = {
includeItemTypes: "LiveTvChannel",
SortBy: sort_field,
SortOrder: sort_order,
recursive: m.top.recursive,
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"
params.append({ isFavorite: true })
end if
url = "LiveTv/Channels"
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
resp = APIRequest(url, params)
data = getJson(resp)
@ -39,7 +65,5 @@ sub loadChannels()
results.push(channel)
end if
end for
m.top.channels = results
end sub

View File

@ -5,7 +5,11 @@
<field id="limit" type="integer" value="" />
<field id="startIndex" type="integer" value="0" />
<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-->
<field id="channels" type="array" />
</interface>

View File

@ -1,5 +1,4 @@
sub init()
m.EPGLaunchCompleteSignaled = false
m.scheduleGrid = m.top.findNode("scheduleGrid")
m.detailsPane = m.top.findNode("detailsPane")
@ -7,7 +6,6 @@ sub init()
m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected")
m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected")
m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected")
m.gridStartDate = CreateObject("roDateTime")
m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800
m.gridEndDate = createObject("roDateTime")
@ -28,10 +26,11 @@ sub init()
m.top.lastFocus = m.scheduleGrid
m.channelIndex = {}
m.spinner = m.top.findNode("spinner")
end sub
sub channelFilterSet()
print "Channel Filter set"
m.scheduleGrid.jumpToChannel = 0
if m.top.filter <> invalid and m.LoadChannelsTask.filter <> m.top.filter
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
@ -42,6 +41,19 @@ sub channelFilterSet()
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
sub onChannelsLoaded()
gridData = createObject("roSGNode", "ContentNode")
@ -49,32 +61,36 @@ sub onChannelsLoaded()
counter = 0
channelIdList = ""
for each item in m.LoadChannelsTask.channels
gridData.appendChild(item)
m.channelIndex[item.Id] = counter
counter = counter + 1
channelIdList = channelIdList + item.Id + ","
end for
'if search returns channels
if m.LoadChannelsTask.channels.count() > 0
for each item in m.LoadChannelsTask.channels
gridData.appendChild(item)
m.channelIndex[item.Id] = counter
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.observeField("schedule", "onScheduleLoaded")
m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString()
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString()
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask")
m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded")
m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask")
m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded")
m.scheduleGrid.setFocus(true)
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
m.LoadChannelsTask.channels = []
end sub
' 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.setFocus(true)
m.LoadScheduleTask.schedule = []
m.spinner.visible = false
end sub
sub onProgramFocused()
@ -118,7 +135,9 @@ sub onProgramFocused()
m.top.focusedChannel = channel
' Exit if Channels not yet loaded
if channel = invalid or channel.getChildCount() = 0
m.detailsPane.programDetails = invalid
return
end if
@ -195,6 +214,22 @@ sub onWatchChannelSelected()
m.top.watchChannel = m.detailsPane.channel
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
sub onRecordChannelSelected()
if m.detailsPane.recordSelectedChannel = false then return
@ -242,27 +277,18 @@ sub onRecordOperationDone()
end if
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
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)
detailsGrp.setFocus(false)
gridGrp.setFocus(true)
return true
else if key = "back"
m.global.sceneManager.callFunc("popScene")
return true
end if

View File

@ -12,6 +12,7 @@
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>
@ -20,6 +21,7 @@
<field id="watchChannel" type="node" alwaysNotify="false" />
<field id="focusedChannel" type="node" alwaysNotify="false" />
<field id="filter" type="string" value="All" onChange="channelFilterSet" />
<field id="searchTerm" type="string" value="" onChange="channelsearchTermSet" />
</interface>
<script type="text/brightscript" uri="schedule.brs" />
</component>

View File

@ -46,8 +46,9 @@ sub itemContentChanged()
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
' 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
SetDefaultAudioTrack(itemData)

View File

@ -408,7 +408,7 @@ sub onMetaDataLoaded()
if data <> invalid and data.count() > 0
' 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.observeField("content", "onBackdropImageLoaded")
m.LoadBackdropImageTask.control = "RUN"

View File

@ -1,3 +1,60 @@
sub init()
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
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" ?>
<component name="SearchResults" extends="JFGroup">
<component name="searchResults" extends="JFGroup">
<children>
<SearchBox id="SearchBox" visible="true" translation="[960, 145]" />
<SearchRow id="SearchSelect" visible="false" />
<Rectangle width="1920" height="1080" color="#000000" opacity="0.75" />
<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" />
<Spinner id = "searchSpinner" visible="false" translation="[1050, 500]"/>
</children>
<interface>
<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>
<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>

View File

@ -11,27 +11,28 @@ sub init()
' TODO - Define a failed to load image background
' m.top.failedBitmapURI
m.top.setFocus(true)
end sub
sub updateSize()
' 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
border = 75
border = 50
m.top.translation = [border, border + 115]
textHeight = 80
itemWidth = (dimensions["width"] - border * 2) / m.top.rowSize
itemHeight = itemWidth * 1.5 + textHeight
itemWidth = (dimensions["width"] - border) / 6
itemHeight = itemWidth + (textHeight / 2.3)
m.top.itemSize = [dimensions["width"] - border * 2, itemHeight]
m.top.itemSpacing = [0, 50]
m.top.itemSize = [1350, itemHeight] ' this is used for setting the row size
m.top.itemSpacing = [0, 105]
m.top.rowItemSize = [itemWidth, itemHeight]
m.top.rowItemSpacing = [0, 0]
m.top.numRows = 2
m.top.translation = "[12,18]"
end sub
function getData()
@ -45,16 +46,17 @@ function getData()
' todo - Or get the old data? I can't remember...
data = CreateObject("roSGNode", "ContentNode")
' 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 = {
"TvChannel": { "label": "Channels", "count": 0 },
"Movie": { "label": "Movies", "count": 0 },
"Series": { "label": "Shows", "count": 0 },
"Episode": { "label": "Episodes", "count": 0 },
"AlbumArtist": { "label": "Artists", "count": 0 },
"Album": { "label": "Albums", "count": 0 },
"MusicArtist": { "label": "Artists", "count": 0 },
"MusicAlbum": { "label": "Albums", "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
@ -84,3 +86,4 @@ sub addRow(data, title, type_filter)
end if
end for
end sub

View File

@ -4,7 +4,12 @@
<field id="rowSize" type="int" />
<field id="itemData" type="assocarray" onChange="getData" />
<field id="query" type="string" />
<field id="itemSelected" type="int" />
<field id="itemSelected" type="node" alwaysNotify="true" />
</interface>
<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>

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>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</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>
<source>%1 of %2</source>
<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 ]
if node.type = "Series"
group = CreateSeriesDetailsGroup(node)
else
else if node.type = "Movie"
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
else if isNodeEvent(msg, "buttonSelected")
' If a button is selected, we have some determining to do
@ -334,8 +355,8 @@ sub Main (args as dynamic) as void
end if
group = CreateSearchPage()
sceneManager.callFunc("pushScene", group)
group.findNode("SearchBox").findNode("search-input").setFocus(true)
group.findNode("SearchBox").findNode("search-input").active = true
group.findNode("SearchBox").findNode("search_Key").setFocus(true)
group.findNode("SearchBox").findNode("search_Key").active = true
else if button.id = "change_server"
unset_setting("server")
unset_setting("port")

View File

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

View File

@ -34,34 +34,40 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
end function
' Search across all libraries
function SearchMedia(query as string)
function searchMedia(query as string)
' This appears to be done differently on the web now
' For each potential type, a separate query is done:
' 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)
results = []
for each item in data.Items
tmp = CreateObject("roSGNode", "SearchData")
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
data.SearchHints = results
return data
if query <> ""
resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), {
"searchTerm": query,
"IncludePeople": true,
"IncludeMedia": true,
"IncludeShows": true,
"IncludeGenres": true,
"IncludeStudios": true,
"IncludeArtists": true,
"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
' MetaData about an item
@ -94,7 +100,7 @@ function ItemMetaData(id as string)
tmp.image = PosterImage(data.id, imgParams)
tmp.json = data
return tmp
else if data.type = "BoxSet"
else if data.type = "BoxSet" or data.type = "Playlist"
tmp = CreateObject("roSGNode", "CollectionData")
tmp.image = PosterImage(data.id, imgParams)
tmp.json = data