From 62d9cc4cd39ac36955b40d0d252f2d8755c3022a Mon Sep 17 00:00:00 2001 From: Nokse22 Date: Tue, 31 Dec 2024 16:36:53 +0100 Subject: [PATCH] finish new design, various improvements... --- data/style.css | 4 + data/ui/pages_ui/artist_page_template.blp | 4 +- data/ui/pages_ui/page_template.blp | 34 ++- data/ui/preferences.ui | 2 +- data/ui/search_entry.blp | 7 + data/ui/window.blp | 149 +++++++----- io.github.nokse22.HighTide.json | 2 +- src/disconnectable_iface.py | 42 ++++ src/lib/player_object.py | 8 +- src/lib/variables.py | 3 + src/pages/__init__.py | 1 + src/pages/album_page.py | 2 +- src/pages/artist_page.py | 4 + src/pages/collection_page.py | 127 +++++++++++ src/pages/explore_page.py | 4 +- src/pages/generic_page.py | 3 +- src/pages/home_page.py | 1 + src/pages/mix_page.py | 5 +- src/pages/page.py | 32 +-- src/widgets/card_widget.py | 26 +-- src/widgets/carousel_widget.py | 28 +-- src/widgets/generic_track_widget.py | 21 +- src/widgets/top_hit_widget.py | 31 +-- src/widgets/tracks_list_widget.py | 28 +-- src/window.py | 264 +++++----------------- 25 files changed, 421 insertions(+), 411 deletions(-) create mode 100644 src/disconnectable_iface.py create mode 100644 src/pages/collection_page.py diff --git a/data/style.css b/data/style.css index 94d7744..e9aca1b 100644 --- a/data/style.css +++ b/data/style.css @@ -3,6 +3,10 @@ transition: opacity 0.5s ease-in-out; } +.rounded { + border-radius: 20px; +} + .hover-show:hover{ opacity:100; } diff --git a/data/ui/pages_ui/artist_page_template.blp b/data/ui/pages_ui/artist_page_template.blp index 8451904..dfc2dea 100644 --- a/data/ui/pages_ui/artist_page_template.blp +++ b/data/ui/pages_ui/artist_page_template.blp @@ -131,6 +131,7 @@ Box _main { ellipsize: end; vexpand: true; xalign: 0.0; + yalign: 0.0; } [first-subtitle-label] @@ -141,8 +142,8 @@ Box _main { ellipsize: end; vexpand: true; - lines: 2; xalign: 0.0; + yalign: 0.0; } [artist-button] @@ -153,6 +154,7 @@ Box _main { [follow-button] Button _follow_button { icon-name: "heart-outline-thick-symbolic"; + valign: center; styles [ "flat", diff --git a/data/ui/pages_ui/page_template.blp b/data/ui/pages_ui/page_template.blp index 0bc51f0..eb35f4e 100644 --- a/data/ui/pages_ui/page_template.blp +++ b/data/ui/pages_ui/page_template.blp @@ -3,7 +3,13 @@ using Adw 1; Adw.ToolbarView _main { [top] - Adw.HeaderBar {} + Adw.HeaderBar { + [end] + MenuButton { + icon-name: "open-menu-symbolic"; + menu-model: primary_menu; + } + } content: Stack _content_stack { StackPage { @@ -48,3 +54,29 @@ Adw.ToolbarView _main { visible-child-name: 'loading'; }; } + +menu primary_menu { + section { + item { + label: _("Logout"); + action: "app.log-out"; + } + } + + section { + item { + label: _("_Preferences"); + action: "app.preferences"; + } + + item { + label: _("_Keyboard Shortcuts"); + action: "win.show-help-overlay"; + } + + item { + label: _("_About High Tide"); + action: "app.about"; + } + } +} \ No newline at end of file diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui index 2b61cb3..01f729e 100644 --- a/data/ui/preferences.ui +++ b/data/ui/preferences.ui @@ -24,4 +24,4 @@ Hi-res Lossless - \ No newline at end of file + diff --git a/data/ui/search_entry.blp b/data/ui/search_entry.blp index 2cf8606..8a17cec 100644 --- a/data/ui/search_entry.blp +++ b/data/ui/search_entry.blp @@ -5,5 +5,12 @@ SearchEntry search_entry { search-delay: 300; margin-end: 12; margin-start: 12; + margin-top: 12; margin-bottom: 6; + halign: center; + width-request: 300; + + styles [ + "rounded", + ] } diff --git a/data/ui/window.blp b/data/ui/window.blp index 2f757d9..f3c7d28 100644 --- a/data/ui/window.blp +++ b/data/ui/window.blp @@ -12,6 +12,10 @@ template $HighTideWindow: Adw.ApplicationWindow { setters { multilayout_view.layout-name: "mobile"; + playing_track_picture.height-request: 200; + playing_track_picture.width-request: 200; + player_headerbar.show-start-title-buttons: false; + player_headerbar.show-end-title-buttons: false; } } @@ -48,7 +52,7 @@ template $HighTideWindow: Adw.ApplicationWindow { content: Box { orientation: vertical; - Adw.BottomSheet { + Adw.BottomSheet bottom_sheet{ show-drag-handle: false; bottom-bar: Box { @@ -65,13 +69,18 @@ template $HighTideWindow: Adw.ApplicationWindow { Box { AspectFrame { height-request: 70; - Picture { + Image { + overflow: hidden; margin-bottom: 6; margin-end: 6; margin-start: 6; margin-top: 6; - // pixel-size: 60; + pixel-size: 60; file: bind playing_track_picture.file; + + styles [ + "small-image", + ] } } @@ -81,22 +90,35 @@ template $HighTideWindow: Adw.ApplicationWindow { orientation: vertical; spacing: 6; - Label { - label: "Go Your Own Way"; - xalign: 0; - - styles [ - "title-4", - ] + Box { + Label { + label: bind song_title_label.label; + ellipsize: end; + xalign: 0; + + styles [ + "title-4", + ] + } + Label { + label: "E"; + + styles [ + "explicit-label", + ] + + visible: bind explicit_label.visible; + valign: end; + margin-start: 6; + } } - Label { - label: "Fleetwood Mac"; - xalign: 0; - styles [ - "dim-label", - ] + Label miniplayer_artist_label { + label: bind artist_label.label; + use-markup: true; + ellipsize: end; + xalign: 0; } } Box { @@ -129,6 +151,7 @@ template $HighTideWindow: Adw.ApplicationWindow { content: Adw.LayoutSlot { id: "content-navigation-view"; + margin-bottom: bind bottom_sheet.sheet-height; }; sheet: Adw.LayoutSlot { @@ -146,6 +169,7 @@ template $HighTideWindow: Adw.ApplicationWindow { [content-navigation-view] Adw.NavigationView navigation_view{ + popped => on_navigation_view_page_popped(); Adw.NavigationPage { child: Adw.ToolbarView { [top] @@ -167,6 +191,7 @@ template $HighTideWindow: Adw.ApplicationWindow { spacing: 6; Button { + clicked => $on_home_button_clicked(); Box { orientation: vertical; @@ -189,6 +214,7 @@ template $HighTideWindow: Adw.ApplicationWindow { } Button { + clicked => $on_explore_button_clicked(); Box { orientation: vertical; @@ -211,6 +237,7 @@ template $HighTideWindow: Adw.ApplicationWindow { } Button { + clicked => $on_collection_button_clicked(); Box { orientation: vertical; @@ -240,7 +267,7 @@ template $HighTideWindow: Adw.ApplicationWindow { child: ScrolledWindow { hscrollbar-policy: never; propagate-natural-height: true; - max-content-height: 600; + // max-content-height: 600; Box { margin-bottom: 12; margin-end: 12; @@ -249,23 +276,22 @@ template $HighTideWindow: Adw.ApplicationWindow { orientation: vertical; spacing: 12; - AspectFrame { + Image playing_track_picture { + overflow: hidden; width-request: 300; height-request: 300; - Picture playing_track_picture { - halign: center; - hexpand: true; - margin-bottom: 12; - margin-end: 12; - margin-start: 12; - margin-top: 12; - valign: center; - vexpand: true; + halign: center; + hexpand: true; + margin-bottom: 12; + margin-end: 12; + margin-start: 12; + margin-top: 12; + valign: center; + vexpand: true; - styles [ - "track-picture" - ] - } + styles [ + "track-picture" + ] } Box { @@ -275,8 +301,6 @@ template $HighTideWindow: Adw.ApplicationWindow { Box { halign: center; Label song_title_label { - label: "Go Your Own Way"; - // xalign: 0; ellipsize: end; styles [ @@ -288,20 +312,19 @@ template $HighTideWindow: Adw.ApplicationWindow { label: "E"; styles [ - "explicit-label", - ] + "explicit-label", + ] - visible: false; - valign: end; - margin-start: 6; - } + visible: false; + valign: end; + margin-start: 6; + } } $HTLinkLabelWidget artist_label { - // xalign: "0"; - ellipsize: "end"; - } + ellipsize: "end"; + } } Box { @@ -316,31 +339,31 @@ template $HighTideWindow: Adw.ApplicationWindow { } VolumeButton volume_button { - styles [ - "flat", - ] - - valign: center; - halign: end; - hexpand: true; - margin-bottom: 6; - margin-start: 6; - margin-top: 6; - value-changed => $on_volume_changed(); + styles [ + "flat", + ] + + valign: center; + halign: end; + hexpand: true; + margin-bottom: 6; + margin-start: 6; + margin-top: 6; + value-changed => $on_volume_changed(); + } } - } - } + } - Box { + Box { Label time_played_label{ label: "00:00"; } Scale progress_bar{ hexpand: true; + change-value => $on_slider_seek(); adjustment: Adjustment { - upper: 0.6; - value: 0.2; + upper: 1; }; } @@ -414,7 +437,7 @@ template $HighTideWindow: Adw.ApplicationWindow { }; icon-name: "emblem-music-symbolic"; - name: "player"; + name: "player_page"; title: "Player"; } @@ -426,6 +449,8 @@ template $HighTideWindow: Adw.ApplicationWindow { opacity: 0.6; margin-start: 12; margin-end: 12; + margin-top: 12; + margin-bottom: 12; styles [ "lyrics", @@ -434,7 +459,7 @@ template $HighTideWindow: Adw.ApplicationWindow { }; icon-name: "chat-bubble-text-symbolic"; - name: "lyrics"; + name: "lyrics_page"; title: "Lyrics"; } @@ -442,13 +467,13 @@ template $HighTideWindow: Adw.ApplicationWindow { child: $HTQueueWidget queue_widget {}; icon-name: "library-symbolic"; - name: "queue"; + name: "queue_page"; title: "Queue"; } }; [top] - Adw.HeaderBar { + Adw.HeaderBar player_headerbar{ title-widget: Adw.ViewSwitcher { stack: sidebar_stack; }; diff --git a/io.github.nokse22.HighTide.json b/io.github.nokse22.HighTide.json index 55316a5..2552991 100644 --- a/io.github.nokse22.HighTide.json +++ b/io.github.nokse22.HighTide.json @@ -1,7 +1,7 @@ { "app-id": "io.github.nokse22.HighTide", "runtime": "org.gnome.Platform", - "runtime-version": "47", + "runtime-version": "master", "sdk": "org.gnome.Sdk", "command": "HighTide", "finish-args": [ diff --git a/src/disconnectable_iface.py b/src/disconnectable_iface.py new file mode 100644 index 0000000..d9e0546 --- /dev/null +++ b/src/disconnectable_iface.py @@ -0,0 +1,42 @@ +# disconnectable_iface.py +# +# Copyright 2024 Nokse22 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later + +class IDisconnectable: + def __init__(self): + self.signals = [] + self.bindings = [] + self.disconnectables = [] + + def disconnect_all(self, *_args): + """Disconnects all signals so that the class can be deleted""" + + for obj, signal_id in self.signals: + obj.disconnect(signal_id) + del self.signals + + for binding in self.bindings: + binding.unbind() + del self.bindings + + for widget in self.disconnectables: + widget.disconnect_all() + del self.disconnectables + + def __del__(self, *args): + print(f"DELETING {self}") diff --git a/src/lib/player_object.py b/src/lib/player_object.py index 46f1e4e..2946efd 100644 --- a/src/lib/player_object.py +++ b/src/lib/player_object.py @@ -356,7 +356,6 @@ def update_slider_call(self): def query_duration(self): success, duration = self._player.query_duration(Gst.Format.TIME) if success: - print(duration) return duration else: return 0 @@ -368,5 +367,8 @@ def query_position(self): else: return 0 - def seek(self, time_format, something, seek_time): - self._player.seek_simple(time_format, something, seek_time) + def seek(self, seek_fraction): + self._player.seek_simple( + Gst.Format.TIME, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, + seek_fraction * self.query_duration()) diff --git a/src/lib/variables.py b/src/lib/variables.py index 96d5e9c..c8ab272 100644 --- a/src/lib/variables.py +++ b/src/lib/variables.py @@ -37,6 +37,7 @@ def init(): def get_favourites(): + global favourite_mixes global favourite_tracks global favourite_artists global favourite_albums @@ -46,9 +47,11 @@ def get_favourites(): favourite_tracks = session.user.favorites.tracks() favourite_albums = session.user.favorites.albums() favourite_playlists = session.user.favorites.playlists() + favourite_mixes = session.user.favorites.mixes() def is_favourited(item): + global favourite_mixes global favourite_tracks global favourite_artists global favourite_albums diff --git a/src/pages/__init__.py b/src/pages/__init__.py index a44d2d0..d353ec0 100644 --- a/src/pages/__init__.py +++ b/src/pages/__init__.py @@ -9,3 +9,4 @@ from .not_logged_in_page import notLoggedInPage from .mix_page import mixPage from .album_page import albumPage +from .collection_page import collectionPage diff --git a/src/pages/album_page.py b/src/pages/album_page.py index c2ad728..50b5aae 100644 --- a/src/pages/album_page.py +++ b/src/pages/album_page.py @@ -43,7 +43,7 @@ def _th_load_page(self): builder.get_object("_first_subtitle_label").set_label( "{} - {}".format( self.item.artist.name, - self.item.release_date.strftime('%d-%m-%Y'))) + self.item.release_date.strftime('%d-%m-%Y') if self.item.release_date else "Unknown")) builder.get_object("_second_subtitle_label").set_label( "{} tracks ({})".format( self.item.num_tracks, diff --git a/src/pages/artist_page.py b/src/pages/artist_page.py index 4357f03..1093a3b 100644 --- a/src/pages/artist_page.py +++ b/src/pages/artist_page.py @@ -91,6 +91,7 @@ def _th_load_page(self): content_box.append(tracks_list_widget) carousel = HTCarouselWidget("Albums") + self.disconnectables.append(carousel) try: albums = self.artist.get_albums(limit=10) carousel.set_more_function("album", self.artist.get_albums) @@ -102,6 +103,7 @@ def _th_load_page(self): carousel.set_items(albums, "album") carousel = HTCarouselWidget("EP & Singles") + self.disconnectables.append(carousel) try: albums = self.artist.get_albums_ep_singles(limit=10) carousel.set_more_function( @@ -114,6 +116,7 @@ def _th_load_page(self): carousel.set_items(albums, "album") carousel = HTCarouselWidget("Appears On") + self.disconnectables.append(carousel) try: albums = self.artist.get_albums_other(limit=10) carousel.set_more_function("album", self.artist.get_albums_other) @@ -125,6 +128,7 @@ def _th_load_page(self): carousel.set_items(albums, "album") carousel = HTCarouselWidget("Similar Artists") + self.disconnectables.append(carousel) try: artists = self.artist.get_similar() except Exception as e: diff --git a/src/pages/collection_page.py b/src/pages/collection_page.py new file mode 100644 index 0000000..8cfebaa --- /dev/null +++ b/src/pages/collection_page.py @@ -0,0 +1,127 @@ +# collection_page.py +# +# Copyright 2024 Nokse22 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gi.repository import Gtk + +from tidalapi.page import PageItem, PageLink +from tidalapi.artist import Artist +from tidalapi.mix import Mix +from tidalapi.album import Album +from tidalapi.media import Track +from tidalapi.playlist import Playlist + +from .page import Page +from ..widgets import HTCarouselWidget +from ..widgets import HTTracksListWidget + +from ..lib import variables + + +class collectionPage(Page): + __gtype_name__ = 'collectionPage' + + """It is used for the collection""" + + def _th_load_page(self): + self.set_tag("collection") + self.set_title("Collection") + + self.new_carousel_for( + "My Mixes and Radios", variables.favourite_mixes) + self.new_carousel_for( + "Playlists", variables.favourite_playlists) + self.new_carousel_for( + "Albums", variables.favourite_albums) + self.new_carousel_for( + "Tracks", variables.favourite_tracks) + self.new_carousel_for( + "Artists", variables.favourite_artists) + + self._page_loaded() + + def new_carousel_for(self, carousel_title, carousel_content): + print(carousel_title, len(carousel_content)) + if len(carousel_content) == 0: + return + + carousel = HTCarouselWidget(carousel_title) + self.disconnectables.append(carousel) + self.page_content.append(carousel) + + if isinstance(carousel_content[0], Mix): + carousel.set_items(carousel_content, "mix") + elif isinstance(carousel_content[0], Album): + carousel.set_items(carousel_content, "album") + elif isinstance(carousel_content, Artist): + carousel.set_items(carousel_content, "artist") + elif isinstance(carousel_content[0], Playlist): + carousel.set_items(carousel_content, "playlist") + elif isinstance(carousel_content[0], Track): + carousel.set_items(carousel_content, "track") + + # @Gtk.Template.Callback("on_playlists_sidebar_row_activated") + # def on_playlists_sidebar_row_activated_func(self, list_box, row): + # """Handles the click on an user playlist on the sidebar""" + + # if row is None: + # return + # index = row.get_child().get_name() + + # playlist = self.my_playlists[int(index)] + + # page = playlistPage(playlist, playlist.name) + # page.load() + # self.navigation_view.push(page) + + # @Gtk.Template.Callback("on_new_playlist_button_clicked") + # def on_new_playlist_button_clicked_func(self, btn): + # new_playlist_win = NewPlaylistWindow() + # new_playlist_win.connect( + # "create-playlist", self.on_create_new_playlist_requested) + # new_playlist_win.present(self) + + # @Gtk.Template.Callback("on_track_radio_button_clicked") + # def on_track_radio_button_clicked_func(self, widget): + # track = self.player_object.playing_track + # page = trackRadioPage(track, f"{track.name} Radio") + # page.load() + # self.navigation_view.push(page) + + def update_my_playlists(self): + child = self.sidebar_playlists.get_first_child() + while child is not None: + self.sidebar_playlists.remove(child) + del child + child = self.sidebar_playlists.get_first_child() + + playlists = self.session.user.playlists() + + for index, playlist in enumerate(playlists): + if playlist.creator: + if playlist.creator.name != "me": + playlists.remove(playlist) + continue + label = Gtk.Label(xalign=0, label=playlist.name, name=index) + self.sidebar_playlists.append(label) + + self.my_playlists = playlists + + def on_create_new_playlist_requested(self, window, p_title, p_description): + self.session.user.create_playlist(p_title, p_description) + window.close() diff --git a/src/pages/explore_page.py b/src/pages/explore_page.py index 36236ae..a61472b 100644 --- a/src/pages/explore_page.py +++ b/src/pages/explore_page.py @@ -42,7 +42,9 @@ def _th_load_page(self): builder = Gtk.Builder.new_from_resource( "/io/github/nokse22/HighTide/ui/search_entry.ui") search_entry = builder.get_object("search_entry") - search_entry.connect("activate", self.on_search_activated) + self.signals.append(( + search_entry, + search_entry.connect("activate", self.on_search_activated))) self.page_content.append(search_entry) diff --git a/src/pages/generic_page.py b/src/pages/generic_page.py index 6a9a05c..6df2699 100644 --- a/src/pages/generic_page.py +++ b/src/pages/generic_page.py @@ -43,14 +43,13 @@ def _th_load_page(self): continue items = [] - carousel = HTCarouselWidget(category.title) - if isinstance(category.items[0], Track): tracks_list_widget = HTTracksListWidget(category.title) tracks_list_widget.set_tracks_list(category.items) self.page_content.append(tracks_list_widget) else: carousel = HTCarouselWidget(category.title) + self.disconnectables.append(carousel) self.page_content.append(carousel) for item in category.items: diff --git a/src/pages/home_page.py b/src/pages/home_page.py index 0e6b296..bd7cf00 100644 --- a/src/pages/home_page.py +++ b/src/pages/home_page.py @@ -53,6 +53,7 @@ def _th_load_page(self): self.page_content.append(tracks_list_widget) else: carousel = HTCarouselWidget(category.title) + self.disconnectables.append(carousel) self.page_content.append(carousel) if isinstance(category.items[0], Mix): diff --git a/src/pages/mix_page.py b/src/pages/mix_page.py index 81aa229..d86d8bf 100644 --- a/src/pages/mix_page.py +++ b/src/pages/mix_page.py @@ -66,9 +66,8 @@ def _th_load_page(self): in_my_collection_btn.set_icon_name("heart-filled-symbolic") image = builder.get_object("_image") - th = threading.Thread(target=utils.add_image, args=(image, self.item)) - th.deamon = True - th.start() + threading.Thread( + target=utils.add_image, args=(image, self.item)).start() if isinstance(self.item, MixV2): self.item = variables.session.mix(self.item.id) diff --git a/src/pages/page.py b/src/pages/page.py index eab6651..b8e3808 100644 --- a/src/pages/page.py +++ b/src/pages/page.py @@ -28,21 +28,19 @@ from ..lib import variables +from ..disconnectable_iface import IDisconnectable -class Page(Adw.NavigationPage): + +class Page(Adw.NavigationPage, IDisconnectable): __gtype_name__ = 'Page' """It's the base class for all types of pages, it contains all the shared functions""" def __init__(self, _item=None, _name=None): + IDisconnectable.__init__(self) super().__init__() - self.signals = [] - - self.signals.append(( - self, self.connect("unrealize", self.__on_unrealized))) - if _name: self.set_title(_name) @@ -79,22 +77,27 @@ def _add_content_to_page(): def get_album_card(self, item): card = HTCardWidget(item) + self.disconnectables.append(card) return card def get_track_listing(self, track): track_listing = HTGenericTrackWidget(track, False) + self.disconnectables.append(track_listing) return track_listing def get_mix_card(self, item): card = HTCardWidget(item) + self.disconnectables.append(card) return card def get_album_track_listing(self, track): track_listing = HTGenericTrackWidget(track, True) + self.disconnectables.append(track_listing) return track_listing def get_playlist_card(self, playlist): card = HTCardWidget(playlist) + self.disconnectables.append(card) return card def on_mix_button_clicked(self, btn, mix): @@ -205,10 +208,12 @@ def get_artist_page(self, artist): def get_artist_card(self, item): card = HTCardWidget(item) + self.disconnectables.append(card) return card def get_page_item_card(self, page_item): card = HTCardWidget(page_item) + self.disconnectables.append(card) return card def get_page_link_card(self, page_link): @@ -231,18 +236,3 @@ def on_page_link_clicked(self, btn, page_link): page = genericPage(page_link, page_link.title) page.load() variables.navigation_view.push(page) - - def delete_signals(self): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - print(f"disconnected {disconnected_signals} signals from {self}") - - def __on_unrealized(self, *args): - self.delete_signals() - - def __del__(self, *args): - print(f"DELETING {self}") diff --git a/src/widgets/card_widget.py b/src/widgets/card_widget.py index c9625bb..6329122 100644 --- a/src/widgets/card_widget.py +++ b/src/widgets/card_widget.py @@ -33,10 +33,12 @@ from ..lib import variables +from ..disconnectable_iface import IDisconnectable + @Gtk.Template( resource_path='/io/github/nokse22/HighTide/ui/widgets/card_widget.ui') -class HTCardWidget(Adw.BreakpointBin): +class HTCardWidget(Adw.BreakpointBin, IDisconnectable): __gtype_name__ = 'HTCardWidget' """It is card that adapts to the content it needs to display, @@ -50,14 +52,9 @@ class HTCardWidget(Adw.BreakpointBin): track_artist_label = Gtk.Template.Child() def __init__(self, _item): + IDisconnectable.__init__(self) super().__init__() - self.signals = [] - - self.signals.append(( - self, - self.connect("unrealize", self.__on_unrealized))) - self.signals.append(( self.track_artist_label, self.track_artist_label.connect( @@ -178,20 +175,5 @@ def on_button_clicked(self, *args): page.load() variables.navigation_view.push(page) - def delete_signals(self): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - print(f"disconnected {disconnected_signals} signals from {self}") - def __repr__(self, *args): return "" - - def __on_unrealized(self, *args): - self.delete_signals() - - def __del__(self, *args): - print(f"DELETING {self}") diff --git a/src/widgets/carousel_widget.py b/src/widgets/carousel_widget.py index 7224cfe..c53b9ae 100644 --- a/src/widgets/carousel_widget.py +++ b/src/widgets/carousel_widget.py @@ -17,15 +17,17 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -from gi.repository import Gtk +from gi.repository import Gtk, Gio from ..widgets import HTCardWidget from ..lib import variables +from ..disconnectable_iface import IDisconnectable + @Gtk.Template( resource_path='/io/github/nokse22/HighTide/ui/widgets/carousel_widget.ui') -class HTCarouselWidget(Gtk.Box): +class HTCarouselWidget(Gtk.Box, IDisconnectable): __gtype_name__ = 'HTCarouselWidget' """It is used to display multiple elements side by side with @@ -38,14 +40,11 @@ class HTCarouselWidget(Gtk.Box): more_button = Gtk.Template.Child() def __init__(self, _title=""): + IDisconnectable.__init__(self) super().__init__() self.signals = [] - self.signals.append(( - self, - self.connect("unrealize", self.__on_unrealized))) - self.signals.append(( self.next_button, self.next_button.connect("clicked", self.carousel_go_next))) @@ -88,7 +87,7 @@ def set_items(self, items_list, items_type): self.type = items_type for index, item in enumerate(self.items): - if index > 8: + if index >= 8: self.more_button.set_visible(True) break self.append_card(HTCardWidget(item)) @@ -138,20 +137,5 @@ def carousel_go_prev(self, btn): if pos - 2 <= 0: self.prev_button.set_sensitive(False) - def delete_signals(self): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - print(f"disconnected {disconnected_signals} signals from {self}") - def __repr__(self, *args): return "" - - def __on_unrealized(self, *args): - self.delete_signals() - - def __del__(self, *args): - print(f"DELETING {self}") diff --git a/src/widgets/generic_track_widget.py b/src/widgets/generic_track_widget.py index 9ba7906..abbcb76 100644 --- a/src/widgets/generic_track_widget.py +++ b/src/widgets/generic_track_widget.py @@ -22,12 +22,13 @@ from gi.repository import Gio from ..lib import utils from ..lib import variables +from ..disconnectable_iface import IDisconnectable import threading @Gtk.Template( resource_path='/io/github/nokse22/HighTide/ui/widgets/generic_track_widget.ui') -class HTGenericTrackWidget(Gtk.ListBoxRow): +class HTGenericTrackWidget(Gtk.ListBoxRow, IDisconnectable): __gtype_name__ = 'HTGenericTrackWidget' """It is used to display a single track""" @@ -44,15 +45,11 @@ class HTGenericTrackWidget(Gtk.ListBoxRow): track_album_label = Gtk.Template.Child() def __init__(self, _track=None, is_album=False): + IDisconnectable.__init__(self) super().__init__() if not _track: return - self.signals = [] - - self.signals.append(( - self, self.connect("unrealize", self.__on_unrealized))) - self.set_track(_track, is_album) def set_track(self, _track, is_album=False): @@ -164,15 +161,3 @@ def on_open_uri(self, label, uri, *args): def __repr__(self, *args): return "" - - def __on_unrealized(self, *args): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - print(f"disconnected {disconnected_signals} signals from {self}") - - def __del__(self, *args): - print(f"DELETING {self}") diff --git a/src/widgets/top_hit_widget.py b/src/widgets/top_hit_widget.py index f0692ea..4da13e4 100644 --- a/src/widgets/top_hit_widget.py +++ b/src/widgets/top_hit_widget.py @@ -28,15 +28,17 @@ from ..lib import utils from ..lib import variables +from ..disconnectable_iface import IDisconnectable import threading @Gtk.Template( resource_path='/io/github/nokse22/HighTide/ui/widgets/top_hit_widget.ui') -class HTTopHitWidget(Gtk.Box): +class HTTopHitWidget(Gtk.Box, IDisconnectable): __gtype_name__ = 'HTTopHitWidget' - """It is used to display the top hit when searching regardless of the type""" + """It is used to display the top hit when searching regardless + of the type""" artist_avatar = Gtk.Template.Child() artist_label = Gtk.Template.Child() @@ -46,13 +48,12 @@ class HTTopHitWidget(Gtk.Box): # track_artist_button = Gtk.Template.Child() def __init__(self, _item): + IDisconnectable.__init__(self) super().__init__() - self.signals = [] - - self.signals.append( - (self, self.connect("unrealize", self.__on_unrealized)) - ) + self.signals.append(( + self, + self.connect("unrealize", self.__on_unrealized))) self.item = _item @@ -169,21 +170,5 @@ def _on_image_button_clicked(self, *args): page.load() variables.navigation_view.push(page) - def delete_signals(self): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - # print(f"disconnected {disconnected_signals} signals from {self}") - def __repr__(self, *args): return "" - - def __on_unrealized(self, *args): - self.delete_signals() - - def __del__(self, *args): - # print(f"DELETING {self}") - pass diff --git a/src/widgets/tracks_list_widget.py b/src/widgets/tracks_list_widget.py index 3165e0c..dcf2e50 100644 --- a/src/widgets/tracks_list_widget.py +++ b/src/widgets/tracks_list_widget.py @@ -18,15 +18,14 @@ # SPDX-License-Identifier: GPL-3.0-or-later from gi.repository import Gtk - from . import HTGenericTrackWidget - from ..lib import variables +from ..disconnectable_iface import IDisconnectable @Gtk.Template( resource_path='/io/github/nokse22/HighTide/ui/widgets/tracks_list_widget.ui') -class HTTracksListWidget(Gtk.Box): +class HTTracksListWidget(Gtk.Box, IDisconnectable): __gtype_name__ = 'HTTracksListWidget' """It is used to display multiple elements side by side @@ -37,14 +36,9 @@ class HTTracksListWidget(Gtk.Box): title_label = Gtk.Template.Child() def __init__(self, _title): + IDisconnectable.__init__(self) super().__init__() - self.signals = [] - - self.signals.append(( - self, - self.connect("unrealize", self.__on_unrealized))) - self.signals.append(( self.more_button, self.more_button.connect("clicked", self.on_more_clicked))) @@ -78,6 +72,7 @@ def set_tracks_list(self, tracks_list): def _add_tracks(self): for index, track in enumerate(self.tracks): listing = HTGenericTrackWidget(track, False) + self.disconnectables.append(listing) listing.set_name(str(index)) self.tracks_list_box.append(listing) @@ -94,20 +89,5 @@ def on_tracks_row_selected(self, list_box, row): variables.player_object.play_this(self.tracks, index) - def delete_signals(self): - disconnected_signals = 0 - for obj, signal_id in self.signals: - disconnected_signals += 1 - obj.disconnect(signal_id) - - self.signals = [] - print(f"disconnected {disconnected_signals} signals from {self}") - def __repr__(self, *args): return "" - - def __on_unrealized(self, *args): - self.delete_signals() - - def __del__(self, *args): - print(f"DELETING {self}") diff --git a/src/window.py b/src/window.py index 5bc8f2a..15320f5 100644 --- a/src/window.py +++ b/src/window.py @@ -36,7 +36,7 @@ from .login import LoginDialog from .new_playlist import NewPlaylistWindow -from .pages import homePage, explorePage, notLoggedInPage +from .pages import homePage, explorePage, notLoggedInPage, collectionPage from .pages import trackRadioPage, playlistPage, startUpPage, fromFunctionPage from .lib import SecretStore @@ -54,7 +54,6 @@ class HighTideWindow(Adw.ApplicationWindow): __gtype_name__ = 'HighTideWindow' progress_bar = Gtk.Template.Child() - # sidebar_list = Gtk.Template.Child() duration_label = Gtk.Template.Child() time_played_label = Gtk.Template.Child() shuffle_button = Gtk.Template.Child() @@ -64,17 +63,11 @@ class HighTideWindow(Adw.ApplicationWindow): song_title_label = Gtk.Template.Child() playing_track_picture = Gtk.Template.Child() artist_label = Gtk.Template.Child() - # mobile_artist_label = Gtk.Template.Child() - # sidebar_collection = Gtk.Template.Child() - # sidebar_playlists = Gtk.Template.Child() + miniplayer_artist_label = Gtk.Template.Child() volume_button = Gtk.Template.Child() in_my_collection_button = Gtk.Template.Child() explicit_label = Gtk.Template.Child() - # main_view_stack = Gtk.Template.Child() - # playbar_main_box = Gtk.Template.Child() - # current_carousel_picture = Gtk.Template.Child() queue_widget = Gtk.Template.Child() - # mobile_stack = Gtk.Template.Child() lyrics_label = Gtk.Template.Child() repeat_button = Gtk.Template.Child() @@ -124,16 +117,13 @@ def __init__(self, **kwargs): self.artist_label.connect( "activate-link", variables.open_uri) - # self.mobile_artist_label.connect( - # "activate-link", variables.open_uri) - # self.mobile_artist_label.connect( - # "activate-link", self.toggle_mobile_view) + self.miniplayer_artist_label.connect( + "activate-link", variables.open_uri) self.session = tidalapi.Session() variables.session = self.session variables.navigation_view = self.navigation_view - # variables.stack = self.main_view_stack self.user = self.session.user @@ -141,7 +131,7 @@ def __init__(self, **kwargs): self.current_mix = None self.player_object.current_song_index = 0 - self.previous_time = 0 + self.previous_fraction = 0 self.favourite_playlists = [] self.my_playlists = [] @@ -162,17 +152,11 @@ def on_logged_in(self): variables.get_favourites() # FIXME if it doesn't login fast enough it doesn't let the user login - # self.sidebar_list.set_sensitive(True) - page = homePage(self) page.load() self.navigation_view.replace([page]) - th = threading.Thread(target=self.th_set_last_playing_song, args=()) - th.deamon = True - th.start() - - # self.update_my_playlists() + threading.Thread(target=self.th_set_last_playing_song, args=()).start() def on_login_failed(self): print("login failed") @@ -181,30 +165,6 @@ def on_login_failed(self): page.load() self.navigation_view.replace([page]) - def on_create_new_playlist_requested(self, window, p_title, p_description): - self.session.user.create_playlist(p_title, p_description) - # self.update_my_playlists() - window.close() - - def update_my_playlists(self): - child = self.sidebar_playlists.get_first_child() - while child is not None: - self.sidebar_playlists.remove(child) - del child - child = self.sidebar_playlists.get_first_child() - - playlists = self.session.user.playlists() - - for index, playlist in enumerate(playlists): - if playlist.creator: - if playlist.creator.name != "me": - playlists.remove(playlist) - continue - label = Gtk.Label(xalign=0, label=playlist.name, name=index) - self.sidebar_playlists.append(label) - - self.my_playlists = playlists - def th_set_last_playing_song(self): track_id = self.settings.get_int("last-playing-song-id") list_id = self.settings.get_string("last-playing-list-id") @@ -222,8 +182,6 @@ def th_set_last_playing_song(self): if track_id == -1: return - # self.playbar_main_box.set_visible(True) - # track = self.session.track(track_id) self.player_object.play_this(album_mix_playlist, 0) @@ -255,41 +213,26 @@ def on_song_changed(self, *args): self.settings.set_int("last-playing-song-id", track.id) threading.Thread( - target=utils.add_picture, + target=utils.add_image, args=(self.playing_track_picture, album)).start() - # threading.Thread( - # target=utils.add_picture, - # args=(self.current_carousel_picture, album)).start() - - # next_track = self.player_object.get_next_track() - # if next_track: - # threading.Thread(target=utils.add_picture, args=(self.next_carousel_picture, next_track.album)).start() - - # prev_track = self.player_object.get_prev_track() - # if prev_track: - # threading.Thread(target=utils.add_picture, args=(self.previous_carousel_picture, prev_track.album)).start() if self.player_object.is_playing: self.play_button.set_icon_name("media-playback-pause-symbolic") else: self.play_button.set_icon_name("media-playback-start-symbolic") - th = threading.Thread(target=self.th_add_lyrics_to_page, args=()) - th.deamon = True - th.start() + threading.Thread(target=self.th_add_lyrics_to_page, args=()).start() self.control_bar_artist = track.artist self.update_slider() - # if (self.main_view_stack.get_visible_child_name() == "mobile_view" and - # self.mobile_stack.get_visible_child_name() == "queue_page"): - # self.queue_widget.update_all(self.player_object) - # self.queue_widget_updated = True - # else: - # self.queue_widget_updated = False + if self.queue_widget.get_mapped(): + self.queue_widget.update_all(self.player_object) + self.queue_widget_updated = True + else: + self.queue_widget_updated = False def update_controls(self, is_playing, *arg): - # self.playbar_main_box.set_visible(True) if not is_playing: self.play_button.set_icon_name("media-playback-pause-symbolic") print("pause") @@ -306,7 +249,7 @@ def new_login(self): def th_login(self): """Logs the user in, if it doesn't work it calls on_login_failed()""" try: - result = self.session.load_oauth_session( + self.session.load_oauth_session( self.secret_store.token_dictionary["token-type"], self.secret_store.token_dictionary["access-token"], self.secret_store.token_dictionary["refresh-token"], @@ -320,8 +263,6 @@ def th_login(self): def logout(self): self.secret_store.clear() - self.sidebar_list.set_sensitive(False) - page = notLoggedInPage(self) page.load() self.navigation_view.replace([page]) @@ -352,29 +293,23 @@ def update_slider(self, *args): return False # cancel timeout else: self.duration = self.player_object.query_duration() + end_value = self.duration / Gst.SECOND - self.progress_bar.set_range(0, end_value) + position = self.player_object.query_position() / Gst.SECOND + fraction = 0 self.duration_label.set_label(utils.pretty_duration(end_value)) - position = self.player_object.query_position() - position = position / Gst.SECOND - self.progress_bar.get_adjustment().set_value(position) if end_value != 0: - self.small_progress_bar.set_fraction(position/end_value) - self.previous_time = position + fraction = position/end_value + self.small_progress_bar.set_fraction(fraction) + self.progress_bar.get_adjustment().set_value(fraction) + + self.previous_fraction = fraction self.time_played_label.set_label(utils.pretty_duration(position)) return True - # @Gtk.Template.Callback("on_lyrics_button_clicked") - # def on_lyrics_button_clicked_func(self, widget): - # self.main_view_stack.set_visible_child_name("mobile_view") - # self.mobile_stack.set_visible_child_name("lyrics_page") - - # threading.Thread( - # target=self.th_add_lyrics_to_page, deamon=True).start() - def th_add_lyrics_to_page(self): try: lyrics = self.player_object.playing_track.lyrics() @@ -413,61 +348,43 @@ def select_quality(self, pos): @Gtk.Template.Callback("on_in_my_collection_button_clicked") def on_in_my_collection_button_clicked(self, btn): if self.in_my_collection_button.get_icon_name() == "heart-outline-thick-symbolic": - th = threading.Thread( + threading.Thread( target=self.th_add_track_to_my_collection, - args=(self.player_object.playing_track.id,)) - self.in_my_collection_button.set_icon_name( - "heart-filled-symbolic") + args=(self.player_object.playing_track.id,)).start() else: - th = threading.Thread( + threading.Thread( target=self.th_remove_track_from_my_collection, - args=(self.player_object.playing_track.id,)) - self.in_my_collection_button.set_icon_name( - "heart-outline-thick-symbolic") - th.deamon = True - th.start() + args=(self.player_object.playing_track.id,)).start() def th_add_track_to_my_collection(self, track_id): result = self.session.user.favorites.add_track(track_id) if result: + self.in_my_collection_button.set_icon_name( + "heart-filled-symbolic") print("successfully added to my collection") def th_remove_track_from_my_collection(self, track_id): result = self.session.user.favorites.remove_track(track_id) if result: + self.in_my_collection_button.set_icon_name( + "heart-outline-thick-symbolic") print("successfully removed from my collection") @Gtk.Template.Callback("on_volume_changed") def on_volume_changed_func(self, widget, value): - print(f"volume changed to {value}") self.player_object.change_volume(value) self.settings.set_int("last-volume", int(value*10)) - # @Gtk.Template.Callback("on_new_playlist_button_clicked") - # def on_new_playlist_button_clicked_func(self, btn): - # new_playlist_win = NewPlaylistWindow() - # new_playlist_win.connect( - # "create-playlist", self.on_create_new_playlist_requested) - # new_playlist_win.present(self) - - # @Gtk.Template.Callback("on_track_radio_button_clicked") - # def on_track_radio_button_clicked_func(self, widget): - # track = self.player_object.playing_track - # page = trackRadioPage(track, f"{track.name} Radio") - # page.load() - # self.navigation_view.push(page) - - # @Gtk.Template.Callback("on_slider_seek") - # def on_slider_seek(self, *args): - # seek_time_secs = self.progress_bar.get_value() - # if abs(seek_time_secs - self.previous_time) > 6: - # print("seeking") - # print(abs(seek_time_secs - self.previous_time)) - # self.player_object.seek( - # Gst.Format.TIME, - # Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, - # seek_time_secs * Gst.SECOND) - # self.previous_time = seek_time_secs + @Gtk.Template.Callback("on_slider_seek") + def on_slider_seek(self, *args): + seek_fraction = self.progress_bar.get_value() + print("seeking: ", abs(seek_fraction - self.previous_fraction)) + + if abs(seek_fraction - self.previous_fraction) == 0.0: + return + + self.player_object.seek(seek_fraction) + self.previous_fraction = seek_fraction @Gtk.Template.Callback("on_skip_forward_button_clicked") def on_skip_forward_button_clicked_func(self, widget): @@ -479,67 +396,21 @@ def on_skip_backward_button_clicked_func(self, widget): print("skip backward") self.player_object.play_previous() - # @Gtk.Template.Callback("on_playlists_sidebar_row_activated") - # def on_playlists_sidebar_row_activated_func(self, list_box, row): - # """Handles the click on an user playlist on the sidebar""" - - # if row is None: - # return - # index = row.get_child().get_name() - - # playlist = self.my_playlists[int(index)] - - # page = playlistPage(playlist, playlist.name) - # page.load() - # self.navigation_view.push(page) - - # @Gtk.Template.Callback("on_sidebar_row_selected_clicked") - # def on_sidebar_row_selected_clicked_func(self, list_box, row): - # if row is None: - # return - - # name = row.get_child().get_last_child().get_name() - - # if name == "HOME": - # self.navigation_view.pop_to_tag("home") - # elif name == "EXPLORE": - # page = explorePage(None, "Explore") - # page.load() - # self.navigation_view.push(page) - # elif name == "F-TRACK": - # page = fromFunctionPage("track", _("Favorite Tracks")) - # page.set_function(self.session.user.favorites.tracks) - # page.load() - # self.navigation_view.push(page) - # elif name == "F-MIX": - # page = fromFunctionPage("mix", _("Favorite Mixes")) - # page.set_function(self.session.user.favorites.mixes) - # page.load() - # self.navigation_view.push(page) - # elif name == "F-ARTIST": - # page = fromFunctionPage("artist", _("Favorite Artists")) - # page.set_function(self.session.user.favorites.artists) - # page.load() - # self.navigation_view.push(page) - # elif name == "F-PLAYLIST": - # page = fromFunctionPage("playlist", _("Favorite Playlists")) - # page.set_function(self.session.user.favorites.playlists) - # page.load() - # self.navigation_view.push(page) - # elif name == "F-ALBUM": - # page = fromFunctionPage("album", _("Favorite Albums")) - # page.set_function(self.session.user.favorites.albums) - # page.load() - # self.navigation_view.push(page) - - # @Gtk.Template.Callback("on_mobile_view_button_clicked") - # def toggle_mobile_view(self, *args): - # if self.main_view_stack.get_visible_child_name() == "normal_view": - # self.main_view_stack.set_visible_child_name("mobile_view") - # else: - # self.main_view_stack.set_visible_child_name("normal_view") - - # return True + @Gtk.Template.Callback("on_home_button_clicked") + def on_home_button_clicked_func(self, widget): + self.navigation_view.pop_to_tag("home") + + @Gtk.Template.Callback("on_explore_button_clicked") + def on_explore_button_clicked_func(self, widget): + page = explorePage(None, "Explore") + page.load() + self.navigation_view.push(page) + + @Gtk.Template.Callback("on_collection_button_clicked") + def on_collection_button_clicked_func(self, widget): + page = collectionPage(None, "Collection") + page.load() + self.navigation_view.push(page) @Gtk.Template.Callback("on_repeat_clicked") def on_repeat_clicked(self, *args): @@ -559,8 +430,7 @@ def on_repeat_clicked(self, *args): self.settings.set_int("repeat", self.player_object.repeat) def on_song_added_to_queue(self, *args): - if (self.main_view_stack.get_visible_child_name() == "mobile_view" and - self.mobile_stack.get_visible_child_name() == "queue_page"): + if self.queue_widget.get_mapped(): self.queue_widget.update_queue(self.player_object) self.queue_widget_updated = True else: @@ -572,22 +442,6 @@ def on_queue_widget_mapped(self, *args): self.queue_widget.update_all(self.player_object) self.queue_widget_updated = True - # - # CREATE ACTIONS WITH OR WITHOUT TARGETS - # - - def create_action(self, name, callback): - """Used to create a new action without target""" - - action = Gio.SimpleAction.new(name, None) - action.connect("activate", callback) - self.add_action(action) - return action - - def create_action_with_target(self, name, target_type, callback): - """Used to create a new action with a target""" - - action = Gio.SimpleAction.new(name, target_type) - action.connect("activate", callback) - self.add_action(action) - return action + @Gtk.Template.Callback("on_navigation_view_page_popped") + def on_navigation_view_page_popped_func(self, nav_view, nav_page): + nav_page.disconnect_all()