Play queue enabled/disabled depending on cache state
This commit is contained in:
6
pyproject.toml
Normal file
6
pyproject.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[tool.black]
|
||||
exclude = '''
|
||||
(
|
||||
/flatpak/
|
||||
)
|
||||
'''
|
@@ -181,6 +181,10 @@
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
#play-queue-image-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* ********** General ********** */
|
||||
.menu-button {
|
||||
padding: 5px;
|
||||
@@ -258,15 +262,18 @@
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
@define-color box_shadow_color rgba(0, 0, 0, 0.2);
|
||||
|
||||
#artist-info-panel {
|
||||
box-shadow: 0 5px 5px @box_shadow_color;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
@define-color detail_color rgba(0, 0, 0, 0.2);
|
||||
#artist-detail-box {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
box-shadow: inset 0 5px 5px @detail_color,
|
||||
inset 0 -5px 5px @detail_color;
|
||||
background-color: @detail_color;
|
||||
box-shadow: inset 0 5px 5px @box_shadow_color,
|
||||
inset 0 -5px 5px @box_shadow_color;
|
||||
background-color: @box_shadow_color;
|
||||
}
|
||||
|
@@ -234,13 +234,13 @@ class ArtistDetailPanel(Gtk.Box):
|
||||
|
||||
# TODO: make these disabled if there are no songs that can be played.
|
||||
play_button = IconButton(
|
||||
"media-playback-start-symbolic", label="Play All", relief=True,
|
||||
"media-playback-start-symbolic", label="Play All", relief=True
|
||||
)
|
||||
play_button.connect("clicked", self.on_play_all_clicked)
|
||||
self.play_shuffle_buttons.pack_start(play_button, False, False, 0)
|
||||
|
||||
shuffle_button = IconButton(
|
||||
"media-playlist-shuffle-symbolic", label="Shuffle All", relief=True,
|
||||
"media-playlist-shuffle-symbolic", label="Shuffle All", relief=True
|
||||
)
|
||||
shuffle_button.connect("clicked", self.on_shuffle_all_button)
|
||||
self.play_shuffle_buttons.pack_start(shuffle_button, False, False, 5)
|
||||
|
@@ -7,7 +7,7 @@ from typing import Any, Callable, List, Optional, Tuple
|
||||
from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, Pango
|
||||
from pychromecast import Chromecast
|
||||
|
||||
from sublime.adapters import AdapterManager, Result
|
||||
from sublime.adapters import AdapterManager, Result, SongCacheStatus
|
||||
from sublime.adapters.api_objects import Song
|
||||
from sublime.config import AppConfiguration
|
||||
from sublime.players import ChromecastPlayer
|
||||
@@ -177,6 +177,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.update_device_list()
|
||||
|
||||
# Short circuit if no changes to the play queue
|
||||
force |= self.offline_mode != app_config.offline_mode
|
||||
self.offline_mode = app_config.offline_mode
|
||||
self.load_play_queue_button.set_sensitive(not self.offline_mode)
|
||||
|
||||
@@ -224,7 +225,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
if order_token != self.play_queue_update_order_token:
|
||||
return
|
||||
|
||||
self.play_queue_store[idx][0] = cover_art_filename
|
||||
self.play_queue_store[idx][1] = cover_art_filename
|
||||
|
||||
def get_cover_art_filename_or_create_future(
|
||||
cover_art_id: Optional[str], idx: int, order_token: int
|
||||
@@ -247,21 +248,26 @@ class PlayerControls(Gtk.ActionBar):
|
||||
if order_token != self.play_queue_update_order_token:
|
||||
return
|
||||
|
||||
self.play_queue_store[idx][1] = calculate_label(song_details)
|
||||
self.play_queue_store[idx][2] = calculate_label(song_details)
|
||||
|
||||
# Cover Art
|
||||
filename = get_cover_art_filename_or_create_future(
|
||||
song_details.cover_art, idx, order_token
|
||||
)
|
||||
if filename:
|
||||
self.play_queue_store[idx][0] = filename
|
||||
self.play_queue_store[idx][1] = filename
|
||||
|
||||
current_play_queue = [x[-1] for x in self.play_queue_store]
|
||||
if app_config.state.play_queue != current_play_queue:
|
||||
self.play_queue_update_order_token += 1
|
||||
|
||||
song_details_results = []
|
||||
for i, song_id in enumerate(app_config.state.play_queue):
|
||||
for i, (song_id, cached_status) in enumerate(
|
||||
zip(
|
||||
app_config.state.play_queue,
|
||||
AdapterManager.get_cached_statuses(app_config.state.play_queue),
|
||||
)
|
||||
):
|
||||
song_details_result = AdapterManager.get_song_details(song_id)
|
||||
|
||||
cover_art_filename = ""
|
||||
@@ -282,6 +288,11 @@ class PlayerControls(Gtk.ActionBar):
|
||||
|
||||
new_store.append(
|
||||
[
|
||||
(
|
||||
not self.offline_mode
|
||||
or cached_status
|
||||
in (SongCacheStatus.CACHED, SongCacheStatus.PERMANENTLY_CACHED)
|
||||
),
|
||||
cover_art_filename,
|
||||
label,
|
||||
i == app_config.state.current_song_index,
|
||||
@@ -361,6 +372,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.play_queue_popover.show_all()
|
||||
|
||||
def on_song_activated(self, t: Any, idx: Gtk.TreePath, c: Any):
|
||||
if not self.play_queue_store[idx[0]][0]:
|
||||
return
|
||||
# The song ID is in the last column of the model.
|
||||
self.emit(
|
||||
"song-clicked",
|
||||
@@ -481,7 +494,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
# reordering_play_queue_song_list flag.
|
||||
if self.reordering_play_queue_song_list:
|
||||
currently_playing_index = [
|
||||
i for i, s in enumerate(self.play_queue_store) if s[2]
|
||||
i for i, s in enumerate(self.play_queue_store) if s[3] # playing
|
||||
][0]
|
||||
self.emit(
|
||||
"refresh-window",
|
||||
@@ -706,6 +719,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
)
|
||||
|
||||
self.play_queue_store = Gtk.ListStore(
|
||||
bool, # playable
|
||||
str, # image filename
|
||||
str, # title, album, artist
|
||||
bool, # playing
|
||||
@@ -714,30 +728,35 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.play_queue_list = Gtk.TreeView(
|
||||
model=self.play_queue_store, reorderable=True, headers_visible=False,
|
||||
)
|
||||
self.play_queue_list.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
selection = self.play_queue_list.get_selection()
|
||||
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
selection.set_select_function(lambda _, model, path, current: model[path[0]][0])
|
||||
|
||||
# Album Art column.
|
||||
# Album Art column. This function defines what image to use for the play queue
|
||||
# song icon.
|
||||
def filename_to_pixbuf(
|
||||
column: Any,
|
||||
cell: Gtk.CellRendererPixbuf,
|
||||
model: Gtk.ListStore,
|
||||
iter: Gtk.TreeIter,
|
||||
tree_iter: Gtk.TreeIter,
|
||||
flags: Any,
|
||||
):
|
||||
filename = model.get_value(iter, 0)
|
||||
cell.set_property("sensitive", model.get_value(tree_iter, 0))
|
||||
filename = model.get_value(tree_iter, 1)
|
||||
if not filename:
|
||||
cell.set_property("icon_name", "")
|
||||
return
|
||||
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, 50, 50, True)
|
||||
|
||||
# If this is the playing song, then overlay the play icon.
|
||||
if model.get_value(iter, 2):
|
||||
if model.get_value(tree_iter, 3):
|
||||
play_overlay_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
|
||||
str(Path(__file__).parent.joinpath("images/play-queue-play.png"))
|
||||
)
|
||||
|
||||
play_overlay_pixbuf.composite(
|
||||
pixbuf, 0, 0, 50, 50, 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255
|
||||
pixbuf, 0, 0, 50, 50, 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 200
|
||||
)
|
||||
|
||||
cell.set_property("pixbuf", pixbuf)
|
||||
@@ -749,8 +768,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
column.set_resizable(True)
|
||||
self.play_queue_list.append_column(column)
|
||||
|
||||
renderer = Gtk.CellRendererText(markup=True, ellipsize=Pango.EllipsizeMode.END,)
|
||||
column = Gtk.TreeViewColumn("", renderer, markup=1)
|
||||
renderer = Gtk.CellRendererText(markup=True, ellipsize=Pango.EllipsizeMode.END)
|
||||
column = Gtk.TreeViewColumn("", renderer, markup=2, sensitive=0)
|
||||
self.play_queue_list.append_column(column)
|
||||
|
||||
self.play_queue_list.connect("row-activated", self.on_song_activated)
|
||||
|
@@ -713,6 +713,8 @@ class PlaylistDetailPanel(Gtk.Overlay):
|
||||
)
|
||||
|
||||
def on_song_activated(self, _, idx: Gtk.TreePath, col: Any):
|
||||
if not self.playlist_song_store[idx[0]][0]:
|
||||
return
|
||||
# The song ID is in the last column of the model.
|
||||
self.emit(
|
||||
"song-clicked",
|
||||
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
from typing import Any, Generator, List, Tuple
|
||||
|
||||
import pytest
|
||||
from dateutil.tz import tzutc
|
||||
|
||||
from sublime.adapters.subsonic import (
|
||||
api_objects as SubsonicAPI,
|
||||
@@ -111,8 +112,8 @@ def test_get_playlists(adapter: SubsonicAdapter):
|
||||
name="Test",
|
||||
song_count=132,
|
||||
duration=timedelta(seconds=33072),
|
||||
created=datetime(2020, 3, 27, 5, 38, 45, 0, tzinfo=timezone.utc),
|
||||
changed=datetime(2020, 4, 9, 16, 3, 26, 0, tzinfo=timezone.utc),
|
||||
created=datetime(2020, 3, 27, 5, 38, 45, 0, tzinfo=tzutc()),
|
||||
changed=datetime(2020, 4, 9, 16, 3, 26, 0, tzinfo=tzutc()),
|
||||
comment="Foo",
|
||||
owner="foo",
|
||||
public=True,
|
||||
@@ -123,8 +124,8 @@ def test_get_playlists(adapter: SubsonicAdapter):
|
||||
name="Bar",
|
||||
song_count=23,
|
||||
duration=timedelta(seconds=847),
|
||||
created=datetime(2020, 3, 27, 5, 39, 4, 0, tzinfo=timezone.utc),
|
||||
changed=datetime(2020, 3, 27, 5, 45, 23, 0, tzinfo=timezone.utc),
|
||||
created=datetime(2020, 3, 27, 5, 39, 4, 0, tzinfo=tzutc()),
|
||||
changed=datetime(2020, 3, 27, 5, 45, 23, 0, tzinfo=tzutc()),
|
||||
comment="",
|
||||
owner="foo",
|
||||
public=False,
|
||||
@@ -136,7 +137,7 @@ def test_get_playlists(adapter: SubsonicAdapter):
|
||||
logging.info(filename)
|
||||
logging.debug(data)
|
||||
adapter._set_mock_data(data)
|
||||
assert adapter.get_playlists() == expected
|
||||
assert adapter.get_playlists() == sorted(expected, key=lambda e: e.name)
|
||||
|
||||
# When playlists is null, expect an empty list.
|
||||
adapter._set_mock_data(mock_json())
|
||||
|
@@ -46,14 +46,15 @@ def test_yaml_load_unload():
|
||||
|
||||
|
||||
def test_config_migrate():
|
||||
config = AppConfiguration()
|
||||
config = AppConfiguration(always_stream=True)
|
||||
server = ServerConfiguration(
|
||||
name="Test", server_address="https://test.host", username="test"
|
||||
)
|
||||
config.servers.append(server)
|
||||
config.migrate()
|
||||
|
||||
assert config.version == 3
|
||||
assert config.version == 4
|
||||
assert config.allow_song_downloads is False
|
||||
for server in config.servers:
|
||||
server.version == 0
|
||||
|
||||
|
Reference in New Issue
Block a user