More UI fixes for offline mode

This commit is contained in:
Sumner Evans
2020-05-24 19:14:33 -06:00
parent ac6bcec89c
commit 654b0902e7
16 changed files with 257 additions and 244 deletions

View File

@@ -1,5 +1,5 @@
v0.9.3
======
v0.10.0
=======
.. warning::
@@ -9,40 +9,47 @@ v0.9.3
``~/.config/sublime-music``) and re-run Sublime Music to restart the
configuration process.
**Note:** this release does not have Flatpak support due to the fact that
Flatpak does not support Python 3.8 yet.
Features
--------
* **UI Features**
**Albums Tab Improvements**
* **Albums Tab**
* The Albums tab is now paginated with configurable page sizes.
* You can sort the Albums tab ascending or descending.
* Opening an closing an album on the Albums tab now has a nice animation.
* The Albums tab is now paginated with configurable page sizes.
* You can sort the Albums tab ascending or descending.
* Opening an closing an album on the Albums tab now has a nice animation.
**Player Controls**
* **Player Controls**
* The amount of the song that is cached is now shown while streaming a song.
* The notification for resuming a play queue is now a non-modal notification
that pops up right above the player controls.
* The amount of the song that is cached is now shown while streaming a song.
* The notification for resuming a play queue is now a non-modal
notification that pops up right above the player controls.
**New Icons**
* **New Icons**
* The Devices button now uses the Chromecast logo.
* Custom icons for "Add to play queue", and "Play next" buttons. Thanks to
@samsartor for contributing the SVGs!
* A new icon for indicating the connection state to the Subsonic server.
Contributed by @samsartor.
* The Devices button now uses the Chromecast logo.
* Custom icons for "Add to play queue", and "Play next" buttons. Thanks to
@samsartor for contributing the SVGs!
* A new icon for indicating the connection state to the Subsonic server.
Contributed by @samsartor.
**Settings**
* **Settings**
* Settings are now in the popup under the gear icon rather than in a separate
popup window.
* The music provider configuration has gotten a major revamp.
* You can now clear the cache via an option in the Downloads popup. There are
options for removing the entire cache and removing just the song file cache.
* Settings are now in the popup under the gear icon rather than in a
separate popup window.
* You can now clear the cache via an option in the Downloads popup. There
are options for removing the entire cache and removing just the song file
cache.
**Offline Mode**
* **Backend**
* You can enable *Offline Mode* from the server menu.
* Features that require network access are disabled in offline mode.
* You can still browse anything that is already cached offline.
.. MENTION man page
Under The Hood
--------------
This release has a ton of under-the-hood changes to make things more robust
and performant.

View File

@@ -77,7 +77,6 @@
<update_contact>me_AT_sumnerevans.com</update_contact>
<releases>
<release version="0.9.2" date="2020-05-07">
</release>
<release version="0.10.0" date="2020-05-07"></release>
</releases>
</component>

View File

@@ -1 +1 @@
__version__ = "0.9.2"
__version__ = "0.10.0"

View File

@@ -251,9 +251,11 @@ class FilesystemAdapter(CachingAdapter):
)
if cover_art:
filename = self.cover_art_dir.joinpath(str(cover_art.file_hash))
if cover_art.valid and filename.exists():
return str(filename)
raise CacheMissError(partial_data=str(filename))
if filename.exists():
if cover_art.valid:
return str(filename)
else:
raise CacheMissError(partial_data=str(filename))
raise CacheMissError()
@@ -269,9 +271,11 @@ class FilesystemAdapter(CachingAdapter):
if (song_file := song.file) and (
filename := self._compute_song_filename(song_file)
):
if song_file.valid and filename.exists():
return str(filename)
raise CacheMissError(partial_data=str(filename))
if filename.exists():
if song_file.valid:
return str(filename)
else:
raise CacheMissError(partial_data=str(filename))
except models.CacheInfo.DoesNotExist:
pass

View File

