Skip to content

Commit

Permalink
Customize home screen (#250)
Browse files Browse the repository at this point in the history
* Customize home screen

* remove logs

* Lint fix

* move func

* Fix for checklist

* Fix save state

* open api

* Fix spec

* Changelog

* Add TODO

* Button size

---------

Co-authored-by: github-action linter <githubaction@githubaction.com>
  • Loading branch information
iBicha and github-action linter authored Jan 6, 2024
1 parent e732449 commit fd7bdde
Show file tree
Hide file tree
Showing 21 changed files with 638 additions and 16 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- A home screen editor allowing to enable/disable feeds, and change their order.

## [0.18.1] - 2024-01-02

### Fixed
Expand Down
39 changes: 37 additions & 2 deletions docs/playlet-web-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ paths:
properties:
index:
type: integer
playlistIndex
playlistIndex:
type: integer
items:
type: array
Expand All @@ -178,7 +178,7 @@ paths:
properties:
index:
type: integer
playlistIndex
playlistIndex:
type: integer
items:
type: array
Expand Down Expand Up @@ -227,6 +227,41 @@ paths:
description: No Content
"400":
description: Bad Request
/api/home-layout:
get:
summary: Get home layout
description: Get the current home layout. This is the layout of the home screen, based on user preferences.
operationId: getHomeLayout
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: string
title:
type: string
feedSources:
type: array
items:
type: object
properties:
apiType:
type: string
endpoint:
type: string
id:
type: string
queryParams:
type: object
additionalProperties: true
title:
type: string
/api/playlet-lib-urls:
get:
summary: Get Playlet lib URLs
Expand Down
11 changes: 10 additions & 1 deletion playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "HomeScreenUtils.bs"
import "pkg:/components/ContextMenu/ContextMenuUtils.bs"
import "pkg:/components/Navigation/Navigation.bs"
import "pkg:/source/utils/FocusManagement.bs"
Expand All @@ -14,7 +15,15 @@ function OnNodeReady()
SetNavigation(invalid, "left", m.navBar)

m.rowList@.BindNode()
m.rowList.feeds = ParseJson(ReadAsciiFile(m.top.feedFile))

' TODO:P1 home screen should only be refreshed when the user navigates to it
' (similar to bookmarks screen)
m.preferences.observeFieldScoped("misc.home_screen_layout", FuncName(OnHomeLayoutChange))
OnHomeLayoutChange()
end function

function OnHomeLayoutChange() as void
m.rowList.feeds = HomeScreenUtils.GetFeed(m.top.feedFile, m.preferences)
end function

function OnFocusChange() as void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<interface>
<field id="navBar" type="node" bind="/NavBar" />
<field id="invidious" type="node" bind="/Invidious" />
<field id="preferences" type="node" bind="/Preferences" />
<field id="feedFile" type="string" value="libpkg:/config/default_home_layout.yaml" />
</interface>
<children>
Expand Down
28 changes: 28 additions & 0 deletions playlet-lib/src/components/Screens/HomeScreen/HomeScreenUtils.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import "pkg:/source/utils/Types.bs"

namespace HomeScreenUtils

function GetFeed(feedFileName as string, preferences as object) as object
feed = ParseJson(ReadAsciiFile(feedFileName))

homeLayout = preferences["misc.home_screen_layout"]
if not IsArray(homeLayout) or homeLayout.Count() = 0
return feed
end if

feedItems = {}
for each item in feed
feedItems[item["id"]] = item
end for

filteredFeed = []
for each item in homeLayout
if item.enabled = true
filteredFeed.push(feedItems[item.id])
end if
end for

return filteredFeed
end function

end namespace
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "pkg:/components/parts/AutoBind/OnNodeReadyNoOp.bs"
import "pkg:/source/utils/FocusManagement.bs"
import "pkg:/source/utils/Types.bs"

function Init()
m.top.focusable = true
m.top.itemSpacings = [8]

m.button = m.top.findNode("button")
m.button.observeField("buttonSelected", FuncName(OpenHomeScreenEditor))
end function

function OnFocusChange() as void
if not m.top.focus
return
end if

NodeSetFocus(m.button, true)
end function

function BindPreference(preferences as object, key as string)
if m.preferences <> invalid and m.key <> invalid
m.preferences.unobserveFieldScoped(m.key)
end if

m.preferences = preferences
m.key = key

if preferences <> invalid and key <> invalid
preferences.observeFieldScoped(key, FuncName(OnPreferenceChange))
OnPreferenceChange()
end if
end function

function OpenHomeScreenEditor()
editor = CreateObject("roSGNode", "HomeScreenEditor")
m.appController@.PushScreen(editor)
editor@.BindNode()
editor.value = m.top.value
editor.observeField("save", FuncName(OnSaveHomeScreenEditor))
end function

function OnSaveHomeScreenEditor(event as object)
editor = event.GetRoSGNode()
m.top.value = editor.value
end function

function OnPreferenceChange()
m.top.value = m.preferences[m.key]
end function

function OnValueChange() as void
if m.preferences = invalid or m.key = invalid
return
end if

m.preferences[m.key] = m.top.value
end function
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<component name="EditHomeScreenControl" extends="LayoutGroup" includes="AutoBind,Focus">
<interface>
<field id="displayText" type="string" alias="Button.text" />
<field id="description" type="string" alias="DescriptionLabel.text" />
<field id="value" type="array" onChange="OnValueChange" />
<field id="appController" type="node" bind="/AppController" />
<function name="BindPreference" />
</interface>
<children>
<Button
id="button"
minWidth="300"
showFocusFootprint="true" />
<Label id="DescriptionLabel"
width="450"
color="0xb4b4b4ff"
wrap="true">
<Font role="font" uri="font:SystemFontFile" size="18" />
</Label>
</children>
</component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import "pkg:/components/Navigation/Navigation.bs"
import "pkg:/components/parts/AutoBind/OnNodeReadyNoOp.bs"
import "pkg:/source/utils/MathUtils.bs"
import "pkg:/source/utils/Types.bs"

function Init()
m.homeLayout = {}
homeLayout = ParseJson(ReadAsciiFile("libpkg:/config/default_home_layout.yaml"))
for each item in homeLayout
m.homeLayout[item.id] = item
end for

m.checkList = m.top.findNode("checkList")
m.moveUpButton = m.top.findNode("moveUpButton")
m.moveDownButton = m.top.findNode("moveDownButton")
m.closeButton = m.top.findNode("closeButton")
m.saveButton = m.top.findNode("saveButton")

SetNavigation(m.checkList, "down", m.saveButton)
SetNavigation(m.saveButton, "up", m.checkList)
SetNavigation(m.closeButton, "up", m.checkList)
SetNavigation(m.saveButton, "right", m.closeButton)
SetNavigation(m.closeButton, "left", m.saveButton)
SetNavigation(m.moveUpButton, "left", m.checkList)
SetNavigation(m.moveDownButton, "left", m.checkList)
SetNavigation(m.checkList, "right", m.moveUpButton)
SetNavigation(m.moveUpButton, "down", m.moveDownButton)
SetNavigation(m.moveDownButton, "up", m.moveUpButton)
SetNavigation(m.moveDownButton, "down", m.closeButton)

m.moveUpButton.observeField("buttonSelected", FuncName(OnMoveUpButtonSelected))
m.moveDownButton.observeField("buttonSelected", FuncName(OnMoveDownButtonSelected))
m.saveButton.observeField("buttonSelected", FuncName(OnSaveButtonSelected))
m.closeButton.observeField("buttonSelected", FuncName(Close))

m.checkList.observeField("checkedState", FuncName(OnCheckedStateChange))
end function

function OnFocusChange() as void
if not m.top.focus
return
end if

NodeSetFocus(m.checkList, true)
end function

function OnkeyEvent(key as string, press as boolean) as boolean
if NavigationKeyHandler(key, press).handled
return true
end if

if key = "options" and press
' A pass-through to the app controller, so it can toggle picture-in-picture
return false
end if

if key = "back" and press
Close()
return true
end if

return true
end function

function OnValueChange()
content = m.checkList.content
value = m.top.value

nodes = []
checkedState = []
for each item in value
node = CreateObject("roSGNode", "ContentNode")
node.id = item.id
node.title = m.homeLayout[item.id].title
nodes.push(node)
checkedState.push(item.enabled)
end for

labelCount = content.getChildCount()
if labelCount > 0
content.removeChildrenIndex(labelCount, 0)
end if

content.appendChildren(nodes)
m.checkList.checkedState = checkedState
end function

function OnCheckedStateChange() as void
content = m.checkList.content
if content = invalid or content.getChildCount() = 0
return
end if
checkedState = m.checkList.checkedState
if checkedState = invalid or checkedState.Count() = 0
return
end if

value = []
checkboxes = content.getChildren(-1, 0)

for i = 0 to checkboxes.Count() - 1
checkbox = checkboxes[i]
value.push({
id: checkbox.id
enabled: checkedState[i]
})
end for

m.top.value = value
end function

function OnMoveUpButtonSelected() as void
MoveItem(-1)
end function

function OnMoveDownButtonSelected() as void
MoveItem(1)
end function

function MoveItem(offset as integer) as void
content = m.checkList.content
if content = invalid or content.getChildCount() = 0
return
end if

itemCount = content.getChildCount()
index = m.checkList.itemFocused
newIndex = MathUtils.Max(0, MathUtils.Min(index + offset, itemCount - 1))

if index = newIndex
return
end if

checkedState = m.checkList.checkedState
node = content.getChild(index)
content.insertChild(node, newIndex)

tmp = checkedState[index]
checkedState[index] = checkedState[newIndex]
checkedState[newIndex] = tmp

m.checkList.itemFocused = newIndex
m.checkList.jumpToItem = newIndex
m.checkList.checkedState = checkedState
end function

function OnSaveButtonSelected()
' Save the new layout to m.top.value
OnCheckedStateChange()
m.top.save = true
Close()
end function

function Close()
m.appController@.PopScreen()
end function
Loading

0 comments on commit fd7bdde

Please sign in to comment.