diff --git a/lua/file_browser.lua b/lua/file_browser.lua index 98261d55..6289828f 100644 --- a/lua/file_browser.lua +++ b/lua/file_browser.lua @@ -8,11 +8,11 @@ local backstack = require("backstack") local font = require("font") local queue = require("queue") local playing = require("playing") -local styles = require("styles") local playback = require("playback") local theme = require("theme") local screen = require("screen") local filesystem = require("filesystem") +local playlist_iterator = require("playlist_iterator") return screen:new { create_ui = function(self) @@ -70,8 +70,7 @@ return screen:new { breadcrumb = item:filepath() }) elseif - item:filepath():match("%.playlist$") or - item:filepath():match("%.m3u8?$") then + playlist_iterator:is_playlist(item) then queue.open_playlist(item:filepath()) playback.playing:set(true) backstack.push(playing:new()) diff --git a/lua/main_menu.lua b/lua/main_menu.lua index 3b512d72..8754df85 100644 --- a/lua/main_menu.lua +++ b/lua/main_menu.lua @@ -11,7 +11,6 @@ local browser = require("browser") local playing = require("playing") local styles = require("styles") local filesystem = require("filesystem") -local screen = require("screen") local font = require("font") local theme = require("theme") local img = require("images") @@ -129,6 +128,15 @@ return widgets.MenuScreen:new { }) end + local playlist_btn = indexes_list:add_btn(nil, "Playlists") + playlist_btn:onClicked(function() + backstack.push(require("playlist_browser"):new { + title = "Playlists", + iterator = filesystem.iterator("/Playlists") + }) + end) + playlist_btn:add_style(styles.list_item) + local function show_no_indexes(msg) indexes_list:add_flag(lvgl.FLAG.HIDDEN) no_indexes_container:clear_flag(lvgl.FLAG.HIDDEN) @@ -144,6 +152,14 @@ return widgets.MenuScreen:new { end end + local function hide_playlist_listing() + playlist_btn:add_flag(lvgl.FLAG.HIDDEN) + end + + local function show_playlist_listing() + playlist_btn:clear_flag(lvgl.FLAG.HIDDEN) + end + local function update_visible_indexes() local has_valid_index = false for _, idx in ipairs(indexes) do @@ -157,6 +173,13 @@ return widgets.MenuScreen:new { end if has_valid_index then hide_no_indexes() + + -- If we have valid indexes, then also check for playlists + if filesystem.iterator("/Playlists/"):next() == nil then + hide_playlist_listing() + else + show_playlist_listing() + end else if require("database").updating:get() then show_no_indexes("The database is updating for the first time. Please wait.") diff --git a/lua/playlist_browser.lua b/lua/playlist_browser.lua new file mode 100644 index 00000000..0339d59b --- /dev/null +++ b/lua/playlist_browser.lua @@ -0,0 +1,94 @@ +-- SPDX-FileCopyrightText: 2025 Sam Lord +-- +-- SPDX-License-Identifier: GPL-3.0-only + +local lvgl = require("lvgl") +local widgets = require("widgets") +local backstack = require("backstack") +local playing = require("playing") +local filesystem = require("filesystem") +local screen = require("screen") +local font = require("font") +local theme = require("theme") +local playback = require("playback") +local queue = require("queue") +local playlist_iterator = require("playlist_iterator") +local img = require("images") + + +return screen:new { + create_ui = function(self) + self.root = lvgl.Object(nil, { + flex = { + flex_direction = "column", + flex_wrap = "wrap", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start" + }, + w = lvgl.HOR_RES(), + h = lvgl.VER_RES() + }) + self.root:center() + + self.status_bar = widgets.StatusBar(self, { + back_cb = backstack.pop, + title = self.title + }) + + local header = self.root:Object { + flex = { + flex_direction = "column", + flex_wrap = "wrap", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start" + }, + w = lvgl.HOR_RES(), + h = lvgl.SIZE_CONTENT, + pad_left = 4, + pad_right = 4, + pad_bottom = 2, + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF + } + theme.set_subject(header, "header") + + if self.breadcrumb then + header:Label { + text = self.breadcrumb, + text_font = font.fusion_10 + } + end + + local get_icon_func = function(item) + if item:is_directory() then + return img.files + else + return img.enqueue + end + end + + widgets.InfiniteList(self.root, playlist_iterator:create(self.iterator), { + focus_first_item = true, + get_icon = get_icon_func, + callback = function(item) + return function() + if item:is_directory() then + backstack.push( + require("playlist_browser"):new { + title = self.title, + iterator = filesystem.iterator(item:filepath()), + breadcrumb = item:filepath() + }) + elseif + playlist_iterator:is_playlist(item) then + -- TODO: playlist viewer + queue.open_playlist(item:filepath()) + playback.playing:set(true) + backstack.push(playing:new()) + end + end + end + }) + end +} diff --git a/lua/playlist_iterator.lua b/lua/playlist_iterator.lua new file mode 100644 index 00000000..06e80ad2 --- /dev/null +++ b/lua/playlist_iterator.lua @@ -0,0 +1,35 @@ +local PlaylistIterator = {} + +function PlaylistIterator:is_playlist(item) + return item:filepath():match("%.playlist$") + or item:filepath():match("%.m3u8?$") +end + +function PlaylistIterator:create(fs_iterator) + local iterator = fs_iterator:clone() + local obj = {}; + + local find_matching = function(iterate_fn) + local next = iterate_fn(iterator); + while next and (not PlaylistIterator:is_playlist(next) and not next:is_directory()) do + next = iterate_fn(); + end + return next; + end + + function obj:clone() + return PlaylistIterator:create(iterator) + end + + function obj:next() + return find_matching(iterator.next) + end + + function obj:prev() + return find_matching(iterator.prev) + end + + return obj +end + +return PlaylistIterator diff --git a/lua/widgets.lua b/lua/widgets.lua index 0aac7705..20f0cd2a 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -361,7 +361,7 @@ function widgets.InfiniteList(parent, iterator, opts) end for idx = 0, 8 do - local val = fwd_iterator() + local val = fwd_iterator:next() if not val then break end