From d7c91980a171d8e0580fd8ddaa2a03cc61ec46fa Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sun, 24 Sep 2023 12:36:02 -0400 Subject: [PATCH 01/32] Empty bookmarks screen --- .../src/components/ChannelView/ChannelView.bs | 1 - playlet-lib/src/components/Logger/Logger.bs | 2 +- .../src/components/MainScene.transpiled.xml | 7 ++++--- playlet-lib/src/components/MainScene.xml | 14 +++++++------ .../src/components/NavBar/IconButton.bs | 4 ++-- playlet-lib/src/components/NavBar/NavBar.bs | 6 +++--- playlet-lib/src/components/NavBar/NavBar.xml | 2 +- .../src/components/NavBar/NavBarItem.xml | 1 - .../BookmarksScreen/BookmarksScreen.bs | 4 ++++ .../BookmarksScreen/BookmarksScreen.xml | 19 ++++++++++++++++++ .../Screens/HomeScreen/HomeScreen.bs | 1 + .../Screens/HomeScreen/HomeScreen.xml | 4 ++-- .../Screens/SearchScreen/SearchScreen.bs | 1 + .../Screens/SearchScreen/SearchScreen.xml | 4 ++-- .../Screens/SettingsScreen/SettingsScreen.bs | 1 + playlet-lib/src/images/icons/star.png | Bin 0 -> 1236 bytes 16 files changed, 49 insertions(+), 22 deletions(-) create mode 100644 playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs create mode 100644 playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml create mode 100644 playlet-lib/src/images/icons/star.png diff --git a/playlet-lib/src/components/ChannelView/ChannelView.bs b/playlet-lib/src/components/ChannelView/ChannelView.bs index 7fa03d4a..7c923a59 100644 --- a/playlet-lib/src/components/ChannelView/ChannelView.bs +++ b/playlet-lib/src/components/ChannelView/ChannelView.bs @@ -38,7 +38,6 @@ function OnContentSet() as void ' NOTE: "_author" not "author". See PlaylistContentNode.xml for explanation. m.authorLabel.text = content._author - ' TODO:P1 handle case where there's no banner - a big portion of the screen is blank m.banner.uri = content.banner if StringUtils.IsNullOrEmpty(content.thumbnail) m.thumbnail.uri = "" diff --git a/playlet-lib/src/components/Logger/Logger.bs b/playlet-lib/src/components/Logger/Logger.bs index 0132a079..35139d3d 100644 --- a/playlet-lib/src/components/Logger/Logger.bs +++ b/playlet-lib/src/components/Logger/Logger.bs @@ -54,7 +54,7 @@ function OnLineSysLog(event as object, logsFile as string, buffer as object) as if info.LogType <> "http.error" return end if - line = `[SysLog] ${FormatJson(info)}` + line = `[ERROR][SysLog] ${FormatJson(info)}` ' bs:disable-next-line LINT3012 print line diff --git a/playlet-lib/src/components/MainScene.transpiled.xml b/playlet-lib/src/components/MainScene.transpiled.xml index 2d17d13f..99949d4e 100644 --- a/playlet-lib/src/components/MainScene.transpiled.xml +++ b/playlet-lib/src/components/MainScene.transpiled.xml @@ -24,9 +24,10 @@ - - - + + + + diff --git a/playlet-lib/src/components/MainScene.xml b/playlet-lib/src/components/MainScene.xml index f8440da8..190afbf6 100644 --- a/playlet-lib/src/components/MainScene.xml +++ b/playlet-lib/src/components/MainScene.xml @@ -10,20 +10,22 @@ + icon="pkg:/images/icons/search.png"> + icon="pkg:/images/icons/home.png"> + + + icon="pkg:/images/icons/settings.png"> true - return false - end if screen = m.appController@.GetRootScreen(navBarItem.screen) if screen = invalid return false end if + if not screen.focusable + return false + end if NodeSetFocus(screen, true) return true end if diff --git a/playlet-lib/src/components/NavBar/NavBar.xml b/playlet-lib/src/components/NavBar/NavBar.xml index dba9fe23..4610c576 100644 --- a/playlet-lib/src/components/NavBar/NavBar.xml +++ b/playlet-lib/src/components/NavBar/NavBar.xml @@ -10,7 +10,7 @@ - \ No newline at end of file diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs new file mode 100644 index 00000000..b473313c --- /dev/null +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs @@ -0,0 +1,4 @@ +function Init() + m.noBookmarks = m.top.findNode("noBookmarks") + m.noBookmarks.visible = true +end function diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml new file mode 100644 index 00000000..383546d3 --- /dev/null +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs index 2ea87fbe..956dc5af 100644 --- a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs +++ b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs @@ -2,6 +2,7 @@ import "pkg:/source/utils/FocusManagement.bs" import "pkg:/components/Navigation/Navigation.bs" function Init() + m.top.focusable = true m.rowList = m.top.FindNode("rowList") m.pendingLoadTasks = {} end function diff --git a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.xml b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.xml index ffd6caea..0f8da698 100644 --- a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.xml +++ b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.xml @@ -16,8 +16,8 @@ rowItemSpacing="[[25,0]]" itemSize="[1280,326]" itemSpacing="[0,65]" - rowLabelOffset="[[115,20]]" - focusXOffset="[115]" + rowLabelOffset="[[125,20]]" + focusXOffset="[125]" showRowLabel="[true]" rowFocusAnimationStyle="floatingfocus" variableWidthItems="true" diff --git a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs index 07a7c565..3153608e 100644 --- a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs +++ b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs @@ -8,6 +8,7 @@ import "pkg:/source/utils/Logging.bs" import "pkg:/source/utils/ErrorUtils.bs" function Init() + m.top.focusable = true m.container = m.top.FindNode("container") m.keyboard = m.top.FindNode("keyboard") m.rowList = m.top.FindNode("rowList") diff --git a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.xml b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.xml index 29b7dc56..69de3a9d 100644 --- a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.xml +++ b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.xml @@ -42,8 +42,8 @@ rowItemSpacing="[[25,0]]" itemSize="[1280,326]" itemSpacing="[0,65]" - rowLabelOffset="[[115,20]]" - focusXOffset="[115]" + rowLabelOffset="[[125,20]]" + focusXOffset="[125]" showRowLabel="[true]" rowFocusAnimationStyle="floatingfocus" variableWidthItems="true" diff --git a/playlet-lib/src/components/Screens/SettingsScreen/SettingsScreen.bs b/playlet-lib/src/components/Screens/SettingsScreen/SettingsScreen.bs index e7d07c6a..77d4b414 100644 --- a/playlet-lib/src/components/Screens/SettingsScreen/SettingsScreen.bs +++ b/playlet-lib/src/components/Screens/SettingsScreen/SettingsScreen.bs @@ -3,6 +3,7 @@ import "pkg:/source/utils/FocusManagement.bs" import "pkg:/components/Navigation/ScrollTo.bs" function Init() + m.top.focusable = true m.container = m.top.findNode("Container") m.scrollAnimation = m.top.findNode("scrollAnimation") m.scrollAnimationInterpolator = m.scrollAnimation.findNode("scrollAnimationInterpolator") diff --git a/playlet-lib/src/images/icons/star.png b/playlet-lib/src/images/icons/star.png new file mode 100644 index 0000000000000000000000000000000000000000..1fca5959a4174408b0426f52a7afa2c092cf07be GIT binary patch literal 1236 zcmV;_1S|WAP)9764O z`&*%80K_YRQc4g)vl5SlkpaO9z!?8@M)1&Xx4#p`2SB_65=ghg!c-tk0qg7QA3@5% zI-SlBIn#LrkqSt=`X>;g0wD^BqUaZxGoWs_`(sXY9)l1Cc%J9jw%uSb&0Ph#E5LD_ z&lrP;ot>SZv*t4n5egUqfXZQ?s|s>ez}ni{Rp1LA^6n0ERX`8~yQXO_XW%L~738LX ze!u@!M!|#Ud22xsypx5maWE?YV|=UE>)lWk<>gYTbfsJ_U$!i3Sy2={V~&r35HbwI zu#YkB_`ct&R4RdGS&3m7AF}wHnSkTt<1f11?t?<1aLqK$E4FRFV47wFAtY^(G7CwP z#KYn6Ki~KN#Tai|mUXOY+WktU^6502sR^Kz#)}##3mnIJcWG(qR6(6s16(s3((Uc- zx5g*8PcUv%#;LFe09GC=A{bLU$uy17lUxwbY@yQ>L7`Jx(MbCgK$OxxO(7TA@H}q- z4;L-f!lix;B82L#R_j$(VxnN%_N95wpL;WuB+1{3qP#ShkvPnKyRz;E7x7kLs#UC= z;Gz{qWJ8 z_1=N&7Qh(a;<4w2q9{tg-~WKi2amDmdD05t33|OA*U#&?5)ehv3Xgpjs8*}5@z`@E zpkA-vG? zel|BZnL9$}1RNY3Fn552kUjt)P19a9P4g9axTn}<^{4Clo2<6X38>X-b64LW2>t