@@ -152,6 +152,7 @@ class SubsonicAdapter(Adapter):
sleep(15)
def _set_ping_status(self):
# TODO don't ping in offline mode
try:
# Try to ping the server with a timeout of 2 seconds.
self._get_json(self._make_url("ping"), timeout=2)

View File

@@ -824,9 +824,10 @@ class SublimeMusicApp(Gtk.Application):
return False
# Allow spaces to work in the text entry boxes.
if window.search_entry.has_focus():
return False
if window.playlists_panel.playlist_list.new_playlist_entry.has_focus():
if (
window.search_entry.has_focus()
or window.playlists_panel.playlist_list.new_playlist_entry.has_focus()
):
return False
# Spacebar, home/prev

View File

@@ -555,7 +555,9 @@ class AlbumsGrid(Gtk.Overlay):
grid_detail_grid_box.add(self.grid_top)
self.detail_box_revealer = Gtk.Revealer(valign=Gtk.Align.END)
self.detail_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.detail_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, name="artist-detail-box"
)
self.detail_box.pack_start(Gtk.Box(), True, True, 0)
self.detail_box_inner = Gtk.Box()

View File

@@ -261,3 +261,12 @@
#artist-info-panel {
margin-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;
}

View File

@@ -232,6 +232,7 @@ class ArtistDetailPanel(Gtk.Box):
name="playlist-play-shuffle-buttons",
)
# 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,
)
@@ -341,7 +342,11 @@ class ArtistDetailPanel(Gtk.Box):
self.artist_indicator.set_text("ARTIST")
self.artist_stats.set_markup(self.format_stats(artist))
self.artist_bio.set_markup(util.esc(artist.biography))
if artist.biography:
self.artist_bio.set_markup(util.esc(artist.biography))
self.artist_bio.show()
else:
self.artist_bio.hide()
if len(artist.similar_artists or []) > 0:
self.similar_artists_label.set_markup("<b>Similar Artists:</b> ")
@@ -450,7 +455,7 @@ class ArtistDetailPanel(Gtk.Box):
self.albums_list.spinner.hide()
self.artist_artwork.set_loading(False)
def make_label(self, text: str = None, name: str = None, **params,) -> Gtk.Label:
def make_label(self, text: str = None, name: str = None, **params) -> Gtk.Label:
return Gtk.Label(
label=text, name=name, halign=Gtk.Align.START, xalign=0, **params,
)
@@ -538,7 +543,11 @@ class AlbumsListWithSongs(Gtk.Overlay):
album_with_songs.show_all()
self.box.add(album_with_songs)
self.spinner.stop()
# Update everything (no force to ensure that if we are online, then everything
# is clickable)
for c in self.box.get_children():
c.update(app_config=app_config)
self.spinner.hide()
def on_song_selected(self, album_component: AlbumWithSongs):

View File

