From f9e40f6e24a35a331cca80e3729adf001946072b Mon Sep 17 00:00:00 2001 From: cosven Date: Sat, 6 Jan 2024 15:20:39 +0800 Subject: [PATCH] gui: add fav page --- feeluown/gui/base_renderer.py | 10 ++++ feeluown/gui/browser.py | 2 + feeluown/gui/drawers.py | 31 ++++++++++ feeluown/gui/pages/coll_mixed.py | 8 +-- feeluown/gui/pages/my_fav.py | 83 +++++++++++++++++++++++++++ feeluown/gui/uimain/sidebar.py | 15 ++++- feeluown/gui/widgets/__init__.py | 1 + feeluown/gui/widgets/selfpaint_btn.py | 12 +++- feeluown/library/provider_protocol.py | 74 +++++++++++++----------- 9 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 feeluown/gui/pages/my_fav.py diff --git a/feeluown/gui/base_renderer.py b/feeluown/gui/base_renderer.py index d7653f9e08..5502486418 100644 --- a/feeluown/gui/base_renderer.py +++ b/feeluown/gui/base_renderer.py @@ -4,6 +4,7 @@ from abc import abstractmethod from typing import runtime_checkable, Protocol +from feeluown.models import ModelType from feeluown.gui.widgets.tabbar import Tab, TabBar @@ -67,6 +68,15 @@ def render_tab_bar(self): def render_by_tab_index(self, tab_index): raise NotImplementedError + def default_tabs(self): + return [ + ('歌曲', ModelType.song, self.show_songs), + ('专辑', ModelType.album, self.show_albums), + ('歌手', ModelType.artist, self.show_artists), + ('歌单', ModelType.playlist, self.show_playlists), + ('视频', ModelType.video, self.show_videos) + ] + @runtime_checkable class VFillableBg(Protocol): diff --git a/feeluown/gui/browser.py b/feeluown/gui/browser.py index 548f5d8a4f..024e85e257 100644 --- a/feeluown/gui/browser.py +++ b/feeluown/gui/browser.py @@ -196,6 +196,7 @@ def initialize(self): from feeluown.gui.pages.recommendation import render as render_rec from feeluown.gui.pages.recommendation_daily_songs import \ render as render_rec_daily_songs + from feeluown.gui.pages.my_fav import render as render_my_fav model_prefix = f'{MODEL_PAGE_PREFIX}' @@ -214,6 +215,7 @@ async def dummy_render(req, *args, **kwargs): ('/search', render_search), ('/rec', render_rec), ('/rec/daily_songs', render_rec_daily_songs), + ('/my_fav', render_my_fav), ] for url, renderer in urlpatterns: self.route(url)(renderer) diff --git a/feeluown/gui/drawers.py b/feeluown/gui/drawers.py index 49a448e88d..539285b8b2 100644 --- a/feeluown/gui/drawers.py +++ b/feeluown/gui/drawers.py @@ -1,3 +1,4 @@ +import math from typing import Optional from PyQt5.QtCore import Qt, QRect, QPoint, QPointF @@ -255,3 +256,33 @@ def paint(self, painter: QPainter): painter.drawLine(self.p3, self.p4) painter.drawLine(self.p4, self.p5) painter.drawLine(self.p4, self.p6) + + +class StarIconDrawer: + def __init__(self, length, padding): + + radius_outer = (length - 2*padding)//2 + length_half = length // 2 + radius_inner = radius_outer // 2 + center = QPointF(length_half, length_half) + angle = math.pi / 2 + + self._star_polygon = QPolygonF() + for _ in range(5): + outer_point = center + QPointF( + radius_outer * math.cos(angle), + -radius_outer * math.sin(angle) + ) + self._star_polygon.append(outer_point) + inner_point = center + QPointF( + radius_inner * math.cos(angle + math.pi/5), + -radius_inner * math.sin(angle + math.pi/5) + ) + self._star_polygon.append(inner_point) + angle += 2 * math.pi / 5 + + def paint(self, painter: QPainter): + pen = painter.pen() + pen.setWidthF(1.5) + painter.setPen(pen) + painter.drawPolygon(self._star_polygon) diff --git a/feeluown/gui/pages/coll_mixed.py b/feeluown/gui/pages/coll_mixed.py index 2e809dc80f..49f567dd56 100644 --- a/feeluown/gui/pages/coll_mixed.py +++ b/feeluown/gui/pages/coll_mixed.py @@ -51,13 +51,7 @@ def __init__(self, app, tab_index, coll: Collection): self._app = app self._coll = coll self.tab_index = tab_index - self.tabs = [ - ('歌曲', ModelType.song, self.show_songs), - ('专辑', ModelType.album, self.show_albums), - ('歌手', ModelType.artist, self.show_artists), - ('歌单', ModelType.playlist, self.show_playlists), - ('视频', ModelType.video, self.show_videos) - ] + self.tabs = self.default_tabs() async def render(self): coll = self._coll diff --git a/feeluown/gui/pages/my_fav.py b/feeluown/gui/pages/my_fav.py new file mode 100644 index 0000000000..c82884612c --- /dev/null +++ b/feeluown/gui/pages/my_fav.py @@ -0,0 +1,83 @@ +from feeluown.app.gui_app import GuiApp +from feeluown.models import ModelType +from feeluown.utils.aio import run_fn +from feeluown.gui.page_containers.table import Renderer +from feeluown.gui.base_renderer import TabBarRendererMixin +from feeluown.library import ( + SupportsCurrentUserFavSongsReader, + SupportsCurrentUserFavAlbumsReader, + SupportsCurrentUserFavArtistsReader, + SupportsCurrentUserFavPlaylistsReader, + SupportsCurrentUserFavVideosReader, +) +from feeluown.utils.reader import create_reader +from .template import render_error_message + + +async def render(req, **kwargs): + app: GuiApp = req.ctx['app'] + ui = app.ui + tab_index = int(req.query.get('tab_index', 0)) + pvd_ui = app.current_pvd_ui_mgr.get() + if pvd_ui is None: + return await render_error_message(app, '当前资源提供方未知,无法浏览该页面') + + ui.right_panel.set_body(ui.right_panel.scrollarea) + table_container = ui.right_panel.table_container + renderer = MyFavRenderer(app, tab_index, pvd_ui.provider) + await table_container.set_renderer(renderer) + + +class MyFavRenderer(Renderer, TabBarRendererMixin): + def __init__(self, app, tab_index, provider): + self._app = app + self._provider = provider + self.tab_index = tab_index + self.tabs = self.default_tabs() + + async def render(self): + self.meta_widget.show() + self.meta_widget.title = '我的收藏' + self.render_tab_bar() + await self.render_models() + + async def render_models(self): + # pylint: disable=too-many-branches + _, mtype, show_handler = self.tabs[self.tab_index] + err = '' + reader = create_reader([]) + if mtype is ModelType.song: + if isinstance(self._provider, SupportsCurrentUserFavSongsReader): + reader = await run_fn(self._provider.current_user_fav_create_songs_rd) + else: + err = '收藏的歌曲' + elif mtype is ModelType.album: + if isinstance(self._provider, SupportsCurrentUserFavAlbumsReader): + reader = await run_fn(self._provider.current_user_fav_create_albums_rd) + else: + err = '收藏的专辑' + elif mtype is ModelType.artist: + if isinstance(self._provider, SupportsCurrentUserFavArtistsReader): + reader = await run_fn(self._provider.current_user_fav_create_artists_rd) + else: + err = '收藏的歌手' + elif mtype is ModelType.playlist: + if isinstance(self._provider, SupportsCurrentUserFavPlaylistsReader): + reader = await run_fn(self._provider.current_user_fav_create_playlists_rd) # noqa + else: + err = '收藏的歌单' + else: + if isinstance(self._provider, SupportsCurrentUserFavVideosReader): + reader = await run_fn(self._provider.current_user_fav_create_videos_rd) + else: + err = '收藏的视频' + if err: + return await render_error_message( + self._app, + f'当前资源提供方({self._provider.name})不支持获取 {err}' + ) + else: + show_handler(reader) + + def render_by_tab_index(self, tab_index): + self._app.browser.goto(page='/my_fav', query={'tab_index': tab_index}) diff --git a/feeluown/gui/uimain/sidebar.py b/feeluown/gui/uimain/sidebar.py index e6b590b563..7bce864262 100644 --- a/feeluown/gui/uimain/sidebar.py +++ b/feeluown/gui/uimain/sidebar.py @@ -18,6 +18,7 @@ HomeButton, PlusButton, TriagleButton, + StarButton, ) from feeluown.gui.widgets.playlists import PlaylistsView @@ -107,6 +108,7 @@ def __init__(self, app: 'GuiApp', parent=None): self.home_btn = HomeButton(height=30, parent=self) self.discovery_btn = DiscoveryButton(height=30, padding=0.2, parent=self) + self.fav_btn = StarButton('我的收藏', height=30, parent=self) self.collections_header = QLabel('本地收藏集', self) self.collections_header.setToolTip('我们可以在本地建立『收藏集』来收藏自己喜欢的音乐资源\n\n' '每个收藏集都以一个独立 .fuo 文件的存在,' @@ -141,6 +143,7 @@ def __init__(self, app: 'GuiApp', parent=None): self._top_layout.setContentsMargins(15, 16, 16, 0) self._top_layout.addWidget(self.home_btn) self._top_layout.addWidget(self.discovery_btn) + self._top_layout.addWidget(self.fav_btn) self._sub_layout.setContentsMargins(16, 8, 16, 0) self._sub_layout.addWidget(self.collections_con) self._sub_layout.addWidget(self.my_music_con) @@ -156,6 +159,7 @@ def __init__(self, app: 'GuiApp', parent=None): self.playlists_con.hide() self.my_music_con.hide() self.discovery_btn.setDisabled(True) + self.fav_btn.setDisabled(True) self.discovery_btn.setToolTip('当前资源提供方未知') self.home_btn.clicked.connect(self.show_library) @@ -173,6 +177,8 @@ def __init__(self, app: 'GuiApp', parent=None): self.on_current_pvd_ui_changed) self.discovery_btn.clicked.connect( lambda: self._app.browser.goto(page='/rec')) + self.fav_btn.clicked.connect( + lambda: self._app.browser.goto(page='/my_fav')) def popup_collection_adding_dialog(self): dialog = QDialog(self) @@ -282,5 +288,10 @@ def do(): box.open() def on_current_pvd_ui_changed(self, pvd_ui, _): - self.discovery_btn.setEnabled(True) - self.discovery_btn.setToolTip(f'点击进入 {pvd_ui.provider.name} 推荐页') + if pvd_ui: + self.discovery_btn.setEnabled(True) + self.fav_btn.setEnabled(True) + self.discovery_btn.setToolTip(f'点击进入 {pvd_ui.provider.name} 推荐页') + else: + self.discovery_btn.setEnabled(False) + self.fav_btn.setEnabled(False) diff --git a/feeluown/gui/widgets/__init__.py b/feeluown/gui/widgets/__init__.py index 690815aecd..07608f7d14 100644 --- a/feeluown/gui/widgets/__init__.py +++ b/feeluown/gui/widgets/__init__.py @@ -5,4 +5,5 @@ HomeButton, LeftArrowButton, RightArrowButton, SearchButton, SettingsButton, PlusButton, TriagleButton, DiscoveryButton, SelfPaintAbstractIconTextButton, CalendarButton, RankButton, + StarButton, ) diff --git a/feeluown/gui/widgets/selfpaint_btn.py b/feeluown/gui/widgets/selfpaint_btn.py index a1b906f25d..fc9b494910 100644 --- a/feeluown/gui/widgets/selfpaint_btn.py +++ b/feeluown/gui/widgets/selfpaint_btn.py @@ -4,7 +4,7 @@ from feeluown.gui.drawers import ( HomeIconDrawer, PlusIconDrawer, TriangleIconDrawer, CalendarIconDrawer, - RankIconDrawer, + RankIconDrawer, StarIconDrawer, ) from feeluown.gui.helpers import darker_or_lighter @@ -296,6 +296,15 @@ def draw_icon(self, painter): self.rank_icon.paint(painter) +class StarButton(SelfPaintAbstractIconTextButton): + def __init__(self, text='收藏', *args, **kwargs): + super().__init__(text, *args, **kwargs) + self.star_icon = StarIconDrawer(self.height(), self._padding) + + def draw_icon(self, painter): + self.star_icon.paint(painter) + + if __name__ == '__main__': from feeluown.gui.debug import simple_layout @@ -315,3 +324,4 @@ def draw_icon(self, painter): layout.addWidget(TriagleButton(length=length, direction='up')) layout.addWidget(CalendarButton(height=length)) layout.addWidget(RankButton(height=length)) + layout.addWidget(StarButton(height=length)) diff --git a/feeluown/library/provider_protocol.py b/feeluown/library/provider_protocol.py index 21c00195da..cc03394f09 100644 --- a/feeluown/library/provider_protocol.py +++ b/feeluown/library/provider_protocol.py @@ -13,40 +13,6 @@ from .flags import Flags as PF -__all__ = ( - 'SupportsAlbumGet', - 'SupportsAlbumSongsReader', - - 'SupportsArtistAlbumsReader', - 'SupportsArtistGet', - 'SupportsArtistSongsReader', - - 'SupportsCurrentUser', - - 'SupportsPlaylistAddSong', - 'SupportsPlaylistGet', - 'SupportsPlaylistCreateByName', - 'SupportsPlaylistDelete', - 'SupportsPlaylistRemoveSong', - 'SupportsPlaylistSongsReader', - - 'SupportsSongGet', - 'SupportsSongHotComments', - 'SupportsSongLyric', - 'SupportsSongMV', - 'SupportsSongMultiQuality', - 'SupportsSongSimilar', - 'SupportsSongWebUrl', - - 'SupportsVideoGet', - 'SupportsVideoMultiQuality', - - 'SupportsRecListDailySongs', - 'SupportsRecListDailyAlbums', - 'SupportsRecListDailyPlaylists', -) - - ID = str _FlagProtocolMapping: Dict[Tuple[ModelType, PF], type] = {} @@ -349,6 +315,46 @@ def get_current_user(self) -> UserModel: """ +# +# Protocols for current user favorites/collections +# +@runtime_checkable +class SupportsCurrentUserFavSongsReader(Protocol): + @abstractmethod + def current_user_fav_create_songs_rd(self): + """ + : raises NoUserLoggedIn: + """ + + +@runtime_checkable +class SupportsCurrentUserFavAlbumsReader(Protocol): + @abstractmethod + def current_user_fav_create_albums_rd(self): + pass + + +@runtime_checkable +class SupportsCurrentUserFavArtistsReader(Protocol): + @abstractmethod + def current_user_fav_create_artists_rd(self): + pass + + +@runtime_checkable +class SupportsCurrentUserFavPlaylistsReader(Protocol): + @abstractmethod + def current_user_fav_create_playlists_rd(self): + pass + + +@runtime_checkable +class SupportsCurrentUserFavVideosReader(Protocol): + @abstractmethod + def current_user_fav_create_videos_rd(self): + pass + + # # Protocols for recommendation. #