*Km^C+$#vI2`ULN%B`(--M9g!Z7Tm z_3!)s1J*d0i(nkbSktuftaC8NJCQ!Y*`48v-vR9Z@LI#VHJP5_-oB_GrhoJt^pY1+Y^X*01)cBekrTq0RRv}&l`qO zi{tnXAtW3>-tBfjVfM}22OJ(AZV^I~Mx(Ke5aQaJrp3|G(f50Md(RY$MPp@UCq-26UZ8ZHtbn}BRwBtSL++4vu&b0Qq=mIosM0000 Date: Mon, 25 Sep 2023 20:56:54 -0400 Subject: [PATCH 02/32] Bookmarks object --- playlet-lib/src/components/MainScene.xml | 1 + .../BookmarksScreen/BookmarksScreen.bs | 5 +- .../BookmarksScreen/BookmarksScreen.xml | 1 + .../Services/Bookmarks/Bookmarks.bs | 110 ++++++++++++++++++ .../Services/Bookmarks/Bookmarks.xml | 7 ++ playlet-lib/src/source/utils/RegistryUtils.bs | 1 + 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs create mode 100644 playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml diff --git a/playlet-lib/src/components/MainScene.xml b/playlet-lib/src/components/MainScene.xml index 190afbf6..3b2ccf8c 100644 --- a/playlet-lib/src/components/MainScene.xml +++ b/playlet-lib/src/components/MainScene.xml @@ -50,6 +50,7 @@ invidious="bind:../Invidious" /> + + diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs new file mode 100644 index 00000000..f85062f1 --- /dev/null +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -0,0 +1,110 @@ +import "pkg:/source/utils/Types.bs" +import "pkg:/source/utils/RegistryUtils.bs" + +function Init() + m.bookmarksString = "" + Load() + m.top.ObserveField("change", FuncName(OnChange)) +end function + +function Load() as void + bookmarksString = RegistryUtils.Read(RegistryUtils.BOOKMARKS) + if bookmarksString = invalid + return + end if + + m.bookmarksString = bookmarksString + bookmarks = ParseJson(bookmarksString) + if bookmarks = invalid + return + end if + + for each bookmark in bookmarks.bookmarks + AddBookmarkGroup(bookmark.title) + for each item in bookmark.items + AddBookmark(item.type, item.id, bookmark.title) + end for + end for +end function + +function Save() as void + bookmarkGroups = m.top.getChildren(-1, 0) + if bookmarkGroups.Count() = 0 + RegistryUtils.Delete(RegistryUtils.BOOKMARKS) + return + end if + + bookmarks = [] + for each group in bookmarkGroups + items = [] + nodes = group.getChildren(-1, 0) + for each node in nodes + items.Push({ + type: node.type, + id: node.itemId + }) + end for + bookmarks.push({ + title: group.title, + items: items + }) + end for + + bookmarksString = FormatJson({ + __version: m.top.__version, + bookmarks: bookmarks + }) + + if m.bookmarksString = bookmarksString + return + end if + + RegistryUtils.Write(RegistryUtils.BOOKMARKS, bookmarksString) + m.bookmarksString = bookmarksString +end function + +function AddBookmarkGroup(groupName as string) as object + node = CreateObject("roSGNode", "ContentNode") + node.id = groupName + node.title = groupName + m.top.appendChild(node) + return node +end function + +function AddBookmark(bookmarkType as string, id as string, groupName as string) + groupNode = m.top.findNode(groupName) + if groupNode = invalid + groupNode = AddBookmarkGroup(groupName) + end if + + node = CreateObject("roSGNode", "ContentNode") + node.id = id + node.addFields({ + type: bookmarkType, + itemId: id + }) + groupNode.appendChild(node) +end function + +function OnChange(event as object) as void + change = event.getData() + if change.Operation = "none" + return + end if + + Save() +end function + +' When a video is selected: +' - Add to "Videos" bookmark +' - Add to new bookmark group... + +' When a channel is selected: +' - Add to "Channels" bookmark +' - Add to bookmark +' - Add to new bookmark group... + +' When a playlist is selected: +' - Add to "Playlists" bookmark +' - Add to bookmark +' - Add to new bookmark group... diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml new file mode 100644 index 00000000..39bb9df6 --- /dev/null +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/source/utils/RegistryUtils.bs b/playlet-lib/src/source/utils/RegistryUtils.bs index a52b1f75..c0b8cb6b 100644 --- a/playlet-lib/src/source/utils/RegistryUtils.bs +++ b/playlet-lib/src/source/utils/RegistryUtils.bs @@ -6,6 +6,7 @@ namespace RegistryUtils const SEARCH_HISTORY = "search_history" const PLAYLET_LIB_URLS = "playlet_lib_urls" + const BOOKMARKS = "bookmarks" const INVIDIOUS_INSTANCES = "invidious_instances" const INVIDIOUS_TOKEN = "invidious_token" From 8d6a646751cab4bb4f84b438d6e527fd2ea02586 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Tue, 26 Sep 2023 17:38:53 -0400 Subject: [PATCH 03/32] Long press support --- playlet-lib/src/components/Logger/Logger.xml | 2 +- .../src/components/Navigation/LongPress.bs | 30 ++++++++ .../components/PlaylistView/PlaylistView.bs | 46 +++++++++++- .../src/components/VideoFeed/VideoRowList.bs | 75 ++++++++++++++++++- playlet-lib/src/source/utils/Logging.bs | 17 +++++ 5 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 playlet-lib/src/components/Navigation/LongPress.bs diff --git a/playlet-lib/src/components/Logger/Logger.xml b/playlet-lib/src/components/Logger/Logger.xml index 783890be..2fb89af2 100644 --- a/playlet-lib/src/components/Logger/Logger.xml +++ b/playlet-lib/src/components/Logger/Logger.xml @@ -2,7 +2,7 @@ - + diff --git a/playlet-lib/src/components/Navigation/LongPress.bs b/playlet-lib/src/components/Navigation/LongPress.bs new file mode 100644 index 00000000..90b5f36e --- /dev/null +++ b/playlet-lib/src/components/Navigation/LongPress.bs @@ -0,0 +1,30 @@ +function InitializeLongPressTimer(duration = 1.0 as float) + m._longPressTimer = CreateObject("roSGNode", "Timer") + m._longPressTimer.duration = duration + m._longPressFired = false + m._longPressTimer.ObserveField("fire", FuncName(LongPressTimerFired)) +end function + +function LongPressHandler(key as string, press as boolean) as boolean + if key = "OK" and press + m._longPressTimer.control = "stop" + m._longPressTimer.control = "start" + m._longPressFired = false + return true + end if + + if key = "OK" and not press + m._longPressTimer.control = "stop" + if m._longPressFired = true + m._longPressFired = false + return true + end if + end if + + return false +end function + +function LongPressTimerFired() + m._longPressFired = true + OnkeyEvent("longPress", true) +end function diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.bs b/playlet-lib/src/components/PlaylistView/PlaylistView.bs index 4f3c2403..923fbd96 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.bs +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.bs @@ -8,6 +8,7 @@ import "pkg:/components/PlaylistView/PlaylistContentTask.bs" import "pkg:/components/VideoFeed/FeedLoadState.bs" import "pkg:/source/utils/ErrorUtils.bs" import "pkg:/components/Dialog/DialogUtils.bs" +import "pkg:/components/Navigation/LongPress.bs" function Init() m.background = m.top.findNode("background") @@ -21,7 +22,7 @@ end function function OnNodeReady() m.list.ObserveField("itemFocused", FuncName(OnItemFocused)) - m.list.observeField("itemSelected", FuncName(OnItemSelected)) + InitializeLongPressTimer() end function function OnFocusChange() as void @@ -58,6 +59,20 @@ function OnContentSet() as void end function function OnkeyEvent(key as string, press as boolean) as boolean + if LongPressHandler(key, press) + return true + end if + + if key = "longPress" and press + OnItemLongPressed(m.list.itemFocused) + return true + end if + + if key = "OK" and not press + OnItemSelected(m.list.itemFocused) + return true + end if + if press = false return false end if @@ -81,9 +96,16 @@ function OnItemFocused() LoadPlaylistIfNeeded() end function -function OnItemSelected(event as object) - index = event.getData() +function OnItemSelected(index as integer) as void playlist = m.list.content + if playlist = invalid + return + end if + + if index < 0 or index >= playlist.getChildCount() + return + end if + playlistId = playlist.playlistId video = playlist.getChild(index) videoId = video.videoId @@ -93,6 +115,24 @@ function OnItemSelected(event as object) end if end function +function OnItemLongPressed(index as integer) as void + playlist = m.list.content + if playlist = invalid + return + end if + + if index < 0 or index >= playlist.getChildCount() + return + end if + + playlistId = playlist.playlistId + video = playlist.getChild(index) + videoId = video.videoId + if not StringUtils.IsNullOrEmpty(videoId) + LogInfo("Long press: playlist:", playlistId, "video:" + videoId) + end if +end function + function LoadPlaylistIfNeeded() as void content = m.top.content if content = invalid diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.bs b/playlet-lib/src/components/VideoFeed/VideoRowList.bs index 022bf4fe..ba60c3e5 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.bs @@ -8,6 +8,7 @@ import "pkg:/components/Dialog/DialogUtils.bs" import "pkg:/components/VideoPlayer/VideoUtils.bs" import "pkg:/components/PlaylistView/PlaylistUtils.bs" import "pkg:/components/ChannelView/ChannelUtils.bs" +import "pkg:/components/Navigation/LongPress.bs" ' TODO:P1 add the option to remove a row if it does not have any content ' E.g. NBC News does not return any related chnanels @@ -18,11 +19,11 @@ end function function OnNodeReady() m.top.ObserveField("itemFocused", FuncName(OnItemFocused)) m.top.ObserveField("rowItemFocused", FuncName(OnRowItemFocused)) - m.top.ObserveField("rowItemSelected", FuncName(OnRowItemSelected)) m.top.observeField("focusedChild", FuncName(OnFocusedChildChange)) m.top.ObserveField("visible", FuncName(OnVisibilityChange)) m.invidious.ObserveFieldScoped("authToken", FuncName(OnAuthTokenChange)) + InitializeLongPressTimer() end function function OnFocusedChildChange() as void @@ -60,12 +61,25 @@ function OnRowItemFocused(event as object) as void end if end function -function OnRowItemSelected(event as object) - index = event.GetData() +function OnRowItemSelected(index as object) as void + content = m.top.content + if content = invalid + return + end if + rowIndex = index[0] rowItemIndex = index[1] - row = m.top.content.GetChild(rowIndex) + if rowIndex < 0 or rowIndex >= content.GetChildCount() + LogWarn("Invalid row index:", rowIndex) + return + end if + row = content.GetChild(rowIndex) + + if rowItemIndex < 0 or rowItemIndex >= row.GetChildCount() + LogWarn("Invalid row item index:", rowItemIndex) + return + end if rowItem = row.GetChild(rowItemIndex) if rowItem.type = "video" @@ -89,6 +103,41 @@ function OnRowItemSelected(event as object) end if end function +function OnRowItemLongPressed(index as object) as void + content = m.top.content + if content = invalid + return + end if + + rowIndex = index[0] + rowItemIndex = index[1] + + if rowIndex < 0 or rowIndex >= content.GetChildCount() + LogWarn("Invalid row index:", rowIndex) + return + end if + row = content.GetChild(rowIndex) + + if rowItemIndex < 0 or rowItemIndex >= row.GetChildCount() + LogWarn("Invalid row item index:", rowItemIndex) + return + end if + rowItem = row.GetChild(rowItemIndex) + + if rowItem.type = "video" + videoId = rowItem.videoId + LogInfo("Video long pressed:", videoId) + else if rowItem.type = "playlist" + playlistId = rowItem.playlistId + LogInfo("Playlist long pressed:", playlistId) + else if rowItem.type = "channel" + authorId = rowItem.authorId + LogInfo("Channel long pressed:", authorId) + else + LogWarn("Unknown long pressed item type:", rowItem.type) + end if +end function + function OnAuthTokenChange() ' TODO:P2: Reload only content that is authenticated ' Should not reload if the page is not visible. Instead, capture state and reload when the page becomes visible @@ -207,3 +256,21 @@ function OnVideoRowListRowContentTaskResult(output as object) as void LoadRowsIfNeeded() end function + +function OnkeyEvent(key as string, press as boolean) as boolean + ' LogInfo("OnkeyEvent", key, press) + if LongPressHandler(key, press) + return true + end if + + if key = "longPress" and press + OnRowItemLongPressed(m.top.rowItemFocused) + return true + end if + + if key = "OK" and not press + OnRowItemSelected(m.top.rowItemFocused) + return true + end if + return false +end function diff --git a/playlet-lib/src/source/utils/Logging.bs b/playlet-lib/src/source/utils/Logging.bs index 2f255386..5df26ded 100644 --- a/playlet-lib/src/source/utils/Logging.bs +++ b/playlet-lib/src/source/utils/Logging.bs @@ -45,6 +45,8 @@ function ToString(value as dynamic) as string return "Node(" + value.subType() + ":" + value.id + ")" else if valueType = "roAssociativeArray" return AssocArrayToString(value) + else if valueType = "roArray" + return ArrayToString(value) end if return "<" + valueType + ">" @@ -64,3 +66,18 @@ function AssocArrayToString(dict as object) as string result += "}" return result end function + +function ArrayToString(array as object) as string + result = "[" + count = array.count() + i = 0 + for each item in array + result += ToString(item) + if i < count - 1 + result += ", " + end if + i++ + end for + result += "]" + return result +end function From 81309fc4d62629ca68085f65e2771d8481a4f219 Mon Sep 17 00:00:00 2001 From: github-action linter Date: Tue, 26 Sep 2023 21:39:52 +0000 Subject: [PATCH 04/32] Lint fix --- playlet-lib/src/components/MainScene.transpiled.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/playlet-lib/src/components/MainScene.transpiled.xml b/playlet-lib/src/components/MainScene.transpiled.xml index 99949d4e..6206064c 100644 --- a/playlet-lib/src/components/MainScene.transpiled.xml +++ b/playlet-lib/src/components/MainScene.transpiled.xml @@ -39,6 +39,7 @@ + From 1d3f811ebc8477196633bcf35ae4f86c57619956 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Tue, 26 Sep 2023 23:55:34 -0400 Subject: [PATCH 05/32] Context menu v1 --- .../src/components/ContextMenu/ContextMenu.bs | 89 +++++++++++++++++++ .../components/ContextMenu/ContextMenu.xml | 14 +++ .../src/components/VideoFeed/VideoRowList.bs | 46 ++++++++++ 3 files changed, 149 insertions(+) create mode 100644 playlet-lib/src/components/ContextMenu/ContextMenu.bs create mode 100644 playlet-lib/src/components/ContextMenu/ContextMenu.xml diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs new file mode 100644 index 00000000..abb37f65 --- /dev/null +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -0,0 +1,89 @@ +import "pkg:/source/utils/Types.bs" +import "pkg:/source/utils/FocusManagement.bs" +import "pkg:/source/utils/Logging.bs" +import "pkg:/source/utils/StringUtils.bs" + +' TODO:P1 animation (fade in/out) +function Init() + m.buttonGroup = m.top.findNode("buttonGroup") +end function + +function OnNodeReady() + m.buttonGroup.ObserveField("buttonSelected", FuncName(OnButtonSelected)) +end function + +function OnFocusChange() as void + if not m.top.focus + return + end if + NodeSetFocus(m.buttonGroup, true) +end function + +function OnMenuSet() as void + menu = m.top.menu + if menu = invalid + return + end if + + buttons = [] + for each item in menu + buttons.push(item.text) + end for + + m.buttonGroup.buttons = buttons + + rect = m.buttonGroup.boundingRect() + m.buttonGroup.translation = [1280 / 2 - rect.width / 2, 720 / 2 - rect.height / 2] +end function + +function OnButtonSelected(event as object) as void + index = event.getData() + button = m.buttonGroup.buttons[index] + LogInfo("Menu button selected:", button) + Close() + + menu = m.top.menu + if index >= menu.count() + LogError("Menu button index out of range:", index) + return + end if + + menuItem = menu[index] + + if menuItem.action.node = invalid or StringUtils.IsNullOrEmpty(menuItem.action.func) or menuItem.action.args = invalid + LogError("Menu button action is invalid") + return + end if + + node = menuItem.action.node + func = menuItem.action.func + args = menuItem.action.args + + if args.count() = 1 + node.callFunc(func, args[0]) + else if args.count() = 2 + node.callFunc(func, args[0], args[1]) + else if args.count() = 3 + node.callFunc(func, args[0], args[1], args[2]) + else if args.count() = 4 + node.callFunc(func, args[0], args[1], args[2], args[3]) + else if args.count() = 5 + node.callFunc(func, args[0], args[1], args[2], args[3], args[4]) + end if +end function + +function OnkeyEvent(key as string, press as boolean) as boolean + if press = false + return false + end if + + if key = "back" + Close() + return true + end if + return false +end function + +function Close() + m.appController@.PopScreen(invalid) +end function diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.xml b/playlet-lib/src/components/ContextMenu/ContextMenu.xml new file mode 100644 index 00000000..13c16b48 --- /dev/null +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.bs b/playlet-lib/src/components/VideoFeed/VideoRowList.bs index ba60c3e5..36ad65ca 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.bs @@ -124,18 +124,64 @@ function OnRowItemLongPressed(index as object) as void end if rowItem = row.GetChild(rowItemIndex) + OpenContextMenu(rowItem) +end function + +function OpenContextMenu(rowItem as object) as void + menu = [] + if rowItem.type = "video" videoId = rowItem.videoId LogInfo("Video long pressed:", videoId) + + menu.push({ + text: `Play "${rowItem.title}"`, + action: { + node: m.playQueue, + func: "Play", + args: [rowItem, -1] + } + }) + menu.push({ + text: `Queue "${rowItem.title}"`, + action: { + node: m.playQueue, + func: "AddToQueue", + args: [rowItem] + } + }) else if rowItem.type = "playlist" playlistId = rowItem.playlistId LogInfo("Playlist long pressed:", playlistId) + + menu.push({ + text: `Play "${rowItem.title}"`, + action: { + node: m.playQueue, + func: "Play", + args: [rowItem, -1] + } + }) + menu.push({ + text: `Queue "${rowItem.title}"`, + action: { + node: m.playQueue, + func: "AddToQueue", + args: [rowItem] + } + }) else if rowItem.type = "channel" authorId = rowItem.authorId LogInfo("Channel long pressed:", authorId) else LogWarn("Unknown long pressed item type:", rowItem.type) + return end if + + contextMenu = CreateObject("roSGNode", "ContextMenu") + m.appController@.PushScreen(contextMenu) + contextMenu@.BindNode(invalid) + contextMenu.menu = menu end function function OnAuthTokenChange() From 6b8c4cf4d168a3c06995020b49e44a965991d97f Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 28 Sep 2023 16:05:54 -0400 Subject: [PATCH 06/32] A working context menu --- .../ContentNode/VideoContentNode.xml | 1 + .../src/components/ContextMenu/ContextMenu.bs | 3 - .../components/ContextMenu/ContextMenu.xml | 41 ++++++- .../components/PlaylistView/PlaylistView.xml | 3 +- .../Invidious/InvidiousToContentNode.bs | 1 + .../src/components/VideoFeed/VideoRowList.bs | 111 ++++++++++++++---- .../src/components/VideoFeed/VideoRowList.xml | 3 + .../src/config/invidious_video_api.json5 | 2 + 8 files changed, 136 insertions(+), 29 deletions(-) diff --git a/playlet-lib/src/components/ContentNode/VideoContentNode.xml b/playlet-lib/src/components/ContentNode/VideoContentNode.xml index 3607fea9..b09d9b3f 100644 --- a/playlet-lib/src/components/ContentNode/VideoContentNode.xml +++ b/playlet-lib/src/components/ContentNode/VideoContentNode.xml @@ -3,6 +3,7 @@ + diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs index abb37f65..67dbafa4 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.bs +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -31,9 +31,6 @@ function OnMenuSet() as void end for m.buttonGroup.buttons = buttons - - rect = m.buttonGroup.boundingRect() - m.buttonGroup.translation = [1280 / 2 - rect.width / 2, 720 / 2 - rect.height / 2] end function function OnButtonSelected(event as object) as void diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.xml b/playlet-lib/src/components/ContextMenu/ContextMenu.xml index 13c16b48..c4a1412b 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.xml +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.xml @@ -1,5 +1,8 @@ + + + @@ -9,6 +12,42 @@ height="720" color="#000000" opacity="0.8" /> - + + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.xml b/playlet-lib/src/components/PlaylistView/PlaylistView.xml index cf5dccb0..33e4f72e 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.xml +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.xml @@ -9,8 +9,7 @@ - + color="0x242424" /> + + + \ No newline at end of file diff --git a/playlet-lib/src/config/invidious_video_api.json5 b/playlet-lib/src/config/invidious_video_api.json5 index eb5d9ddc..643f5753 100644 --- a/playlet-lib/src/config/invidious_video_api.json5 +++ b/playlet-lib/src/config/invidious_video_api.json5 @@ -25,6 +25,7 @@ "lengthSeconds", "viewCount", "author", + "authorId", "publishedText", "liveNow", "isUpcoming", @@ -50,6 +51,7 @@ "lengthSeconds", "viewCount", "author", + "authorId", "publishedText", "liveNow", "isUpcoming", From 5f16d94ee1dc4bff2598b563b2108cd73f2d283e Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Fri, 29 Sep 2023 17:55:46 -0400 Subject: [PATCH 07/32] Basic bookmarks in context menu --- .../ContextMenuItemContentNode.xml | 7 + .../src/components/ContextMenu/ContextMenu.bs | 16 ++- .../components/ContextMenu/ContextMenu.xml | 2 +- .../components/PlaylistView/PlaylistView.bs | 1 + .../Services/Bookmarks/Bookmarks.bs | 135 ++++++++++++++++-- .../Services/Bookmarks/Bookmarks.xml | 5 + .../src/components/VideoFeed/VideoRowList.bs | 77 +++++----- .../src/components/VideoFeed/VideoRowList.xml | 1 + 8 files changed, 187 insertions(+), 57 deletions(-) create mode 100644 playlet-lib/src/components/ContentNode/ContextMenuItemContentNode.xml diff --git a/playlet-lib/src/components/ContentNode/ContextMenuItemContentNode.xml b/playlet-lib/src/components/ContentNode/ContextMenuItemContentNode.xml new file mode 100644 index 00000000..1b95484f --- /dev/null +++ b/playlet-lib/src/components/ContentNode/ContextMenuItemContentNode.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs index 67dbafa4..b673d7bf 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.bs +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -27,7 +27,7 @@ function OnMenuSet() as void buttons = [] for each item in menu - buttons.push(item.text) + buttons.push(item.title) end for m.buttonGroup.buttons = buttons @@ -36,8 +36,8 @@ end function function OnButtonSelected(event as object) as void index = event.getData() button = m.buttonGroup.buttons[index] - LogInfo("Menu button selected:", button) Close() + LogInfo("Menu button selected:", button) menu = m.top.menu if index >= menu.count() @@ -47,15 +47,15 @@ function OnButtonSelected(event as object) as void menuItem = menu[index] - if menuItem.action.node = invalid or StringUtils.IsNullOrEmpty(menuItem.action.func) or menuItem.action.args = invalid + node = menuItem.node + func = menuItem.func + args = menuItem.args + + if node = invalid or StringUtils.IsNullOrEmpty(func) or args = invalid LogError("Menu button action is invalid") return end if - node = menuItem.action.node - func = menuItem.action.func - args = menuItem.action.args - if args.count() = 1 node.callFunc(func, args[0]) else if args.count() = 2 @@ -66,6 +66,8 @@ function OnButtonSelected(event as object) as void node.callFunc(func, args[0], args[1], args[2], args[3]) else if args.count() = 5 node.callFunc(func, args[0], args[1], args[2], args[3], args[4]) + else + LogError("Menu button action has too many arguments") end if end function diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.xml b/playlet-lib/src/components/ContextMenu/ContextMenu.xml index c4a1412b..360ae927 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.xml +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.xml @@ -3,7 +3,7 @@ - + diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.bs b/playlet-lib/src/components/PlaylistView/PlaylistView.bs index 923fbd96..a364ce63 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.bs +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.bs @@ -10,6 +10,7 @@ import "pkg:/source/utils/ErrorUtils.bs" import "pkg:/components/Dialog/DialogUtils.bs" import "pkg:/components/Navigation/LongPress.bs" +' TODO:P0 context menu (play/queue/bookmarks) function Init() m.background = m.top.findNode("background") m.backgroundSmall = m.top.findNode("backgroundSmall") diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs index f85062f1..3de11707 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -1,5 +1,11 @@ import "pkg:/source/utils/Types.bs" import "pkg:/source/utils/RegistryUtils.bs" +import "pkg:/source/utils/StringUtils.bs" +import "pkg:/source/utils/Logging.bs" + +const VIDEOS_GROUP = "Videos" +const CHANNELS_GROUP = "Channels" +const PLAYLISTS_GROUP = "Playlists" function Init() m.bookmarksString = "" @@ -68,6 +74,7 @@ function AddBookmarkGroup(groupName as string) as object node.id = groupName node.title = groupName m.top.appendChild(node) + LogInfo("Added bookmark group:", groupName) return node end function @@ -83,7 +90,24 @@ function AddBookmark(bookmarkType as string, id as string, groupName as string) type: bookmarkType, itemId: id }) - groupNode.appendChild(node) + groupNode.insertChild(node, 0) + LogInfo("Added bookmark:", id) +end function + +function RemoveBookmark(id as string) as void + node = m.top.findNode(id) + if node = invalid + return + end if + + parent = node.getParent() + parent.removeChild(node) + LogInfo("Removed bookmark:", id) + + if parent.getChildCount() = 0 + m.top.removeChild(parent) + LogInfo("Removed bookmark group:", parent.title) + end if end function function OnChange(event as object) as void @@ -95,16 +119,109 @@ function OnChange(event as object) as void Save() end function +function GetMenuForVideo(videoNode as object) as object + videoId = videoNode.videoId + if StringUtils.IsNullOrEmpty(videoId) + return [] + end if + + menu = [] + isInBookmarks = m.top.findNode(videoId) <> invalid + if isInBookmarks + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Remove from bookmarks" + item.node = m.top + item.func = "RemoveBookmark" + item.args = [videoId] + menu.push(item) + else + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = `Add to "${VIDEOS_GROUP}" bookmark` + item.node = m.top + item.func = "AddBookmark" + item.args = ["video", videoId, VIDEOS_GROUP] + menu.push(item) + end if + return menu +end function + +function GetMenuForPlaylist(playlistNode as object) as object + playlistId = playlistNode.playlistId + if StringUtils.IsNullOrEmpty(playlistId) + return [] + end if + + menu = [] + isInBookmarks = m.top.findNode(playlistId) <> invalid + if isInBookmarks + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Remove from bookmarks" + item.node = m.top + item.func = "RemoveBookmark" + item.args = [playlistId] + menu.push(item) + else + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = `Add to "${PLAYLISTS_GROUP}" bookmark` + item.node = m.top + item.func = "AddBookmark" + item.args = ["playlist", playlistId, PLAYLISTS_GROUP] + menu.push(item) + + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = `Add to "${playlistNode.title}" bookmark` + item.node = m.top + item.func = "AddBookmark" + item.args = ["playlist", playlistId, playlistNode.title] + menu.push(item) + end if + return menu +end function + +function GetMenuForChannel(channelNode as object) as object + authorId = channelNode.authorId + if StringUtils.IsNullOrEmpty(authorId) + return [] + end if + + menu = [] + isInBookmarks = m.top.findNode(authorId) <> invalid + if isInBookmarks + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Remove from bookmarks" + item.node = m.top + item.func = "RemoveBookmark" + item.args = [authorId] + menu.push(item) + else + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = `Add to "${CHANNELS_GROUP}" bookmark` + item.node = m.top + item.func = "AddBookmark" + item.args = ["channel", authorId, CHANNELS_GROUP] + menu.push(item) + + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = `Add to "${channelNode._author}" bookmark` + item.node = m.top + item.func = "AddBookmark" + item.args = ["channel", authorId, channelNode._author] + menu.push(item) + end if + return menu +end function + +' TODO:P0 ' When a video is selected: -' - Add to "Videos" bookmark -' - Add to new bookmark group... +' - Add to "Videos" bookmark - DONE +' - Add to bookmarks... ' When a channel is selected: -' - Add to "Channels" bookmark -' - Add to bookmark -' - Add to new bookmark group... +' - Add to "Channels" bookmark - DONE +' - Add to bookmark - DONE +' - Add to bookmarks... ' When a playlist is selected: -' - Add to "Playlists" bookmark -' - Add to bookmark -' - Add to new bookmark group... +' - Add to "Playlists" bookmark - DONE +' - Add to bookmark - DONE +' - Add to bookmarks... diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml index 39bb9df6..d21c85db 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -3,5 +3,10 @@ + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.bs b/playlet-lib/src/components/VideoFeed/VideoRowList.bs index 84596f62..97f80cfc 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.bs @@ -149,22 +149,17 @@ function OpenContextMenu(rowItem as object) as void subtitle = "" thumbnail = "" - playAction = { - text: "Play", - action: { - node: m.playQueue, - func: "Play", - args: [rowItem, -1] - } - } - queueAction = { - text: "Queue", - action: { - node: m.playQueue, - func: "AddToQueue", - args: [rowItem] - } - } + playAction = CreateObject("roSGNode", "ContextMenuItemContentNode") + playAction.title = "Play" + playAction.node = m.playQueue + playAction.func = "Play" + playAction.args = [rowItem, -1] + + queueAction = CreateObject("roSGNode", "ContextMenuItemContentNode") + queueAction.title = "Queue" + queueAction.node = m.playQueue + queueAction.func = "AddToQueue" + queueAction.args = [rowItem] if rowItem.type = "video" videoId = rowItem.videoId @@ -176,14 +171,15 @@ function OpenContextMenu(rowItem as object) as void menu.push(playAction) menu.push(queueAction) - menu.push({ - text: "Open channel", - action: { - node: m.top, - func: "OpenVideoChannel", - args: [rowItem] - } - }) + + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Open channel" + item.node = m.top + item.func = "OpenVideoChannel" + item.args = [rowItem] + menu.push(item) + + menu.append(m.bookmarks@.GetMenuForVideo(rowItem)) else if rowItem.type = "playlist" playlistId = rowItem.playlistId LogInfo("Playlist long pressed:", playlistId) @@ -194,14 +190,15 @@ function OpenContextMenu(rowItem as object) as void menu.push(playAction) menu.push(queueAction) - menu.push({ - text: "Open", - action: { - node: m.top, - func: "OpenPlaylist", - args: [rowItem] - } - }) + + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Open" + item.node = m.top + item.func = "OpenPlaylist" + item.args = [rowItem] + menu.push(item) + + menu.append(m.bookmarks@.GetMenuForPlaylist(rowItem)) else if rowItem.type = "channel" authorId = rowItem.authorId LogInfo("Channel long pressed:", authorId) @@ -209,14 +206,14 @@ function OpenContextMenu(rowItem as object) as void title = rowItem._author thumbnail = rowItem.thumbnail - menu.push({ - text: "Open", - action: { - node: m.top, - func: "OpenChannel", - args: [rowItem] - } - }) + item = CreateObject("roSGNode", "ContextMenuItemContentNode") + item.title = "Open" + item.node = m.top + item.func = "OpenChannel" + item.args = [rowItem] + menu.push(item) + + menu.append(m.bookmarks@.GetMenuForChannel(rowItem)) else LogWarn("Unknown long pressed item type:", rowItem.type) return diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.xml b/playlet-lib/src/components/VideoFeed/VideoRowList.xml index 899d6cb6..c8ece642 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.xml +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.xml @@ -5,6 +5,7 @@ + From 5061cf8ed8f1d2690cd7b6b29a1d7d06ffd2fbca Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sat, 30 Sep 2023 09:42:29 -0400 Subject: [PATCH 08/32] Use a root content node for bookmarks --- .../BookmarksScreen/BookmarksScreen.bs | 54 ++++++++++++++++++- .../BookmarksScreen/BookmarksScreen.xml | 20 ++++++- .../Services/Bookmarks/Bookmarks.bs | 30 ++++++----- .../Services/Bookmarks/Bookmarks.xml | 4 ++ 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs index f04e56b8..8624cfe0 100644 --- a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs @@ -1,7 +1,59 @@ +import "pkg:/components/Navigation/Navigation.bs" +import "pkg:/source/utils/FocusManagement.bs" +import "pkg:/source/utils/Types.bs" + function Init() m.noBookmarks = m.top.findNode("noBookmarks") + m.yesBookmarks = m.top.findNode("yesBookmarks") + m.rowList = m.top.FindNode("rowList") end function function OnNodeReady() - m.noBookmarks.visible = m.bookmarks.getChildCount() = 0 + SetNavigation(invalid, "back", m.navBar) + SetNavigation(invalid, "left", m.navBar) + + m.rowList@.BindNode(invalid) + + OnBookmarksChange() + m.bookmarks.content.ObserveField("change", FuncName(OnBookmarksChange)) +end function + +function OnFocusChange() as void + if not m.top.focus + return + end if + + if m.yesBookmarks.visible + NodeSetFocus(m.rowList, true) + else + NodeSetFocus(m.navBar, true) + end if +end function + +' TODO:P0 handle visiblity: only refresh bookmarks if screen visible. +' Else mark as dirty and refresh when visible +function OnBookmarksChange() + hasBookmarks = m.bookmarks.content.getChildCount() > 0 + m.noBookmarks.visible = not hasBookmarks + m.yesBookmarks.visible = hasBookmarks + m.top.focusable = hasBookmarks + + if hasBookmarks + SetRowListContent() + else + if m.rowList.hasFocus() + NodeSetFocus(m.navBar, true) + end if + end if +end function + +function SetRowListContent() + +end function + +function OnkeyEvent(key as string, press as boolean) as boolean + if NavigationKeyHandler(key, press).handled + return true + end if + return false end function diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml index 9f21a482..855d51d4 100644 --- a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml @@ -1,4 +1,4 @@ - + @@ -14,5 +14,23 @@ text="You currently have no bookmarks. To add bookmarks, long-press 'OK' on a video, playlist or channel." wrap="true" /> + + + \ No newline at end of file diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs index 2d79b664..5764253a 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -1,3 +1,4 @@ +import "pkg:/source/utils/CryptoUtils.bs" import "pkg:/source/utils/Logging.bs" import "pkg:/source/utils/RegistryUtils.bs" import "pkg:/source/utils/StringUtils.bs" @@ -8,9 +9,10 @@ const CHANNELS_GROUP = "Channels" const PLAYLISTS_GROUP = "Playlists" function Init() + m.top.content = m.top.findNode("content") m.bookmarksString = "" Load() - m.top.ObserveField("change", FuncName(OnChange)) + m.top.content.ObserveField("change", FuncName(OnChange)) end function function Load() as void @@ -34,7 +36,7 @@ function Load() as void end function function Save() as void - bookmarkGroups = m.top.getChildren(-1, 0) + bookmarkGroups = m.top.content.getChildren(-1, 0) if bookmarkGroups.Count() = 0 RegistryUtils.Delete(RegistryUtils.BOOKMARKS) return @@ -71,15 +73,15 @@ end function function AddBookmarkGroup(groupName as string) as object node = CreateObject("roSGNode", "ContentNode") - node.id = groupName + node.id = CryptoUtils.GetMd5(groupName) node.title = groupName - m.top.appendChild(node) + m.top.content.appendChild(node) LogInfo("Added bookmark group:", groupName) return node end function function AddBookmark(bookmarkType as string, id as string, groupName as string) - groupNode = m.top.findNode(groupName) + groupNode = m.top.content.findNode(CryptoUtils.GetMd5(groupName)) if groupNode = invalid groupNode = AddBookmarkGroup(groupName) end if @@ -95,18 +97,18 @@ function AddBookmark(bookmarkType as string, id as string, groupName as string) end function function RemoveBookmark(id as string) as void - node = m.top.findNode(id) + node = m.top.content.findNode(id) if node = invalid return end if - parent = node.getParent() - parent.removeChild(node) + group = node.getParent() + group.removeChild(node) LogInfo("Removed bookmark:", id) - if parent.getChildCount() = 0 - m.top.removeChild(parent) - LogInfo("Removed bookmark group:", parent.title) + if group.getChildCount() = 0 + m.top.content.removeChild(group) + LogInfo("Removed bookmark group:", group.title) end if end function @@ -126,7 +128,7 @@ function GetMenuForVideo(videoNode as object) as object end if menu = [] - isInBookmarks = m.top.findNode(videoId) <> invalid + isInBookmarks = m.top.content.findNode(videoId) <> invalid if isInBookmarks item = CreateObject("roSGNode", "ContextMenuItemContentNode") item.title = "Remove from bookmarks" @@ -152,7 +154,7 @@ function GetMenuForPlaylist(playlistNode as object) as object end if menu = [] - isInBookmarks = m.top.findNode(playlistId) <> invalid + isInBookmarks = m.top.content.findNode(playlistId) <> invalid if isInBookmarks item = CreateObject("roSGNode", "ContextMenuItemContentNode") item.title = "Remove from bookmarks" @@ -185,7 +187,7 @@ function GetMenuForChannel(channelNode as object) as object end if menu = [] - isInBookmarks = m.top.findNode(authorId) <> invalid + isInBookmarks = m.top.content.findNode(authorId) <> invalid if isInBookmarks item = CreateObject("roSGNode", "ContextMenuItemContentNode") item.title = "Remove from bookmarks" diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml index d371cc9f..14030525 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -1,10 +1,14 @@ + + + + \ No newline at end of file From 84e93a429acf85ae6cfa6da9164f3a5b3927294a Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sat, 30 Sep 2023 09:57:35 -0400 Subject: [PATCH 09/32] context menu animation --- .../src/components/ContextMenu/ContextMenu.bs | 5 +- .../components/ContextMenu/ContextMenu.xml | 80 ++++++++++++------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs index cd39ab55..9b99a7e1 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.bs +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -3,13 +3,16 @@ import "pkg:/source/utils/Logging.bs" import "pkg:/source/utils/StringUtils.bs" import "pkg:/source/utils/Types.bs" -' TODO:P1 animation (fade in/out) +' TODO:P0 test animation (fade in) +' TODO:P1 animation (fade out) function Init() m.buttonGroup = m.top.findNode("buttonGroup") + m.showAnimation = m.top.findNode("showAnimation") end function function OnNodeReady() m.buttonGroup.ObserveField("buttonSelected", FuncName(OnButtonSelected)) + m.showAnimation.control = "start" end function function OnFocusChange() as void diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.xml b/playlet-lib/src/components/ContextMenu/ContextMenu.xml index 360ae927..be12dc0f 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.xml +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.xml @@ -8,46 +8,64 @@ + translation="[-470,0]" + color="0x242424"> + - + + - - + + - + + + + \ No newline at end of file From 23c2a8130f2ff989f281cd7d49e0faa1730bae90 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sat, 30 Sep 2023 10:08:36 -0400 Subject: [PATCH 10/32] small tweak --- .../components/Screens/BookmarksScreen/BookmarksScreen.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml index 855d51d4..956e6391 100644 --- a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.xml @@ -6,9 +6,9 @@