@@ -207,27 +207,28 @@ class MusicDirectoryList(Gtk.Box):
self.list.bind_model(self.drilldown_directories_store, self.create_row)
scrollbox.add(self.list)
self.directory_song_store = Gtk.ListStore(
str, str, str, str, # cache status, title, duration, song ID
)
# clickable, cache status, title, duration, song ID
self.directory_song_store = Gtk.ListStore(bool, str, str, str, str)
self.directory_song_list = Gtk.TreeView(
model=self.directory_song_store,
name="directory-songs-list",
headers_visible=False,
)
self.directory_song_list.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
selection = self.directory_song_list.get_selection()
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
selection.set_select_function(lambda _, model, path, current: model[path[0]][0])
# Song status column.
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(30, 35)
column = Gtk.TreeViewColumn("", renderer, icon_name=0)
column = Gtk.TreeViewColumn("", renderer, icon_name=1)
column.set_resizable(True)
self.directory_song_list.append_column(column)
self.directory_song_list.append_column(SongListColumn("TITLE", 1, bold=True))
self.directory_song_list.append_column(SongListColumn("TITLE", 2, bold=True))
self.directory_song_list.append_column(
SongListColumn("DURATION", 2, align=1, width=40)
SongListColumn("DURATION", 3, align=1, width=40)
)
self.directory_song_list.connect("row-activated", self.on_song_activated)
@@ -253,6 +254,10 @@ class MusicDirectoryList(Gtk.Box):
)
if app_config:
# Deselect everything if switching online to offline.
if self.offline_mode != app_config.offline_mode:
self.directory_song_list.get_selection().unselect_all()
self.offline_mode = app_config.offline_mode
self.refresh_button.set_sensitive(not self.offline_mode)
@@ -316,6 +321,11 @@ class MusicDirectoryList(Gtk.Box):
new_songs_store = [
[
(
not self.offline_mode
or status_icon
in ("folder-download-symbolic", "view-pin-symbolic")
),
status_icon,
util.esc(song.title),
util.format_song_duration(song.duration),
@@ -327,7 +337,15 @@ class MusicDirectoryList(Gtk.Box):
]
else:
new_songs_store = [
[status_icon] + song_model[1:]
[
(
not self.offline_mode
or status_icon
in ("folder-download-symbolic", "view-pin-symbolic")
),
status_icon,
*song_model[2:],
]
for status_icon, song_model in zip(
util.get_cached_status_icons(song_ids), self.directory_song_store
)
@@ -384,6 +402,8 @@ class MusicDirectoryList(Gtk.Box):
# Event Handlers
# ==================================================================================
def on_song_activated(self, treeview: Any, idx: Gtk.TreePath, column: Any):
if not self.directory_song_store[idx[0]][0]:
return
# The song ID is in the last column of the model.
self.emit(
"song-clicked",

View File

@@ -1,5 +1,5 @@
from random import randint
from typing import Any, List
from typing import Any, cast, List
from gi.repository import Gdk, GLib, GObject, Gtk, Pango
@@ -125,8 +125,8 @@ class AlbumWithSongs(Gtk.Box):
self.loading_indicator_container = Gtk.Box()
album_details.add(self.loading_indicator_container)
# cache status, title, duration, song ID
self.album_song_store = Gtk.ListStore(str, str, str, str)
# clickable, cache status, title, duration, song ID
self.album_song_store = Gtk.ListStore(bool, str, str, str, str)
self.album_songs = Gtk.TreeView(
model=self.album_song_store,
@@ -137,17 +137,19 @@ class AlbumWithSongs(Gtk.Box):
margin_right=10,
margin_bottom=10,
)
self.album_songs.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
selection = self.album_songs.get_selection()
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
selection.set_select_function(lambda _, model, path, current: model[path[0]][0])
# Song status column.
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(30, 35)
column = Gtk.TreeViewColumn("", renderer, icon_name=0)
column = Gtk.TreeViewColumn("", renderer, icon_name=1)
column.set_resizable(True)
self.album_songs.append_column(column)
self.album_songs.append_column(SongListColumn("TITLE", 1, bold=True))
self.album_songs.append_column(SongListColumn("DURATION", 2, align=1, width=40))
self.album_songs.append_column(SongListColumn("TITLE", 2, bold=True))
self.album_songs.append_column(SongListColumn("DURATION", 3, align=1, width=40))
self.album_songs.connect("row-activated", self.on_song_activated)
self.album_songs.connect("button-press-event", self.on_song_button_press)
@@ -167,6 +169,8 @@ class AlbumWithSongs(Gtk.Box):
self.emit("song-selected")
def on_song_activated(self, treeview: Any, idx: Gtk.TreePath, column: Any):
if not self.album_song_store[idx[0]][0]:
return
# The song ID is in the last column of the model.
self.emit(
"song-clicked",
@@ -243,6 +247,9 @@ class AlbumWithSongs(Gtk.Box):
def update(self, app_config: AppConfiguration = None, force: bool = False):
if app_config:
# Deselect everything if switching online to offline.
if self.offline_mode != app_config.offline_mode:
self.album_songs.get_selection().unselect_all()
self.offline_mode = app_config.offline_mode
self.update_album_songs(self.album.id, app_config=app_config, force=force)
@@ -273,30 +280,42 @@ class AlbumWithSongs(Gtk.Box):
order_token: int = None,
):
song_ids = [s.id for s in album.songs or []]
new_store = [
[
cached_status,
util.esc(song.title),
util.format_song_duration(song.duration),
song.id,
]
for cached_status, song in zip(
util.get_cached_status_icons(song_ids), album.songs or []
new_store = []
any_song_playable = False
for cached_status, song in zip(
util.get_cached_status_icons(song_ids), album.songs or []
):
playable = not self.offline_mode or cached_status in (
"folder-download-symbolic",
"view-pin-symbolic",
)
]
new_store.append(
[
playable,
cached_status,
util.esc(song.title),
util.format_song_duration(song.duration),
song.id,
]
)
any_song_playable |= playable
song_ids = [song[-1] for song in new_store]
song_ids = [cast(str, song[-1]) for song in new_store]
self.play_btn.set_sensitive(True)
self.shuffle_btn.set_sensitive(True)
self.play_btn.set_sensitive(any_song_playable)
self.shuffle_btn.set_sensitive(any_song_playable)
self.download_all_btn.set_sensitive(
not self.offline_mode and AdapterManager.can_batch_download_songs()
)
self.play_next_btn.set_action_target_value(GLib.Variant("as", song_ids))
self.add_to_queue_btn.set_action_target_value(GLib.Variant("as", song_ids))
self.play_next_btn.set_action_name("app.add-to-queue")
self.add_to_queue_btn.set_action_name("app.play-next")
if any_song_playable:
self.play_next_btn.set_action_target_value(GLib.Variant("as", song_ids))
self.add_to_queue_btn.set_action_target_value(GLib.Variant("as", song_ids))
self.play_next_btn.set_action_name("app.add-to-queue")
self.add_to_queue_btn.set_action_name("app.play-next")
else:
self.play_next_btn.set_action_name("")
self.add_to_queue_btn.set_action_name("")
util.diff_song_store(self.album_song_store, new_store)

View File

@@ -17,6 +17,6 @@ class SongListColumn(Gtk.TreeViewColumn):
)
renderer.set_fixed_size(width or -1, 35)
super().__init__(header, renderer, text=text_idx)
super().__init__(header, renderer, text=text_idx, sensitive=0)
self.set_resizable(True)
self.set_expand(not width)

View File

@@ -1,97 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="50"
height="50"
viewBox="0 0 13.229166 13.229167"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="play-queue-play.svg"
inkscape:export-filename="/home/sumner/projects/sublime-music/sublime/ui/images/play-queue-play.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="15.843479"
inkscape:cy="55.759456"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1271"
inkscape:window-height="1404"
inkscape:window-x="1283"
inkscape:window-y="30"
inkscape:window-maximized="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-283.77082)">
<path
sodipodi:type="star"
style="fill:#000000;fill-opacity:1;stroke-width:0.39981332"
id="path4520"
sodipodi:sides="3"
sodipodi:cx="6.6130843"
sodipodi:cy="291.79547"
sodipodi:r1="5.6454182"
sodipodi:r2="1.3420769"
sodipodi:arg1="0.52306766"
sodipodi:arg2="1.6763933"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 11.503658,294.61558 -9.7781494,0.005 4.8845773,-8.47073 z"
inkscape:transform-center-x="-1.6314957"
inkscape:transform-center-y="0.0017367273"
transform="matrix(0,1.1570367,-1.1570367,0,342.71928,282.73209)" />
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.39981332"
id="path4520-9"
sodipodi:sides="3"
sodipodi:cx="6.6130843"
sodipodi:cy="291.79547"
sodipodi:r1="5.6454182"
sodipodi:r2="1.3420769"
sodipodi:arg1="0.52306766"
sodipodi:arg2="1.6763933"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 11.503658,294.61558 -9.7781494,0.005 4.8845773,-8.47073 z"
inkscape:transform-center-x="-1.1634043"
inkscape:transform-center-y="0.0012332389"
transform="matrix(0,0.8250756,-0.8250756,0,245.84428,284.92787)" />
</g>
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 13.229 13.229">
<path d="M1.838 12.271L1.832.958l9.801 5.651z"/>
<path d="M2.764 10.648L2.76 2.581l6.989 4.03z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 213 B

View File

@@ -406,6 +406,7 @@ class MainWindow(Gtk.ApplicationWindow):
lambda _: print("switch"),
menu_name="switch-provider",
)
# TODO
music_provider_button.set_action_name("app.configure-servers")
vbox.add(music_provider_button)

View File

@@ -290,17 +290,17 @@ class PlaylistDetailPanel(Gtk.Overlay):
name="playlist-play-shuffle-buttons",
)
play_button = IconButton(
self.play_all_button = IconButton(
"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)
self.play_all_button.connect("clicked", self.on_play_all_clicked)
self.play_shuffle_buttons.pack_start(self.play_all_button, False, False, 0)
shuffle_button = IconButton(
self.shuffle_all_button = IconButton(
"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)
self.shuffle_all_button.connect("clicked", self.on_shuffle_all_button)
self.play_shuffle_buttons.pack_start(self.shuffle_all_button, False, False, 5)
playlist_details_box.add(self.play_shuffle_buttons)
@@ -352,6 +352,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
playlist_view_scroll_window = Gtk.ScrolledWindow()
self.playlist_song_store = Gtk.ListStore(
bool, # clickable
str, # cache status
str, # title
str, # album
@@ -391,20 +392,22 @@ class PlaylistDetailPanel(Gtk.Overlay):
enable_search=True,
)
self.playlist_songs.set_search_equal_func(playlist_song_list_search_fn)
self.playlist_songs.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
selection = self.playlist_songs.get_selection()
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
selection.set_select_function(lambda _, model, path, current: model[path[0]][0])
# Song status column.
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(30, 35)
column = Gtk.TreeViewColumn("", renderer, icon_name=0)
column = Gtk.TreeViewColumn("", renderer, icon_name=1)
column.set_resizable(True)
self.playlist_songs.append_column(column)
self.playlist_songs.append_column(SongListColumn("TITLE", 1, bold=True))
self.playlist_songs.append_column(SongListColumn("ALBUM", 2))
self.playlist_songs.append_column(SongListColumn("ARTIST", 3))
self.playlist_songs.append_column(SongListColumn("TITLE", 2, bold=True))
self.playlist_songs.append_column(SongListColumn("ALBUM", 3))
self.playlist_songs.append_column(SongListColumn("ARTIST", 4))
self.playlist_songs.append_column(
SongListColumn("DURATION", 4, align=1, width=40)
SongListColumn("DURATION", 5, align=1, width=40)
)
self.playlist_songs.connect("row-activated", self.on_song_activated)
@@ -434,6 +437,10 @@ class PlaylistDetailPanel(Gtk.Overlay):
update_playlist_view_order_token = 0
def update(self, app_config: AppConfiguration, force: bool = False):
# Deselect everything if switching online to offline.
if self.offline_mode != app_config.offline_mode:
self.playlist_songs.get_selection().unselect_all()
self.offline_mode = app_config.offline_mode
if app_config.state.selected_playlist_id is None:
self.playlist_box.hide()
@@ -532,33 +539,47 @@ class PlaylistDetailPanel(Gtk.Overlay):
song_ids.append(c.id)
songs.append(c)
new_songs_store = []
can_play_any_song = False
cached_status_icons = ("folder-download-symbolic", "view-pin-symbolic")
if force:
self._current_song_ids = song_ids
new_songs_store = [
[
status_icon,
song.title,
album.name if (album := song.album) else None,
artist.name if (artist := song.artist) else None,
util.format_song_duration(song.duration),
song.id,
]
for status_icon, song in zip(
util.get_cached_status_icons(song_ids),
[cast(API.Song, s) for s in songs],
# Regenerate the store from the actual song data (this is more expensive
# because when coming from the cache, we are doing 2N fk requests to
# albums).
for status_icon, song in zip(
util.get_cached_status_icons(song_ids),
[cast(API.Song, s) for s in songs],
):
playable = not self.offline_mode or status_icon in cached_status_icons
can_play_any_song |= playable
new_songs_store.append(
[
playable,
status_icon,
song.title,
album.name if (album := song.album) else None,
artist.name if (artist := song.artist) else None,
util.format_song_duration(song.duration),
song.id,
]
)
]
else:
new_songs_store = [
[status_icon] + song_model[1:]
for status_icon, song_model in zip(
util.get_cached_status_icons(song_ids), self.playlist_song_store
)
]
# Just update the clickable state and download state.
for status_icon, song_model in zip(
util.get_cached_status_icons(song_ids), self.playlist_song_store
):
playable = not self.offline_mode or status_icon in cached_status_icons
can_play_any_song |= playable
new_songs_store.append([playable, status_icon, *song_model[2:]])
util.diff_song_store(self.playlist_song_store, new_songs_store)
self.play_all_button.set_sensitive(can_play_any_song)
self.shuffle_all_button.set_sensitive(can_play_any_song)
self.editing_playlist_song_list = False
self.playlist_view_loading_box.hide()
@@ -657,7 +678,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
def download_state_change(song_id: str):
GLib.idle_add(
lambda: self.update_playlist_view(
self.playlist_id, order_token=self.update_playlist_view_order_token,
self.playlist_id, order_token=self.update_playlist_view_order_token
)
)
@@ -757,7 +778,12 @@ class PlaylistDetailPanel(Gtk.Overlay):
self.offline_mode,
on_download_state_change=on_download_state_change,
extra_menu_items=[
(Gtk.ModelButton(text=remove_text), on_remove_songs_click),
(
Gtk.ModelButton(
text=remove_text, sensitive=not self.offline_mode
),
on_remove_songs_click,
)
],
on_playlist_state_change=lambda: self.emit("refresh-window", {}, True),
)

View File

@@ -218,14 +218,18 @@ def show_song_popover(
# Add all of the menu items to the popover.
song_count = len(song_ids)
go_to_album_button = Gtk.ModelButton(
text="Go to album", action_name="app.go-to-album"
)
go_to_artist_button = Gtk.ModelButton(
text="Go to artist", action_name="app.go-to-artist"
)
play_next_button = Gtk.ModelButton(text="Play next", sensitive=False)
add_to_queue_button = Gtk.ModelButton(text="Add to queue", sensitive=False)
if not offline_mode:
play_next_button.set_action_name("app.play-next")
play_next_button.set_action_target_value(GLib.Variant("as", song_ids))
add_to_queue_button.set_action_name("app.add-to-queue")
add_to_queue_button.set_action_target_value(GLib.Variant("as", song_ids))
go_to_album_button = Gtk.ModelButton(text="Go to album", sensitive=False)
go_to_artist_button = Gtk.ModelButton(text="Go to artist", sensitive=False)
browse_to_song = Gtk.ModelButton(
text=f"Browse to {pluralize('song', song_count)}", action_name="app.browse-to",
text=f"Browse to {pluralize('song', song_count)}", sensitive=False
)
download_song_button = Gtk.ModelButton(
text=f"Download {pluralize('song', song_count)}", sensitive=False
@@ -246,6 +250,10 @@ def show_song_popover(
for status in song_cache_statuses
):
remove_download_button.set_sensitive(True)
play_next_button.set_action_target_value(GLib.Variant("as", song_ids))
play_next_button.set_action_name("app.play-next")
add_to_queue_button.set_action_target_value(GLib.Variant("as", song_ids))
add_to_queue_button.set_action_name("app.add-to-queue")
albums, artists, parents = set(), set(), set()
for song in songs:
@@ -258,14 +266,18 @@ def show_song_popover(
artists.add(id_)
if len(albums) == 1 and list(albums)[0] is not None:
album_value = GLib.Variant("s", list(albums)[0])
go_to_album_button.set_action_target_value(album_value)
go_to_album_button.set_action_target_value(
GLib.Variant("s", list(albums)[0])
)
go_to_album_button.set_action_name("app.go-to-album")
if len(artists) == 1 and list(artists)[0] is not None:
artist_value = GLib.Variant("s", list(artists)[0])
go_to_artist_button.set_action_target_value(artist_value)
go_to_artist_button.set_action_target_value(
GLib.Variant("s", list(artists)[0])
)
go_to_artist_button.set_action_name("app.go-to-artist")
if len(parents) == 1 and list(parents)[0] is not None:
parent_value = GLib.Variant("s", list(parents)[0])
browse_to_song.set_action_target_value(parent_value)
browse_to_song.set_action_target_value(GLib.Variant("s", list(parents)[0]))
browse_to_song.set_action_name("app.browse-to")
def batch_get_song_details() -> List[Song]:
return [
@@ -278,16 +290,8 @@ def show_song_popover(
)
menu_items = [
Gtk.ModelButton(
text="Play next",
action_name="app.play-next",
action_target=GLib.Variant("as", song_ids),
),
Gtk.ModelButton(
text="Add to queue",
action_name="app.add-to-queue",
action_target=GLib.Variant("as", song_ids),
),
play_next_button,
add_to_queue_button,
Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL),
go_to_album_button,
go_to_artist_button,
@@ -300,6 +304,7 @@ def show_song_popover(
text=f"Add {pluralize('song', song_count)} to playlist",
menu_name="add-to-playlist",
name="menu-item-add-to-playlist",
sensitive=not offline_mode,
),
*(extra_menu_items or []),
]
@@ -319,27 +324,30 @@ def show_song_popover(
# Create the "Add song(s) to playlist" sub-menu.
playlists_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Back button
playlists_vbox.add(Gtk.ModelButton(inverted=True, centered=True, menu_name="main"))
if not offline_mode:
# Back button
playlists_vbox.add(
Gtk.ModelButton(inverted=True, centered=True, menu_name="main")
)
# Loading indicator
loading_indicator = Gtk.Spinner(name="menu-item-spinner")
loading_indicator.start()
playlists_vbox.add(loading_indicator)
# Loading indicator
loading_indicator = Gtk.Spinner(name="menu-item-spinner")
loading_indicator.start()
playlists_vbox.add(loading_indicator)
# Create a future to make the actual playlist buttons
def on_get_playlists_done(f: Result[List[Playlist]]):
playlists_vbox.remove(loading_indicator)
# Create a future to make the actual playlist buttons
def on_get_playlists_done(f: Result[List[Playlist]]):
playlists_vbox.remove(loading_indicator)
for playlist in f.result():
button = Gtk.ModelButton(text=playlist.name)
button.get_style_context().add_class("menu-button")
button.connect("clicked", on_add_to_playlist_click, playlist)
button.show()
playlists_vbox.pack_start(button, False, True, 0)
for playlist in f.result():
button = Gtk.ModelButton(text=playlist.name)
button.get_style_context().add_class("menu-button")
button.connect("clicked", on_add_to_playlist_click, playlist)
button.show()
playlists_vbox.pack_start(button, False, True, 0)
playlists_result = AdapterManager.get_playlists()
playlists_result.add_done_callback(on_get_playlists_done)
playlists_result = AdapterManager.get_playlists()
playlists_result.add_done_callback(on_get_playlists_done)
popover.add(playlists_vbox)
popover.child_set_property(playlists_vbox, "submenu", "add-to-playlist")