You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
7.4 KiB
260 lines
7.4 KiB
-- SPDX-FileCopyrightText: 2023 jacqueline <me@jacqueline.id.au>
|
|
--
|
|
-- SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
local lvgl = require("lvgl")
|
|
local widgets = require("widgets")
|
|
local database = require("database")
|
|
local sd_card = require("sd_card")
|
|
local backstack = require("backstack")
|
|
local browser = require("browser")
|
|
local playing = require("playing")
|
|
local styles = require("styles")
|
|
local filesystem = require("filesystem")
|
|
local font = require("font")
|
|
local theme = require("theme")
|
|
local img = require("images")
|
|
local playback = require("playback")
|
|
|
|
return widgets.MenuScreen:new {
|
|
create_ui = function(self)
|
|
widgets.MenuScreen.create_ui(self)
|
|
|
|
-- At the top, a card showing details about the current track. Hidden if
|
|
-- there is no track currently playing.
|
|
-- || Cool Song 0:01
|
|
|
|
local now_playing = lvgl.Button(self.root, {
|
|
flex = {
|
|
flex_direction = "row",
|
|
flex_wrap = "nowrap",
|
|
justify_content = "flex-start",
|
|
align_items = "center",
|
|
align_content = "flex-start",
|
|
},
|
|
w = lvgl.PCT(100),
|
|
h = lvgl.SIZE_CONTENT,
|
|
margin_all = 2,
|
|
pad_bottom = 2,
|
|
pad_column = 4,
|
|
border_width = 1,
|
|
})
|
|
theme.set_subject(now_playing, "now_playing");
|
|
|
|
local play_pause = now_playing:Image { src = img.play_circ }
|
|
local title = now_playing:Label {
|
|
flex_grow = 1,
|
|
h = lvgl.SIZE_CONTENT,
|
|
text = " ",
|
|
long_mode = 1,
|
|
}
|
|
local time_remaining = now_playing:Label {
|
|
text = " ",
|
|
text_font = font.fusion_10,
|
|
}
|
|
|
|
now_playing:onClicked(function() backstack.push(playing:new()) end)
|
|
|
|
local track_duration = nil
|
|
|
|
self.bindings = self.bindings + {
|
|
playback.playing:bind(function(playing)
|
|
if playing then
|
|
play_pause:set_src(img.play_circ)
|
|
else
|
|
play_pause:set_src(img.pause_circ)
|
|
end
|
|
end),
|
|
playback.track:bind(function(track)
|
|
if not track then
|
|
now_playing:add_flag(lvgl.FLAG.HIDDEN)
|
|
return
|
|
else
|
|
now_playing:clear_flag(lvgl.FLAG.HIDDEN)
|
|
end
|
|
title:set { text = track.title }
|
|
if track.duration then
|
|
track_duration = track.duration
|
|
end
|
|
end),
|
|
playback.position:bind(function(pos)
|
|
if not pos then return end
|
|
if not track_duration then return end
|
|
local remaining = track_duration - pos
|
|
if remaining < 0 then remaining = 0 end
|
|
time_remaining:set {
|
|
text = string.format("%d:%02d", remaining // 60, remaining % 60)
|
|
}
|
|
end),
|
|
}
|
|
|
|
-- Next, a list showing the user's prefer's music source. This defaults to
|
|
-- a list of all available database indexes, but could also be the contents
|
|
-- of the SD card root.
|
|
local no_indexes_container = self.root:Object {
|
|
w = lvgl.PCT(100),
|
|
flex_grow = 1,
|
|
}
|
|
local no_indexes_label = no_indexes_container:Label {
|
|
w = lvgl.PCT(100),
|
|
h = lvgl.SIZE_CONTENT,
|
|
text_align = 2,
|
|
long_mode = 0,
|
|
margin_all = 4,
|
|
text = "",
|
|
}
|
|
no_indexes_label:center()
|
|
|
|
local indexes_list = lvgl.List(self.root, {
|
|
w = lvgl.PCT(100),
|
|
h = lvgl.PCT(100),
|
|
flex_grow = 1,
|
|
})
|
|
local indexes = {}
|
|
|
|
for _, idx in ipairs(database.indexes()) do
|
|
local btn = indexes_list:add_btn(nil, tostring(idx))
|
|
btn:onClicked(function()
|
|
backstack.push(browser:new {
|
|
title = tostring(idx),
|
|
iterator = idx:iter(),
|
|
mediatype = idx:type(),
|
|
})
|
|
end)
|
|
btn:add_style(styles.list_item)
|
|
table.insert(indexes, {
|
|
object = btn,
|
|
index = idx,
|
|
})
|
|
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)
|
|
no_indexes_label:set { text = msg }
|
|
end
|
|
|
|
local function hide_no_indexes()
|
|
no_indexes_container:add_flag(lvgl.FLAG.HIDDEN)
|
|
indexes_list:clear_flag(lvgl.FLAG.HIDDEN)
|
|
|
|
if indexes[1] then
|
|
indexes[1].object:focus()
|
|
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
|
|
local it = idx.index:iter():next()
|
|
if it then
|
|
has_valid_index = true
|
|
idx.object:clear_flag(lvgl.FLAG.HIDDEN)
|
|
else
|
|
idx.object:add_flag(lvgl.FLAG.HIDDEN)
|
|
end
|
|
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.")
|
|
else
|
|
show_no_indexes("No compatible media was found on your SD Card.")
|
|
end
|
|
end
|
|
end
|
|
|
|
self.bindings = self.bindings + {
|
|
database.updating:bind(function()
|
|
update_visible_indexes()
|
|
end),
|
|
sd_card.mounted:bind(function(mounted)
|
|
if not mounted then
|
|
show_no_indexes("SD Card is not inserted or could not be opened.")
|
|
end
|
|
end),
|
|
}
|
|
|
|
-- Finally, the bottom bar with icon buttons for other device features.
|
|
|
|
local bottom_bar = lvgl.Object(self.root, {
|
|
flex = {
|
|
flex_direction = "row",
|
|
flex_wrap = "nowrap",
|
|
justify_content = "space-evenly",
|
|
align_items = "center",
|
|
align_content = "flex-start",
|
|
},
|
|
w = lvgl.PCT(100),
|
|
h = lvgl.SIZE_CONTENT,
|
|
pad_top = 4,
|
|
pad_bottom = 2,
|
|
})
|
|
|
|
-- local queue_btn = bottom_bar:Button {}
|
|
-- queue_btn:Image { src = img.queue }
|
|
-- theme.set_subject(queue_btn, "icon_enabled")
|
|
|
|
local usb_btn = bottom_bar:Button {}
|
|
usb_btn:onClicked(function()
|
|
backstack.push(require("settings").MassStorageSettings:new())
|
|
end)
|
|
usb_btn:Image { src = img.usb }
|
|
widgets.Description(usb_btn, "USB Settings")
|
|
theme.set_subject(usb_btn, "menu_icon")
|
|
|
|
self.bindings = self.bindings + {
|
|
require("power").plugged_in:bind(function(attached)
|
|
if (attached) then
|
|
usb_btn:clear_flag(lvgl.FLAG.HIDDEN)
|
|
else
|
|
usb_btn:add_flag(lvgl.FLAG.HIDDEN)
|
|
end
|
|
end)
|
|
}
|
|
|
|
local files_btn = bottom_bar:Button {}
|
|
files_btn:onClicked(function()
|
|
backstack.push(require("file_browser"):new {
|
|
title = "Files",
|
|
iterator = filesystem.iterator(""),
|
|
})
|
|
end)
|
|
files_btn:Image { src = img.files }
|
|
widgets.Description(files_btn, "File browser")
|
|
theme.set_subject(files_btn, "menu_icon")
|
|
|
|
local settings_btn = bottom_bar:Button {}
|
|
settings_btn:onClicked(function()
|
|
backstack.push(require("settings").Root:new())
|
|
end)
|
|
settings_btn:Image { src = img.settings }
|
|
widgets.Description(settings_btn, "Settings")
|
|
theme.set_subject(settings_btn, "menu_icon")
|
|
end,
|
|
}
|
|
|