From ea41326d1b1fba1e5e34fed2a5e3550a9f14e4c3 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Sat, 23 May 2020 12:05:23 -0600 Subject: [PATCH] Wiring up the UI for offline mode --- sublime/adapters/manager.py | 3 ++ sublime/app.py | 7 +++-- sublime/ui/albums.py | 9 +++--- sublime/ui/artists.py | 31 ++++++++++++-------- sublime/ui/browse.py | 13 +++++++-- sublime/ui/common/album_with_songs.py | 14 +++++++-- sublime/ui/player_controls.py | 13 ++++++--- sublime/ui/playlists.py | 42 ++++++++++++++++----------- sublime/ui/util.py | 7 +++-- 9 files changed, 92 insertions(+), 47 deletions(-) diff --git a/sublime/adapters/manager.py b/sublime/adapters/manager.py index f4b1347..4dcc910 100644 --- a/sublime/adapters/manager.py +++ b/sublime/adapters/manager.py @@ -164,6 +164,7 @@ class AdapterManager: executor: ThreadPoolExecutor = ThreadPoolExecutor() download_executor: ThreadPoolExecutor = ThreadPoolExecutor() is_shutting_down: bool = False + offline_mode: bool = False @dataclass class _AdapterManagerInternal: @@ -233,6 +234,8 @@ class AdapterManager: if AdapterManager._instance: AdapterManager._instance.shutdown() + AdapterManager.offline_mode = config.offline_mode + # TODO (#197): actually do stuff with the config to determine which adapters to # create, etc. assert config.server is not None diff --git a/sublime/app.py b/sublime/app.py index a40fc36..521ac4e 100644 --- a/sublime/app.py +++ b/sublime/app.py @@ -260,9 +260,9 @@ class SublimeMusicApp(Gtk.Application): if self.exiting: return self.update_window() - GLib.timeout_add(5000, ping_update) + GLib.timeout_add(10000, ping_update) - GLib.timeout_add(5000, ping_update) + GLib.timeout_add(10000, ping_update) # Prompt to load the play queue from the server. if self.app_config.server.sync_enabled: @@ -505,6 +505,9 @@ class SublimeMusicApp(Gtk.Application): if settings := state_updates.get("__settings__"): for k, v in settings.items(): setattr(self.app_config, k, v) + if (offline_mode := settings.get("offline_mode")) is not None: + AdapterManager.offline_mode = offline_mode + del state_updates["__settings__"] for k, v in state_updates.items(): diff --git a/sublime/ui/albums.py b/sublime/ui/albums.py index 3f0617c..31bddc9 100644 --- a/sublime/ui/albums.py +++ b/sublime/ui/albums.py @@ -143,11 +143,11 @@ class AlbumsPanel(Gtk.Box): page_widget.add(self.next_page) actionbar.set_center_widget(page_widget) - refresh = IconButton( + self.refresh_button = IconButton( "view-refresh-symbolic", "Refresh list of albums", relief=True ) - refresh.connect("clicked", self.on_refresh_clicked) - actionbar.pack_end(refresh) + self.refresh_button.connect("clicked", self.on_refresh_clicked) + actionbar.pack_end(self.refresh_button) actionbar.pack_end(Gtk.Label(label="albums per page")) self.show_count_dropdown, _ = self.make_combobox( @@ -251,6 +251,7 @@ class AlbumsPanel(Gtk.Box): if app_config: self.album_page = app_config.state.album_page self.album_page_size = app_config.state.album_page_size + self.refresh_button.set_sensitive(not app_config.offline_mode) self.prev_page.set_sensitive(self.album_page > 0) self.page_entry.set_text(str(self.album_page + 1)) @@ -619,7 +620,7 @@ class AlbumsGrid(Gtk.Overlay): # Update the detail panel. children = self.detail_box_inner.get_children() if len(children) > 0 and hasattr(children[0], "update"): - children[0].update(force=force) + children[0].update(app_config=app_config, force=force) error_dialog = None diff --git a/sublime/ui/artists.py b/sublime/ui/artists.py index a43483d..a45cad9 100644 --- a/sublime/ui/artists.py +++ b/sublime/ui/artists.py @@ -64,9 +64,11 @@ class ArtistList(Gtk.Box): list_actions = Gtk.ActionBar() - refresh = IconButton("view-refresh-symbolic", "Refresh list of artists") - refresh.connect("clicked", lambda *a: self.update(force=True)) - list_actions.pack_end(refresh) + self.refresh_button = IconButton( + "view-refresh-symbolic", "Refresh list of artists" + ) + self.refresh_button.connect("clicked", lambda *a: self.update(force=True)) + list_actions.pack_end(self.refresh_button) self.add(list_actions) @@ -126,6 +128,7 @@ class ArtistList(Gtk.Box): ): if app_config: self._app_config = app_config + self.refresh_button.set_sensitive(not app_config.offline_mode) new_store = [] selected_idx = None @@ -250,15 +253,15 @@ class ArtistDetailPanel(Gtk.Box): orientation=Gtk.Orientation.HORIZONTAL, spacing=10 ) - download_all_btn = IconButton( + self.download_all_button = IconButton( "folder-download-symbolic", "Download all songs by this artist" ) - download_all_btn.connect("clicked", self.on_download_all_click) - self.artist_action_buttons.add(download_all_btn) + self.download_all_button.connect("clicked", self.on_download_all_click) + self.artist_action_buttons.add(self.download_all_button) - view_refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info") - view_refresh_button.connect("clicked", self.on_view_refresh_click) - self.artist_action_buttons.add(view_refresh_button) + self.refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info") + self.refresh_button.connect("clicked", self.on_view_refresh_click) + self.artist_action_buttons.add(self.refresh_button) action_buttons_container.pack_start( self.artist_action_buttons, False, False, 10 @@ -300,6 +303,8 @@ class ArtistDetailPanel(Gtk.Box): app_config=app_config, order_token=self.update_order_token, ) + self.refresh_button.set_sensitive(not app_config.offline_mode) + self.download_all_button.set_sensitive(not app_config.offline_mode) @util.async_callback( AdapterManager.get_artist, @@ -370,7 +375,7 @@ class ArtistDetailPanel(Gtk.Box): ) self.albums = artist.albums or [] - self.albums_list.update(artist) + self.albums_list.update(artist, app_config, force=force) @util.async_callback( AdapterManager.get_cover_art_filename, @@ -498,7 +503,9 @@ class AlbumsListWithSongs(Gtk.Overlay): self.albums = [] - def update(self, artist: API.Artist): + def update( + self, artist: API.Artist, app_config: AppConfiguration, force: bool = False + ): def remove_all(): for c in self.box.get_children(): self.box.remove(c) @@ -513,7 +520,7 @@ class AlbumsListWithSongs(Gtk.Overlay): if self.albums == new_albums: # Just go through all of the colidren and update them. for c in self.box.get_children(): - c.update() + c.update(app_config=app_config, force=force) self.spinner.hide() return diff --git a/sublime/ui/browse.py b/sublime/ui/browse.py index 9999c7f..3cd921e 100644 --- a/sublime/ui/browse.py +++ b/sublime/ui/browse.py @@ -170,6 +170,7 @@ class MusicDirectoryList(Gtk.Box): update_order_token = 0 directory_id: Optional[str] = None selected_id: Optional[str] = None + offline_mode = False class DrilldownElement(GObject.GObject): id = GObject.Property(type=str) @@ -185,9 +186,9 @@ class MusicDirectoryList(Gtk.Box): list_actions = Gtk.ActionBar() - refresh = IconButton("view-refresh-symbolic", "Refresh folder") - refresh.connect("clicked", lambda *a: self.update(force=True)) - list_actions.pack_end(refresh) + self.refresh_button = IconButton("view-refresh-symbolic", "Refresh folder") + self.refresh_button.connect("clicked", lambda *a: self.update(force=True)) + list_actions.pack_end(self.refresh_button) self.add(list_actions) @@ -251,6 +252,11 @@ class MusicDirectoryList(Gtk.Box): self.directory_id, force=force, order_token=self.update_order_token, ) + if app_config: + self.offline_mode = app_config.offline_mode + + self.refresh_button.set_sensitive(not self.offline_mode) + _current_child_ids: List[str] = [] @util.async_callback( @@ -412,6 +418,7 @@ class MusicDirectoryList(Gtk.Box): event.x, event.y + abs(bin_coords.by - widget_coords.wy), tree, + self.offline_mode, on_download_state_change=self.on_download_state_change, ) diff --git a/sublime/ui/common/album_with_songs.py b/sublime/ui/common/album_with_songs.py index 9d2369b..13acf62 100644 --- a/sublime/ui/common/album_with_songs.py +++ b/sublime/ui/common/album_with_songs.py @@ -21,6 +21,8 @@ class AlbumWithSongs(Gtk.Box): ), } + offline_mode = True + def __init__( self, album: API.Album, @@ -202,6 +204,7 @@ class AlbumWithSongs(Gtk.Box): event.x, event.y + abs(bin_coords.by - widget_coords.wy), tree, + self.offline_mode, on_download_state_change=on_download_state_change, ) @@ -238,8 +241,11 @@ class AlbumWithSongs(Gtk.Box): def deselect_all(self): self.album_songs.get_selection().unselect_all() - def update(self, force: bool = False): - self.update_album_songs(self.album.id, force=force) + def update(self, app_config: AppConfiguration = None, force: bool = False): + if app_config: + self.offline_mode = app_config.offline_mode + + self.update_album_songs(self.album.id, app_config=app_config, force=force) def set_loading(self, loading: bool): if loading: @@ -283,7 +289,9 @@ class AlbumWithSongs(Gtk.Box): self.play_btn.set_sensitive(True) self.shuffle_btn.set_sensitive(True) - self.download_all_btn.set_sensitive(AdapterManager.can_batch_download_songs()) + 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)) diff --git a/sublime/ui/player_controls.py b/sublime/ui/player_controls.py index 8282387..2c83b37 100644 --- a/sublime/ui/player_controls.py +++ b/sublime/ui/player_controls.py @@ -48,6 +48,7 @@ class PlayerControls(Gtk.ActionBar): cover_art_update_order_token = 0 play_queue_update_order_token = 0 devices_requested = False + offline_mode = False def __init__(self): Gtk.ActionBar.__init__(self) @@ -176,6 +177,9 @@ class PlayerControls(Gtk.ActionBar): self.update_device_list() # Short circuit if no changes to the play queue + self.offline_mode = app_config.offline_mode + self.load_play_queue_button.set_sensitive(not self.offline_mode) + if not force and ( self.current_play_queue == app_config.state.play_queue and self.current_playing_index == app_config.state.current_song_index @@ -422,7 +426,7 @@ class PlayerControls(Gtk.ActionBar): def on_device_refresh_click(self, _: Any): self.update_device_list(force=True) - def on_play_queue_button_press(self, tree: Any, event: Gdk.EventButton,) -> bool: + def on_play_queue_button_press(self, tree: Any, event: Gdk.EventButton) -> bool: if event.button == 3: # Right click clicked_path = tree.get_path_at_pos(event.x, event.y) @@ -454,6 +458,7 @@ class PlayerControls(Gtk.ActionBar): event.x, event.y, tree, + self.offline_mode, on_download_state_change=on_download_state_change, extra_menu_items=[ (Gtk.ModelButton(text=remove_text), on_remove_songs_click), @@ -688,11 +693,11 @@ class PlayerControls(Gtk.ActionBar): ) play_queue_popover_header.add(self.popover_label) - load_play_queue = IconButton( + self.load_play_queue_button = IconButton( "folder-download-symbolic", "Load Queue from Server", margin=5 ) - load_play_queue.set_action_name("app.update-play-queue-from-server") - play_queue_popover_header.pack_end(load_play_queue, False, False, 0) + self.load_play_queue_button.set_action_name("app.update-play-queue-from-server") + play_queue_popover_header.pack_end(self.load_play_queue_button, False, False, 0) play_queue_popover_box.add(play_queue_popover_header) diff --git a/sublime/ui/playlists.py b/sublime/ui/playlists.py index 294c12b..92a6abd 100644 --- a/sublime/ui/playlists.py +++ b/sublime/ui/playlists.py @@ -87,15 +87,15 @@ class PlaylistList(Gtk.Box): playlist_list_actions = Gtk.ActionBar() - new_playlist_button = IconButton("list-add-symbolic", label="New Playlist") - new_playlist_button.connect("clicked", self.on_new_playlist_clicked) - playlist_list_actions.pack_start(new_playlist_button) + self.new_playlist_button = IconButton("list-add-symbolic", label="New Playlist") + self.new_playlist_button.connect("clicked", self.on_new_playlist_clicked) + playlist_list_actions.pack_start(self.new_playlist_button) - list_refresh_button = IconButton( + self.list_refresh_button = IconButton( "view-refresh-symbolic", "Refresh list of playlists" ) - list_refresh_button.connect("clicked", self.on_list_refresh_click) - playlist_list_actions.pack_end(list_refresh_button) + self.list_refresh_button.connect("clicked", self.on_list_refresh_click) + playlist_list_actions.pack_end(self.list_refresh_button) self.add(playlist_list_actions) @@ -164,9 +164,11 @@ class PlaylistList(Gtk.Box): list_scroll_window.add(self.list) self.pack_start(list_scroll_window, True, True, 0) - def update(self, **kwargs): + def update(self, app_config: AppConfiguration, force: bool = False): + self.new_playlist_button.set_sensitive(not app_config.offline_mode) + self.list_refresh_button.set_sensitive(not app_config.offline_mode) self.new_playlist_row.hide() - self.update_list(**kwargs) + self.update_list(app_config=app_config, force=force) @util.async_callback( AdapterManager.get_playlists, @@ -247,6 +249,7 @@ class PlaylistDetailPanel(Gtk.Overlay): playlist_id = None playlist_details_expanded = False + offline_mode = False editing_playlist_song_list: bool = False reordering_playlist_song_list: bool = False @@ -308,23 +311,23 @@ class PlaylistDetailPanel(Gtk.Overlay): orientation=Gtk.Orientation.HORIZONTAL, spacing=10 ) - download_all_button = IconButton( + self.download_all_button = IconButton( "folder-download-symbolic", "Download all songs in the playlist" ) - download_all_button.connect( + self.download_all_button.connect( "clicked", self.on_playlist_list_download_all_button_click ) - self.playlist_action_buttons.add(download_all_button) + self.playlist_action_buttons.add(self.download_all_button) - playlist_edit_button = IconButton("document-edit-symbolic", "Edit paylist") - playlist_edit_button.connect("clicked", self.on_playlist_edit_button_click) - self.playlist_action_buttons.add(playlist_edit_button) + self.playlist_edit_button = IconButton("document-edit-symbolic", "Edit paylist") + self.playlist_edit_button.connect("clicked", self.on_playlist_edit_button_click) + self.playlist_action_buttons.add(self.playlist_edit_button) - view_refresh_button = IconButton( + self.view_refresh_button = IconButton( "view-refresh-symbolic", "Refresh playlist info" ) - view_refresh_button.connect("clicked", self.on_view_refresh_click) - self.playlist_action_buttons.add(view_refresh_button) + self.view_refresh_button.connect("clicked", self.on_view_refresh_click) + self.playlist_action_buttons.add(self.view_refresh_button) action_buttons_container.pack_start( self.playlist_action_buttons, False, False, 10 @@ -430,6 +433,7 @@ class PlaylistDetailPanel(Gtk.Overlay): update_playlist_view_order_token = 0 def update(self, app_config: AppConfiguration, force: bool = False): + self.offline_mode = app_config.offline_mode if app_config.state.selected_playlist_id is None: self.playlist_box.hide() self.playlist_view_loading_box.hide() @@ -442,6 +446,9 @@ class PlaylistDetailPanel(Gtk.Overlay): force=force, order_token=self.update_playlist_view_order_token, ) + self.download_all_button.set_sensitive(not app_config.offline_mode) + self.playlist_edit_button.set_sensitive(not app_config.offline_mode) + self.view_refresh_button.set_sensitive(not app_config.offline_mode) _current_song_ids: List[str] = [] @@ -746,6 +753,7 @@ class PlaylistDetailPanel(Gtk.Overlay): event.x, event.y + abs(bin_coords.by - widget_coords.wy), tree, + self.offline_mode, on_download_state_change=on_download_state_change, extra_menu_items=[ (Gtk.ModelButton(text=remove_text), on_remove_songs_click), diff --git a/sublime/ui/util.py b/sublime/ui/util.py index e11c4ef..e4ef3b4 100644 --- a/sublime/ui/util.py +++ b/sublime/ui/util.py @@ -187,6 +187,7 @@ def show_song_popover( x: int, y: int, relative_to: Any, + offline_mode: bool, position: Gtk.PositionType = Gtk.PositionType.BOTTOM, on_download_state_change: Callable[[str], None] = lambda _: None, on_playlist_state_change: Callable[[], None] = lambda: None, @@ -236,13 +237,15 @@ def show_song_popover( # Retrieve songs and set the buttons as sensitive later. def on_get_song_details_done(songs: List[Song]): song_cache_statuses = AdapterManager.get_cached_statuses([s.id for s in songs]) - if any(status == SongCacheStatus.NOT_CACHED for status in song_cache_statuses): + if not offline_mode and any( + status == SongCacheStatus.NOT_CACHED for status in song_cache_statuses + ): download_song_button.set_sensitive(True) if any( status in (SongCacheStatus.CACHED, SongCacheStatus.PERMANENTLY_CACHED) for status in song_cache_statuses ): - download_song_button.set_sensitive(True) + remove_download_button.set_sensitive(True) albums, artists, parents = set(), set(), set() for song in songs: