95e760791b
In continuation of the previous change, prepend `Store` to the name to specify the difference. The goal is to add `StoreDetailsWidget` as a second view in `GameDetails` with information sourced from the Epic Games Store.
239 lines
9.8 KiB
Python
239 lines
9.8 KiB
Python
import datetime
|
|
import logging
|
|
from typing import List
|
|
|
|
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QObject, QEvent
|
|
from PyQt5.QtGui import QShowEvent, QHideEvent, QResizeEvent
|
|
from PyQt5.QtWidgets import (
|
|
QHBoxLayout,
|
|
QWidget,
|
|
QSizePolicy,
|
|
QVBoxLayout,
|
|
QSpacerItem,
|
|
QScrollArea,
|
|
QFrame,
|
|
)
|
|
|
|
from rare.components.tabs.store.api.models.response import CatalogOfferModel, WishlistItemModel
|
|
from rare.widgets.flow_layout import FlowLayout
|
|
from rare.widgets.side_tab import SideTabContents
|
|
from rare.widgets.sliding_stack import SlidingStackedWidget
|
|
from .store_api import StoreAPI
|
|
from .widgets.details import StoreDetailsWidget
|
|
from .widgets.groups import StoreGroup
|
|
from .widgets.items import StoreItemWidget
|
|
|
|
logger = logging.getLogger("StoreLanding")
|
|
|
|
|
|
class LandingPage(SlidingStackedWidget, SideTabContents):
|
|
|
|
def __init__(self, store_api: StoreAPI, parent=None):
|
|
super(LandingPage, self).__init__(parent=parent)
|
|
self.implements_scrollarea = True
|
|
|
|
self.landing_widget = LandingWidget(store_api, parent=self)
|
|
self.landing_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
self.landing_widget.set_title.connect(self.set_title)
|
|
self.landing_widget.show_details.connect(self.show_details)
|
|
|
|
self.landing_scroll = QScrollArea(self)
|
|
self.landing_scroll.setWidgetResizable(True)
|
|
self.landing_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain)
|
|
self.landing_scroll.setWidget(self.landing_widget)
|
|
self.landing_scroll.widget().setAutoFillBackground(False)
|
|
self.landing_scroll.viewport().setAutoFillBackground(False)
|
|
|
|
self.details_widget = StoreDetailsWidget([], store_api, parent=self)
|
|
self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
self.details_widget.set_title.connect(self.set_title)
|
|
self.details_widget.back_clicked.connect(self.show_main)
|
|
|
|
self.setDirection(Qt.Horizontal)
|
|
self.addWidget(self.landing_scroll)
|
|
self.addWidget(self.details_widget)
|
|
|
|
@pyqtSlot()
|
|
def show_main(self):
|
|
self.slideInWidget(self.landing_scroll)
|
|
|
|
@pyqtSlot(object)
|
|
def show_details(self, game: CatalogOfferModel):
|
|
self.details_widget.update_game(game)
|
|
self.slideInWidget(self.details_widget)
|
|
|
|
|
|
class FreeGamesScroll(QScrollArea):
|
|
def __init__(self, parent=None):
|
|
super(FreeGamesScroll, self).__init__(parent=parent)
|
|
self.setObjectName(type(self).__name__)
|
|
|
|
def setWidget(self, w):
|
|
super().setWidget(w)
|
|
w.installEventFilter(self)
|
|
|
|
def eventFilter(self, a0: QObject, a1: QEvent) -> bool:
|
|
if a0 is self.widget() and a1.type() == QEvent.Resize:
|
|
self.__resize(a0)
|
|
return a0.event(a1)
|
|
return False
|
|
|
|
def __resize(self, e: QResizeEvent):
|
|
minh = self.horizontalScrollBar().minimum()
|
|
maxh = self.horizontalScrollBar().maximum()
|
|
# lk: when the scrollbar is not visible, min and max are 0
|
|
if maxh > minh:
|
|
height = (
|
|
e.size().height()
|
|
+ self.rect().height() // 2
|
|
- self.contentsRect().height() // 2
|
|
+ self.widget().layout().spacing()
|
|
+ self.horizontalScrollBar().sizeHint().height()
|
|
)
|
|
else:
|
|
height = e.size().height() + self.rect().height() - self.contentsRect().height()
|
|
self.setMinimumHeight(max(height, self.minimumHeight()))
|
|
|
|
|
|
class LandingWidget(QWidget, SideTabContents):
|
|
show_details = pyqtSignal(CatalogOfferModel)
|
|
|
|
def __init__(self, api: StoreAPI, parent=None):
|
|
super(LandingWidget, self).__init__(parent=parent)
|
|
self.api = api
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 3, 0)
|
|
self.setLayout(layout)
|
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
|
|
self.free_games_now = StoreGroup(self.tr("Free now"), layout=QHBoxLayout, parent=self)
|
|
self.free_games_now.main_layout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
self.free_games_now.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
|
|
self.free_games_next = StoreGroup(self.tr("Free next week"), layout=QHBoxLayout, parent=self)
|
|
self.free_games_next.main_layout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
self.free_games_next.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
|
|
self.discounts_group = StoreGroup(self.tr("Wishlist discounts"), layout=FlowLayout, parent=self)
|
|
self.discounts_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
|
|
self.games_group = StoreGroup(self.tr("Free to play"), FlowLayout, self)
|
|
self.games_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
self.games_group.loading(False)
|
|
self.games_group.setVisible(False)
|
|
|
|
free_scroll = FreeGamesScroll(self)
|
|
free_container = QWidget(free_scroll)
|
|
free_scroll.setWidget(free_container)
|
|
free_container_layout = QHBoxLayout(free_container)
|
|
|
|
free_scroll.setWidgetResizable(True)
|
|
free_scroll.setFrameShape(QScrollArea.NoFrame)
|
|
free_scroll.setSizeAdjustPolicy(QScrollArea.AdjustToContents)
|
|
free_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
free_container_layout.setContentsMargins(0, 0, 0, 0)
|
|
free_container_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
free_container_layout.setSizeConstraint(QHBoxLayout.SetFixedSize)
|
|
free_container_layout.addWidget(self.free_games_now)
|
|
free_container_layout.addWidget(self.free_games_next)
|
|
|
|
free_scroll.widget().setAutoFillBackground(False)
|
|
free_scroll.viewport().setAutoFillBackground(False)
|
|
|
|
# layout.addWidget(self.free_games_now, alignment=Qt.AlignTop)
|
|
# layout.addWidget(self.free_games_next, alignment=Qt.AlignTop)
|
|
layout.addWidget(free_scroll, alignment=Qt.AlignTop)
|
|
layout.addWidget(self.discounts_group, alignment=Qt.AlignTop)
|
|
layout.addWidget(self.games_group, alignment=Qt.AlignTop)
|
|
layout.addItem(QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding))
|
|
|
|
def showEvent(self, a0: QShowEvent) -> None:
|
|
if a0.spontaneous():
|
|
return super().showEvent(a0)
|
|
self.api.get_free(self.__update_free_games)
|
|
self.api.get_wishlist(self.__update_wishlist_discounts)
|
|
return super().showEvent(a0)
|
|
|
|
def hideEvent(self, a0: QHideEvent) -> None:
|
|
if a0.spontaneous():
|
|
return super().hideEvent(a0)
|
|
# TODO: Implement tab unloading
|
|
return super().hideEvent(a0)
|
|
|
|
def __update_wishlist_discounts(self, wishlist: List[WishlistItemModel]):
|
|
for w in self.discounts_group.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
|
|
self.discounts_group.layout().removeWidget(w)
|
|
w.deleteLater()
|
|
|
|
for item in filter(lambda x: bool(x.offer.price.totalPrice.discount), wishlist):
|
|
w = StoreItemWidget(self.api.cached_manager, item.offer)
|
|
w.show_details.connect(self.show_details)
|
|
self.discounts_group.layout().addWidget(w)
|
|
have_discounts = any(map(lambda x: bool(x.offer.price.totalPrice.discount), wishlist))
|
|
self.discounts_group.setVisible(have_discounts)
|
|
self.discounts_group.loading(False)
|
|
|
|
def __update_free_games(self, free_games: List[CatalogOfferModel]):
|
|
for w in self.free_games_now.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
|
|
self.free_games_now.layout().removeWidget(w)
|
|
w.deleteLater()
|
|
|
|
for w in self.free_games_next.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
|
|
self.free_games_next.layout().removeWidget(w)
|
|
w.deleteLater()
|
|
|
|
date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
|
free_now = []
|
|
free_next = []
|
|
for item in free_games:
|
|
try:
|
|
if item.price.totalPrice.discountPrice == 0:
|
|
free_now.append(item)
|
|
continue
|
|
if item.title == "Mystery Game":
|
|
free_next.append(item)
|
|
continue
|
|
except KeyError as e:
|
|
logger.warning(str(e))
|
|
|
|
if item.promotions is not None:
|
|
if not item.promotions.promotionalOffers:
|
|
start_date = item.promotions.upcomingPromotionalOffers[0].promotionalOffers[0].startDate
|
|
else:
|
|
start_date = item.promotions.promotionalOffers[0].promotionalOffers[0].startDate
|
|
|
|
if start_date > date:
|
|
free_next.append(item)
|
|
|
|
# free games now
|
|
self.free_games_now.setVisible(bool(free_now))
|
|
for item in free_now:
|
|
w = StoreItemWidget(self.api.cached_manager, item)
|
|
w.show_details.connect(self.show_details)
|
|
self.free_games_now.layout().addWidget(w)
|
|
self.free_games_now.loading(False)
|
|
|
|
# free games next week
|
|
self.free_games_next.setVisible(bool(free_next))
|
|
for item in free_next:
|
|
w = StoreItemWidget(self.api.cached_manager, item)
|
|
if item.title != "Mystery Game":
|
|
w.show_details.connect(self.show_details)
|
|
self.free_games_next.layout().addWidget(w)
|
|
self.free_games_next.loading(False)
|
|
|
|
def show_games(self, data):
|
|
if not data:
|
|
return
|
|
|
|
for w in self.games_group.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly):
|
|
self.games_group.layout().removeWidget(w)
|
|
w.deleteLater()
|
|
|
|
for game in data:
|
|
w = StoreItemWidget(self.api.cached_manager, game)
|
|
w.show_details.connect(self.show_details)
|
|
self.games_group.layout().addWidget(w)
|
|
self.games_group.loading(False)
|