- {#each $homeLayoutFileStore as homeLayoutItem} - + {#each $homeLayoutFileStore as feed} + {/each}
From 659ff0b83977dd1d3e0c538297ff22cf442189dd Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Tue, 17 Oct 2023 18:06:13 -0400 Subject: [PATCH 25/32] Restructure things --- .../src/components/ChannelView/ChannelView.bs | 4 +- .../ContentNode/ChannelContentNode.xml | 3 +- .../ContentNode/FeedContentNode.xml | 7 +- .../ContentNode/PlaylistContentNode.xml | 2 + .../ContentNode/VideoContentNode.xml | 2 + .../src/components/ContextMenu/ContextMenu.bs | 2 +- .../BookmarksScreen/BookmarksScreen.bs | 75 +++--- .../Screens/HomeScreen/HomeScreen.bs | 2 +- .../Screens/SearchScreen/SearchScreen.bs | 7 +- .../Services/Bookmarks/Bookmarks.bs | 87 ++++--- .../Services/Bookmarks/Bookmarks.xml | 2 +- .../Services/Invidious/InvidiousService.bs | 51 ++-- .../src/components/VideoFeed/VideoRowList.bs | 24 +- .../src/components/VideoFeed/VideoRowList.xml | 2 +- .../VideoFeed/VideoRowListContentTask.bs | 18 +- .../VideoFeed/VideoRowListRowContentTask.bs | 236 ++++++++++++------ .../src/config/default_home_layout.yaml | 27 +- .../src/config/invidious_video_api.yaml | 15 ++ playlet-lib/src/source/utils/ErrorUtils.bs | 2 +- 19 files changed, 340 insertions(+), 228 deletions(-) diff --git a/playlet-lib/src/components/ChannelView/ChannelView.bs b/playlet-lib/src/components/ChannelView/ChannelView.bs index 3c7a7355..21377f52 100644 --- a/playlet-lib/src/components/ChannelView/ChannelView.bs +++ b/playlet-lib/src/components/ChannelView/ChannelView.bs @@ -57,7 +57,7 @@ function OnContentSet() as void if (authorId <> m.authorId or author <> m.author) and IsArray(content.tabs) m.authorId = authorId m.author = author - m.rowList.contentData = CreateChannelFeeds(m.authorId, author, content.tabs) + m.rowList.feeds = CreateChannelFeeds(m.authorId, author, content.tabs) end if end function @@ -145,8 +145,8 @@ end function function CreateChannelFeed(title as string, endpoint as string, ucid as string, author as string) as object return { title: title, - bookmarkTitle: `${author} - ${title}`, feedSources: [{ + title: `${author} - ${title}`, apiType: "Invidious", endpoint: endpoint, pathParams: { diff --git a/playlet-lib/src/components/ContentNode/ChannelContentNode.xml b/playlet-lib/src/components/ContentNode/ChannelContentNode.xml index 5a29053f..5b484f1d 100644 --- a/playlet-lib/src/components/ContentNode/ChannelContentNode.xml +++ b/playlet-lib/src/components/ContentNode/ChannelContentNode.xml @@ -3,7 +3,8 @@ - + + diff --git a/playlet-lib/src/components/ContentNode/FeedContentNode.xml b/playlet-lib/src/components/ContentNode/FeedContentNode.xml index e285c4de..88ae1c5e 100644 --- a/playlet-lib/src/components/ContentNode/FeedContentNode.xml +++ b/playlet-lib/src/components/ContentNode/FeedContentNode.xml @@ -1,10 +1,7 @@ - - + + - - - \ No newline at end of file diff --git a/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml b/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml index 19176626..5bfcd476 100644 --- a/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml +++ b/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml @@ -2,6 +2,8 @@ + + + + diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs index f1d9f518..8216d1a6 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.bs +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -89,7 +89,7 @@ import "pkg:/source/utils/Types.bs" ' - Once the there are no items left, the second item is loaded, and so on. ' - This is basically nested pagination, but it would allow us to handle large bookmarks, and avoid loading ' all items at once (per row). -' + function Init() m.optionsList = m.top.findNode("optionsList") m.showAnimation = m.top.findNode("showAnimation") diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs index 6eaa456b..dde3da3a 100644 --- a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs @@ -68,73 +68,82 @@ function OnBookmarksChange() as void end function function SetRowListContent(bookmarksContent as object) - bookmarks = bookmarksContent.getChildren(-1, 0) - - contentData = [] - for each bookmarkGroup in bookmarks - items = bookmarkGroup.getChildren(-1, 0) - rowData = { - title: bookmarkGroup.title, - items: [] + bookmarkGroupNodes = bookmarksContent.getChildren(-1, 0) + + feeds = [] + for each bookmarkGroupNode in bookmarkGroupNodes + bookmarkNodes = bookmarkGroupNode.getChildren(-1, 0) + feed = { + title: bookmarkGroupNode.title, + feedSources: [] } - for each item in items - if item.type = "video" - rowItemData = { + for each bookmarkNode in bookmarkNodes + if bookmarkNode.type = "video" + feedSource = { + title: "Video", apiType: "Invidious", endpoint: "video_info", pathParams: { - id: item.itemId + id: bookmarkNode.itemId } } - rowData.items.push(rowItemData) - else if item.type = "playlist" - rowItemData = { + feed.feedSources.push(feedSource) + else if bookmarkNode.type = "playlist" + feedSource = { + title: "Playlist", apiType: "Invidious", endpoint: "playlist_info", pathParams: { - plid: item.itemId + plid: bookmarkNode.itemId } } - rowData.items.push(rowItemData) - if items.Count() = 1 - rowItemData = { + feed.feedSources.push(feedSource) + ' TODO:P0 expanding playlists like this is not a good idea + ' This should be defined at the bookmark level + if bookmarkNodes.Count() = 1 + feedSource = { + title: "Playlist Videos", apiType: "Invidious", endpoint: "playlist", pathParams: { - plid: item.itemId + plid: bookmarkNode.itemId } } - rowData.items.push(rowItemData) + feed.feedSources.push(feedSource) end if - else if item.type = "channel" - rowItemData = { + else if bookmarkNode.type = "channel" + feedSource = { + title: "Channel", apiType: "Invidious", endpoint: "channel_info", pathParams: { - ucid: item.itemId + ucid: bookmarkNode.itemId } } - rowData.items.push(rowItemData) - if items.Count() = 1 - rowItemData = { + feed.feedSources.push(feedSource) + ' TODO:P0 expanding channel videos like this is not a good idea + ' This should be defined at the bookmark level + if bookmarkNodes.Count() = 1 + feedSource = { + title: "Channel Videos", apiType: "Invidious", endpoint: "channel_videos", pathParams: { - ucid: item.itemId + ucid: bookmarkNode.itemId } } - rowData.items.push(rowItemData) + feed.feedSources.push(feedSource) end if - else if item.type = "feed" - rowData.items.append(item.feed.items) + else if bookmarkNode.type = "feedSource" + feed.feedSources.push(bookmarkNode.feedSource) end if end for - contentData.push(rowData) + feeds.push(feed) end for - m.rowList.contentData = contentData + m.rowList.feeds = feeds end function function OnkeyEvent(key as string, press as boolean) as boolean diff --git a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs index 59799ddf..8eb37d5b 100644 --- a/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs +++ b/playlet-lib/src/components/Screens/HomeScreen/HomeScreen.bs @@ -14,7 +14,7 @@ function OnNodeReady() SetNavigation(invalid, "left", m.navBar) m.rowList@.BindNode(invalid) - m.rowList.contentData = ParseJson(ReadAsciiFile(m.top.feedFile)) + m.rowList.feeds = ParseJson(ReadAsciiFile(m.top.feedFile)) end function function OnFocusChange() as void diff --git a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs index 221511b9..bdcbfb0b 100644 --- a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs +++ b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs @@ -204,6 +204,7 @@ function Search(text as string) ShowLoadingScreen() feedSource = { + title: `Search - ${text}`, apiType: "Invidious", endpoint: "search", queryParams: { @@ -226,7 +227,7 @@ function Search(text as string) end if end for - m.rowList.contentData = [{ + m.rowList.feeds = [{ title: `Search - ${text}`, feedSources: [feedSource] }] @@ -248,7 +249,7 @@ function OnSearchContentReady() as void end function function OnSearchError() - m.rowlist.contentData = invalid + m.rowlist.feeds = invalid m.rowlist.focusable = false HideLoadingScreen() end function @@ -263,7 +264,7 @@ end function function ClearSearch() m.keyboard.text = "" - m.rowlist.contentData = invalid + m.rowlist.feeds = invalid m.rowlist.focusable = false ScrollUp() end function diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs index a0aacd8e..bc96569d 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -23,14 +23,19 @@ import "pkg:/source/utils/Types.bs" ' - endpoint: ApiEndpoint ' - pathParams ' - queryParams -' - page (Runtime field) -' - continuation (Runtime field) +' - state: +' - paginationtype (Runtime field) +' - page (Runtime field) +' - continuation (Runtime field) +' - queryParams.page (Runtime field) +' - queryParams.continuation (Runtime field) ' ' Feed/FeedContentNode: ' - Title ' - feedSources: FeedSource[] +' - feedSourceIndex (Runtime field) -' ScreenLayout +' RowListLayout ' - Feed[] ' Bookmarks @@ -41,7 +46,7 @@ import "pkg:/source/utils/Types.bs" ' - Bookmark[] ' Bookmark -' - type: video|playlist|channel|feed +' - type: video|playlist|channel|feedSource ' - ItemId ' - FeedSource @@ -71,8 +76,8 @@ function Load() as void for each bookmark in bookmarks.bookmarks AddBookmarkGroup(bookmark.title) for each item in bookmark.items - if item.type = "feed" - AddFeedBookmark(item.feed) + if item.type = "feedSource" + AddFeedSourceBookmark(item.feedSource) else AddBookmark(item.type, item.id, bookmark.title) end if @@ -81,28 +86,28 @@ function Load() as void end function function Save() as void - bookmarkGroups = m.top.content.getChildren(-1, 0) - if bookmarkGroups.Count() = 0 + bookmarkGroupNodes = m.top.content.getChildren(-1, 0) + if bookmarkGroupNodes.Count() = 0 RegistryUtils.Delete(RegistryUtils.BOOKMARKS) return end if bookmarks = [] - for each group in bookmarkGroups + for each bookmarkGroupNode in bookmarkGroupNodes items = [] - nodes = group.getChildren(-1, 0) - for each node in nodes + bookmarkNodes = bookmarkGroupNode.getChildren(-1, 0) + for each bookmarkNode in bookmarkNodes item = { - type: node.type, - id: node.itemId + type: bookmarkNode.type, + id: bookmarkNode.itemId } - if item.type = "feed" - item.feed = node.feed + if item.type = "feedSource" + item.feedSource = bookmarkNode.feedSource end if items.Push(item) end for bookmarks.push({ - title: group.title, + title: bookmarkGroupNode.title, items: items }) end for @@ -146,27 +151,25 @@ function AddBookmark(bookmarkType as string, id as string, groupName as string) m.top.contentChange = true end function -function AddFeedBookmark(feed as object) - title = feed.bookmarkTitle - if StringUtils.IsNullOrEmpty(title) - title = feed.title - end if - +function AddFeedSourceBookmark(feedSource as object) + title = feedSource.title id = CryptoUtils.GetMd5(title) - groupNode = m.top.content.findNode(id) - if groupNode = invalid - groupNode = AddBookmarkGroup(title) + bookmarkGroupNode = m.top.content.findNode(id) + if bookmarkGroupNode = invalid + bookmarkGroupNode = AddBookmarkGroup(title) end if + feedSource.Delete("state") + node = CreateObject("roSGNode", "ContentNode") node.id = id node.addFields({ - type: "feed", + type: "feedSource", itemId: id, - feed: feed + feedSource: feedSource }) - groupNode.insertChild(node, 0) - LogInfo("Added feed bookmark:", title, "id:", id) + bookmarkGroupNode.insertChild(node, 0) + LogInfo("Added feedSource bookmark:", title, "id:", id) m.top.contentChange = true end function @@ -200,7 +203,7 @@ function GetContextMenuOptionsForItem(item as object) as object else if item.type = "channel" options.append(GetMenuForChannel(item)) end if - options.append(GetMenuForParentFeed(item)) + options.append(GetMenuForParentFeedSource(item)) return options end function @@ -266,38 +269,34 @@ function GetMenuForChannel(channelNode as object) as object return menu end function -function GetMenuForParentFeed(itemNode as object) as object +function GetMenuForParentFeedSource(itemNode as object) as object if itemNode = invalid return [] end if if itemNode.type <> "video" and itemNode.type <> "playlist" and itemNode.type <> "channel" return [] end if - parent = itemNode.getParent() - if parent = invalid + if not IsInt(itemNode.feedSourcesIndex) or itemNode.feedSourcesIndex = -1 return [] end if - if parent.subtype() <> "FeedContentNode" + feedContentNode = itemNode.getParent() + if feedContentNode = invalid or feedContentNode.subtype() <> "FeedContentNode" return [] end if - feed = parent.feed - - title = feed.bookmarkTitle - if StringUtils.IsNullOrEmpty(title) - title = feed.title - end if + feedSource = feedContentNode.feedSources[itemNode.feedSourcesIndex] - feedId = CryptoUtils.GetMd5(title) + title = feedSource.title + feedSourceId = CryptoUtils.GetMd5(title) menu = [] - isInBookmarks = m.top.content.findNode(feedId) <> invalid + isInBookmarks = m.top.content.findNode(feedSourceId) <> invalid if isInBookmarks - item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [feedId]) + item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [feedSourceId]) menu.push(item) else - item = ContextMenuUtils.CreateOption(`Add to "${title}" bookmark`, m.top, "AddFeedBookmark", [feed]) + item = ContextMenuUtils.CreateOption(`Add to "${title}" bookmark`, m.top, "AddFeedSourceBookmark", [feedSource]) menu.push(item) end if return menu diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml index 3d52fa0c..2436bd51 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -4,7 +4,7 @@ - + diff --git a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs index f0512834..f2fec376 100644 --- a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs +++ b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs @@ -156,40 +156,45 @@ namespace Invidious return `${instance}/latest_version?id=${videoId}` end function - function MarkFeedPagination(contentNode as object) as void - requestData = contentNode.feed + function MarkFeedPagination(feedContentNode as object) as object + feedSources = feedContentNode.feedSources + feedSourcesIndex = feedContentNode.feedSourcesIndex - ' Only one item feeds will be paginated. - if requestData.feedSources.Count() <> 1 - return - end if - feedSource = requestData.feedSources[0] + feedSource = feedSources[feedSourcesIndex] endpoint = m.endpoints[feedSource.endpoint] if endpoint = invalid or StringUtils.IsNullOrEmpty(endpoint.paginationType) - return + return feedSource end if - if not feedSource.DoesExist("queryParams") - feedSource.queryParams = {} + feedSourceState = feedSource.state + if not feedSourceState.DoesExist("queryParams") + feedSourceState.queryParams = {} end if - contentNode.paginationType = endpoint.paginationType - - if endpoint.paginationType = PaginationType.Pages - contentNode.page += 1 - feedSource.queryParams.page = contentNode.page - else if endpoint.paginationType = PaginationType.Continuation - continuation = contentNode.continuation + feedSourceState.paginationType = endpoint.paginationType + if feedSourceState.paginationType = PaginationType.Pages + if not IsInt(feedSourceState.page) + feedSourceState.page = 0 + end if + feedSourceState.page += 1 + feedSourceState.queryParams.page = feedSourceState.page + else if feedSourceState.paginationType = PaginationType.Continuation + continuation = feedSourceState.continuation if not StringUtils.IsNullOrEmpty(continuation) - feedSource.queryParams.continuation = continuation + feedSourceState.queryParams.continuation = continuation + else + feedSourceState.queryParams.Delete("continuation") end if end if - requestData.feedSources[0] = feedSource - contentNode.feed = requestData + feedSource.state = feedSourceState + feedSources[feedSourcesIndex] = feedSource + feedContentNode.feedSources = feedSources + return feedSource end function + ' TODO:P0 remove this function, as it is not used function MakeRequest(requestData as object, cancellation = invalid as object) as object aggregateResponse = { success: true, @@ -217,7 +222,7 @@ namespace Invidious return aggregateResponse end function - function MakeRequestSingle(feedSource as object, cancellation = invalid as object) as object + function MakeRequestSingle(feedSource as object, additionalQueryParams = invalid as object, cancellation = invalid as object) as object endpoint = m.endpoints[feedSource.endpoint] if endpoint = invalid return { @@ -275,6 +280,10 @@ namespace Invidious request.QueryParams(feedSource.queryParams) end if + if additionalQueryParams <> invalid + request.QueryParams(additionalQueryParams) + end if + if feedSource.pathParams <> invalid request.PathParams(feedSource.pathParams) end if diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.bs b/playlet-lib/src/components/VideoFeed/VideoRowList.bs index 22caf666..96b63b98 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.bs @@ -39,7 +39,7 @@ function OnVisibilityChange() end if end function -function OnContentData() as void +function OnFeedsChange() as void InitContent() end function @@ -200,14 +200,14 @@ function InitContent() as void CancelCurrentTasks() m.top.someContentReady = false - if m.top.contentData = invalid + if m.top.feeds = invalid m.top.content = invalid return end if task = StartAsyncTask(VideoRowListContentTask, { node: m.top, - contentData: m.top.contentData + feeds: m.top.feeds }, OnVideoRowListContentTaskResult) m.pendingLoadTasks[task.id] = task end function @@ -279,8 +279,8 @@ function LoadRowContent(contentNode as object) contentNode.loadState = FeedLoadState.Loading task = StartAsyncTask(VideoRowListRowContentTask, { - node: m.top, - contentNode: contentNode, + rowList: m.top, + feedContentNode: contentNode, invidious: m.invidious }, OnVideoRowListRowContentTaskResult) m.pendingLoadTasks[task.id] = task @@ -292,20 +292,6 @@ function OnVideoRowListRowContentTaskResult(output as object) as void return end if - if not output.success or not output.result.success - ' output.error for unhandled exception - error = output.error - if error = invalid - ' output.result.error for network errors - error = output.result.error - end if - error = ErrorUtils.Format(error) - LogError(error) - m.top.onError = error - message = `Failed to load feed\n${error}` - DialogUtils.ShowDialog(message, "Feed load fail", true) - end if - LoadRowsIfNeeded() end function diff --git a/playlet-lib/src/components/VideoFeed/VideoRowList.xml b/playlet-lib/src/components/VideoFeed/VideoRowList.xml index fd2473f9..3231e7ec 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowList.xml +++ b/playlet-lib/src/components/VideoFeed/VideoRowList.xml @@ -5,7 +5,7 @@ - + diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListContentTask.bs index 71e1cb5c..190f2658 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListContentTask.bs @@ -3,19 +3,23 @@ import "pkg:/source/utils/StringUtils.bs" @asynctask function VideoRowListContentTask(input as object) - contentData = input.contentData + feeds = input.feeds rowList = input.node contentNode = CreateObject("roSGNode", "ContentNode") - for each feed in contentData + for each feed in feeds feedContentNode = CreateObject("roSGNode", "FeedContentNode") - feedContentNode.feed = feed - feedContentNode.loadState = FeedLoadState.None feedContentNode.title = feed.title - if not StringUtils.IsNullOrEmpty(feed.bookmarkTitle) - feedContentNode.bookmarkTitle = feed.bookmarkTitle - end if + feedContentNode.feedSourcesIndex = 0 + feedContentNode.loadState = FeedLoadState.None + feedSources = feed.feedSources + for i = 0 to feedSources.count() - 1 + feedSources[i].state = { + loadState: FeedLoadState.None + } + end for + feedContentNode.feedSources = feedSources for i = 1 to 4 feedContentNode.createChild("LoadingContentNode") diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs index 667e701b..5e0121ae 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs @@ -1,95 +1,166 @@ import "pkg:/components/Services/Invidious/InvidiousService.bs" import "pkg:/components/Services/Invidious/InvidiousToContentNode.bs" +import "pkg:/components/Dialog/DialogUtils.bs" @asynctask function VideoRowListRowContentTask(input as object) as object - rowList = input.node - contentNode = input.contentNode - invidiousNode = input.invidious - - if m.top.cancel - contentNode.loadState = FeedLoadState.None - return invalid - end if - - service = new Invidious.InvidiousService(invidiousNode) - - service.MarkFeedPagination(contentNode) - response = service.MakeRequest(contentNode.feed, m.top.cancellation) - - if m.top.cancel - contentNode.loadState = FeedLoadState.None - return invalid - end if - - if not response.success - if response.error = Invidious.ERROR_NOT_AUTHENTICATED - RemovePlaceHolderItems(contentNode) - actionNode = CreateObject("roSGNode", "ActionContentNode") - actionNode.title = `Login to view ${contentNode.title}` - actionNode.action = "LoginDialog" - - contentNode.appendChild(actionNode) - contentNode.loadState = FeedLoadState.Loaded - - return { - success: true - } - end if + ' TODO:P0 - test a response error + ' TODO:P0 - test a code error + try + rowList = input.rowList + feedContentNode = input.feedContentNode + invidiousNode = input.invidious + + service = new Invidious.InvidiousService(invidiousNode) + instance = service.GetInstance() + + totalfetchedItems = 0 + + while true + if m.top.cancel + feedContentNode.loadState = FeedLoadState.None + return invalid + end if + + ' Get feed at current index + ' Check we need to increment the index -> increment if needed + ' Get feed at current index + ' Mark pagination + ' Load data for feed + ' Check for items loaded count + + feedSources = feedContentNode.feedSources + feedSourcesIndex = feedContentNode.feedSourcesIndex - ' Invidious returns 500 and "{"error":"Closed stream"}" when the Popular feed is disabled - if response.error.instr("Closed stream") > -1 - feed = contentNode.feed - if feed.feedSources.Count() = 1 and feed.feedSources[0].endpoint = "popular" - LogWarn("Deleting feed", contentNode.title, "-", response.error) - parent = contentNode.getParent() - parent.removeChild(contentNode) + if feedSourcesIndex >= feedSources.Count() + feedContentNode.loadState = FeedLoadState.Loaded return { success: true } end if - end if - contentNode.loadState = FeedLoadState.Error - contentNode.title += " (Failed to load)" - return response - end if - - instance = service.GetInstance() - itemNodes = [] - for each item in response.result.items - itemNode = InvidiousContent.ToRowCellContentNode(item, instance) - if itemNode <> invalid - itemNodes.Push(itemNode) - end if - end for - - RemovePlaceHolderItems(contentNode) - contentNode.appendChildren(itemNodes) - - hasContinuation = not StringUtils.IsNullOrEmpty(response.result.continuation) - if hasContinuation - contentNode.continuation = response.result.continuation - end if - - if response.result.items.Count() > 0 - pageType = contentNode.paginationType - if pageType = PaginationType.Continuation and hasContinuation - contentNode.loadState = FeedLoadState.LoadedPage - else if pageType = PaginationType.Pages - contentNode.loadState = FeedLoadState.LoadedPage - else - contentNode.loadState = FeedLoadState.Loaded - end if - else - contentNode.loadState = FeedLoadState.Loaded - end if + feedSource = feedSources[feedSourcesIndex] + + if feedSource.state.loadState = FeedLoadState.Loaded or feedSource.state.loadState = FeedLoadState.Error + feedSourcesIndex += 1 + if feedSourcesIndex >= feedSources.Count() + feedContentNode.loadState = FeedLoadState.Loaded + return { + success: true + } + end if + feedContentNode.feedSourcesIndex = feedSourcesIndex + continue while + end if + + feedSource = service.MarkFeedPagination(feedContentNode) + + response = service.MakeRequestSingle(feedSource, feedSource.state.queryParams, m.top.cancellation) + + if not response.success + if response.error = Invidious.ERROR_NOT_AUTHENTICATED + RemovePlaceHolderItems(feedContentNode) + actionNode = CreateObject("roSGNode", "ActionContentNode") + actionNode.title = `Login to view ${feedSource.title}` + actionNode.action = "LoginDialog" + + feedSource.state.loadState = FeedLoadState.Loaded + feedSources[feedSourcesIndex] = feedSource + feedContentNode.feedSources = feedSources + + feedContentNode.appendChild(actionNode) + feedContentNode.loadState = FeedLoadState.Loaded + + continue while + end if + + ' Invidious returns 500 and "{"error":"Closed stream"}" when the Popular feed is disabled + if feedSource.endpoint = "popular" and response.error.instr("Closed stream") > -1 + LogWarn("Popular feedSource not available:", response.error) + if feedSources.Count() = 1 + parent = feedContentNode.getParent() + parent.removeChild(feedContentNode) + return { + success: true + } + end if + + continue while + end if - if itemNodes.Count() > 0 - rowList.someContentReady = true - end if + feedSource.state.loadState = FeedLoadState.Error + feedSources[feedSourcesIndex] = feedSource + feedContentNode.feedSources = feedSources - return response + if feedContentNode.title.instr(" (Failed to load)") = -1 + feedContentNode.title += " (Failed to load)" + end if + + HandleFeedLoadErrorDialog(response.error, rowList) + ' TODO:P0 - Show error dialog and continue loading the feed + continue while + end if + + itemNodes = [] + for each item in response.result.items + itemNode = InvidiousContent.ToRowCellContentNode(item, instance) + if itemNode <> invalid + itemNode.feedSourcesIndex = feedSourcesIndex + itemNodes.Push(itemNode) + end if + end for + + RemovePlaceHolderItems(feedContentNode) + feedContentNode.appendChildren(itemNodes) + + hasContinuation = not StringUtils.IsNullOrEmpty(response.result.continuation) + if hasContinuation + feedSource.state.continuation = response.result.continuation + end if + + if response.result.items.Count() > 0 + pageType = feedSource.state.paginationType + if pageType = PaginationType.Continuation and hasContinuation + feedSource.state.loadState = FeedLoadState.LoadedPage + else if pageType = PaginationType.Pages + feedSource.state.loadState = FeedLoadState.LoadedPage + else + feedSource.state.loadState = FeedLoadState.Loaded + end if + else + feedSource.state.loadState = FeedLoadState.Loaded + end if + + feedSources[feedSourcesIndex] = feedSource + feedContentNode.feedSources = feedSources + + if feedSource.state.loadState = FeedLoadState.Loaded and feedSourcesIndex = feedSources.Count() - 1 + feedContentNode.loadState = FeedLoadState.Loaded + else + feedContentNode.loadState = FeedLoadState.LoadedPage + end if + + if itemNodes.Count() > 0 + rowList.someContentReady = true + end if + + totalfetchedItems += response.result.items.Count() + if totalfetchedItems > 3 + exit while + end if + end while + + catch error + HandleFeedLoadErrorDialog(error, rowList) + return { + success: false, + error: error + } + end try + + return { + success: true + } end function function RemovePlaceHolderItems(contentNode as object) @@ -103,3 +174,10 @@ function RemovePlaceHolderItems(contentNode as object) end if end while end function + +function HandleFeedLoadErrorDialog(error as object, rowList as object) + error = ErrorUtils.Format(error) + LogError(error) + rowList.onError = error + DialogUtils.ShowDialog(error, "Failed to load feed", true) +end function diff --git a/playlet-lib/src/config/default_home_layout.yaml b/playlet-lib/src/config/default_home_layout.yaml index 96307599..eb65f08e 100644 --- a/playlet-lib/src/config/default_home_layout.yaml +++ b/playlet-lib/src/config/default_home_layout.yaml @@ -1,42 +1,49 @@ - title: Subscriptions feedSources: - - apiType: Invidious + - title: Subscriptions + apiType: Invidious endpoint: auth_feed - title: Trending feedSources: - - apiType: Invidious + - title: Trending + apiType: Invidious endpoint: trending - title: Trending - Music feedSources: - - apiType: Invidious + - title: Trending - Music + apiType: Invidious endpoint: trending queryParams: type: Music - title: Trending - Gaming feedSources: - - apiType: Invidious + - title: Trending - Gaming + apiType: Invidious endpoint: trending queryParams: type: Gaming - title: Trending - Movies feedSources: - - apiType: Invidious + - title: Trending - Movies + apiType: Invidious endpoint: trending queryParams: type: Movies - title: Popular feedSources: - - apiType: Invidious + - title: Popular + apiType: Invidious endpoint: popular - title: Funny feedSources: - - apiType: Invidious + - title: Search - Funny + apiType: Invidious endpoint: search queryParams: q: Funny @@ -44,7 +51,8 @@ - title: News feedSources: - - apiType: Invidious + - title: Search - News + apiType: Invidious endpoint: search queryParams: q: News @@ -52,5 +60,6 @@ - title: Playlists feedSources: - - apiType: Invidious + - title: Playlists + apiType: Invidious endpoint: auth_playlists diff --git a/playlet-lib/src/config/invidious_video_api.yaml b/playlet-lib/src/config/invidious_video_api.yaml index b4b48a7f..fe4de3ac 100644 --- a/playlet-lib/src/config/invidious_video_api.yaml +++ b/playlet-lib/src/config/invidious_video_api.yaml @@ -1,4 +1,5 @@ trending: + title: Trending url: /api/v1/trending cacheSeconds: 21600 # 6 hours queryParams: @@ -27,6 +28,7 @@ trending: - premiereTimestamp popular: + title: Popular url: /api/v1/popular queryParams: fields: @@ -46,6 +48,7 @@ popular: - premiereTimestamp search: + title: Search url: /api/v1/search paginationType: Pages queryParams: @@ -99,6 +102,7 @@ search: # Auth endpoints auth_feed: + title: Subscriptions url: /api/v1/auth/feed authenticated: true queryParams: @@ -109,10 +113,12 @@ auth_feed: responseHandler: AuthFeedHandler auth_playlists: + title: Playlists url: /api/v1/auth/playlists authenticated: true playlist: + title: Playlist url: /api/v1/playlists/{plid} responseHandler: PlaylistHandler paginationType: Pages @@ -120,22 +126,26 @@ playlist: # video_info, playlist_info and channel_info are used by the # Bookmarks, so they are cached for longer video_info: + title: Video url: /api/v1/videos/{id} responseHandler: VideoInfoHandler cacheSeconds: 259200 # 3 days playlist_info: + title: Playlist url: /api/v1/playlists/{plid} responseHandler: PlaylistInfoHandler cacheSeconds: 259200 # 3 days channel_info: + title: Channel url: /api/v1/channels/{ucid} responseHandler: ChannelInfoHandler cacheSeconds: 259200 # 3 days # Channel endpoints channel_videos: + title: Channel videos url: /api/v1/channels/{ucid}/videos responseHandler: ChannelVideosHandler paginationType: Continuation @@ -148,6 +158,7 @@ channel_videos: - popular channel_playlists: + title: Channel playlists url: /api/v1/channels/{ucid}/playlists responseHandler: ChannelPlaylistsHandler paginationType: Continuation @@ -160,21 +171,25 @@ channel_playlists: - last channel_shorts: + title: Channel shorts url: /api/v1/channels/{ucid}/shorts responseHandler: ChannelVideosHandler paginationType: Continuation channel_streams: + title: Channel streams url: /api/v1/channels/{ucid}/streams responseHandler: ChannelVideosHandler paginationType: Continuation channel_podcasts: + title: Channel podcasts url: /api/v1/channels/{ucid}/podcasts responseHandler: ChannelPlaylistsHandler paginationType: Continuation channel_channels: + title: Related channels url: /api/v1/channels/{ucid}/channels responseHandler: ChannelRelatedChannelsHandler paginationType: Continuation diff --git a/playlet-lib/src/source/utils/ErrorUtils.bs b/playlet-lib/src/source/utils/ErrorUtils.bs index 8c9ff9aa..e6d7731d 100644 --- a/playlet-lib/src/source/utils/ErrorUtils.bs +++ b/playlet-lib/src/source/utils/ErrorUtils.bs @@ -14,7 +14,7 @@ namespace ErrorUtils result = `${error.message} (0x${StrI(error.number, 16)}). backtrace:` for i = error.backtrace.count() - 1 to 0 step -1 backtrace = error.backtrace[i] - result += `\n ${backtrace.function} @ ${backtrace.filename}:${backtrace.line_number}\n` + result += `\n ${backtrace.function} @${backtrace.filename}:${backtrace.line_number}\n` end for return result end if From 45f35bbf83abf6ea094061a4e738489a601bce8f Mon Sep 17 00:00:00 2001 From: github-action linter Date: Tue, 17 Oct 2023 22:07:31 +0000 Subject: [PATCH 26/32] Lint fix --- .../src/components/VideoFeed/VideoRowListRowContentTask.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs index 5e0121ae..860dbafb 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs @@ -1,6 +1,6 @@ +import "pkg:/components/Dialog/DialogUtils.bs" import "pkg:/components/Services/Invidious/InvidiousService.bs" import "pkg:/components/Services/Invidious/InvidiousToContentNode.bs" -import "pkg:/components/Dialog/DialogUtils.bs" @asynctask function VideoRowListRowContentTask(input as object) as object From 81425a0a0ab5984dfc18af3738442f3023a5cb21 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Wed, 18 Oct 2023 09:03:21 -0400 Subject: [PATCH 27/32] remove comment --- .../src/components/VideoFeed/VideoRowListRowContentTask.bs | 1 - 1 file changed, 1 deletion(-) diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs index 860dbafb..3bfc6ace 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs @@ -97,7 +97,6 @@ function VideoRowListRowContentTask(input as object) as object end if HandleFeedLoadErrorDialog(response.error, rowList) - ' TODO:P0 - Show error dialog and continue loading the feed continue while end if From d50a8c71b83effeb644babcff343b6adfe34dcb7 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 19 Oct 2023 17:24:03 -0400 Subject: [PATCH 28/32] introduce feedSource id --- .../src/components/ChannelView/ChannelView.bs | 1 + .../ContentNode/BookmarkContentNode.xml | 5 + .../BookmarksScreen/BookmarksScreen.bs | 60 +---- .../Screens/SearchScreen/SearchScreen.bs | 1 + .../Services/Bookmarks/Bookmarks.bs | 216 ++++++++++++------ .../Services/Bookmarks/Bookmarks.xml | 6 +- .../VideoFeed/VideoRowListRowContentTask.bs | 9 - .../src/config/default_home_layout.yaml | 27 ++- 8 files changed, 172 insertions(+), 153 deletions(-) create mode 100644 playlet-lib/src/components/ContentNode/BookmarkContentNode.xml diff --git a/playlet-lib/src/components/ChannelView/ChannelView.bs b/playlet-lib/src/components/ChannelView/ChannelView.bs index 21377f52..0e56da1a 100644 --- a/playlet-lib/src/components/ChannelView/ChannelView.bs +++ b/playlet-lib/src/components/ChannelView/ChannelView.bs @@ -146,6 +146,7 @@ function CreateChannelFeed(title as string, endpoint as string, ucid as string, return { title: title, feedSources: [{ + id: `inv_${endpoint}_${ucid}`, title: `${author} - ${title}`, apiType: "Invidious", endpoint: endpoint, diff --git a/playlet-lib/src/components/ContentNode/BookmarkContentNode.xml b/playlet-lib/src/components/ContentNode/BookmarkContentNode.xml new file mode 100644 index 00000000..43061404 --- /dev/null +++ b/playlet-lib/src/components/ContentNode/BookmarkContentNode.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs index dde3da3a..a6c9112d 100644 --- a/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs +++ b/playlet-lib/src/components/Screens/BookmarksScreen/BookmarksScreen.bs @@ -79,65 +79,7 @@ function SetRowListContent(bookmarksContent as object) } for each bookmarkNode in bookmarkNodes - if bookmarkNode.type = "video" - feedSource = { - title: "Video", - apiType: "Invidious", - endpoint: "video_info", - pathParams: { - id: bookmarkNode.itemId - } - } - feed.feedSources.push(feedSource) - else if bookmarkNode.type = "playlist" - feedSource = { - title: "Playlist", - apiType: "Invidious", - endpoint: "playlist_info", - pathParams: { - plid: bookmarkNode.itemId - } - } - feed.feedSources.push(feedSource) - ' TODO:P0 expanding playlists like this is not a good idea - ' This should be defined at the bookmark level - if bookmarkNodes.Count() = 1 - feedSource = { - title: "Playlist Videos", - apiType: "Invidious", - endpoint: "playlist", - pathParams: { - plid: bookmarkNode.itemId - } - } - feed.feedSources.push(feedSource) - end if - else if bookmarkNode.type = "channel" - feedSource = { - title: "Channel", - apiType: "Invidious", - endpoint: "channel_info", - pathParams: { - ucid: bookmarkNode.itemId - } - } - feed.feedSources.push(feedSource) - ' TODO:P0 expanding channel videos like this is not a good idea - ' This should be defined at the bookmark level - if bookmarkNodes.Count() = 1 - feedSource = { - title: "Channel Videos", - apiType: "Invidious", - endpoint: "channel_videos", - pathParams: { - ucid: bookmarkNode.itemId - } - } - feed.feedSources.push(feedSource) - end if - else if bookmarkNode.type = "feedSource" - feed.feedSources.push(bookmarkNode.feedSource) - end if + feed.feedSources.push(bookmarkNode.feedSource) end for feeds.push(feed) diff --git a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs index bdcbfb0b..26e8007a 100644 --- a/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs +++ b/playlet-lib/src/components/Screens/SearchScreen/SearchScreen.bs @@ -204,6 +204,7 @@ function Search(text as string) ShowLoadingScreen() feedSource = { + id: `inv_search_${CryptoUtils.GetMd5(text)}`, title: `Search - ${text}`, apiType: "Invidious", endpoint: "search", diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs index bc96569d..d5fb8d59 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -19,6 +19,8 @@ import "pkg:/source/utils/Types.bs" ' - responseHandler ' ' FeedSource: +' - id +' - title ' - apiType: string ("Invidious") ' - endpoint: ApiEndpoint ' - pathParams @@ -43,12 +45,7 @@ import "pkg:/source/utils/Types.bs" ' BookmarkGroup ' - title -' - Bookmark[] - -' Bookmark -' - type: video|playlist|channel|feedSource -' - ItemId -' - FeedSource +' - feedSources: FeedSource[] const VIDEOS_GROUP = "Videos" const CHANNELS_GROUP = "Channels" @@ -73,14 +70,10 @@ function Load() as void return end if - for each bookmark in bookmarks.bookmarks - AddBookmarkGroup(bookmark.title) - for each item in bookmark.items - if item.type = "feedSource" - AddFeedSourceBookmark(item.feedSource) - else - AddBookmark(item.type, item.id, bookmark.title) - end if + for each bookmarkGroup in bookmarks.groups + bookmarkGroupNode = GetOrCreateBookmarkGroup(bookmarkGroup.title) + for each bookmark in bookmarkGroup.bookmarks + AddFeedSourceBookmark(bookmark.feedSource, bookmark.id, bookmarkGroupNode) end for end for end function @@ -92,29 +85,25 @@ function Save() as void return end if - bookmarks = [] + groups = [] for each bookmarkGroupNode in bookmarkGroupNodes - items = [] + bookmarks = [] bookmarkNodes = bookmarkGroupNode.getChildren(-1, 0) for each bookmarkNode in bookmarkNodes - item = { - type: bookmarkNode.type, - id: bookmarkNode.itemId - } - if item.type = "feedSource" - item.feedSource = bookmarkNode.feedSource - end if - items.Push(item) + bookmarks.push({ + id: bookmarkNode.id, + feedSource: bookmarkNode.feedSource + }) end for - bookmarks.push({ + groups.push({ title: bookmarkGroupNode.title, - items: items + bookmarks: bookmarks }) end for bookmarksString = FormatJson({ __version: m.top.__version, - bookmarks: bookmarks + groups: groups }) if m.bookmarksString = bookmarksString @@ -125,52 +114,122 @@ function Save() as void m.bookmarksString = bookmarksString end function -function AddBookmarkGroup(groupName as string) as object +function GetOrCreateBookmarkGroup(groupName as string) as object + id = CryptoUtils.GetMd5(groupName) + node = m.top.content.findNode(id) + if node <> invalid + return node + end if node = CreateObject("roSGNode", "ContentNode") - node.id = CryptoUtils.GetMd5(groupName) + node.id = id node.title = groupName m.top.content.appendChild(node) LogInfo("Added bookmark group:", groupName) return node end function -function AddBookmark(bookmarkType as string, id as string, groupName as string) - groupNode = m.top.content.findNode(CryptoUtils.GetMd5(groupName)) - if groupNode = invalid - groupNode = AddBookmarkGroup(groupName) +function AddFeedSourceBookmark(feedSource as object, id as string, bookmarkGroupNode as dynamic) + if IsString(bookmarkGroupNode) + bookmarkGroupNode = GetOrCreateBookmarkGroup(bookmarkGroupNode) end if - node = CreateObject("roSGNode", "ContentNode") - node.id = id - node.addFields({ - type: bookmarkType, - itemId: id - }) - groupNode.insertChild(node, 0) - LogInfo("Added bookmark:", id) + feedSource.Delete("state") + + bookmarkNode = CreateObject("roSGNode", "BookmarkContentNode") + bookmarkNode.id = id + bookmarkNode.feedSource = feedSource + + bookmarkGroupNode.insertChild(bookmarkNode, 0) + LogInfo("Added feedSource bookmark:", feedSource.title, "id:", id) m.top.contentChange = true end function -function AddFeedSourceBookmark(feedSource as object) - title = feedSource.title - id = CryptoUtils.GetMd5(title) - bookmarkGroupNode = m.top.content.findNode(id) - if bookmarkGroupNode = invalid - bookmarkGroupNode = AddBookmarkGroup(title) - end if +function AddVideoBookmark(id as string, groupName as string) + bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) + + feedSource = { + apiType: "Invidious", + endpoint: "video_info", + pathParams: { + id: id + } + } + AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) +end function - feedSource.Delete("state") +function AddPlaylistBookmark(id as string, groupName as string) + bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) + + feedSource = { + apiType: "Invidious", + endpoint: "playlist_info", + pathParams: { + plid: id + } + } + AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) +end function - node = CreateObject("roSGNode", "ContentNode") - node.id = id - node.addFields({ - type: "feedSource", - itemId: id, - feedSource: feedSource - }) - bookmarkGroupNode.insertChild(node, 0) - LogInfo("Added feedSource bookmark:", title, "id:", id) - m.top.contentChange = true +'TODO:P0 Remove bookmark is not woring +function AddPlaylistBookmarkWithSpread(id as string, groupName as string) + bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) + + feedSource = { + id: `inv_playlist_${id}`, + title: `${groupName} videos`, + apiType: "Invidious", + endpoint: "playlist", + pathParams: { + plid: id + } + } + AddFeedSourceBookmark(feedSource, `inv_playlist_${id}`, bookmarkGroupNode) + + feedSource = { + apiType: "Invidious", + endpoint: "playlist_info", + pathParams: { + plid: id + } + } + AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) +end function + +function AddChannelBookmark(id as string, groupName as string) + bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) + + feedSource = { + apiType: "Invidious", + endpoint: "channel_info", + pathParams: { + ucid: id + } + } + AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) +end function + +function AddChannelBookmarkWithSpread(id as string, groupName as string) + bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) + + feedSource = { + id: `inv_channel_videos_${id}`, + title: `${groupName} videos`, + apiType: "Invidious", + endpoint: "channel_videos", + pathParams: { + ucid: id + } + } + AddFeedSourceBookmark(feedSource, `inv_channel_videos_${id}`, bookmarkGroupNode) + + feedSource = { + apiType: "Invidious", + endpoint: "channel_info", + pathParams: { + ucid: id + } + } + AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) end function function RemoveBookmark(id as string) as void @@ -214,12 +273,14 @@ function GetMenuForVideo(videoNode as object) as object end if menu = [] - isInBookmarks = m.top.content.findNode(videoId) <> invalid + bookmark = m.top.content.findNode(videoId) + isInBookmarks = bookmark <> invalid if isInBookmarks - item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [videoId]) + bookmarkGroup = bookmark.getParent() + item = ContextMenuUtils.CreateOption(`Remove from "${bookmarkGroup.title}" bookmarks`, m.top, "RemoveBookmark", [videoId]) menu.push(item) else - item = ContextMenuUtils.CreateOption(`Add to "${VIDEOS_GROUP}" bookmark`, m.top, "AddBookmark", ["video", videoId, VIDEOS_GROUP]) + item = ContextMenuUtils.CreateOption(`Add to "${VIDEOS_GROUP}" bookmark`, m.top, "AddVideoBookmark", [videoId, VIDEOS_GROUP]) menu.push(item) end if return menu @@ -232,17 +293,17 @@ function GetMenuForPlaylist(playlistNode as object) as object end if menu = [] - ' TODO:P0 create function for checking if a bookmark exists - ' Function should rely on bookmarks, not nodes in content - isInBookmarks = m.top.content.findNode(playlistId) <> invalid + bookmark = m.top.content.findNode(playlistId) + isInBookmarks = bookmark <> invalid if isInBookmarks - item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [playlistId]) + bookmarkGroup = bookmark.getParent() + item = ContextMenuUtils.CreateOption(`Remove from "${bookmarkGroup.title}" bookmarks`, m.top, "RemoveBookmark", [playlistId]) menu.push(item) else - item = ContextMenuUtils.CreateOption(`Add to "${PLAYLISTS_GROUP}" bookmark`, m.top, "AddBookmark", ["playlist", playlistId, PLAYLISTS_GROUP]) + item = ContextMenuUtils.CreateOption(`Add to "${PLAYLISTS_GROUP}" bookmark`, m.top, "AddPlaylistBookmark", [playlistId, PLAYLISTS_GROUP]) menu.push(item) - item = ContextMenuUtils.CreateOption(`Add to "${playlistNode.title}" bookmark`, m.top, "AddBookmark", ["playlist", playlistId, playlistNode.title]) + item = ContextMenuUtils.CreateOption(`Add to "${playlistNode.title}" bookmark`, m.top, "AddPlaylistBookmarkWithSpread", [playlistId, playlistNode.title]) menu.push(item) end if return menu @@ -255,15 +316,17 @@ function GetMenuForChannel(channelNode as object) as object end if menu = [] - isInBookmarks = m.top.content.findNode(authorId) <> invalid + bookmark = m.top.content.findNode(authorId) + isInBookmarks = bookmark <> invalid if isInBookmarks - item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [authorId]) + bookmarkGroup = bookmark.getParent() + item = ContextMenuUtils.CreateOption(`Remove from "${bookmarkGroup.title}" bookmarks`, m.top, "RemoveBookmark", [authorId]) menu.push(item) else - item = ContextMenuUtils.CreateOption(`Add to "${CHANNELS_GROUP}" bookmark`, m.top, "AddBookmark", ["channel", authorId, CHANNELS_GROUP]) + item = ContextMenuUtils.CreateOption(`Add to "${CHANNELS_GROUP}" bookmark`, m.top, "AddChannelBookmark", [authorId, CHANNELS_GROUP]) menu.push(item) - item = ContextMenuUtils.CreateOption(`Add to "${channelNode._author}" bookmark`, m.top, "AddBookmark", ["channel", authorId, channelNode._author]) + item = ContextMenuUtils.CreateOption(`Add to "${channelNode._author}" bookmark`, m.top, "AddChannelBookmarkWithSpread", [authorId, channelNode._author]) menu.push(item) end if return menu @@ -285,18 +348,21 @@ function GetMenuForParentFeedSource(itemNode as object) as object end if feedSource = feedContentNode.feedSources[itemNode.feedSourcesIndex] + if StringUtils.IsNullOrEmpty(feedSource.title) or StringUtils.IsNullOrEmpty(feedSource.id) + return [] + end if title = feedSource.title - feedSourceId = CryptoUtils.GetMd5(title) + feedSourceId = feedSource.id menu = [] isInBookmarks = m.top.content.findNode(feedSourceId) <> invalid if isInBookmarks - item = ContextMenuUtils.CreateOption("Remove from bookmarks", m.top, "RemoveBookmark", [feedSourceId]) + item = ContextMenuUtils.CreateOption(`Remove "${feedSource.title}" from bookmarks`, m.top, "RemoveBookmark", [feedSourceId]) menu.push(item) else - item = ContextMenuUtils.CreateOption(`Add to "${title}" bookmark`, m.top, "AddFeedSourceBookmark", [feedSource]) + item = ContextMenuUtils.CreateOption(`Add to "${title}" bookmark`, m.top, "AddFeedSourceBookmark", [feedSource, feedSourceId, title]) menu.push(item) end if return menu diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml index 2436bd51..acd4a351 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -3,8 +3,12 @@ - + + + + + diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs index 3bfc6ace..7eb023a4 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs @@ -4,8 +4,6 @@ import "pkg:/components/Services/Invidious/InvidiousToContentNode.bs" @asynctask function VideoRowListRowContentTask(input as object) as object - ' TODO:P0 - test a response error - ' TODO:P0 - test a code error try rowList = input.rowList feedContentNode = input.feedContentNode @@ -22,13 +20,6 @@ function VideoRowListRowContentTask(input as object) as object return invalid end if - ' Get feed at current index - ' Check we need to increment the index -> increment if needed - ' Get feed at current index - ' Mark pagination - ' Load data for feed - ' Check for items loaded count - feedSources = feedContentNode.feedSources feedSourcesIndex = feedContentNode.feedSourcesIndex diff --git a/playlet-lib/src/config/default_home_layout.yaml b/playlet-lib/src/config/default_home_layout.yaml index eb65f08e..7c7f53e8 100644 --- a/playlet-lib/src/config/default_home_layout.yaml +++ b/playlet-lib/src/config/default_home_layout.yaml @@ -1,18 +1,21 @@ - title: Subscriptions feedSources: - - title: Subscriptions + - id: inv_auth_feed + title: Subscriptions apiType: Invidious endpoint: auth_feed - title: Trending feedSources: - - title: Trending + - id: inv_trending + title: Trending apiType: Invidious endpoint: trending - title: Trending - Music feedSources: - - title: Trending - Music + - id: inv_trending_music + title: Trending - Music apiType: Invidious endpoint: trending queryParams: @@ -20,7 +23,8 @@ - title: Trending - Gaming feedSources: - - title: Trending - Gaming + - id: inv_trending_gaming + title: Trending - Gaming apiType: Invidious endpoint: trending queryParams: @@ -28,7 +32,8 @@ - title: Trending - Movies feedSources: - - title: Trending - Movies + - id: inv_trending_movies + title: Trending - Movies apiType: Invidious endpoint: trending queryParams: @@ -36,13 +41,15 @@ - title: Popular feedSources: - - title: Popular + - id: inv_popular + title: Popular apiType: Invidious endpoint: popular - title: Funny feedSources: - - title: Search - Funny + - id: inv_search_funny + title: Search - Funny apiType: Invidious endpoint: search queryParams: @@ -51,7 +58,8 @@ - title: News feedSources: - - title: Search - News + - id: inv_search_news + title: Search - News apiType: Invidious endpoint: search queryParams: @@ -60,6 +68,7 @@ - title: Playlists feedSources: - - title: Playlists + - id: inv_auth_playlists + title: Playlists apiType: Invidious endpoint: auth_playlists From f873fcfceef1b22e0a536ca8fa13bcf46ada1bd9 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 19 Oct 2023 17:52:18 -0400 Subject: [PATCH 29/32] small cleanup --- docs/models.md | 48 +++++++++++++++++++ .../Services/Bookmarks/Bookmarks.bs | 43 ----------------- .../Services/Invidious/InvidiousService.bs | 30 +----------- .../VideoFeed/VideoRowListRowContentTask.bs | 2 +- .../Middleware/InvidiousRouter.bs | 2 +- 5 files changed, 51 insertions(+), 74 deletions(-) create mode 100644 docs/models.md diff --git a/docs/models.md b/docs/models.md new file mode 100644 index 00000000..cedb6b2c --- /dev/null +++ b/docs/models.md @@ -0,0 +1,48 @@ + + +# Models + +ApiEndpoint: + +- id +- url +- queryParams +- pathParams +- cacheSeconds +- paginationType +- authenticated +- responseHandler + +FeedSource: + +- id +- title +- apiType: string ("Invidious") +- endpoint: ApiEndpoint +- pathParams +- queryParams +- state: + - paginationtype (Runtime field) + - page (Runtime field) + - continuation (Runtime field) + - queryParams.page (Runtime field) + - queryParams.continuation (Runtime field) + +Feed/FeedContentNode: + +- Title +- feedSources: FeedSource[] +- feedSourceIndex (Runtime field) + +RowListLayout + +- Feed[] + +Bookmarks + +- BookmarkGroup[] + +BookmarkGroup + +- title +- feedSources: FeedSource[] diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs index d5fb8d59..7b16e32d 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.bs @@ -5,48 +5,6 @@ import "pkg:/source/utils/RegistryUtils.bs" import "pkg:/source/utils/StringUtils.bs" import "pkg:/source/utils/Types.bs" -' TODO:P0 redo models -' Models - -' ApiEndpoint: -' - id -' - url -' - queryParams -' - pathParams -' - cacheSeconds -' - paginationType -' - authenticated -' - responseHandler -' -' FeedSource: -' - id -' - title -' - apiType: string ("Invidious") -' - endpoint: ApiEndpoint -' - pathParams -' - queryParams -' - state: -' - paginationtype (Runtime field) -' - page (Runtime field) -' - continuation (Runtime field) -' - queryParams.page (Runtime field) -' - queryParams.continuation (Runtime field) -' -' Feed/FeedContentNode: -' - Title -' - feedSources: FeedSource[] -' - feedSourceIndex (Runtime field) - -' RowListLayout -' - Feed[] - -' Bookmarks -' - BookmarkGroup[] - -' BookmarkGroup -' - title -' - feedSources: FeedSource[] - const VIDEOS_GROUP = "Videos" const CHANNELS_GROUP = "Channels" const PLAYLISTS_GROUP = "Playlists" @@ -170,7 +128,6 @@ function AddPlaylistBookmark(id as string, groupName as string) AddFeedSourceBookmark(feedSource, id, bookmarkGroupNode) end function -'TODO:P0 Remove bookmark is not woring function AddPlaylistBookmarkWithSpread(id as string, groupName as string) bookmarkGroupNode = GetOrCreateBookmarkGroup(groupName) diff --git a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs index f2fec376..269784af 100644 --- a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs +++ b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs @@ -194,35 +194,7 @@ namespace Invidious return feedSource end function - ' TODO:P0 remove this function, as it is not used - function MakeRequest(requestData as object, cancellation = invalid as object) as object - aggregateResponse = { - success: true, - result: { - items: [] - } - } - - for i = 0 to requestData.feedSources.Count() - 1 - feedSource = requestData.feedSources[i] - response = m.MakeRequestSingle(feedSource, cancellation) - if not response.success - ' TODO:P1 handle errors better - ' I don't feel confortable aborting the whole feed just because a single item failed - ' This could cause the whole feed to fail if one item (like a video or playlist) got deleted - return response - end if - aggregateResponse.result.items.Append(response.result.items) - - if requestData.feedSources.Count() = 1 and IsString(response.result.continuation) - aggregateResponse.result.continuation = response.result.continuation - end if - end for - - return aggregateResponse - end function - - function MakeRequestSingle(feedSource as object, additionalQueryParams = invalid as object, cancellation = invalid as object) as object + function MakeRequest(feedSource as object, additionalQueryParams = invalid as object, cancellation = invalid as object) as object endpoint = m.endpoints[feedSource.endpoint] if endpoint = invalid return { diff --git a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs index 7eb023a4..5bceb720 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowListRowContentTask.bs @@ -46,7 +46,7 @@ function VideoRowListRowContentTask(input as object) as object feedSource = service.MarkFeedPagination(feedContentNode) - response = service.MakeRequestSingle(feedSource, feedSource.state.queryParams, m.top.cancellation) + response = service.MakeRequest(feedSource, feedSource.state.queryParams, m.top.cancellation) if not response.success if response.error = Invidious.ERROR_NOT_AUTHENTICATED diff --git a/playlet-lib/src/components/Web/PlayletWebServer/Middleware/InvidiousRouter.bs b/playlet-lib/src/components/Web/PlayletWebServer/Middleware/InvidiousRouter.bs index a368ca54..3455cd9b 100644 --- a/playlet-lib/src/components/Web/PlayletWebServer/Middleware/InvidiousRouter.bs +++ b/playlet-lib/src/components/Web/PlayletWebServer/Middleware/InvidiousRouter.bs @@ -69,7 +69,7 @@ namespace Http ' TODO:P0 handle pagination ' Perhaps the page arg can be added to queryParam of requestData directly invService = new Invidious.InvidiousService(invidiousNode) - invResponse = invService.MakeRequestSingle(requestData) + invResponse = invService.MakeRequest(requestData) if not invResponse.success response.Default(500, `Failed to make request: ${invResponse.error}`) return true From 8f6e2991d2805d7902b4c4d8bde3e0f7c0357bfb Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 19 Oct 2023 18:37:37 -0400 Subject: [PATCH 30/32] playlist context menu --- .../ContentNode/PlaylistContentNode.xml | 1 + .../src/components/ContextMenu/ContextMenu.bs | 16 +++--- .../components/ContextMenu/ContextMenu.xml | 2 +- .../components/PlaylistView/PlaylistView.bs | 57 ++++++++++++++++++- .../components/PlaylistView/PlaylistView.xml | 4 +- .../Services/Bookmarks/Bookmarks.xml | 2 + .../Invidious/InvidiousToContentNode.bs | 1 + 7 files changed, 70 insertions(+), 13 deletions(-) diff --git a/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml b/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml index 5bfcd476..491c4cac 100644 --- a/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml +++ b/playlet-lib/src/components/ContentNode/PlaylistContentNode.xml @@ -24,6 +24,7 @@ we can query it's original owner) --> + diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.bs b/playlet-lib/src/components/ContextMenu/ContextMenu.bs index 8216d1a6..c491e9a4 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.bs +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.bs @@ -34,14 +34,14 @@ import "pkg:/source/utils/Types.bs" ' ' From Playlist view: ' When a video is selected: -' - "Play" -' - "Queue" -' - "Play video" -' - "Queue video" -' - "Open Channel" -' - "Add to "Playlists" bookmark" -' - "Add to bookmark" -' - "Add to "Videos" bookmark" +' - "Play" - DONE +' - "Queue" - DONE +' - "Play video" - DONE +' - "Queue video" - DONE +' - "Open Channel" - DONE +' - "Add to "Playlists" bookmark" - DONE +' - "Add to bookmark" - DONE +' - "Add to "Videos" bookmark" - DONE ' ' From Channel view: ' When a video is selected: diff --git a/playlet-lib/src/components/ContextMenu/ContextMenu.xml b/playlet-lib/src/components/ContextMenu/ContextMenu.xml index 24ce746d..d75bff31 100644 --- a/playlet-lib/src/components/ContextMenu/ContextMenu.xml +++ b/playlet-lib/src/components/ContextMenu/ContextMenu.xml @@ -49,7 +49,7 @@ translation="[25,310]" itemComponentName="ContextMenuRow" vertFocusAnimationStyle="floatingFocus" - numRows="7" /> + numRows="8" />
= playlist.getChildCount() + return [] + end if + + selectedVideo = playlist.getChild(m.list.itemFocused) + if selectedVideo = invalid or not selectedVideo.isSameNode(video) + return [] + end if + + options = [ + ContextMenuUtils.CreateOption("Play", m.playQueue, "Play", [playlist, index]), + ContextMenuUtils.CreateOption("Queue", m.playQueue, "AddToQueue", [playlist]) + ContextMenuUtils.CreateOption("Play video", m.playQueue, "Play", [video, -1]), + ContextMenuUtils.CreateOption("Queue video", m.playQueue, "AddToQueue", [video]), + ] + + if not StringUtils.IsNullOrEmpty(playlist.authorId) + options.push(ContextMenuUtils.CreateOption("Open channel", m.top, "OpenPlaylistChannel", [playlist])) + end if + + options.append(m.bookmarks@.GetMenuForPlaylist(playlist)) + options.append(m.bookmarks@.GetMenuForVideo(video)) + + return options +end function + +function OpenPlaylistChannel(playlist as object) as void + authorId = playlist.authorId + if StringUtils.IsNullOrEmpty(authorId) + LogWarn("Invalid authorId:", authorId) + return end if + channel = InvidiousContent.ToChannelContentNode(invalid, { authorId: authorId }, invalid) + ChannelUtils.Open(channel) end function function LoadPlaylistIfNeeded() as void diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.xml b/playlet-lib/src/components/PlaylistView/PlaylistView.xml index 33e4f72e..ddc6f216 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.xml +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.xml @@ -1,9 +1,11 @@ - + + + + + diff --git a/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs b/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs index 115ad2d4..a29c0ba2 100644 --- a/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs +++ b/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs @@ -63,6 +63,7 @@ namespace InvidiousContent ' NOTE: "_author" not "author". See PlaylistContentNode.xml for explanation. SetIfExists(node, "_author", item, "author") + SetIfExists(node, "authorId", item, "authorId") SetIfExists(node, "description", item, "description") SetIfExists(node, "playlistId", item, "playlistId") SetIfExists(node, "title", item, "title") From 89818c3e5f842724d8b89c682c422e6248064dfc Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 19 Oct 2023 18:59:19 -0400 Subject: [PATCH 31/32] Context menu for the channel view --- .../src/components/ChannelView/ChannelView.bs | 10 ++++++ .../components/ChannelView/ChannelView.xml | 3 +- .../src/components/ContextMenu/ContextMenu.bs | 35 ++++++++----------- .../Services/Bookmarks/Bookmarks.xml | 1 + 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/playlet-lib/src/components/ChannelView/ChannelView.bs b/playlet-lib/src/components/ChannelView/ChannelView.bs index 0e56da1a..f045e27a 100644 --- a/playlet-lib/src/components/ChannelView/ChannelView.bs +++ b/playlet-lib/src/components/ChannelView/ChannelView.bs @@ -12,6 +12,7 @@ function Init() m.thumbnail = m.top.findNode("thumbnail") m.authorLabel = m.top.findNode("authorLabel") m.rowList = m.top.FindNode("rowList") + m.rowList.screen = m.top m.banner.ObserveField("loadStatus", FuncName(OnBannerLoadStatus)) m.author = "" @@ -156,3 +157,12 @@ function CreateChannelFeed(title as string, endpoint as string, ucid as string, }] } end function + +function GetContextMenuOptionsForItem(video as object) as object + content = m.top.content + if content = invalid or StringUtils.IsNullOrEmpty(content.authorId) + return [] + end if + + return m.bookmarks@.GetMenuForChannel(content) +end function diff --git a/playlet-lib/src/components/ChannelView/ChannelView.xml b/playlet-lib/src/components/ChannelView/ChannelView.xml index b304bf1c..83ae63fa 100644 --- a/playlet-lib/src/components/ChannelView/ChannelView.xml +++ b/playlet-lib/src/components/ChannelView/ChannelView.xml @@ -1,8 +1,9 @@ - + + - bookmark" feed being latest videos, live, shorts, etc -' - "Add to "Videos" bookmark" +' - "Play" - DONE +' - "Queue" - DONE +' - "Add to "Channels" bookmark" - DONE +' - "Add to - bookmark" feed being latest videos, live, shorts, etc - DONE +' - "Add to "Videos" bookmark" - DONE ' ' When a playlist is selected: -' - "Play" -' - "Queue" -' - "Open" -' - "Add to "Channels" bookmark" -' - "Add to - bookmark" feed being latest videos, live, shorts, etc -' - "Add to "Playlists" bookmark" -' - "Add to bookmark" +' - "Play" - DONE +' - "Queue" - DONE +' - "Open" - DONE +' - "Add to "Channels" bookmark" - DONE +' - "Add to - bookmark" feed being latest videos, live, shorts, etc - DONE +' - "Add to "Playlists" bookmark" - DONE +' - "Add to bookmark" - DONE ' ' From search view: ' When a video/channel/playlist is selected: -' - Inherit options -' - "Add to "Search - ${q}" bookmark" - this includes the search filters used +' - Inherit options - DONE +' - "Add to "Search - ${q}" bookmark" - this includes the search filters used - DONE ' ' From bookmarks view: ' When a video/channel/playlist is selected: @@ -83,12 +82,6 @@ import "pkg:/source/utils/Types.bs" ' - Add to "Playlists" bookmark ' - "Play/Queue" ' - Other "Playlists" related context menu items -' -' TODO:P1 consider a bookmark that has multiple paginated items. -' - This would require to to load x items from the first item, and load more as the user scrolls right. -' - Once the there are no items left, the second item is loaded, and so on. -' - This is basically nested pagination, but it would allow us to handle large bookmarks, and avoid loading -' all items at once (per row). function Init() m.optionsList = m.top.findNode("optionsList") diff --git a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml index 0f5891ac..89e6bf93 100644 --- a/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml +++ b/playlet-lib/src/components/Services/Bookmarks/Bookmarks.xml @@ -12,6 +12,7 @@ + From 9387d1dbbbb2281e9d9fbf0f9633d35b09d23848 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 19 Oct 2023 19:15:08 -0400 Subject: [PATCH 32/32] changelog --- CHANGELOG.md | 16 ++++++++++++++++ .../src/components/PlaylistView/PlaylistView.bs | 2 ++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1945f007..f8cd2dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Bookmarks: different items can now be bookmarked, and be found in the `Bookmarks` screen. + Things that can be bookmarked: + - A video + - A channel + - A playlist + - Subscriptions + - Trending + - Popular + - Playlists + - Search results + - Channel tabs (latest videos, live streams, playlists, shorts, podcasts, related channels) +- Context menu: press and hold `OK` to show a context menu to: + - Play/queue a video or a playlist + - Open the channel of a playlist or of a video + - Manage bookmarks - Local DASH manifest generation - This adds support for multi languages audio tracks - Also adds thumbnails/preview in trick play mode @@ -17,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Layout in channel view so that the upload time and view count of videos is visible +- A few things in the logger ### Changed diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.bs b/playlet-lib/src/components/PlaylistView/PlaylistView.bs index 1a92be06..f84b80f6 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.bs +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.bs @@ -184,6 +184,8 @@ function OpenPlaylistChannel(playlist as object) as void ChannelUtils.Open(channel) end function +'TODO:P1 a playlist could have been opened from the bookmarks, which can be as old as 3 days +' We should make a request in this case, and let the cache handle freshness. function LoadPlaylistIfNeeded() as void content = m.top.content if content = invalid