From 6b15c0f2cf7473d2b61ace615472d88c42fdd0d0 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Mon, 30 Jan 2023 01:56:29 +0200 Subject: [PATCH 01/21] Store: Clean up store UI by using library widgets --- rare/components/tabs/__init__.py | 4 +- rare/components/tabs/store/__init__.py | 59 +- rare/components/tabs/store/constants.py | 539 +++++++++++++++--- rare/components/tabs/store/game_info.py | 157 +++-- rare/components/tabs/store/game_widgets.py | 42 +- rare/components/tabs/store/image_widget.py | 76 +++ rare/components/tabs/store/search_results.py | 109 ++-- rare/components/tabs/store/shop_api_core.py | 3 +- rare/components/tabs/store/shop_models.py | 97 ++-- rare/components/tabs/store/shop_widget.py | 97 ++-- rare/components/tabs/store/wishlist.py | 22 +- .../components/tabs/store/shop_game_info.py | 168 +++--- .../components/tabs/store/shop_game_info.ui | 184 +++--- rare/ui/components/tabs/store/store.py | 224 ++++---- rare/ui/components/tabs/store/store.ui | 476 ++++++++-------- rare/ui/components/tabs/store/wishlist.py | 49 +- rare/ui/components/tabs/store/wishlist.ui | 315 +++++----- .../components/tabs/store/wishlist_widget.py | 4 +- .../components/tabs/store/wishlist_widget.ui | 2 +- rare/utils/extra_widgets.py | 2 +- 20 files changed, 1536 insertions(+), 1093 deletions(-) create mode 100644 rare/components/tabs/store/image_widget.py diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py index 688d0ea7..a8025a02 100644 --- a/rare/components/tabs/__init__.py +++ b/rare/components/tabs/__init__.py @@ -8,7 +8,7 @@ from .downloads import DownloadsTab from .games import GamesTab from .settings import SettingsTab from .settings.debug import DebugSettings -from .store import Shop +from .store import StoreTab from .tab_widgets import MainTabBar, TabButtonWidget @@ -39,7 +39,7 @@ class MainTabWidget(QTabWidget): self.setTabEnabled(self.downloads_index, not self.args.offline) if not self.args.offline: - self.store_tab = Shop(self.core) + self.store_tab = StoreTab(self.core, parent=self) self.store_index = self.addTab(self.store_tab, self.tr("Store (Beta)")) self.setTabEnabled(self.store_index, not self.args.offline) diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index 1c9493fe..c72e88d9 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -1,9 +1,8 @@ -from PyQt5.QtGui import QShowEvent, QHideEvent -from PyQt5.QtWidgets import QStackedWidget, QTabWidget from legendary.core import LegendaryCore -from rare.shared.rare_core import RareCore +from rare.shared import RareCore from rare.utils.paths import cache_dir +from rare.widgets.side_tab import SideTabWidget from .game_info import ShopGameInfo from .search_results import SearchResults from .shop_api_core import ShopApiCore @@ -11,11 +10,12 @@ from .shop_widget import ShopWidget from .wishlist import WishlistWidget, Wishlist -class Shop(QStackedWidget): - init = False +class StoreTab(SideTabWidget): + + def __init__(self, core: LegendaryCore, parent=None): + super(StoreTab, self).__init__(parent=parent) + self.init = False - def __init__(self, core: LegendaryCore): - super(Shop, self).__init__() self.core = core self.rcore = RareCore.instance() self.api_core = ShopApiCore( @@ -24,32 +24,32 @@ class Shop(QStackedWidget): self.core.country_code, ) - self.shop = ShopWidget(cache_dir(), self.core, self.api_core) - self.wishlist_widget = Wishlist(self.api_core) + self.shop = ShopWidget(cache_dir(), self.core, self.api_core, parent=self) + self.shop_index = self.addTab(self.shop, self.tr("Games")) + self.shop.show_game.connect(self.show_game) + self.shop.show_info.connect(self.show_search) - self.store_tabs = QTabWidget(parent=self) - self.store_tabs.addTab(self.shop, self.tr("Games")) - self.store_tabs.addTab(self.wishlist_widget, self.tr("Wishlist")) + self.search = SearchResults(self.api_core, parent=self) + self.search_index = self.addTab(self.search, self.tr("Search")) + self.search.show_info.connect(self.show_game) + # self.search.back_button.clicked.connect(lambda: self.setCurrentIndex(self.shop_index)) - self.addWidget(self.store_tabs) - - self.search_results = SearchResults(self.api_core) - self.addWidget(self.search_results) - self.search_results.show_info.connect(self.show_game_info) self.info = ShopGameInfo( [i.asset_infos["Windows"].namespace for i in self.rcore.game_list if bool(i.asset_infos)], self.api_core, + parent=self ) - self.addWidget(self.info) - self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) + self.info_index = self.addTab(self.info, self.tr("Information")) + # self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(self.previous_index)) - self.search_results.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) - self.shop.show_info.connect(self.show_search_results) + self.wishlist = Wishlist(self.api_core, parent=self) + self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist")) + self.wishlist.update_wishlist_signal.connect(self.update_wishlist) + self.wishlist.show_game_info.connect(self.show_game) - self.wishlist_widget.show_game_info.connect(self.show_game_info) - self.shop.show_game.connect(self.show_game_info) self.api_core.update_wishlist.connect(self.update_wishlist) - self.wishlist_widget.update_wishlist_signal.connect(self.update_wishlist) + + self.previous_index = self.shop_index def showEvent(self, a0: QShowEvent) -> None: if a0.spontaneous() or self.init: @@ -68,10 +68,11 @@ class Shop(QStackedWidget): def update_wishlist(self): self.shop.update_wishlist() - def show_game_info(self, data): + def show_game(self, data): + self.previous_index = self.currentIndex() self.info.update_game(data) - self.setCurrentIndex(2) + self.setCurrentIndex(self.info_index) - def show_search_results(self, text: str): - self.search_results.load_results(text) - self.setCurrentIndex(1) + def show_search(self, text: str): + self.search.load_results(text) + self.setCurrentIndex(self.search_index) diff --git a/rare/components/tabs/store/constants.py b/rare/components/tabs/store/constants.py index 246729ef..e254dbd3 100644 --- a/rare/components/tabs/store/constants.py +++ b/rare/components/tabs/store/constants.py @@ -44,74 +44,475 @@ class Constants(QObject): ] -game_query = ( - "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " - "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " - "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " - "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " - "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " - "category: $category\n count: $count\n country: $country\n keywords: $keywords\n " - "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " - "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " - "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " - "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " - "description\n effectiveDate\n keyImages {\n type\n url\n }\n " - " currentPrice\n seller {\n id\n name\n }\n productSlug\n " - " urlSlug\n url\n tags {\n id\n }\n items {\n id\n " - " namespace\n }\n customAttributes {\n key\n value\n }\n " - "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " - 'mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n ' - "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " - "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " - "discountPrice\n originalPrice\n voucherDiscount\n discount\n " - " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" - "locale: $locale) {\n originalPrice\n discountPrice\n " - "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " - " id\n endDate\n discountSetting {\n discountType\n " - " }\n }\n }\n }\n promotions(category: $category) @include(if: " - "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " - "startDate\n endDate\n discountSetting {\n discountType\n " - " discountPercentage\n }\n }\n }\n " - "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " - "endDate\n discountSetting {\n discountType\n " - "discountPercentage\n }\n }\n }\n }\n }\n paging {\n " - " count\n total\n }\n }\n }\n}\n " -) +game_query = """ +query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, $sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, $effectiveDate: String) { + Catalog { + searchStore( + allowCountries: $allowCountries + category: $category + count: $count + country: $country + keywords: $keywords + locale: $locale + namespace: $namespace + itemNs: $itemNs + sortBy: $sortBy + sortDir: $sortDir + releaseDate: $releaseDate + start: $start + tag: $tag + priceRange: $priceRange + freeGame: $freeGame + onSale: $onSale + effectiveDate: $effectiveDate + ) { + elements { + title + id + namespace + description + effectiveDate + keyImages { + type + url + } + currentPrice + seller { + id + name + } + productSlug + urlSlug + url + tags { + id + } + items { + id + namespace + } + customAttributes { + key + value + } + categories { + path + } + catalogNs @include(if: $withMapping) { + mappings(pageType: "productHome") { + pageSlug + pageType + } + } + offerMappings @include(if: $withMapping) { + pageSlug + pageType + } + price(country: $country) @include(if: $withPrice) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + currencyCode + currencyInfo { + decimals + } + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + discountSetting { + discountType + } + } + } + } + promotions(category: $category) @include(if: $withPromotions) { + promotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + upcomingPromotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + } + } + paging { + count + total + } + } + } +} +""" -search_query = ( - "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " - "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " - "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " - "false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " - "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " - "category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " - "$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " - "$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " - "$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" - "\n elements {\n title\n id\n namespace\n description\n " - "effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " - "seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " - " tags {\n id\n }\n items {\n id\n namespace\n }\n " - "customAttributes {\n key\n value\n }\n categories {\n path\n " - '}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: "productHome") {\n ' - " pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " - "{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " - "$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " - "voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " - "decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " - "discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " - " appliedRules {\n id\n endDate\n discountSetting {\n " - "discountType\n }\n }\n }\n }\n promotions(category: " - "$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " - " startDate\n endDate\n discountSetting {\n " - "discountType\n discountPercentage\n }\n }\n }\n " - "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " - "endDate\n discountSetting {\n discountType\n discountPercentage\n " - " }\n }\n }\n }\n }\n paging {\n count\n " - "total\n }\n }\n }\n}\n " -) +search_query = """ +query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $itemNs: String, $sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, $effectiveDate: String) { + Catalog { + searchStore( + allowCountries: $allowCountries + category: $category + count: $count + country: $country + keywords: $keywords + locale: $locale + namespace: $namespace + itemNs: $itemNs + sortBy: $sortBy + sortDir: $sortDir + releaseDate: $releaseDate + start: $start + tag: $tag + priceRange: $priceRange + freeGame: $freeGame + onSale: $onSale + effectiveDate: $effectiveDate + ) { + elements { + title + id + namespace + description + effectiveDate + keyImages { + type + url + } + currentPrice + seller { + id + name + } + productSlug + urlSlug + url + tags { + id + } + items { + id + namespace + } + customAttributes { + key + value + } + categories { + path + } + catalogNs { + mappings(pageType: "productHome") { + pageSlug + pageType + } + } + offerMappings { + pageSlug + pageType + } + price(country: $country) @include(if: $withPrice) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + currencyCode + currencyInfo { + decimals + } + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + discountSetting { + discountType + } + } + } + } + promotions(category: $category) @include(if: $withPromotions) { + promotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + upcomingPromotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + } + } + paging { + count + total + } + } + } +} +""" -wishlist_query = '\n query wishlistQuery($country:String!, $locale:String) {\n Wishlist {\n wishlistItems {\n elements {\n id\n order\n created\n offerId\n updated\n namespace\n \n offer {\n productSlug\n urlSlug\n title\n id\n namespace\n offerType\n expiryDate\n status\n isCodeRedemptionOnly\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n catalogNs {\n mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n }\n offerMappings {\n pageSlug\n pageType\n }\n categories {\n path\n }\n price(country: $country) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n currencyCode\n currencyInfo {\n decimals\n symbol\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n }\n }\n }\n }\n\n }\n }\n }\n }\n' -add_to_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n" -remove_from_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n" -coupon_query = "\n query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) {\n CodeRedemption {\n coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) {\n code\n codeStatus\n codeType\n consumptionMetadata {\n amountDisplay {\n amount\n currency\n placement\n symbol\n }\n minSalesPriceDisplay {\n amount\n currency\n placement\n symbol\n }\n }\n endDate\n namespace\n salesEvent(locale: $locale) {\n eventName\n eventSlug\n voucherImages {\n type\n url\n }\n voucherLink\n }\n startDate\n }\n }\n }\n" +wishlist_query = """ +query wishlistQuery($country:String!, $locale:String) { + Wishlist { + wishlistItems { + elements { + id + order + created + offerId + updated + namespace + offer(locale: $locale) { + productSlug + urlSlug + title + id + namespace + offerType + expiryDate + status + isCodeRedemptionOnly + description + effectiveDate + keyImages { + type + url + } + seller { + id + name + } + productSlug + urlSlug + items { + id + namespace + } + customAttributes { + key + value + } + catalogNs { + mappings(pageType: "productHome") { + pageSlug + pageType + } + } + offerMappings { + pageSlug + pageType + } + categories { + path + } + price(country: $country) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + currencyCode + currencyInfo { + decimals + symbol + } + } + lineOffers { + appliedRules { + id + endDate + } + } + } + } + } + } + } +} +""" + +add_to_wishlist_query = """ +mutation addWishlistMutation($namespace: String!, $offerId: String!, $country:String!, $locale:String) { + Wishlist { + addToWishlist(namespace: $namespace, offerId: $offerId) { + wishlistItem { + id, + order, + created, + offerId, + updated, + namespace, + isFirstTime + offer { + productSlug + urlSlug + title + id + namespace + offerType + expiryDate + status + isCodeRedemptionOnly + description + effectiveDate + keyImages { + type + url + } + seller { + id + name + } + productSlug + urlSlug + items { + id + namespace + } + customAttributes { + key + value + } + catalogNs { + mappings(pageType: "productHome") { + pageSlug + pageType + } + } + offerMappings { + pageSlug + pageType + } + categories { + path + } + price(country: $country) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + currencyCode + currencyInfo { + decimals + symbol + } + } + lineOffers { + appliedRules { + id + endDate + } + } + } + } + + } + success + } + } +} +""" + +remove_from_wishlist_query = """ +mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) { + Wishlist { + removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) { + success + } + } +} +""" + +coupon_query = """ +query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) { + CodeRedemption { + coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) { + code + codeStatus + codeType + consumptionMetadata { + amountDisplay { + amount + currency + placement + symbol + } + minSalesPriceDisplay { + amount + currency + placement + symbol + } + } + endDate + namespace + salesEvent(locale: $locale) { + eventName + eventSlug + voucherImages { + type + url + } + voucherLink + } + startDate + } + } +} +""" + + +# if __name__ == "__main__": +# from sgqlc import introspection, codegen +# +# coupon = codegen.operation.parse_graphql(coupon_query) +# codegen.schema. +# print(coupon.) diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/game_info.py index 263f91e9..0df65d85 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/game_info.py @@ -1,53 +1,53 @@ import logging from PyQt5.QtCore import Qt, QUrl -from PyQt5.QtGui import QPixmap, QFont, QDesktopServices +from PyQt5.QtGui import QFont, QDesktopServices from PyQt5.QtWidgets import ( QWidget, QLabel, QPushButton, - QHBoxLayout, - QSpacerItem, - QGroupBox, - QTabWidget, QGridLayout, + QSizePolicy, ) from rare.components.tabs.store.shop_models import ShopGame from rare.shared import LegendaryCoreSingleton -from rare.ui.components.tabs.store.shop_game_info import Ui_shop_info -from rare.utils.extra_widgets import ImageLabel -from rare.utils.misc import qta_icon as icon -from rare.widgets.loading_widget import LoadingWidget +from rare.shared.image_manager import ImageSize +from rare.ui.components.tabs.store.shop_game_info import Ui_ShopGameInfo +from rare.utils.misc import icon +from rare.widgets.side_tab import SideTabWidget +from .image_widget import ShopImageWidget logger = logging.getLogger("ShopInfo") -class ShopGameInfo(QWidget, Ui_shop_info): - game: ShopGame - data: dict +class ShopGameInfo(QWidget, Ui_ShopGameInfo): # TODO Design - def __init__(self, installed_titles: list, api_core): - super(ShopGameInfo, self).__init__() + def __init__(self, installed_titles: list, api_core, parent=None): + super(ShopGameInfo, self).__init__(parent=parent) self.setupUi(self) self.core = LegendaryCoreSingleton() self.api_core = api_core self.installed = installed_titles self.open_store_button.clicked.connect(self.button_clicked) - self.image = ImageLabel() - self.image_stack.addWidget(self.image) - self.image_stack.addWidget(LoadingWidget()) - warn_label = QLabel() - warn_label.setPixmap( - icon("fa.warning").pixmap(160, 160).scaled(240, 320, Qt.IgnoreAspectRatio) - ) - self.image_stack.addWidget(warn_label) + self.image = ShopImageWidget(self) + self.image.setFixedSize(ImageSize.Normal) + self.image_info_layout.insertWidget(0, self.image) + + self.game: ShopGame = None + self.data: dict = {} self.wishlist_button.clicked.connect(self.add_to_wishlist) self.in_wishlist = False self.wishlist = [] + self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.requirements_group) + self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.requirements_layout.addWidget(self.requirements_tabs) + + self.setDisabled(True) + def handle_wishlist_update(self, data): if data and data[0] == "error": return @@ -61,12 +61,15 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.wishlist_button.setVisible(False) def update_game(self, data: dict): - self.image_stack.setCurrentIndex(1) self.title.setText(data["title"]) self.title_str = data["title"] + self.id_str = data["id"] self.api_core.get_wishlist(self.handle_wishlist_update) - for i in reversed(range(self.req_group_box.layout().count())): - self.req_group_box.layout().itemAt(i).widget().deleteLater() + # lk: delete tabs in inverse order because indices are updated on deletion + while self.requirements_tabs.count(): + self.requirements_tabs.widget(0).deleteLater() + self.requirements_tabs.removeTab(0) + self.requirements_tabs.clear() slug = data["productSlug"] if not slug: for mapping in data["offerMappings"]: @@ -87,13 +90,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.open_store_button.setText(self.tr("Buy Game in Epic Games Store")) self.owned_label.setVisible(False) - for i in range(self.req_group_box.layout().count()): - self.req_group_box.layout().itemAt(i).widget().deleteLater() - self.price.setText(self.tr("Loading")) self.wishlist_button.setVisible(False) # self.title.setText(self.tr("Loading")) - self.image.setPixmap(QPixmap()) + # self.image.setPixmap(QPixmap()) self.data = data is_bundle = False for i in data["categories"]: @@ -103,8 +103,8 @@ class ShopGameInfo(QWidget, Ui_shop_info): # init API request if slug: self.api_core.get_game(slug, is_bundle, self.data_received) - else: - self.data_received({}) + # else: + # self.data_received({}) def add_to_wishlist(self): if not self.in_wishlist: @@ -125,9 +125,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): try: self.game = ShopGame.from_json(game, self.data) except Exception as e: + raise e logger.error(str(e)) self.price.setText("Error") - self.req_group_box.setVisible(False) + self.requirements_tabs.setEnabled(False) for img in self.data.get("keyImages"): if img["type"] in [ "DieselStoreFrontWide", @@ -135,14 +136,11 @@ class ShopGameInfo(QWidget, Ui_shop_info): "VaultClosed", "ProductLogo", ]: - self.image.update_image(img["url"], self.title_str, size=(240, 320)) - self.image_stack.setCurrentIndex(0) + self.image.fetchPixmap(img["url"], self.id_str, self.title_str) break - else: - self.image_stack.setCurrentIndex(2) self.price.setText("") self.discount_price.setText("") - self.social_link_gb.setVisible(False) + self.social_group.setEnabled(False) self.tags.setText("") self.dev.setText(self.data.get("seller", {}).get("name", "")) return @@ -170,33 +168,34 @@ class ShopGameInfo(QWidget, Ui_shop_info): bold_font.setBold(True) if self.game.reqs: - req_tabs = QTabWidget() for system in self.game.reqs: - min_label = QLabel(self.tr("Minimum")) + req_widget = QWidget(self.requirements_tabs) + req_layout = QGridLayout(req_widget) + req_layout.setSizeConstraint(QGridLayout.SetFixedSize) + req_widget.layout().setAlignment(Qt.AlignTop) + req_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + min_label = QLabel(self.tr("Minimum"), parent=req_widget) min_label.setFont(bold_font) - rec_label = QLabel(self.tr("Recommend")) + rec_label = QLabel(self.tr("Recommend"), parent=req_widget) rec_label.setFont(bold_font) - req_widget = QWidget() - req_widget.setLayout(QGridLayout()) - req_widget.layout().addWidget(min_label, 0, 1) - req_widget.layout().addWidget(rec_label, 0, 2) - for i, (key, value) in enumerate( - self.game.reqs.get(system, {}).items() - ): - req_widget.layout().addWidget(QLabel(key), i + 1, 0) - min_label = QLabel(value[0]) - min_label.setWordWrap(True) - req_widget.layout().addWidget(min_label, i + 1, 1) - rec_label = QLabel(value[1]) - rec_label.setWordWrap(True) - req_widget.layout().addWidget(rec_label, i + 1, 2) - req_tabs.addTab(req_widget, system) - self.req_group_box.layout().addWidget(req_tabs) - else: - self.req_group_box.layout().addWidget( - QLabel(self.tr("Could not get requirements")) - ) - self.req_group_box.setVisible(True) + req_layout.addWidget(min_label, 0, 1) + req_layout.addWidget(rec_label, 0, 2) + for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()): + req_layout.addWidget(QLabel(key, parent=req_widget), i + 1, 0) + min_label = QLabel(value[0], parent=req_widget) + min_label.setWordWrap(False) + req_layout.addWidget(min_label, i + 1, 1) + rec_label = QLabel(value[1], parent=req_widget) + rec_label.setWordWrap(False) + req_layout.addWidget(rec_label, i + 1, 2) + self.requirements_tabs.addTab(req_widget, system) + # self.req_group_box.layout().addWidget(req_tabs) + # self.req_group_box.layout().setAlignment(Qt.AlignTop) + # else: + # self.req_group_box.layout().addWidget( + # QLabel(self.tr("Could not get requirements")) + # ) + self.requirements_tabs.setEnabled(True) if self.game.image_urls.front_tall: img_url = self.game.image_urls.front_tall elif self.game.image_urls.offer_image_tall: @@ -205,9 +204,9 @@ class ShopGameInfo(QWidget, Ui_shop_info): img_url = self.game.image_urls.product_logo else: img_url = "" - self.image.update_image(img_url, self.game.title, (240, 320)) + self.image.fetchPixmap(img_url, self.game.id, self.game.title) - self.image_stack.setCurrentIndex(0) + # self.image_stack.setCurrentIndex(0) try: if isinstance(self.game.developer, list): self.dev.setText(", ".join(self.game.developer)) @@ -218,19 +217,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.tags.setText(", ".join(self.game.tags)) # clear Layout - for widget in ( - self.social_link_gb.layout().itemAt(i) - for i in range(self.social_link_gb.layout().count()) - ): - if not isinstance(widget, QSpacerItem): - widget.widget().deleteLater() - self.social_link_gb.deleteLater() - self.social_link_gb = QGroupBox(self.tr("Social Links")) - self.social_link_gb.setLayout(QHBoxLayout()) + for b in self.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): + self.social_layout.removeWidget(b) + b.deleteLater() - self.layout().insertWidget(3, self.social_link_gb) - - self.social_link_gb.layout().addStretch(1) link_count = 0 for name, url in self.game.links: @@ -243,16 +233,13 @@ class ShopGameInfo(QWidget, Ui_shop_info): logger.error(str(e)) continue - button = SocialButton(icn, url) - self.social_link_gb.layout().addWidget(button) + button = SocialButton(icn, url, parent=self.social_group) + self.social_layout.addWidget(button) link_count += 1 - self.social_link_gb.layout().addStretch(1) - if link_count == 0: - self.social_link_gb.setVisible(False) - else: - self.social_link_gb.setVisible(True) - self.social_link_gb.layout().addStretch(1) + self.social_group.setEnabled(bool(link_count)) + + self.setEnabled(True) def add_wishlist_items(self, wishlist): wishlist = wishlist["data"]["Wishlist"]["wishlistItems"]["elements"] @@ -264,8 +251,8 @@ class ShopGameInfo(QWidget, Ui_shop_info): class SocialButton(QPushButton): - def __init__(self, icn, url): - super(SocialButton, self).__init__(icn, "") + def __init__(self, icn, url, parent=None): + super(SocialButton, self).__init__(icn, "", parent=parent) self.url = url self.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url))) self.setToolTip(url) diff --git a/rare/components/tabs/store/game_widgets.py b/rare/components/tabs/store/game_widgets.py index 8085965b..e5c164d4 100644 --- a/rare/components/tabs/store/game_widgets.py +++ b/rare/components/tabs/store/game_widgets.py @@ -1,15 +1,18 @@ import logging from PyQt5 import QtGui -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QFont from PyQt5.QtNetwork import QNetworkAccessManager -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QSizePolicy from rare.components.tabs.store.shop_models import ImageUrlModel +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.store.wishlist_widget import Ui_WishlistWidget from rare.utils.extra_widgets import ImageLabel -from rare.utils.misc import qta_icon as icon +from rare.utils.misc import qta_icon +from rare.widgets.elide_label import ElideLabel +from .image_widget import ShopImageWidget logger = logging.getLogger("GameWidgets") @@ -17,17 +20,18 @@ logger = logging.getLogger("GameWidgets") class GameWidget(QWidget): show_info = pyqtSignal(dict) - def __init__(self, path, json_info=None, width=300): + def __init__(self, path, json_info=None): super(GameWidget, self).__init__() self.manager = QNetworkAccessManager() - self.width = width self.path = path if json_info: self.init_ui(json_info) def init_ui(self, json_info): self.layout = QVBoxLayout() - self.image = ImageLabel() + self.layout.setSizeConstraint(QVBoxLayout.SetFixedSize) + self.image = ShopImageWidget(self) + self.image.setFixedSize(ImageSize.Wide) self.layout.addWidget(self.image) mini_layout = QHBoxLayout() self.layout.addLayout(mini_layout) @@ -37,10 +41,10 @@ class GameWidget(QWidget): self.setLayout(self.layout) return - self.title_label = QLabel(json_info.get("title")) - self.title_label.setWordWrap(True) + self.title_label = ElideLabel(json_info.get("title"), parent=self) + self.title_label.setWordWrap(False) mini_layout.addWidget(self.title_label) - mini_layout.addStretch(1) + # mini_layout.addStretch(1) price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"] @@ -49,14 +53,16 @@ class GameWidget(QWidget): font = QFont() font.setStrikeOut(True) price_label.setFont(font) - mini_layout.addWidget( - QLabel(discount_price if discount_price != "0" else self.tr("Free")) - ) - mini_layout.addWidget(price_label) + free_label = QLabel(discount_price if discount_price != "0" else self.tr("Free")) + free_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + free_label.setAlignment(Qt.AlignRight) + mini_layout.addWidget(free_label) else: if price == "0": price_label.setText(self.tr("Free")) - mini_layout.addWidget(price_label) + price_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + price_label.setAlignment(Qt.AlignRight) + mini_layout.addWidget(price_label) for c in r'<>?":|\/*': json_info["title"] = json_info["title"].replace(c, "") @@ -74,19 +80,13 @@ class GameWidget(QWidget): ]: if img["type"] == "VaultClosed" and self.title != "Mystery Game": continue - self.image.update_image( - img["url"], - json_info["title"], - (self.width, int(self.width * 9 / 16)), - ) + self.image.fetchPixmap(img["url"], json_info["id"], json_info["title"]) break else: logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) self.setLayout(self.layout) - self.setFixedSize(self.width + 10, self.width * 9 // 16 + 50) - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: self.show_info.emit(self.json_info) diff --git a/rare/components/tabs/store/image_widget.py b/rare/components/tabs/store/image_widget.py new file mode 100644 index 00000000..38ee56fb --- /dev/null +++ b/rare/components/tabs/store/image_widget.py @@ -0,0 +1,76 @@ +from typing import Dict + +from PyQt5.QtCore import Qt, QRect +from PyQt5.QtGui import ( + QPixmap, + QImage, QMovie, +) +from PyQt5.QtWidgets import QLabel + +from rare.utils.qt_requests import QtRequestManager +from rare.widgets.image_widget import ImageWidget + + +class WaitingSpinner(QLabel): + def __init__(self, autostart=False, parent=None): + super(WaitingSpinner, self).__init__(parent=parent) + self.setObjectName(type(self).__name__) + self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + self.movie = QMovie(":/images/loader.gif") + self.setMovie(self.movie) + if autostart: + self.movie.start() + + def setGeometry(self, a0: QRect) -> None: + self.rect().moveCenter(self.parent().rect().center()) + super(WaitingSpinner, self).setGeometry(self.rect()) + + def start(self): + self.movie.start() + + def stop(self): + self.movie.stop() + + +class ShopImageWidget(ImageWidget): + __image_cache: Dict[str, Dict[str, QPixmap]] = {} + + def __init__(self, parent=None): + super(ShopImageWidget, self).__init__(parent=parent) + self.spinner = WaitingSpinner(parent=self) + self.spinner.setVisible(False) + self.manager = QtRequestManager("bytes") + self.app_id = "" + self.orientation = "" + + def fetchPixmap(self, url, app_id: str, title: str = ""): + self.setPixmap(QPixmap()) + self.app_id = app_id + if self._image_size.size.width() > self._image_size.size.height(): + self.orientation = "wide" + else: + self.orientation = "tall" + + if ShopImageWidget.__image_cache.get(self.app_id, None) is not None: + if pixmap := ShopImageWidget.__image_cache[self.app_id].get(self.orientation, None): + self.setPixmap(pixmap) + return + self.spinner.setFixedSize(self._image_size.size) + self.spinner.setVisible(True) + self.spinner.start() + self.manager.get( + url, self.__on_image_ready, payload={ + "resize": 1, "w": self._image_size.size.width(), "h": self._image_size.size.height() + } + ) + + def __on_image_ready(self, data): + cover = QImage() + cover.loadFromData(data) + cover = cover.scaled(self._image_size.size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied) + cover = QPixmap(cover) + ShopImageWidget.__image_cache.update({self.app_id: {self.orientation: cover}}) + super(ShopImageWidget, self).setPixmap(cover) + self.spinner.stop() + self.spinner.setVisible(False) diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py index 763aac3e..ff3baa59 100644 --- a/rare/components/tabs/store/search_results.py +++ b/rare/components/tabs/store/search_results.py @@ -6,105 +6,106 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QHBoxLayout, QLabel, - QScrollArea, - QGroupBox, - QPushButton, - QStackedWidget, + QFrame, + QSizePolicy, ) -from rare.utils.extra_widgets import ImageLabel, WaitingSpinner +from rare.shared.image_manager import ImageSize from rare.widgets.flow_layout import FlowLayout +from widgets.elide_label import ElideLabel +from .image_widget import ShopImageWidget -class SearchResults(QStackedWidget): +class SearchResults(QWidget): show_info = pyqtSignal(dict) - def __init__(self, api_core): - super(SearchResults, self).__init__() - self.search_result_widget = QWidget() + def __init__(self, api_core, parent=None): + super(SearchResults, self).__init__(parent=parent) self.api_core = api_core - self.addWidget(self.search_result_widget) + + self.results_frame = QFrame(self) + self.results_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.results_frame.setFrameStyle(QFrame.StyledPanel) + self.resutls_layout = FlowLayout(self.results_frame) + self.results_frame.setLayout(self.resutls_layout) + self.main_layout = QVBoxLayout() - self.back_button = QPushButton(self.tr("Back")) - self.main_layout.addWidget(self.back_button) - self.main_layout.addWidget(self.back_button) - self.result_area = QScrollArea() - self.widget = QWidget() - self.result_area.setWidgetResizable(True) - self.main_layout.addWidget(self.result_area) - self.result_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.addWidget(self.results_frame) - self.result_area.setWidget(self.widget) - self.layout = FlowLayout() - self.widget.setLayout(self.layout) - - self.search_result_widget.setLayout(self.main_layout) - - self.addWidget(WaitingSpinner()) - self.setCurrentIndex(1) + self.setLayout(self.main_layout) + self.setEnabled(False) def load_results(self, text: str): - self.setCurrentIndex(1) + self.setEnabled(False) if text != "": self.api_core.search_game(text, self.show_results) def show_results(self, results: dict): - self.widget.deleteLater() - self.widget = QWidget() - self.layout = FlowLayout() + for w in self.results_frame.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): + self.results_frame.layout().removeWidget(w) + w.deleteLater() + for w in self.results_frame.findChildren(_SearchResultItem, options=Qt.FindDirectChildrenOnly): + self.results_frame.layout().removeWidget(w) + w.deleteLater() + if not results: - self.layout.addWidget(QLabel(self.tr("No results found"))) + self.results_frame.layout().addWidget(QLabel(self.tr("No results found"))) else: for res in results: - w = _SearchResultItem(res) + w = _SearchResultItem(res, parent=self.results_frame) w.show_info.connect(self.show_info.emit) - self.layout.addWidget(w) - self.widget.setLayout(self.layout) - self.result_area.setWidget(self.widget) - self.setCurrentIndex(0) + self.results_frame.layout().addWidget(w) + self.setEnabled(True) -class _SearchResultItem(QGroupBox): +class _SearchResultItem(QFrame): res: dict show_info = pyqtSignal(dict) - def __init__(self, result: dict): - super(_SearchResultItem, self).__init__() - self.layout = QVBoxLayout() - self.image = ImageLabel() + def __init__(self, result: dict, parent=None): + super(_SearchResultItem, self).__init__(parent=parent) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.widget_layout = QVBoxLayout() + self.widget_layout.setSizeConstraint(QVBoxLayout.SetFixedSize) + self.image = ShopImageWidget(parent=self) + self.image.setFixedSize(ImageSize.Normal) for img in result["keyImages"]: - if img["type"] == "DieselStoreFrontTall": - width = 240 - self.image.update_image(img["url"], result["title"], (width, 360)) + if img["type"] in ["DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo"]: + self.image.fetchPixmap(img["url"], result["id"], result["title"]) break else: print("No image found") - self.layout.addWidget(self.image) + self.widget_layout.addWidget(self.image) self.res = result - self.title = QLabel(self.res["title"]) + self.title = ElideLabel(self.res["title"], parent=self) title_font = QFont() title_font.setPixelSize(15) self.title.setFont(title_font) - self.title.setWordWrap(True) - self.layout.addWidget(self.title) + self.title.setWordWrap(False) + self.widget_layout.addWidget(self.title) price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"] price_layout = QHBoxLayout() - price_label = QLabel(price if price != "0" else self.tr("Free")) + price_layout.addStretch(1) + price_label = QLabel(price if price != "0" else self.tr("Free"), parent=self) + price_label.setAlignment(Qt.AlignRight) + price_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) price_layout.addWidget(price_label) if price != discount_price: font = QFont() font.setStrikeOut(True) price_label.setFont(font) - price_layout.addWidget(QLabel(discount_price)) + discount_label = QLabel(discount_price if discount_price != "0" else self.tr("Free"), parent=self) + discount_label.setAlignment(Qt.AlignRight) + discount_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + price_layout.addWidget(discount_label) # self.discount_price = QLabel(f"{self.tr('Discount price: ')}{discount_price}") - self.layout.addLayout(price_layout) + self.widget_layout.addLayout(price_layout) - self.setLayout(self.layout) - - self.setFixedWidth(260) + self.setLayout(self.widget_layout) def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: if a0.button() == 1: diff --git a/rare/components/tabs/store/shop_api_core.py b/rare/components/tabs/store/shop_api_core.py index f1368b43..906ef814 100644 --- a/rare/components/tabs/store/shop_api_core.py +++ b/rare/components/tabs/store/shop_api_core.py @@ -173,8 +173,9 @@ class ShopApiCore(QObject): try: handle_func(data) except Exception as e: + raise e logger.error(str(e)) - handle_func({}) + # handle_func({}) # needs a captcha def add_to_wishlist(self, namespace, offer_id, handle_func: callable): diff --git a/rare/components/tabs/store/shop_models.py b/rare/components/tabs/store/shop_models.py index b850a71f..d6bb0054 100644 --- a/rare/components/tabs/store/shop_models.py +++ b/rare/components/tabs/store/shop_models.py @@ -1,5 +1,6 @@ import datetime from dataclasses import dataclass +from typing import List, Dict class ImageUrlModel: @@ -43,19 +44,21 @@ class ShopGame: def __init__( self, title: str = "", + id: str = "", image_urls: ImageUrlModel = None, - social_links: dict = None, - langs: list = None, - reqs: dict = None, + social_links: Dict = None, + langs: Dict = None, + reqs: Dict = None, publisher: str = "", developer: str = "", original_price: str = "", discount_price: str = "", - tags: list = None, + tags: List = None, namespace: str = "", offer_id: str = "", ): self.title = title + self.id = id self.image_urls = image_urls self.links = [] if social_links: @@ -66,13 +69,13 @@ class ShopGame: ) else: self.links = [] - self.languages = langs - self.reqs = reqs + self.languages = langs if langs is not None else {} + self.reqs = reqs if reqs is not None else {} self.publisher = publisher self.developer = developer self.price = original_price self.discount_price = discount_price - self.tags = tags + self.tags = tags if tags is not None else [] self.namespace = namespace self.offer_id = offer_id @@ -89,46 +92,50 @@ class ShopGame: api_data = page break tmp = cls() - tmp.title = search_data.get("title", "Fail") - tmp.image_urls = ImageUrlModel.from_json(search_data["keyImages"]) - links = api_data["data"]["socialLinks"] - tmp.links = [] - for item in links: - if item.startswith("link"): - tmp.links.append(tuple((item.replace("link", ""), links[item]))) - tmp.available_voice_langs = api_data["data"]["requirements"].get( - "languages", "Failed" - ) - tmp.reqs = {} - for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])): - try: - tmp.reqs[system["systemType"]] = {} - except KeyError: - continue - for req in system["details"]: + if search_data: + tmp.title = search_data.get("title", "Fail") + tmp.id = search_data.get("id") + tmp.image_urls = ImageUrlModel.from_json(search_data["keyImages"]) + if not tmp.developer: + for i in search_data["customAttributes"]: + if i["key"] == "developerName": + tmp.developer = i["value"] + tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][ + "discountPrice" + ] + tmp.namespace = search_data["namespace"] + tmp.offer_id = search_data["id"] + + if api_data: + links = api_data["data"]["socialLinks"] + tmp.links = [] + for item in links: + if item.startswith("link"): + tmp.links.append(tuple((item.replace("link", ""), links[item]))) + tmp.available_voice_langs = api_data["data"]["requirements"].get( + "languages", "Failed" + ) + tmp.reqs = {} + for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])): try: - tmp.reqs[system["systemType"]][req["title"]] = ( - req["minimum"], - req["recommended"], - ) + tmp.reqs[system["systemType"]] = {} except KeyError: - pass - tmp.publisher = api_data["data"]["meta"].get("publisher", "") - tmp.developer = api_data["data"]["meta"].get("developer", "") - if not tmp.developer: - for i in search_data["customAttributes"]: - if i["key"] == "developerName": - tmp.developer = i["value"] - tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][ - "discountPrice" - ] - tmp.tags = [ - i.replace("_", " ").capitalize() - for i in api_data["data"]["meta"].get("tags", []) - ] - tmp.namespace = search_data["namespace"] - tmp.offer_id = search_data["id"] + continue + for req in system["details"]: + try: + tmp.reqs[system["systemType"]][req["title"]] = ( + req["minimum"], + req["recommended"], + ) + except KeyError: + pass + tmp.publisher = api_data["data"]["meta"].get("publisher", "") + tmp.developer = api_data["data"]["meta"].get("developer", "") + tmp.tags = [ + i.replace("_", " ").capitalize() + for i in api_data["data"]["meta"].get("tags", []) + ] return tmp diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index af5a02ce..f72d111e 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -1,14 +1,15 @@ import datetime import logging +from typing import List -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import ( QGroupBox, - QScrollArea, QCheckBox, QLabel, QPushButton, QHBoxLayout, + QWidget, QSizePolicy, ) from legendary.core import LegendaryCore @@ -24,17 +25,12 @@ logger = logging.getLogger("Shop") # noinspection PyAttributeOutsideInit,PyBroadException -class ShopWidget(QScrollArea, Ui_ShopWidget): +class ShopWidget(QWidget, Ui_ShopWidget): show_info = pyqtSignal(str) show_game = pyqtSignal(dict) - free_game_widgets = [] - active_search_request = False - next_search = "" - wishlist: list = [] - def __init__(self, path, core: LegendaryCore, shop_api: ShopApiCore): - super(ShopWidget, self).__init__() - self.setWidgetResizable(True) + def __init__(self, path, core: LegendaryCore, shop_api: ShopApiCore, parent=None): + super(ShopWidget, self).__init__(parent=parent) self.setupUi(self) self.path = path self.core = core @@ -43,10 +39,17 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.tags = [] self.types = [] self.update_games_allowed = True - self.free_widget.setLayout(FlowLayout()) + free_games_container_layout = QHBoxLayout(self.free_games_container) + free_games_container_layout.setContentsMargins(0, 0, 0, 3) + self.free_games_container.setLayout(free_games_container_layout) + self.free_games_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.free_games_scrollarea.setDisabled(True) + self.free_games_scrollarea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.free_stack.addWidget(WaitingSpinner()) - self.free_stack.setCurrentIndex(1) + self.free_game_widgets = [] + self.active_search_request = False + self.next_search = "" + self.wishlist: List = [] self.discount_widget.setLayout(FlowLayout()) self.discount_stack.addWidget(WaitingSpinner()) @@ -59,7 +62,7 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.search_bar = ButtonLineEdit( "fa.search", placeholder_text=self.tr("Search Games") ) - self.layout().insertWidget(0, self.search_bar) + self.games_container_layout.insertWidget(0, self.search_bar) # self.search_bar.textChanged.connect(self.search_games) @@ -110,42 +113,47 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): try: if game["offer"]["price"]["totalPrice"]["discount"] > 0: w = GameWidget(self.path, game["offer"]) - w.show_info.connect(self.show_game.emit) + w.show_info.connect(self.show_game) self.discount_widget.layout().addWidget(w) discounts += 1 except Exception as e: logger.warning(f"{game} {e}") continue - self.discounts_gb.setVisible(discounts > 0) + self.discounts_group.setVisible(discounts > 0) self.discount_stack.setCurrentIndex(0) # fix widget overlay self.discount_widget.layout().update() def add_free_games(self, free_games: list): - for i in range(self.free_widget.layout().count()): - item = self.free_widget.layout().itemAt(i) - if item: - item.widget().deleteLater() + for w in self.free_games_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): + self.free_games_container.layout().removeWidget(w) + w.deleteLater() if free_games and free_games[0] == "error": - self.free_widget.layout().addWidget( + self.free_games_container.layout().addWidget( QLabel(self.tr("Failed to fetch free games: {}").format(free_games[1])) ) btn = QPushButton(self.tr("Reload")) - self.free_widget.layout().addWidget(btn) + self.free_games_container.layout().addWidget(btn) btn.clicked.connect( lambda: self.api_core.get_free_games(self.add_free_games) ) - self.free_stack.setCurrentIndex(0) + self.free_games_container.setEnabled(True) return - self.free_games_now = QGroupBox(self.tr("Now Free")) - self.free_games_now.setLayout(QHBoxLayout()) - self.free_widget.layout().addWidget(self.free_games_now) + self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.free_games_container) + free_games_now_layout = QHBoxLayout(self.free_games_now) + free_games_now_layout.setContentsMargins(0, 0, 0, 0) + self.free_games_now.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.free_games_now.setLayout(free_games_now_layout) + self.free_games_container.layout().addWidget(self.free_games_now) - self.coming_free_games = QGroupBox(self.tr("Free Games next week")) - self.coming_free_games.setLayout(QHBoxLayout()) - self.free_widget.layout().addWidget(self.coming_free_games) + self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.free_games_container) + free_games_next_layout = QHBoxLayout(self.free_games_next) + free_games_next_layout.setContentsMargins(0, 0, 0, 0) + self.free_games_next.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.free_games_next.setLayout(free_games_next_layout) + self.free_games_container.layout().addWidget(self.free_games_next) date = datetime.datetime.now() free_games_now = [] @@ -197,7 +205,7 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): now_free = 0 for free_game in free_games_now: w = GameWidget(self.path, free_game) - w.show_info.connect(self.show_game.emit) + w.show_info.connect(self.show_game) self.free_games_now.layout().addWidget(w) self.free_game_widgets.append(w) now_free += 1 @@ -210,10 +218,15 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): for free_game in coming_free_games: w = GameWidget(self.path, free_game) if free_game["title"] != "Mystery Game": - w.show_info.connect(self.show_game.emit) - self.coming_free_games.layout().addWidget(w) + w.show_info.connect(self.show_game) + self.free_games_next.layout().addWidget(w) # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) - self.free_stack.setCurrentIndex(0) + + self.free_games_scrollarea.setMinimumHeight( + self.free_games_now.sizeHint().height() + self.free_games_scrollarea.horizontalScrollBar().sizeHint().height() + ) + self.free_games_scrollarea.update() + self.free_games_scrollarea.setEnabled(True) def show_search_results(self): if self.search_bar.text(): @@ -255,10 +268,10 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.checkboxes = [] for groupbox, variables in [ - (self.genre_gb, constants.categories), - (self.platform_gb, constants.platforms), - (self.others_gb, constants.others), - (self.type_gb, constants.types), + (self.genre_group, constants.categories), + (self.platform_group, constants.platforms), + (self.others_group, constants.others), + (self.type_group, constants.types), ]: for text, tag in variables: @@ -307,12 +320,12 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): if removed_type and removed_type in self.types: self.types.remove(removed_type) if (self.types or self.price) or self.tags or self.on_discount.isChecked(): - self.free_game_group_box.setVisible(False) - self.discounts_gb.setVisible(False) + self.free_games_scrollarea.setVisible(False) + self.discounts_group.setVisible(False) else: - self.free_game_group_box.setVisible(True) - if len(self.discounts_gb.layout().children()) > 0: - self.discounts_gb.setVisible(True) + self.free_games_scrollarea.setVisible(True) + if len(self.discounts_group.layout().children()) > 0: + self.discounts_group.setVisible(True) self.game_stack.setCurrentIndex(1) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index fbac0713..48ac9d18 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -1,23 +1,21 @@ from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QStackedWidget, QMessageBox +from PyQt5.QtWidgets import QMessageBox, QWidget -from rare.components.tabs.store import ShopApiCore -from rare.components.tabs.store.game_widgets import WishlistWidget from rare.ui.components.tabs.store.wishlist import Ui_Wishlist -from rare.utils.extra_widgets import WaitingSpinner -from rare.utils.misc import qta_icon as icon +from rare.utils.misc import icon +from .shop_api_core import ShopApiCore +from .game_widgets import WishlistWidget -class Wishlist(QStackedWidget, Ui_Wishlist): +class Wishlist(QWidget, Ui_Wishlist): show_game_info = pyqtSignal(dict) update_wishlist_signal = pyqtSignal() - def __init__(self, api_core: ShopApiCore): - super(Wishlist, self).__init__() + def __init__(self, api_core: ShopApiCore, parent=None): + super(Wishlist, self).__init__(parent=parent) self.api_core = api_core self.setupUi(self) - self.addWidget(WaitingSpinner()) - self.setCurrentIndex(1) + self.setEnabled(False) self.wishlist = [] self.widgets = [] @@ -33,7 +31,7 @@ class Wishlist(QStackedWidget, Ui_Wishlist): ) def update_wishlist(self): - self.setCurrentIndex(1) + self.setEnabled(False) self.api_core.get_wishlist(self.set_wishlist) def delete_from_wishlist(self, game): @@ -116,4 +114,4 @@ class Wishlist(QStackedWidget, Ui_Wishlist): self.list_layout.addWidget(w) w.open_game.connect(self.show_game_info.emit) w.delete_from_wishlist.connect(self.delete_from_wishlist) - self.setCurrentIndex(0) + self.setEnabled(True) diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py index 9e010bc7..710a5b69 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/shop_game_info.ui' # -# Created by: PyQt5 UI code generator 5.15.6 +# Created by: PyQt5 UI code generator 5.15.7 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -11,113 +11,105 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_shop_info(object): - def setupUi(self, shop_info): - shop_info.setObjectName("shop_info") - shop_info.resize(702, 468) - shop_info.setWindowTitle("Form") - self.verticalLayout = QtWidgets.QVBoxLayout(shop_info) - self.verticalLayout.setObjectName("verticalLayout") - self.back_button = QtWidgets.QPushButton(shop_info) - self.back_button.setObjectName("back_button") - self.verticalLayout.addWidget(self.back_button) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.image_stack = QtWidgets.QStackedWidget(shop_info) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.image_stack.sizePolicy().hasHeightForWidth()) - self.image_stack.setSizePolicy(sizePolicy) - self.image_stack.setObjectName("image_stack") - self.horizontalLayout.addWidget(self.image_stack) - self.verticalLayout_2 = QtWidgets.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.title = QtWidgets.QLabel(shop_info) +class Ui_ShopGameInfo(object): + def setupUi(self, ShopGameInfo): + ShopGameInfo.setObjectName("ShopGameInfo") + ShopGameInfo.resize(702, 468) + ShopGameInfo.setWindowTitle("ShopGameInfo") + self.shop_game_info_layout = QtWidgets.QVBoxLayout(ShopGameInfo) + self.shop_game_info_layout.setObjectName("shop_game_info_layout") + self.image_info_layout = QtWidgets.QHBoxLayout() + self.image_info_layout.setObjectName("image_info_layout") + self.info_layout = QtWidgets.QGridLayout() + self.info_layout.setObjectName("info_layout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.info_layout.addItem(spacerItem, 6, 1, 1, 1) + self.tags = QtWidgets.QLabel(ShopGameInfo) + self.tags.setText("tags") + self.tags.setObjectName("tags") + self.info_layout.addWidget(self.tags, 5, 0, 1, 2) + self.discount_price = QtWidgets.QLabel(ShopGameInfo) + self.discount_price.setText("discount") + self.discount_price.setObjectName("discount_price") + self.info_layout.addWidget(self.discount_price, 4, 0, 1, 2) + self.title = QtWidgets.QLabel(ShopGameInfo) font = QtGui.QFont() font.setPointSize(18) self.title.setFont(font) - self.title.setText("") - self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) + self.title.setText("title") + self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.title.setObjectName("title") - self.verticalLayout_2.addWidget(self.title) - self.dev = QtWidgets.QLabel(shop_info) + self.info_layout.addWidget(self.title, 0, 0, 1, 2) + self.button_layout = QtWidgets.QVBoxLayout() + self.button_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.button_layout.setObjectName("button_layout") + self.open_store_button = QtWidgets.QPushButton(ShopGameInfo) + self.open_store_button.setObjectName("open_store_button") + self.button_layout.addWidget(self.open_store_button) + self.wishlist_button = QtWidgets.QPushButton(ShopGameInfo) + self.wishlist_button.setObjectName("wishlist_button") + self.button_layout.addWidget(self.wishlist_button) + self.info_layout.addLayout(self.button_layout, 6, 0, 1, 1) + self.dev = QtWidgets.QLabel(ShopGameInfo) font = QtGui.QFont() font.setPointSize(14) self.dev.setFont(font) - self.dev.setText("") - self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) + self.dev.setText("dev") + self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.dev.setObjectName("dev") - self.verticalLayout_2.addWidget(self.dev) - self.owned_label = QtWidgets.QLabel(shop_info) + self.info_layout.addWidget(self.dev, 1, 0, 1, 2) + self.owned_label = QtWidgets.QLabel(ShopGameInfo) self.owned_label.setObjectName("owned_label") - self.verticalLayout_2.addWidget(self.owned_label) - self.price = QtWidgets.QLabel(shop_info) - self.price.setText("") - self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) + self.info_layout.addWidget(self.owned_label, 2, 0, 1, 2) + self.price = QtWidgets.QLabel(ShopGameInfo) + self.price.setText("price") + self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.price.setObjectName("price") - self.verticalLayout_2.addWidget(self.price) - self.discount_price = QtWidgets.QLabel(shop_info) - self.discount_price.setText("") - self.discount_price.setObjectName("discount_price") - self.verticalLayout_2.addWidget(self.discount_price) - self.tags = QtWidgets.QLabel(shop_info) - self.tags.setText("") - self.tags.setObjectName("tags") - self.verticalLayout_2.addWidget(self.tags) - self.open_store_button = QtWidgets.QPushButton(shop_info) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + self.info_layout.addWidget(self.price, 3, 0, 1, 2) + self.social_group = QtWidgets.QGroupBox(ShopGameInfo) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.open_store_button.sizePolicy().hasHeightForWidth()) - self.open_store_button.setSizePolicy(sizePolicy) - self.open_store_button.setObjectName("open_store_button") - self.verticalLayout_2.addWidget(self.open_store_button) - self.wishlist_button = QtWidgets.QPushButton(shop_info) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.wishlist_button.sizePolicy().hasHeightForWidth()) - self.wishlist_button.setSizePolicy(sizePolicy) - self.wishlist_button.setObjectName("wishlist_button") - self.verticalLayout_2.addWidget(self.wishlist_button) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_2.addItem(spacerItem) - self.horizontalLayout.addLayout(self.verticalLayout_2) + sizePolicy.setHeightForWidth(self.social_group.sizePolicy().hasHeightForWidth()) + self.social_group.setSizePolicy(sizePolicy) + self.social_group.setObjectName("social_group") + self.social_layout = QtWidgets.QHBoxLayout(self.social_group) + self.social_layout.setObjectName("social_layout") + self.info_layout.addWidget(self.social_group, 7, 0, 1, 2) + self.image_info_layout.addLayout(self.info_layout) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem1) - self.verticalLayout.addLayout(self.horizontalLayout) - self.req_group_box = QtWidgets.QGroupBox(shop_info) - self.req_group_box.setObjectName("req_group_box") - self.gridLayout_2 = QtWidgets.QGridLayout(self.req_group_box) - self.gridLayout_2.setObjectName("gridLayout_2") - self.verticalLayout.addWidget(self.req_group_box) - self.social_link_gb = QtWidgets.QGroupBox(shop_info) - self.social_link_gb.setObjectName("social_link_gb") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.social_link_gb) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.verticalLayout.addWidget(self.social_link_gb) + self.image_info_layout.addItem(spacerItem1) + self.shop_game_info_layout.addLayout(self.image_info_layout) + self.requirements_group = QtWidgets.QGroupBox(ShopGameInfo) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.requirements_group.sizePolicy().hasHeightForWidth()) + self.requirements_group.setSizePolicy(sizePolicy) + self.requirements_group.setObjectName("requirements_group") + self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_group) + self.requirements_layout.setContentsMargins(0, 0, 0, 0) + self.requirements_layout.setObjectName("requirements_layout") + self.shop_game_info_layout.addWidget(self.requirements_group) spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem2) + self.shop_game_info_layout.addItem(spacerItem2) - self.retranslateUi(shop_info) - self.image_stack.setCurrentIndex(-1) + self.retranslateUi(ShopGameInfo) - def retranslateUi(self, shop_info): + def retranslateUi(self, ShopGameInfo): _translate = QtCore.QCoreApplication.translate - self.back_button.setText(_translate("shop_info", "Back")) - self.owned_label.setText(_translate("shop_info", "You already own this game")) - self.open_store_button.setText(_translate("shop_info", "Buy Game in Epic Games Store")) - self.wishlist_button.setText(_translate("shop_info", "Add to wishlist")) - self.req_group_box.setTitle(_translate("shop_info", "Requirements")) - self.social_link_gb.setTitle(_translate("shop_info", "Social Links")) + self.open_store_button.setText(_translate("ShopGameInfo", "Buy in Epic Games Store")) + self.wishlist_button.setText(_translate("ShopGameInfo", "Add to wishlist")) + self.owned_label.setText(_translate("ShopGameInfo", "You already own this game")) + self.social_group.setTitle(_translate("ShopGameInfo", "Links")) + self.requirements_group.setTitle(_translate("ShopGameInfo", "System requirements")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - shop_info = QtWidgets.QWidget() - ui = Ui_shop_info() - ui.setupUi(shop_info) - shop_info.show() + ShopGameInfo = QtWidgets.QWidget() + ui = Ui_ShopGameInfo() + ui.setupUi(ShopGameInfo) + ShopGameInfo.show() sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/shop_game_info.ui index b1dd7dfb..5c2223c6 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -1,7 +1,7 @@ - shop_info - + ShopGameInfo + 0 @@ -11,34 +11,41 @@ - Form + ShopGameInfo - + - - - Back - - - - - + - - - - 0 - 0 - - - - -1 - - - - - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + tags + + + + + + + discount + + + + @@ -46,14 +53,35 @@ - + title Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - + + + + QLayout::SetFixedSize + + + + + Buy in Epic Games Store + + + + + + + Add to wishlist + + + + + + @@ -61,87 +89,48 @@ - + dev Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - + You already own this game - + - + price Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - - - - - - - - - - + + - + 0 0 - - Buy Game in Epic Games Store + + Links + - - - - - 0 - 0 - - - - Add to wishlist - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + Qt::Horizontal @@ -156,23 +145,34 @@ - - - Requirements + + + + 0 + 0 + - + + System requirements + + + + 0 + + + 0 + + + 0 + + + 0 + + - - - Social Links - - - - - - + Qt::Vertical diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py index 75e5e72e..0e74a204 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/store.py @@ -2,153 +2,145 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/store.ui' # -# Created by: PyQt5 UI code generator 5.15.6 +# Created by: PyQt5 UI code generator 5.15.7 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopWidget(object): def setupUi(self, ShopWidget): ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(850, 572) - ShopWidget.setWindowTitle("Form") - self.verticalLayout_7 = QtWidgets.QVBoxLayout(ShopWidget) - self.verticalLayout_7.setObjectName("verticalLayout_7") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.scrollArea = QtWidgets.QScrollArea(ShopWidget) - self.scrollArea.setWidgetResizable(True) - self.scrollArea.setObjectName("scrollArea") - self.scrollAreaWidgetContents = QtWidgets.QWidget() - self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 828, 550)) - self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.scrollAreaWidgetContents) - self.horizontalLayout.setObjectName("horizontalLayout") - self.widget = QtWidgets.QWidget(self.scrollAreaWidgetContents) - self.widget.setObjectName("widget") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget) - self.verticalLayout.setObjectName("verticalLayout") - self.free_game_group_box = QtWidgets.QGroupBox(self.widget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.free_game_group_box.sizePolicy().hasHeightForWidth()) - self.free_game_group_box.setSizePolicy(sizePolicy) - self.free_game_group_box.setObjectName("free_game_group_box") - self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.free_game_group_box) - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.free_stack = QtWidgets.QStackedWidget(self.free_game_group_box) - self.free_stack.setObjectName("free_stack") - self.free_widget = QtWidgets.QWidget() - self.free_widget.setObjectName("free_widget") - self.free_stack.addWidget(self.free_widget) - self.verticalLayout_3.addWidget(self.free_stack) - self.verticalLayout.addWidget(self.free_game_group_box) - self.discounts_gb = QtWidgets.QGroupBox(self.widget) - self.discounts_gb.setObjectName("discounts_gb") - self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.discounts_gb) - self.verticalLayout_6.setObjectName("verticalLayout_6") - self.discount_stack = QtWidgets.QStackedWidget(self.discounts_gb) + ShopWidget.resize(843, 569) + ShopWidget.setWindowTitle("Store") + self.shop_layout = QtWidgets.QHBoxLayout(ShopWidget) + self.shop_layout.setObjectName("shop_layout") + self.games_scrollarea = QtWidgets.QScrollArea(ShopWidget) + self.games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.games_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.games_scrollarea.setWidgetResizable(True) + self.games_scrollarea.setObjectName("games_scrollarea") + self.games_container = QtWidgets.QWidget() + self.games_container.setGeometry(QtCore.QRect(0, 0, 659, 557)) + self.games_container.setObjectName("games_container") + self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) + self.games_container_layout.setContentsMargins(0, 0, 3, 0) + self.games_container_layout.setObjectName("games_container_layout") + self.free_games_scrollarea = QtWidgets.QScrollArea(self.games_container) + self.free_games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.free_games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.free_games_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.free_games_scrollarea.setWidgetResizable(True) + self.free_games_scrollarea.setObjectName("free_games_scrollarea") + self.free_games_container = QtWidgets.QWidget() + self.free_games_container.setGeometry(QtCore.QRect(0, 0, 656, 182)) + self.free_games_container.setObjectName("free_games_container") + self.free_games_scrollarea.setWidget(self.free_games_container) + self.games_container_layout.addWidget(self.free_games_scrollarea) + self.discounts_group = QtWidgets.QGroupBox(self.games_container) + self.discounts_group.setObjectName("discounts_group") + self.discounts_layout = QtWidgets.QVBoxLayout(self.discounts_group) + self.discounts_layout.setObjectName("discounts_layout") + self.discount_stack = QtWidgets.QStackedWidget(self.discounts_group) self.discount_stack.setObjectName("discount_stack") self.discount_widget = QtWidgets.QWidget() self.discount_widget.setObjectName("discount_widget") self.discount_stack.addWidget(self.discount_widget) - self.verticalLayout_6.addWidget(self.discount_stack) - self.verticalLayout.addWidget(self.discounts_gb) - self.filter_game_gb = QtWidgets.QGroupBox(self.widget) - self.filter_game_gb.setObjectName("filter_game_gb") - self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.filter_game_gb) - self.verticalLayout_4.setObjectName("verticalLayout_4") - self.game_stack = QtWidgets.QStackedWidget(self.filter_game_gb) + self.discounts_layout.addWidget(self.discount_stack) + self.games_container_layout.addWidget(self.discounts_group) + self.games_group = QtWidgets.QGroupBox(self.games_container) + self.games_group.setObjectName("games_group") + self.games_layout = QtWidgets.QVBoxLayout(self.games_group) + self.games_layout.setObjectName("games_layout") + self.game_stack = QtWidgets.QStackedWidget(self.games_group) self.game_stack.setObjectName("game_stack") self.game_widget = QtWidgets.QWidget() self.game_widget.setObjectName("game_widget") self.game_stack.addWidget(self.game_widget) - self.verticalLayout_4.addWidget(self.game_stack) - self.verticalLayout.addWidget(self.filter_game_gb) - self.horizontalLayout.addWidget(self.widget) - self.filter_gb = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + self.games_layout.addWidget(self.game_stack) + self.games_container_layout.addWidget(self.games_group) + self.games_scrollarea.setWidget(self.games_container) + self.shop_layout.addWidget(self.games_scrollarea) + self.filter_scrollarea = QtWidgets.QScrollArea(ShopWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.filter_gb.sizePolicy().hasHeightForWidth()) - self.filter_gb.setSizePolicy(sizePolicy) - self.filter_gb.setMinimumSize(QtCore.QSize(150, 0)) - self.filter_gb.setMaximumSize(QtCore.QSize(16777215, 16777215)) - self.filter_gb.setObjectName("filter_gb") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.filter_gb) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.reset_button = QtWidgets.QPushButton(self.filter_gb) + sizePolicy.setHeightForWidth(self.filter_scrollarea.sizePolicy().hasHeightForWidth()) + self.filter_scrollarea.setSizePolicy(sizePolicy) + self.filter_scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.filter_scrollarea.setWidgetResizable(True) + self.filter_scrollarea.setObjectName("filter_scrollarea") + self.filter_container = QtWidgets.QWidget() + self.filter_container.setGeometry(QtCore.QRect(0, 0, 162, 553)) + self.filter_container.setObjectName("filter_container") + self.filter_container_layout = QtWidgets.QVBoxLayout(self.filter_container) + self.filter_container_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.filter_container_layout.setObjectName("filter_container_layout") + self.reset_button = QtWidgets.QPushButton(self.filter_container) self.reset_button.setObjectName("reset_button") - self.verticalLayout_2.addWidget(self.reset_button) - self.price_gb = QtWidgets.QGroupBox(self.filter_gb) - self.price_gb.setObjectName("price_gb") - self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.price_gb) - self.verticalLayout_9.setObjectName("verticalLayout_9") - self.none_price = QtWidgets.QRadioButton(self.price_gb) + self.filter_container_layout.addWidget(self.reset_button) + self.price_group = QtWidgets.QGroupBox(self.filter_container) + self.price_group.setObjectName("price_group") + self.price_layout = QtWidgets.QVBoxLayout(self.price_group) + self.price_layout.setObjectName("price_layout") + self.none_price = QtWidgets.QRadioButton(self.price_group) self.none_price.setChecked(True) self.none_price.setObjectName("none_price") - self.verticalLayout_9.addWidget(self.none_price) - self.free_button = QtWidgets.QRadioButton(self.price_gb) + self.price_layout.addWidget(self.none_price) + self.free_button = QtWidgets.QRadioButton(self.price_group) self.free_button.setObjectName("free_button") - self.verticalLayout_9.addWidget(self.free_button) - self.under10 = QtWidgets.QRadioButton(self.price_gb) + self.price_layout.addWidget(self.free_button) + self.under10 = QtWidgets.QRadioButton(self.price_group) self.under10.setObjectName("under10") - self.verticalLayout_9.addWidget(self.under10) - self.under20 = QtWidgets.QRadioButton(self.price_gb) + self.price_layout.addWidget(self.under10) + self.under20 = QtWidgets.QRadioButton(self.price_group) self.under20.setObjectName("under20") - self.verticalLayout_9.addWidget(self.under20) - self.under30 = QtWidgets.QRadioButton(self.price_gb) + self.price_layout.addWidget(self.under20) + self.under30 = QtWidgets.QRadioButton(self.price_group) self.under30.setObjectName("under30") - self.verticalLayout_9.addWidget(self.under30) - self.above = QtWidgets.QRadioButton(self.price_gb) + self.price_layout.addWidget(self.under30) + self.above = QtWidgets.QRadioButton(self.price_group) self.above.setObjectName("above") - self.verticalLayout_9.addWidget(self.above) - self.on_discount = QtWidgets.QCheckBox(self.price_gb) + self.price_layout.addWidget(self.above) + self.on_discount = QtWidgets.QCheckBox(self.price_group) self.on_discount.setObjectName("on_discount") - self.verticalLayout_9.addWidget(self.on_discount) - self.verticalLayout_2.addWidget(self.price_gb) - self.platform_gb = QtWidgets.QGroupBox(self.filter_gb) - self.platform_gb.setObjectName("platform_gb") - self.verticalLayout_13 = QtWidgets.QVBoxLayout(self.platform_gb) - self.verticalLayout_13.setObjectName("verticalLayout_13") - self.verticalLayout_2.addWidget(self.platform_gb) - self.genre_gb = QtWidgets.QGroupBox(self.filter_gb) - self.genre_gb.setObjectName("genre_gb") - self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.genre_gb) - self.verticalLayout_12.setObjectName("verticalLayout_12") - self.verticalLayout_2.addWidget(self.genre_gb) - self.type_gb = QtWidgets.QGroupBox(self.filter_gb) - self.type_gb.setObjectName("type_gb") - self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.type_gb) - self.verticalLayout_11.setObjectName("verticalLayout_11") - self.verticalLayout_2.addWidget(self.type_gb) - self.others_gb = QtWidgets.QGroupBox(self.filter_gb) - self.others_gb.setObjectName("others_gb") - self.verticalLayout_10 = QtWidgets.QVBoxLayout(self.others_gb) - self.verticalLayout_10.setObjectName("verticalLayout_10") - self.verticalLayout_2.addWidget(self.others_gb) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_2.addItem(spacerItem) - self.horizontalLayout.addWidget(self.filter_gb) - self.scrollArea.setWidget(self.scrollAreaWidgetContents) - self.horizontalLayout_2.addWidget(self.scrollArea) - self.verticalLayout_7.addLayout(self.horizontalLayout_2) + self.price_layout.addWidget(self.on_discount) + self.filter_container_layout.addWidget(self.price_group) + self.platform_group = QtWidgets.QGroupBox(self.filter_container) + self.platform_group.setObjectName("platform_group") + self.platfrom_layout = QtWidgets.QVBoxLayout(self.platform_group) + self.platfrom_layout.setObjectName("platfrom_layout") + self.filter_container_layout.addWidget(self.platform_group) + self.genre_group = QtWidgets.QGroupBox(self.filter_container) + self.genre_group.setObjectName("genre_group") + self.genre_layout = QtWidgets.QVBoxLayout(self.genre_group) + self.genre_layout.setObjectName("genre_layout") + self.filter_container_layout.addWidget(self.genre_group) + self.type_group = QtWidgets.QGroupBox(self.filter_container) + self.type_group.setObjectName("type_group") + self.type_layout = QtWidgets.QVBoxLayout(self.type_group) + self.type_layout.setObjectName("type_layout") + self.filter_container_layout.addWidget(self.type_group) + self.others_group = QtWidgets.QGroupBox(self.filter_container) + self.others_group.setObjectName("others_group") + self.others_layout = QtWidgets.QVBoxLayout(self.others_group) + self.others_layout.setObjectName("others_layout") + self.filter_container_layout.addWidget(self.others_group) + self.filter_scrollarea.setWidget(self.filter_container) + self.shop_layout.addWidget(self.filter_scrollarea) self.retranslateUi(ShopWidget) def retranslateUi(self, ShopWidget): _translate = QtCore.QCoreApplication.translate - self.free_game_group_box.setTitle(_translate("ShopWidget", "Free Games")) - self.discounts_gb.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) - self.filter_game_gb.setTitle(_translate("ShopWidget", "Games")) - self.filter_gb.setTitle(_translate("ShopWidget", "Filter")) + self.discounts_group.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) + self.games_group.setTitle(_translate("ShopWidget", "Games")) self.reset_button.setText(_translate("ShopWidget", "Reset")) - self.price_gb.setTitle(_translate("ShopWidget", "Price")) + self.price_group.setTitle(_translate("ShopWidget", "Price")) self.none_price.setText(_translate("ShopWidget", "None")) self.free_button.setText(_translate("ShopWidget", "Free")) self.under10.setText(_translate("ShopWidget", "Under 10")) @@ -156,10 +148,10 @@ class Ui_ShopWidget(object): self.under30.setText(_translate("ShopWidget", "Under 30")) self.above.setText(_translate("ShopWidget", "14.99 and above")) self.on_discount.setText(_translate("ShopWidget", "Discount")) - self.platform_gb.setTitle(_translate("ShopWidget", "Platform")) - self.genre_gb.setTitle(_translate("ShopWidget", "Genre")) - self.type_gb.setTitle(_translate("ShopWidget", "Type")) - self.others_gb.setTitle(_translate("ShopWidget", "Other Tags")) + self.platform_group.setTitle(_translate("ShopWidget", "Platform")) + self.genre_group.setTitle(_translate("ShopWidget", "Genre")) + self.type_group.setTitle(_translate("ShopWidget", "Type")) + self.others_group.setTitle(_translate("ShopWidget", "Other Tags")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui index 4f61053f..1e38513a 100644 --- a/rare/ui/components/tabs/store/store.ui +++ b/rare/ui/components/tabs/store/store.ui @@ -1,238 +1,246 @@ - ShopWidget - - - - 0 - 0 - 850 - 572 - - - - Form - - - - - - - - true - - - - - 0 - 0 - 828 - 550 - - - - - - - - - - - 0 - 0 - - - - Free Games - - - - - - - - - - - - - - Discounts from your wishlist - - - - - - - - - - - - - - Games - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - - 16777215 - 16777215 - - - - Filter - - - - - - Reset - - - - - - - Price - - - - - - None - - - true - - - - - - - Free - - - - - - - Under 10 - - - - - - - Under 20 - - - - - - - Under 30 - - - - - - - 14.99 and above - - - - - - - Discount - - - - - - - - - - Platform - - - - - - - - Genre - - - - - - - - Type - - - - - - - - Other Tags - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - + ShopWidget + + + + 0 + 0 + 843 + 569 + + + + Store + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 659 + 557 + + + + + 0 + + + 0 + + + 3 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 656 + 182 + + + + + + + + + Discounts from your wishlist + + + + + + + + + + + + + + Games + + + + + + + + + + + + - - + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 162 + 553 + + + + + QLayout::SetFixedSize + + + + + Reset + + + + + + + Price + + + + + + None + + + true + + + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + + + Genre + + + + + + + + Type + + + + + + + + Other Tags + + + + + + + + + + + + diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index e45475f4..4e9fd461 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt5 UI code generator 5.15.7 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,29 +14,19 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Wishlist(object): def setupUi(self, Wishlist): Wishlist.setObjectName("Wishlist") - Wishlist.resize(736, 398) - Wishlist.setWindowTitle("StackedWidget") - self.page = QtWidgets.QWidget() - self.page.setObjectName("page") - self.verticalLayout = QtWidgets.QVBoxLayout(self.page) + Wishlist.resize(527, 328) + Wishlist.setWindowTitle("Wishlist") + self.verticalLayout = QtWidgets.QVBoxLayout(Wishlist) self.verticalLayout.setObjectName("verticalLayout") - self.scroll_area = QtWidgets.QScrollArea(self.page) - self.scroll_area.setWidgetResizable(True) - self.scroll_area.setObjectName("scroll_area") - self.scroll_widget = QtWidgets.QWidget() - self.scroll_widget.setGeometry(QtCore.QRect(0, 0, 716, 378)) - self.scroll_widget.setObjectName("scroll_widget") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.scroll_widget) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.title_label = QtWidgets.QLabel(self.scroll_widget) + self.title_label = QtWidgets.QLabel(Wishlist) font = QtGui.QFont() font.setPointSize(15) self.title_label.setFont(font) self.title_label.setObjectName("title_label") - self.verticalLayout_2.addWidget(self.title_label) + self.verticalLayout.addWidget(self.title_label) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") - self.sort_label = QtWidgets.QLabel(self.scroll_widget) + self.sort_label = QtWidgets.QLabel(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -44,29 +34,29 @@ class Ui_Wishlist(object): self.sort_label.setSizePolicy(sizePolicy) self.sort_label.setObjectName("sort_label") self.horizontalLayout.addWidget(self.sort_label) - self.sort_cb = QtWidgets.QComboBox(self.scroll_widget) + self.sort_cb = QtWidgets.QComboBox(Wishlist) self.sort_cb.setObjectName("sort_cb") self.sort_cb.addItem("") self.sort_cb.addItem("") self.sort_cb.addItem("") self.sort_cb.addItem("") self.horizontalLayout.addWidget(self.sort_cb) - self.reverse = QtWidgets.QCheckBox(self.scroll_widget) + self.reverse = QtWidgets.QCheckBox(Wishlist) self.reverse.setObjectName("reverse") self.horizontalLayout.addWidget(self.reverse) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) - self.filter_label = QtWidgets.QLabel(self.scroll_widget) + self.filter_label = QtWidgets.QLabel(Wishlist) self.filter_label.setObjectName("filter_label") self.horizontalLayout.addWidget(self.filter_label) - self.filter_cb = QtWidgets.QComboBox(self.scroll_widget) + self.filter_cb = QtWidgets.QComboBox(Wishlist) self.filter_cb.setObjectName("filter_cb") self.filter_cb.addItem("") self.filter_cb.addItem("") self.horizontalLayout.addWidget(self.filter_cb) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) - self.reload_button = QtWidgets.QPushButton(self.scroll_widget) + self.reload_button = QtWidgets.QPushButton(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -75,18 +65,15 @@ class Ui_Wishlist(object): self.reload_button.setText("") self.reload_button.setObjectName("reload_button") self.horizontalLayout.addWidget(self.reload_button) - self.verticalLayout_2.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.horizontalLayout) self.list_layout = QtWidgets.QVBoxLayout() self.list_layout.setObjectName("list_layout") - self.verticalLayout_2.addLayout(self.list_layout) - self.no_games_label = QtWidgets.QLabel(self.scroll_widget) + self.verticalLayout.addLayout(self.list_layout) + self.no_games_label = QtWidgets.QLabel(Wishlist) self.no_games_label.setObjectName("no_games_label") - self.verticalLayout_2.addWidget(self.no_games_label) + self.verticalLayout.addWidget(self.no_games_label) spacerItem2 = QtWidgets.QSpacerItem(379, 218, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_2.addItem(spacerItem2) - self.scroll_area.setWidget(self.scroll_widget) - self.verticalLayout.addWidget(self.scroll_area) - Wishlist.addWidget(self.page) + self.verticalLayout.addItem(spacerItem2) self.retranslateUi(Wishlist) @@ -108,7 +95,7 @@ class Ui_Wishlist(object): if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - Wishlist = QtWidgets.QStackedWidget() + Wishlist = QtWidgets.QWidget() ui = Ui_Wishlist() ui.setupUi(Wishlist) Wishlist.show() diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index b10eea91..1b341623 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -1,184 +1,163 @@ Wishlist - + 0 0 - 736 - 398 + 527 + 328 - StackedWidget + Wishlist - - - - - - true - - - - - 0 - 0 - 716 - 378 - + + + + + + 15 + + + + Wishlist + + + + + + + + + + 0 + 0 + + + + Sort by - - - - - - 15 - - - - Wishlist - - - - - - - - - - 0 - 0 - - - - Sort by - - - - - - - - Name - - - - - Price - - - - - Developer - - - - - Discount - - - - - - - - Reverse - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter: - - - - - - - - None - - - - - Discount - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - No games matching your filter - - - - - - - Qt::Vertical - - - - 379 - 218 - - - - - - - - - + + + + + + Name + + + + + Price + + + + + Developer + + + + + Discount + + + + + + + + Reverse + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter: + + + + + + + + None + + + + + Discount + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + No games matching your filter + + + + + + + Qt::Vertical + + + + 379 + 218 + + + + + diff --git a/rare/ui/components/tabs/store/wishlist_widget.py b/rare/ui/components/tabs/store/wishlist_widget.py index 5fbb2d69..47119608 100644 --- a/rare/ui/components/tabs/store/wishlist_widget.py +++ b/rare/ui/components/tabs/store/wishlist_widget.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist_widget.ui' # -# Created by: PyQt5 UI code generator 5.15.5 +# Created by: PyQt5 UI code generator 5.15.7 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -15,7 +15,7 @@ class Ui_WishlistWidget(object): def setupUi(self, WishlistWidget): WishlistWidget.setObjectName("WishlistWidget") WishlistWidget.resize(523, 172) - WishlistWidget.setWindowTitle("Form") + WishlistWidget.setWindowTitle("WishlistWIdget") self.horizontalLayout = QtWidgets.QHBoxLayout(WishlistWidget) self.horizontalLayout.setObjectName("horizontalLayout") self.widget = QtWidgets.QWidget(WishlistWidget) diff --git a/rare/ui/components/tabs/store/wishlist_widget.ui b/rare/ui/components/tabs/store/wishlist_widget.ui index 136ff704..63dc3640 100644 --- a/rare/ui/components/tabs/store/wishlist_widget.ui +++ b/rare/ui/components/tabs/store/wishlist_widget.ui @@ -11,7 +11,7 @@ - Form + WishlistWIdget diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index f126f769..a1719765 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -17,7 +17,7 @@ from rare.utils.qt_requests import QtRequests logger = getLogger("ExtraWidgets") - +# FIXME: move this? class WaitingSpinner(QLabel): def __init__(self, parent=None): super(WaitingSpinner, self).__init__(parent=parent) From d3b591952fccf70ff7a335d28025190473fe9e2e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 12 Mar 2023 14:01:11 +0200 Subject: [PATCH 02/21] Store: Use ElideLabel for requirements --- rare/components/tabs/store/game_info.py | 20 +++++++++++--------- rare/components/tabs/store/search_results.py | 2 +- rare/components/tabs/store/wishlist.py | 4 +++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/game_info.py index 0df65d85..7b478766 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/game_info.py @@ -1,7 +1,7 @@ import logging from PyQt5.QtCore import Qt, QUrl -from PyQt5.QtGui import QFont, QDesktopServices +from PyQt5.QtGui import QFont, QDesktopServices, QFontMetrics from PyQt5.QtWidgets import ( QWidget, QLabel, @@ -15,13 +15,14 @@ from rare.shared import LegendaryCoreSingleton from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.store.shop_game_info import Ui_ShopGameInfo from rare.utils.misc import icon -from rare.widgets.side_tab import SideTabWidget +from rare.widgets.side_tab import SideTabWidget, SideTabContents +from rare.widgets.elide_label import ElideLabel from .image_widget import ShopImageWidget logger = logging.getLogger("ShopInfo") -class ShopGameInfo(QWidget, Ui_ShopGameInfo): +class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): # TODO Design def __init__(self, installed_titles: list, api_core, parent=None): @@ -61,6 +62,7 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo): self.wishlist_button.setVisible(False) def update_game(self, data: dict): + self.set_title.emit(data["title"]) self.title.setText(data["title"]) self.title_str = data["title"] self.id_str = data["id"] @@ -144,7 +146,7 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo): self.tags.setText("") self.dev.setText(self.data.get("seller", {}).get("name", "")) return - self.title.setText(self.game.title) + # self.title.setText(self.game.title) self.price.setFont(QFont()) if self.game.price == "0" or self.game.price == 0: @@ -167,11 +169,11 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo): bold_font = QFont() bold_font.setBold(True) + fm = QFontMetrics(self.font()) if self.game.reqs: for system in self.game.reqs: req_widget = QWidget(self.requirements_tabs) req_layout = QGridLayout(req_widget) - req_layout.setSizeConstraint(QGridLayout.SetFixedSize) req_widget.layout().setAlignment(Qt.AlignTop) req_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) min_label = QLabel(self.tr("Minimum"), parent=req_widget) @@ -180,13 +182,13 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo): rec_label.setFont(bold_font) req_layout.addWidget(min_label, 0, 1) req_layout.addWidget(rec_label, 0, 2) + req_layout.setColumnStretch(1, 2) + req_layout.setColumnStretch(2, 2) for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()): req_layout.addWidget(QLabel(key, parent=req_widget), i + 1, 0) - min_label = QLabel(value[0], parent=req_widget) - min_label.setWordWrap(False) + min_label = ElideLabel(value[0], parent=req_widget) req_layout.addWidget(min_label, i + 1, 1) - rec_label = QLabel(value[1], parent=req_widget) - rec_label.setWordWrap(False) + rec_label = ElideLabel(value[1], parent=req_widget) req_layout.addWidget(rec_label, i + 1, 2) self.requirements_tabs.addTab(req_widget, system) # self.req_group_box.layout().addWidget(req_tabs) diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py index ff3baa59..72dc29e7 100644 --- a/rare/components/tabs/store/search_results.py +++ b/rare/components/tabs/store/search_results.py @@ -12,7 +12,7 @@ from PyQt5.QtWidgets import ( from rare.shared.image_manager import ImageSize from rare.widgets.flow_layout import FlowLayout -from widgets.elide_label import ElideLabel +from rare.widgets.elide_label import ElideLabel from .image_widget import ShopImageWidget diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 48ac9d18..b3f7f3b9 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -3,11 +3,12 @@ from PyQt5.QtWidgets import QMessageBox, QWidget from rare.ui.components.tabs.store.wishlist import Ui_Wishlist from rare.utils.misc import icon +from rare.widgets.side_tab import SideTabContents from .shop_api_core import ShopApiCore from .game_widgets import WishlistWidget -class Wishlist(QWidget, Ui_Wishlist): +class Wishlist(QWidget, Ui_Wishlist, SideTabContents): show_game_info = pyqtSignal(dict) update_wishlist_signal = pyqtSignal() @@ -32,6 +33,7 @@ class Wishlist(QWidget, Ui_Wishlist): def update_wishlist(self): self.setEnabled(False) + self.set_title.emit("Wishlist") self.api_core.get_wishlist(self.set_wishlist) def delete_from_wishlist(self, game): From b812e38fb81e5a678d014c53777d213c44e3ea5c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 30 Mar 2023 20:09:27 +0300 Subject: [PATCH 03/21] Wishlist: Remove embedded title --- rare/components/tabs/store/wishlist.py | 29 ++++++++++++----------- rare/ui/components/tabs/store/wishlist.py | 9 +------ rare/ui/components/tabs/store/wishlist.ui | 12 ---------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index b3f7f3b9..06a2d41d 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -8,27 +8,28 @@ from .shop_api_core import ShopApiCore from .game_widgets import WishlistWidget -class Wishlist(QWidget, Ui_Wishlist, SideTabContents): +class Wishlist(QWidget, SideTabContents): show_game_info = pyqtSignal(dict) update_wishlist_signal = pyqtSignal() def __init__(self, api_core: ShopApiCore, parent=None): super(Wishlist, self).__init__(parent=parent) self.api_core = api_core - self.setupUi(self) + self.ui = Ui_Wishlist() + self.ui.setupUi(self) self.setEnabled(False) self.wishlist = [] self.widgets = [] - self.sort_cb.currentIndexChanged.connect( + self.ui.sort_cb.currentIndexChanged.connect( lambda i: self.set_wishlist(self.wishlist, i) ) - self.filter_cb.currentIndexChanged.connect(self.set_filter) - self.reload_button.clicked.connect(self.update_wishlist) - self.reload_button.setIcon(icon("fa.refresh", color="white")) + self.ui.filter_cb.currentIndexChanged.connect(self.set_filter) + self.ui.reload_button.clicked.connect(self.update_wishlist) + self.ui.reload_button.setIcon(icon("fa.refresh", color="white")) - self.reverse.stateChanged.connect( - lambda: self.set_wishlist(sort=self.sort_cb.currentIndex()) + self.ui.reverse.stateChanged.connect( + lambda: self.set_wishlist(sort=self.ui.sort_cb.currentIndex()) ) def update_wishlist(self): @@ -61,9 +62,9 @@ class Wishlist(QWidget, Ui_Wishlist, SideTabContents): w.setVisible(True) if count == 0: - self.no_games_label.setVisible(True) + self.ui.no_games_label.setVisible(True) else: - self.no_games_label.setVisible(False) + self.ui.no_games_label.setVisible(False) def set_wishlist(self, wishlist=None, sort=0): if wishlist and wishlist[0] == "error": @@ -103,17 +104,17 @@ class Wishlist(QWidget, Ui_Wishlist, SideTabContents): self.widgets.clear() if len(sorted_list) == 0: - self.no_games_label.setVisible(True) + self.ui.no_games_label.setVisible(True) else: - self.no_games_label.setVisible(False) + self.ui.no_games_label.setVisible(False) - if self.reverse.isChecked(): + if self.ui.reverse.isChecked(): sorted_list.reverse() for game in sorted_list: w = WishlistWidget(game["offer"]) self.widgets.append(w) - self.list_layout.addWidget(w) + self.ui.list_layout.addWidget(w) w.open_game.connect(self.show_game_info.emit) w.delete_from_wishlist.connect(self.delete_from_wishlist) self.setEnabled(True) diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index 4e9fd461..4a0fb0a2 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -18,12 +18,6 @@ class Ui_Wishlist(object): Wishlist.setWindowTitle("Wishlist") self.verticalLayout = QtWidgets.QVBoxLayout(Wishlist) self.verticalLayout.setObjectName("verticalLayout") - self.title_label = QtWidgets.QLabel(Wishlist) - font = QtGui.QFont() - font.setPointSize(15) - self.title_label.setFont(font) - self.title_label.setObjectName("title_label") - self.verticalLayout.addWidget(self.title_label) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.sort_label = QtWidgets.QLabel(Wishlist) @@ -79,7 +73,6 @@ class Ui_Wishlist(object): def retranslateUi(self, Wishlist): _translate = QtCore.QCoreApplication.translate - self.title_label.setText(_translate("Wishlist", "Wishlist")) self.sort_label.setText(_translate("Wishlist", "Sort by")) self.sort_cb.setItemText(0, _translate("Wishlist", "Name")) self.sort_cb.setItemText(1, _translate("Wishlist", "Price")) diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index 1b341623..a9560ec0 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -14,18 +14,6 @@ Wishlist - - - - - 15 - - - - Wishlist - - - From 2db34324af8d807eb86c714e1187fa03b93eab8e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 30 Mar 2023 20:12:37 +0300 Subject: [PATCH 04/21] ShopWidget: Cleanup shop layout --- rare/components/tabs/store/shop_widget.py | 31 +- rare/ui/components/tabs/store/store.py | 35 +- rare/ui/components/tabs/store/store.ui | 446 ++++++++++++---------- 3 files changed, 277 insertions(+), 235 deletions(-) diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index f72d111e..4e72a8ef 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -40,8 +40,8 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.types = [] self.update_games_allowed = True free_games_container_layout = QHBoxLayout(self.free_games_container) - free_games_container_layout.setContentsMargins(0, 0, 0, 3) - self.free_games_container.setLayout(free_games_container_layout) + free_games_container_layout.setContentsMargins(0, 0, 0, 0) + self.free_games_container.setContentsMargins(0, 0, 0, 3) self.free_games_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_scrollarea.setDisabled(True) self.free_games_scrollarea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -51,18 +51,18 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.next_search = "" self.wishlist: List = [] - self.discount_widget.setLayout(FlowLayout()) - self.discount_stack.addWidget(WaitingSpinner()) + self.discount_widget.setLayout(FlowLayout(self.discount_widget)) + self.discount_stack.addWidget(WaitingSpinner(self.discount_stack)) self.discount_stack.setCurrentIndex(1) - self.game_widget.setLayout(FlowLayout()) - self.game_stack.addWidget(WaitingSpinner()) + self.game_widget.setLayout(FlowLayout(self.game_widget)) + self.game_stack.addWidget(WaitingSpinner(self.game_stack)) self.game_stack.setCurrentIndex(1) self.search_bar = ButtonLineEdit( "fa.search", placeholder_text=self.tr("Search Games") ) - self.games_container_layout.insertWidget(0, self.search_bar) + self.left_layout.insertWidget(0, self.search_bar) # self.search_bar.textChanged.connect(self.search_games) @@ -143,14 +143,14 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.free_games_container) free_games_now_layout = QHBoxLayout(self.free_games_now) - free_games_now_layout.setContentsMargins(0, 0, 0, 0) + # free_games_now_layout.setContentsMargins(0, 0, 0, 0) self.free_games_now.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_now.setLayout(free_games_now_layout) self.free_games_container.layout().addWidget(self.free_games_now) self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.free_games_container) free_games_next_layout = QHBoxLayout(self.free_games_next) - free_games_next_layout.setContentsMargins(0, 0, 0, 0) + # free_games_next_layout.setContentsMargins(0, 0, 0, 0) self.free_games_next.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_next.setLayout(free_games_next_layout) self.free_games_container.layout().addWidget(self.free_games_next) @@ -223,9 +223,11 @@ class ShopWidget(QWidget, Ui_ShopWidget): # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) self.free_games_scrollarea.setMinimumHeight( - self.free_games_now.sizeHint().height() + self.free_games_scrollarea.horizontalScrollBar().sizeHint().height() + self.free_games_now.sizeHint().height() + + self.free_games_container.contentsMargins().top() + + self.free_games_container.contentsMargins().bottom() + + self.free_games_scrollarea.horizontalScrollBar().sizeHint().height() ) - self.free_games_scrollarea.update() self.free_games_scrollarea.setEnabled(True) def show_search_results(self): @@ -283,6 +285,11 @@ class ShopWidget(QWidget, Ui_ShopWidget): groupbox.layout().addWidget(checkbox) self.checkboxes.append(checkbox) self.reset_button.clicked.connect(self.reset_filters) + self.filter_scrollarea.setMinimumWidth( + self.filter_container.sizeHint().width() + + self.filter_container_layout.spacing() + + self.filter_scrollarea.verticalScrollBar().sizeHint().width() + ) def reset_filters(self): self.update_games_allowed = False @@ -350,7 +357,7 @@ class ShopWidget(QWidget, Ui_ShopWidget): item.widget().deleteLater() if data: for game in data: - w = GameWidget(self.path, game, 275) + w = GameWidget(self.path, game) self.game_widget.layout().addWidget(w) w.show_info.connect(self.show_game.emit) diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py index 0e74a204..e25179b9 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/store.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/store.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,10 +14,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopWidget(object): def setupUi(self, ShopWidget): ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(843, 569) + ShopWidget.resize(784, 525) ShopWidget.setWindowTitle("Store") self.shop_layout = QtWidgets.QHBoxLayout(ShopWidget) self.shop_layout.setObjectName("shop_layout") + self.left_layout = QtWidgets.QVBoxLayout() + self.left_layout.setObjectName("left_layout") self.games_scrollarea = QtWidgets.QScrollArea(ShopWidget) self.games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) self.games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) @@ -25,7 +27,7 @@ class Ui_ShopWidget(object): self.games_scrollarea.setWidgetResizable(True) self.games_scrollarea.setObjectName("games_scrollarea") self.games_container = QtWidgets.QWidget() - self.games_container.setGeometry(QtCore.QRect(0, 0, 659, 557)) + self.games_container.setGeometry(QtCore.QRect(0, 0, 611, 511)) self.games_container.setObjectName("games_container") self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) self.games_container_layout.setContentsMargins(0, 0, 3, 0) @@ -37,7 +39,7 @@ class Ui_ShopWidget(object): self.free_games_scrollarea.setWidgetResizable(True) self.free_games_scrollarea.setObjectName("free_games_scrollarea") self.free_games_container = QtWidgets.QWidget() - self.free_games_container.setGeometry(QtCore.QRect(0, 0, 656, 182)) + self.free_games_container.setGeometry(QtCore.QRect(0, 0, 608, 166)) self.free_games_container.setObjectName("free_games_container") self.free_games_scrollarea.setWidget(self.free_games_container) self.games_container_layout.addWidget(self.free_games_scrollarea) @@ -64,25 +66,31 @@ class Ui_ShopWidget(object): self.games_layout.addWidget(self.game_stack) self.games_container_layout.addWidget(self.games_group) self.games_scrollarea.setWidget(self.games_container) - self.shop_layout.addWidget(self.games_scrollarea) + self.left_layout.addWidget(self.games_scrollarea) + self.shop_layout.addLayout(self.left_layout) + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.setObjectName("right_layout") + self.reset_button = QtWidgets.QPushButton(ShopWidget) + self.reset_button.setObjectName("reset_button") + self.right_layout.addWidget(self.reset_button) self.filter_scrollarea = QtWidgets.QScrollArea(ShopWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.filter_scrollarea.sizePolicy().hasHeightForWidth()) self.filter_scrollarea.setSizePolicy(sizePolicy) + self.filter_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.filter_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) self.filter_scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.filter_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) self.filter_scrollarea.setWidgetResizable(True) self.filter_scrollarea.setObjectName("filter_scrollarea") self.filter_container = QtWidgets.QWidget() - self.filter_container.setGeometry(QtCore.QRect(0, 0, 162, 553)) + self.filter_container.setGeometry(QtCore.QRect(0, 0, 151, 479)) self.filter_container.setObjectName("filter_container") self.filter_container_layout = QtWidgets.QVBoxLayout(self.filter_container) - self.filter_container_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.filter_container_layout.setContentsMargins(0, 0, 0, 0) self.filter_container_layout.setObjectName("filter_container_layout") - self.reset_button = QtWidgets.QPushButton(self.filter_container) - self.reset_button.setObjectName("reset_button") - self.filter_container_layout.addWidget(self.reset_button) self.price_group = QtWidgets.QGroupBox(self.filter_container) self.price_group.setObjectName("price_group") self.price_layout = QtWidgets.QVBoxLayout(self.price_group) @@ -131,7 +139,8 @@ class Ui_ShopWidget(object): self.others_layout.setObjectName("others_layout") self.filter_container_layout.addWidget(self.others_group) self.filter_scrollarea.setWidget(self.filter_container) - self.shop_layout.addWidget(self.filter_scrollarea) + self.right_layout.addWidget(self.filter_scrollarea) + self.shop_layout.addLayout(self.right_layout) self.retranslateUi(ShopWidget) @@ -139,7 +148,7 @@ class Ui_ShopWidget(object): _translate = QtCore.QCoreApplication.translate self.discounts_group.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) self.games_group.setTitle(_translate("ShopWidget", "Games")) - self.reset_button.setText(_translate("ShopWidget", "Reset")) + self.reset_button.setText(_translate("ShopWidget", "Reset filters")) self.price_group.setTitle(_translate("ShopWidget", "Price")) self.none_price.setText(_translate("ShopWidget", "None")) self.free_button.setText(_translate("ShopWidget", "Free")) @@ -151,7 +160,7 @@ class Ui_ShopWidget(object): self.platform_group.setTitle(_translate("ShopWidget", "Platform")) self.genre_group.setTitle(_translate("ShopWidget", "Genre")) self.type_group.setTitle(_translate("ShopWidget", "Type")) - self.others_group.setTitle(_translate("ShopWidget", "Other Tags")) + self.others_group.setTitle(_translate("ShopWidget", "Other tags")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui index 1e38513a..89d0f021 100644 --- a/rare/ui/components/tabs/store/store.ui +++ b/rare/ui/components/tabs/store/store.ui @@ -6,238 +6,264 @@ 0 0 - 843 - 569 + 784 + 525 Store - + - - - QFrame::NoFrame - - - QFrame::Plain - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - 0 - 0 - 659 - 557 - - - - - 0 + + + + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 3 + + QAbstractScrollArea::AdjustToContents - - 0 + + true - - - - QFrame::NoFrame + + + + 0 + 0 + 611 + 511 + + + + + 0 - - QFrame::Plain + + 0 - - Qt::ScrollBarAlwaysOff + + 3 - - true + + 0 - - - - 0 - 0 - 656 - 182 - - - - - - - - - Discounts from your wishlist - - - - - + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 608 + 166 + + - - - - - - - - Games - - - - - - - - - - - - - + + + + + + Discounts from your wishlist + + + + + + + + + + + + + + Games + + + + + + + + + + + + + + + - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 162 - 553 - - - - - QLayout::SetFixedSize + + + + + Reset filters - - - - Reset + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 151 + 479 + + + + + 0 - - - - - - Price + + 0 - - - - - None - - - true - - - - - - - Free - - - - - - - Under 10 - - - - - - - Under 20 - - - - - - - Under 30 - - - - - - - 14.99 and above - - - - - - - Discount - - - - - - - - - - Platform + + 0 - - - - - - - Genre + + 0 - - - - - - - Type - - - - - - - - Other Tags - - - - - - - + + + + Price + + + + + + None + + + true + + + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + + + Genre + + + + + + + + Type + + + + + + + + Other tags + + + + + + + + + From d76fc2b68b65bd07c2e38fff18303396daf22d8a Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 31 Mar 2023 01:07:45 +0300 Subject: [PATCH 05/21] ShopWidget: Fix layouting again --- rare/components/tabs/store/__main__.py | 35 ++++++++++++++++++ rare/components/tabs/store/shop_widget.py | 43 +++++++++++----------- rare/ui/components/tabs/store/store.py | 39 ++++++++++++-------- rare/ui/components/tabs/store/store.ui | 44 ++++++++++++++++------- 4 files changed, 111 insertions(+), 50 deletions(-) create mode 100644 rare/components/tabs/store/__main__.py diff --git a/rare/components/tabs/store/__main__.py b/rare/components/tabs/store/__main__.py new file mode 100644 index 00000000..1f62692c --- /dev/null +++ b/rare/components/tabs/store/__main__.py @@ -0,0 +1,35 @@ +import sys + +from PyQt5.QtCore import QSize +from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout +from legendary.core import LegendaryCore + +from . import StoreTab + + +class StoreWindow(QDialog): + def __init__(self): + super().__init__() + + self.core = LegendaryCore() + self.core.login() + self.store_tab = StoreTab(self.core, self) + + layout = QVBoxLayout(self) + layout.addWidget(self.store_tab) + + self.store_tab.load() + + +if __name__ == "__main__": + from rare.utils.misc import set_style_sheet + import rare.resources.static_css + import rare.resources.stylesheets.RareStyle + + app = QApplication(sys.argv) + + set_style_sheet("RareStyle") + window = StoreWindow() + window.resize(QSize(1280, 800)) + window.show() + app.exec() \ No newline at end of file diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index 4e72a8ef..20b3903b 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -39,12 +39,8 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.tags = [] self.types = [] self.update_games_allowed = True - free_games_container_layout = QHBoxLayout(self.free_games_container) - free_games_container_layout.setContentsMargins(0, 0, 0, 0) - self.free_games_container.setContentsMargins(0, 0, 0, 3) - self.free_games_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.free_games_scrollarea.setDisabled(True) - self.free_games_scrollarea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.free_scrollarea.setDisabled(True) self.free_game_widgets = [] self.active_search_request = False @@ -125,35 +121,35 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.discount_widget.layout().update() def add_free_games(self, free_games: list): - for w in self.free_games_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): - self.free_games_container.layout().removeWidget(w) + for w in self.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): + self.free_container.layout().removeWidget(w) w.deleteLater() if free_games and free_games[0] == "error": - self.free_games_container.layout().addWidget( + self.free_container.layout().addWidget( QLabel(self.tr("Failed to fetch free games: {}").format(free_games[1])) ) btn = QPushButton(self.tr("Reload")) - self.free_games_container.layout().addWidget(btn) + self.free_container.layout().addWidget(btn) btn.clicked.connect( lambda: self.api_core.get_free_games(self.add_free_games) ) - self.free_games_container.setEnabled(True) + self.free_container.setEnabled(True) return - self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.free_games_container) + self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.free_container) free_games_now_layout = QHBoxLayout(self.free_games_now) # free_games_now_layout.setContentsMargins(0, 0, 0, 0) self.free_games_now.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_now.setLayout(free_games_now_layout) - self.free_games_container.layout().addWidget(self.free_games_now) + self.free_container.layout().addWidget(self.free_games_now) - self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.free_games_container) + self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.free_container) free_games_next_layout = QHBoxLayout(self.free_games_next) # free_games_next_layout.setContentsMargins(0, 0, 0, 0) self.free_games_next.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_next.setLayout(free_games_next_layout) - self.free_games_container.layout().addWidget(self.free_games_next) + self.free_container.layout().addWidget(self.free_games_next) date = datetime.datetime.now() free_games_now = [] @@ -222,13 +218,13 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.free_games_next.layout().addWidget(w) # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) - self.free_games_scrollarea.setMinimumHeight( + self.free_scrollarea.setMinimumHeight( self.free_games_now.sizeHint().height() - + self.free_games_container.contentsMargins().top() - + self.free_games_container.contentsMargins().bottom() - + self.free_games_scrollarea.horizontalScrollBar().sizeHint().height() + + self.free_container.layout().contentsMargins().top() + + self.free_container.layout().contentsMargins().bottom() + + self.free_scrollarea.horizontalScrollBar().sizeHint().height() ) - self.free_games_scrollarea.setEnabled(True) + self.free_scrollarea.setEnabled(True) def show_search_results(self): if self.search_bar.text(): @@ -287,7 +283,8 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.reset_button.clicked.connect(self.reset_filters) self.filter_scrollarea.setMinimumWidth( self.filter_container.sizeHint().width() - + self.filter_container_layout.spacing() + + self.filter_container.layout().contentsMargins().left() + + self.filter_container.layout().contentsMargins().right() + self.filter_scrollarea.verticalScrollBar().sizeHint().width() ) @@ -327,10 +324,10 @@ class ShopWidget(QWidget, Ui_ShopWidget): if removed_type and removed_type in self.types: self.types.remove(removed_type) if (self.types or self.price) or self.tags or self.on_discount.isChecked(): - self.free_games_scrollarea.setVisible(False) + self.free_scrollarea.setVisible(False) self.discounts_group.setVisible(False) else: - self.free_games_scrollarea.setVisible(True) + self.free_scrollarea.setVisible(True) if len(self.discounts_group.layout().children()) > 0: self.discounts_group.setVisible(True) diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py index e25179b9..3fe0fa68 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/store.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopWidget(object): def setupUi(self, ShopWidget): ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(784, 525) + ShopWidget.resize(750, 588) ShopWidget.setWindowTitle("Store") self.shop_layout = QtWidgets.QHBoxLayout(ShopWidget) self.shop_layout.setObjectName("shop_layout") @@ -27,22 +27,30 @@ class Ui_ShopWidget(object): self.games_scrollarea.setWidgetResizable(True) self.games_scrollarea.setObjectName("games_scrollarea") self.games_container = QtWidgets.QWidget() - self.games_container.setGeometry(QtCore.QRect(0, 0, 611, 511)) + self.games_container.setGeometry(QtCore.QRect(0, 0, 583, 574)) self.games_container.setObjectName("games_container") self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) self.games_container_layout.setContentsMargins(0, 0, 3, 0) self.games_container_layout.setObjectName("games_container_layout") - self.free_games_scrollarea = QtWidgets.QScrollArea(self.games_container) - self.free_games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) - self.free_games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) - self.free_games_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.free_games_scrollarea.setWidgetResizable(True) - self.free_games_scrollarea.setObjectName("free_games_scrollarea") - self.free_games_container = QtWidgets.QWidget() - self.free_games_container.setGeometry(QtCore.QRect(0, 0, 608, 166)) - self.free_games_container.setObjectName("free_games_container") - self.free_games_scrollarea.setWidget(self.free_games_container) - self.games_container_layout.addWidget(self.free_games_scrollarea) + self.free_scrollarea = QtWidgets.QScrollArea(self.games_container) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.free_scrollarea.sizePolicy().hasHeightForWidth()) + self.free_scrollarea.setSizePolicy(sizePolicy) + self.free_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.free_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.free_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.free_scrollarea.setWidgetResizable(True) + self.free_scrollarea.setObjectName("free_scrollarea") + self.free_container = QtWidgets.QWidget() + self.free_container.setGeometry(QtCore.QRect(0, 0, 580, 83)) + self.free_container.setObjectName("free_container") + self.free_container_layout = QtWidgets.QHBoxLayout(self.free_container) + self.free_container_layout.setContentsMargins(0, 0, 0, 3) + self.free_container_layout.setObjectName("free_container_layout") + self.free_scrollarea.setWidget(self.free_container) + self.games_container_layout.addWidget(self.free_scrollarea) self.discounts_group = QtWidgets.QGroupBox(self.games_container) self.discounts_group.setObjectName("discounts_group") self.discounts_layout = QtWidgets.QVBoxLayout(self.discounts_group) @@ -86,10 +94,10 @@ class Ui_ShopWidget(object): self.filter_scrollarea.setWidgetResizable(True) self.filter_scrollarea.setObjectName("filter_scrollarea") self.filter_container = QtWidgets.QWidget() - self.filter_container.setGeometry(QtCore.QRect(0, 0, 151, 479)) + self.filter_container.setGeometry(QtCore.QRect(0, 0, 145, 542)) self.filter_container.setObjectName("filter_container") self.filter_container_layout = QtWidgets.QVBoxLayout(self.filter_container) - self.filter_container_layout.setContentsMargins(0, 0, 0, 0) + self.filter_container_layout.setContentsMargins(0, 0, 3, 0) self.filter_container_layout.setObjectName("filter_container_layout") self.price_group = QtWidgets.QGroupBox(self.filter_container) self.price_group.setObjectName("price_group") @@ -141,6 +149,7 @@ class Ui_ShopWidget(object): self.filter_scrollarea.setWidget(self.filter_container) self.right_layout.addWidget(self.filter_scrollarea) self.shop_layout.addLayout(self.right_layout) + self.shop_layout.setStretch(0, 1) self.retranslateUi(ShopWidget) diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui index 89d0f021..c5350030 100644 --- a/rare/ui/components/tabs/store/store.ui +++ b/rare/ui/components/tabs/store/store.ui @@ -6,14 +6,14 @@ 0 0 - 784 - 525 + 750 + 588 Store - + @@ -35,8 +35,8 @@ 0 0 - 611 - 511 + 583 + 574 @@ -53,7 +53,13 @@ 0 - + + + + 0 + 0 + + QFrame::NoFrame @@ -66,15 +72,29 @@ true - + 0 0 - 608 - 166 + 580 + 83 + + + 0 + + + 0 + + + 0 + + + 3 + + @@ -149,8 +169,8 @@ 0 0 - 151 - 479 + 145 + 542 @@ -161,7 +181,7 @@ 0 - 0 + 3 0 From b6458b1bfcb7b102cb849c402dc377d1e1a3ca68 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 31 Mar 2023 01:08:23 +0300 Subject: [PATCH 06/21] SearchResultItem: Use ShopImageWidget as a base --- rare/components/tabs/store/search_results.py | 79 +++++++------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py index 72dc29e7..5e34e889 100644 --- a/rare/components/tabs/store/search_results.py +++ b/rare/components/tabs/store/search_results.py @@ -1,18 +1,16 @@ -from PyQt5 import QtGui -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QFont +from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import ( + QFrame, QWidget, QVBoxLayout, - QHBoxLayout, - QLabel, - QFrame, QSizePolicy, + QLabel, ) from rare.shared.image_manager import ImageSize from rare.widgets.flow_layout import FlowLayout -from rare.widgets.elide_label import ElideLabel from .image_widget import ShopImageWidget @@ -26,8 +24,8 @@ class SearchResults(QWidget): self.results_frame = QFrame(self) self.results_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.results_frame.setFrameStyle(QFrame.StyledPanel) - self.resutls_layout = FlowLayout(self.results_frame) - self.results_frame.setLayout(self.resutls_layout) + self.results_layout = FlowLayout(self.results_frame) + self.results_frame.setLayout(self.results_layout) self.main_layout = QVBoxLayout() self.main_layout.setContentsMargins(0, 0, 0, 0) @@ -43,70 +41,51 @@ class SearchResults(QWidget): def show_results(self, results: dict): for w in self.results_frame.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): - self.results_frame.layout().removeWidget(w) + self.results_layout.removeWidget(w) w.deleteLater() - for w in self.results_frame.findChildren(_SearchResultItem, options=Qt.FindDirectChildrenOnly): - self.results_frame.layout().removeWidget(w) + for w in self.results_frame.findChildren(SearchResultItem, options=Qt.FindDirectChildrenOnly): + self.results_layout.removeWidget(w) w.deleteLater() if not results: - self.results_frame.layout().addWidget(QLabel(self.tr("No results found"))) + self.results_layout.addWidget(QLabel(self.tr("No results found"))) else: for res in results: - w = _SearchResultItem(res, parent=self.results_frame) + w = SearchResultItem(res, parent=self.results_frame) w.show_info.connect(self.show_info.emit) - self.results_frame.layout().addWidget(w) + self.results_layout.addWidget(w) self.setEnabled(True) -class _SearchResultItem(QFrame): - res: dict +class SearchResultItem(ShopImageWidget): show_info = pyqtSignal(dict) def __init__(self, result: dict, parent=None): - super(_SearchResultItem, self).__init__(parent=parent) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.widget_layout = QVBoxLayout() - self.widget_layout.setSizeConstraint(QVBoxLayout.SetFixedSize) - self.image = ShopImageWidget(parent=self) - self.image.setFixedSize(ImageSize.Normal) + super(SearchResultItem, self).__init__(parent=parent) + self.setFixedSize(ImageSize.Normal) + self.ui.setupUi(self) for img in result["keyImages"]: if img["type"] in ["DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo"]: - self.image.fetchPixmap(img["url"], result["id"], result["title"]) + self.fetchPixmap(img["url"], result["id"], result["title"]) break else: print("No image found") - self.widget_layout.addWidget(self.image) - self.res = result - self.title = ElideLabel(self.res["title"], parent=self) - title_font = QFont() - title_font.setPixelSize(15) - self.title.setFont(title_font) - self.title.setWordWrap(False) - self.widget_layout.addWidget(self.title) + self.ui.title_label.setText(result["title"]) price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"] - price_layout = QHBoxLayout() - price_layout.addStretch(1) - price_label = QLabel(price if price != "0" else self.tr("Free"), parent=self) - price_label.setAlignment(Qt.AlignRight) - price_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - price_layout.addWidget(price_label) - + self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: - font = QFont() + font = self.ui.price_label.font() font.setStrikeOut(True) - price_label.setFont(font) - discount_label = QLabel(discount_price if discount_price != "0" else self.tr("Free"), parent=self) - discount_label.setAlignment(Qt.AlignRight) - discount_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - price_layout.addWidget(discount_label) - # self.discount_price = QLabel(f"{self.tr('Discount price: ')}{discount_price}") - self.widget_layout.addLayout(price_layout) + self.ui.price_label.setFont(font) + self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') + else: + self.ui.discount_label.setVisible(False) - self.setLayout(self.widget_layout) + self.res = result - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - if a0.button() == 1: + def mousePressEvent(self, a0: QMouseEvent) -> None: + if a0.button() == Qt.LeftButton: + a0.accept() self.show_info.emit(self.res) From 247b2c947a69fbfb1ef8f1666d81fcbf52a06f1a Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 31 Mar 2023 01:09:07 +0300 Subject: [PATCH 07/21] ShopImageWidget: Design it to me similar to IconGameWidget --- rare/components/tabs/store/image_widget.py | 72 +++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/rare/components/tabs/store/image_widget.py b/rare/components/tabs/store/image_widget.py index 38ee56fb..a89621cf 100644 --- a/rare/components/tabs/store/image_widget.py +++ b/rare/components/tabs/store/image_widget.py @@ -1,11 +1,19 @@ from typing import Dict -from PyQt5.QtCore import Qt, QRect +from PyQt5.QtCore import QRect +from PyQt5.QtCore import Qt from PyQt5.QtGui import ( QPixmap, QImage, QMovie, ) -from PyQt5.QtWidgets import QLabel +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QSpacerItem, + QSizePolicy, + QHBoxLayout, + QLabel, +) from rare.utils.qt_requests import QtRequestManager from rare.widgets.image_widget import ImageWidget @@ -32,11 +40,71 @@ class WaitingSpinner(QLabel): self.movie.stop() +class IconWidget(object): + def __init__(self): + self.mini_widget: QWidget = None + self.title_label: QLabel = None + self.price_label: QLabel = None + self.discount_label: QLabel = None + + def setupUi(self, widget: QWidget): + # on-hover popup + self.mini_widget = QWidget(parent=widget) + self.mini_widget.setObjectName(f"{type(self).__name__}MiniWidget") + self.mini_widget.setFixedHeight(widget.height() // 4) + + # game title + self.title_label = QLabel(parent=self.mini_widget) + self.title_label.setObjectName(f"{type(self).__name__}TitleLabel") + self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.title_label.setAlignment(Qt.AlignVCenter) + self.title_label.setAutoFillBackground(False) + self.title_label.setWordWrap(True) + + # information below title + self.price_label = QLabel(parent=self.mini_widget) + self.price_label.setObjectName(f"{type(self).__name__}TooltipLabel") + self.price_label.setAlignment(Qt.AlignRight) + self.price_label.setAutoFillBackground(False) + + self.discount_label = QLabel(parent=self.mini_widget) + self.discount_label.setObjectName(f"{type(self).__name__}TooltipLabel") + self.discount_label.setAlignment(Qt.AlignRight) + self.discount_label.setAutoFillBackground(False) + + # Create layouts + # layout on top of the image, holds the status label, a spacer item and the mini widget + image_layout = QVBoxLayout() + image_layout.setContentsMargins(2, 2, 2, 2) + + # layout for the mini widget, holds the top row and the info label + mini_layout = QVBoxLayout() + mini_layout.setSpacing(0) + + # layout for the top row, holds the title and the launch button + row_layout = QHBoxLayout() + row_layout.setSpacing(6) + row_layout.setAlignment(Qt.AlignBottom) + + # Layout the widgets + # (from inner to outer) + row_layout.addWidget(self.price_label, stretch=2) + row_layout.addWidget(self.discount_label) + mini_layout.addWidget(self.title_label) + mini_layout.addLayout(row_layout) + self.mini_widget.setLayout(mini_layout) + + image_layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) + image_layout.addWidget(self.mini_widget) + widget.setLayout(image_layout) + + class ShopImageWidget(ImageWidget): __image_cache: Dict[str, Dict[str, QPixmap]] = {} def __init__(self, parent=None): super(ShopImageWidget, self).__init__(parent=parent) + self.ui = IconWidget() self.spinner = WaitingSpinner(parent=self) self.spinner.setVisible(False) self.manager = QtRequestManager("bytes") From 7246078df31c43a72de9dfd1077fb48d37242aa2 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 31 Mar 2023 01:09:40 +0300 Subject: [PATCH 08/21] ShopGameInfo: Design it to me similar to GameInfo --- rare/components/tabs/store/__init__.py | 5 +- rare/components/tabs/store/game_info.py | 74 ++-- rare/components/tabs/store/game_widgets.py | 73 +--- .../components/tabs/store/shop_game_info.py | 211 ++++++--- .../components/tabs/store/shop_game_info.ui | 402 ++++++++++++------ 5 files changed, 484 insertions(+), 281 deletions(-) diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index c72e88d9..3941be00 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -17,7 +17,7 @@ class StoreTab(SideTabWidget): self.init = False self.core = core - self.rcore = RareCore.instance() + # self.rcore = RareCore.instance() self.api_core = ShopApiCore( self.core.egs.session.headers["Authorization"], self.core.language_code, @@ -35,7 +35,8 @@ class StoreTab(SideTabWidget): # self.search.back_button.clicked.connect(lambda: self.setCurrentIndex(self.shop_index)) self.info = ShopGameInfo( - [i.asset_infos["Windows"].namespace for i in self.rcore.game_list if bool(i.asset_infos)], + # [i.asset_infos["Windows"].namespace for i in self.rcore.game_list if bool(i.asset_infos)], + [], self.api_core, parent=self ) diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/game_info.py index 7b478766..b42dccdc 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/game_info.py @@ -13,7 +13,7 @@ from PyQt5.QtWidgets import ( from rare.components.tabs.store.shop_models import ShopGame from rare.shared import LegendaryCoreSingleton from rare.shared.image_manager import ImageSize -from rare.ui.components.tabs.store.shop_game_info import Ui_ShopGameInfo +from rare.ui.components.tabs.store.shop_game_info import Ui_ShopInfo from rare.utils.misc import icon from rare.widgets.side_tab import SideTabWidget, SideTabContents from rare.widgets.elide_label import ElideLabel @@ -22,30 +22,31 @@ from .image_widget import ShopImageWidget logger = logging.getLogger("ShopInfo") -class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): +class ShopGameInfo(QWidget, SideTabContents): # TODO Design def __init__(self, installed_titles: list, api_core, parent=None): super(ShopGameInfo, self).__init__(parent=parent) - self.setupUi(self) - self.core = LegendaryCoreSingleton() + self.ui = Ui_ShopInfo() + self.ui.setupUi(self) + # self.core = LegendaryCoreSingleton() self.api_core = api_core self.installed = installed_titles - self.open_store_button.clicked.connect(self.button_clicked) + self.ui.open_store_button.clicked.connect(self.button_clicked) self.image = ShopImageWidget(self) self.image.setFixedSize(ImageSize.Normal) - self.image_info_layout.insertWidget(0, self.image) + self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) self.game: ShopGame = None self.data: dict = {} - self.wishlist_button.clicked.connect(self.add_to_wishlist) + self.ui.wishlist_button.clicked.connect(self.add_to_wishlist) self.in_wishlist = False self.wishlist = [] - self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.requirements_group) + self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_group) self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.requirements_layout.addWidget(self.requirements_tabs) + self.ui.requirements_layout.addWidget(self.requirements_tabs) self.setDisabled(True) @@ -55,15 +56,15 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): self.wishlist = [i["offer"]["title"] for i in data] if self.title_str in self.wishlist: self.in_wishlist = True - self.wishlist_button.setVisible(True) - self.wishlist_button.setText(self.tr("Remove from Wishlist")) + self.ui.wishlist_button.setVisible(True) + self.ui.wishlist_button.setText(self.tr("Remove from Wishlist")) else: self.in_wishlist = False - self.wishlist_button.setVisible(False) + self.ui.wishlist_button.setVisible(False) def update_game(self, data: dict): self.set_title.emit(data["title"]) - self.title.setText(data["title"]) + self.ui.title.setText(data["title"]) self.title_str = data["title"] self.id_str = data["id"] self.api_core.get_wishlist(self.handle_wishlist_update) @@ -86,14 +87,14 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): self.slug = slug if data["namespace"] in self.installed: - self.open_store_button.setText(self.tr("Show Game on Epic Page")) - self.owned_label.setVisible(True) + self.ui.open_store_button.setText(self.tr("Show Game on Epic Page")) + self.ui.owned_label.setVisible(True) else: - self.open_store_button.setText(self.tr("Buy Game in Epic Games Store")) - self.owned_label.setVisible(False) + self.ui.open_store_button.setText(self.tr("Buy Game in Epic Games Store")) + self.ui.owned_label.setVisible(False) - self.price.setText(self.tr("Loading")) - self.wishlist_button.setVisible(False) + self.ui.price.setText(self.tr("Loading")) + self.ui.wishlist_button.setVisible(False) # self.title.setText(self.tr("Loading")) # self.image.setPixmap(QPixmap()) self.data = data @@ -118,9 +119,9 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): self.api_core.remove_from_wishlist( self.game.namespace, self.game.offer_id, - lambda success: self.wishlist_button.setVisible(False) + lambda success: self.ui.wishlist_button.setVisible(False) if success - else self.wishlist_button.setText("Something goes wrong"), + else self.ui.wishlist_button.setText("Something goes wrong"), ) def data_received(self, game): @@ -148,23 +149,23 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): return # self.title.setText(self.game.title) - self.price.setFont(QFont()) + self.ui.price.setFont(QFont()) if self.game.price == "0" or self.game.price == 0: - self.price.setText(self.tr("Free")) + self.ui.price.setText(self.tr("Free")) else: - self.price.setText(self.game.price) + self.ui.price.setText(self.game.price) if self.game.price != self.game.discount_price: font = QFont() font.setStrikeOut(True) - self.price.setFont(font) - self.discount_price.setText( + self.ui.price.setFont(font) + self.ui.discount_price.setText( self.game.discount_price if self.game.discount_price != "0" else self.tr("Free") ) - self.discount_price.setVisible(True) + self.ui.discount_price.setVisible(True) else: - self.discount_price.setVisible(False) + self.ui.discount_price.setVisible(False) bold_font = QFont() bold_font.setBold(True) @@ -211,16 +212,16 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): # self.image_stack.setCurrentIndex(0) try: if isinstance(self.game.developer, list): - self.dev.setText(", ".join(self.game.developer)) + self.ui.dev.setText(", ".join(self.game.developer)) else: - self.dev.setText(self.game.developer) + self.ui.dev.setText(self.game.developer) except KeyError: pass - self.tags.setText(", ".join(self.game.tags)) + self.ui.tags.setText(", ".join(self.game.tags)) # clear Layout - for b in self.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): - self.social_layout.removeWidget(b) + for b in self.ui.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): + self.ui.social_layout.removeWidget(b) b.deleteLater() link_count = 0 @@ -235,11 +236,11 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): logger.error(str(e)) continue - button = SocialButton(icn, url, parent=self.social_group) - self.social_layout.addWidget(button) + button = SocialButton(icn, url, parent=self.ui.social_group) + self.ui.social_layout.addWidget(button) link_count += 1 - self.social_group.setEnabled(bool(link_count)) + self.ui.social_group.setEnabled(bool(link_count)) self.setEnabled(True) @@ -249,6 +250,7 @@ class ShopGameInfo(QWidget, Ui_ShopGameInfo, SideTabContents): self.wishlist.append(game["offer"]["title"]) def button_clicked(self): + return QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.core.language_code}/p/{self.slug}")) diff --git a/rare/components/tabs/store/game_widgets.py b/rare/components/tabs/store/game_widgets.py index e5c164d4..85e16c72 100644 --- a/rare/components/tabs/store/game_widgets.py +++ b/rare/components/tabs/store/game_widgets.py @@ -2,93 +2,64 @@ import logging from PyQt5 import QtGui from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QFont -from PyQt5.QtNetwork import QNetworkAccessManager -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QSizePolicy +from PyQt5.QtGui import QFont, QMouseEvent +from PyQt5.QtWidgets import QWidget from rare.components.tabs.store.shop_models import ImageUrlModel from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.store.wishlist_widget import Ui_WishlistWidget from rare.utils.extra_widgets import ImageLabel from rare.utils.misc import qta_icon -from rare.widgets.elide_label import ElideLabel from .image_widget import ShopImageWidget logger = logging.getLogger("GameWidgets") -class GameWidget(QWidget): +class GameWidget(ShopImageWidget): show_info = pyqtSignal(dict) - def __init__(self, path, json_info=None): - super(GameWidget, self).__init__() - self.manager = QNetworkAccessManager() + def __init__(self, path, json_info=None, parent=None): + super(GameWidget, self).__init__(parent=parent) + self.setFixedSize(ImageSize.Wide) + self.ui.setupUi(self) self.path = path + self.json_info = json_info if json_info: self.init_ui(json_info) def init_ui(self, json_info): - self.layout = QVBoxLayout() - self.layout.setSizeConstraint(QVBoxLayout.SetFixedSize) - self.image = ShopImageWidget(self) - self.image.setFixedSize(ImageSize.Wide) - self.layout.addWidget(self.image) - mini_layout = QHBoxLayout() - self.layout.addLayout(mini_layout) - if not json_info: - self.layout.addWidget(QLabel("An error occurred")) - self.setLayout(self.layout) + self.ui.title_label.setText(self.tr("An error occurred")) return - self.title_label = ElideLabel(json_info.get("title"), parent=self) - self.title_label.setWordWrap(False) - mini_layout.addWidget(self.title_label) - # mini_layout.addStretch(1) - + self.ui.title_label.setText(json_info.get("title")) price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"] - price_label = QLabel(price) + self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: - font = QFont() + font = self.ui.price_label.font() font.setStrikeOut(True) - price_label.setFont(font) - free_label = QLabel(discount_price if discount_price != "0" else self.tr("Free")) - free_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - free_label.setAlignment(Qt.AlignRight) - mini_layout.addWidget(free_label) + self.ui.price_label.setFont(font) + self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') else: - if price == "0": - price_label.setText(self.tr("Free")) - price_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - price_label.setAlignment(Qt.AlignRight) - mini_layout.addWidget(price_label) + self.ui.discount_label.setVisible(False) for c in r'<>?":|\/*': json_info["title"] = json_info["title"].replace(c, "") - self.json_info = json_info - self.slug = json_info["productSlug"] - - self.title = json_info["title"] for img in json_info["keyImages"]: - if img["type"] in [ - "DieselStoreFrontWide", - "OfferImageWide", - "VaultClosed", - "ProductLogo", - ]: - if img["type"] == "VaultClosed" and self.title != "Mystery Game": + if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo",]: + if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game": continue - self.image.fetchPixmap(img["url"], json_info["id"], json_info["title"]) + self.fetchPixmap(img["url"], json_info["id"], json_info["title"]) break else: logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) - self.setLayout(self.layout) - - def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: - self.show_info.emit(self.json_info) + def mousePressEvent(self, a0: QMouseEvent) -> None: + if a0.button() == Qt.LeftButton: + a0.accept() + self.show_info.emit(self.json_info) class WishlistWidget(QWidget, Ui_WishlistWidget): diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py index 710a5b69..839506e1 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/shop_game_info.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -11,105 +11,178 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_ShopGameInfo(object): - def setupUi(self, ShopGameInfo): - ShopGameInfo.setObjectName("ShopGameInfo") - ShopGameInfo.resize(702, 468) - ShopGameInfo.setWindowTitle("ShopGameInfo") - self.shop_game_info_layout = QtWidgets.QVBoxLayout(ShopGameInfo) - self.shop_game_info_layout.setObjectName("shop_game_info_layout") - self.image_info_layout = QtWidgets.QHBoxLayout() - self.image_info_layout.setObjectName("image_info_layout") - self.info_layout = QtWidgets.QGridLayout() +class Ui_ShopInfo(object): + def setupUi(self, ShopInfo): + ShopInfo.setObjectName("ShopInfo") + ShopInfo.resize(434, 250) + ShopInfo.setWindowTitle("ShopGameInfo") + self.main_layout = QtWidgets.QHBoxLayout(ShopInfo) + self.main_layout.setObjectName("main_layout") + self.left_layout = QtWidgets.QVBoxLayout() + self.left_layout.setObjectName("left_layout") + self.main_layout.addLayout(self.left_layout) + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.setObjectName("right_layout") + self.info_layout = QtWidgets.QFormLayout() + self.info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.info_layout.setContentsMargins(6, 6, 6, 6) + self.info_layout.setSpacing(12) self.info_layout.setObjectName("info_layout") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.info_layout.addItem(spacerItem, 6, 1, 1, 1) - self.tags = QtWidgets.QLabel(ShopGameInfo) - self.tags.setText("tags") - self.tags.setObjectName("tags") - self.info_layout.addWidget(self.tags, 5, 0, 1, 2) - self.discount_price = QtWidgets.QLabel(ShopGameInfo) - self.discount_price.setText("discount") - self.discount_price.setObjectName("discount_price") - self.info_layout.addWidget(self.discount_price, 4, 0, 1, 2) - self.title = QtWidgets.QLabel(ShopGameInfo) + self.title_label = QtWidgets.QLabel(ShopInfo) font = QtGui.QFont() - font.setPointSize(18) - self.title.setFont(font) + font.setBold(True) + font.setWeight(75) + self.title_label.setFont(font) + self.title_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.title_label.setObjectName("title_label") + self.info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.title_label) + self.title = QtWidgets.QLabel(ShopInfo) self.title.setText("title") self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.title.setObjectName("title") - self.info_layout.addWidget(self.title, 0, 0, 1, 2) - self.button_layout = QtWidgets.QVBoxLayout() - self.button_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.button_layout.setObjectName("button_layout") - self.open_store_button = QtWidgets.QPushButton(ShopGameInfo) - self.open_store_button.setObjectName("open_store_button") - self.button_layout.addWidget(self.open_store_button) - self.wishlist_button = QtWidgets.QPushButton(ShopGameInfo) - self.wishlist_button.setObjectName("wishlist_button") - self.button_layout.addWidget(self.wishlist_button) - self.info_layout.addLayout(self.button_layout, 6, 0, 1, 1) - self.dev = QtWidgets.QLabel(ShopGameInfo) + self.info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.title) + self.developer_label = QtWidgets.QLabel(ShopInfo) font = QtGui.QFont() - font.setPointSize(14) - self.dev.setFont(font) + font.setBold(True) + font.setWeight(75) + self.developer_label.setFont(font) + self.developer_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.developer_label.setObjectName("developer_label") + self.info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.developer_label) + self.dev = QtWidgets.QLabel(ShopInfo) self.dev.setText("dev") self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.dev.setObjectName("dev") - self.info_layout.addWidget(self.dev, 1, 0, 1, 2) - self.owned_label = QtWidgets.QLabel(ShopGameInfo) + self.info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.dev) + self.status_label = QtWidgets.QLabel(ShopInfo) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.status_label.setFont(font) + self.status_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.status_label.setObjectName("status_label") + self.info_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.status_label) + self.owned_label = QtWidgets.QLabel(ShopInfo) self.owned_label.setObjectName("owned_label") - self.info_layout.addWidget(self.owned_label, 2, 0, 1, 2) - self.price = QtWidgets.QLabel(ShopGameInfo) + self.info_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.owned_label) + self.price_label = QtWidgets.QLabel(ShopInfo) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.price_label.setFont(font) + self.price_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.price_label.setObjectName("price_label") + self.info_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.price_label) + self.price_widget = QtWidgets.QWidget(ShopInfo) + self.price_widget.setObjectName("price_widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.price_widget) + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.price = QtWidgets.QLabel(self.price_widget) self.price.setText("price") self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.price.setObjectName("price") - self.info_layout.addWidget(self.price, 3, 0, 1, 2) - self.social_group = QtWidgets.QGroupBox(ShopGameInfo) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.social_group.sizePolicy().hasHeightForWidth()) - self.social_group.setSizePolicy(sizePolicy) + self.horizontalLayout.addWidget(self.price) + self.discount_price = QtWidgets.QLabel(self.price_widget) + self.discount_price.setText("discount") + self.discount_price.setObjectName("discount_price") + self.horizontalLayout.addWidget(self.discount_price) + self.info_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.price_widget) + self.tags_label = QtWidgets.QLabel(ShopInfo) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.tags_label.setFont(font) + self.tags_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.tags_label.setObjectName("tags_label") + self.info_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.tags_label) + self.tags = QtWidgets.QLabel(ShopInfo) + self.tags.setText("tags") + self.tags.setObjectName("tags") + self.info_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.tags) + self.links_label = QtWidgets.QLabel(ShopInfo) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.links_label.setFont(font) + self.links_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.links_label.setObjectName("links_label") + self.info_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.links_label) + self.social_group = QtWidgets.QWidget(ShopInfo) + self.social_group.setMinimumSize(QtCore.QSize(250, 0)) self.social_group.setObjectName("social_group") self.social_layout = QtWidgets.QHBoxLayout(self.social_group) + self.social_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.social_layout.setContentsMargins(0, 0, 0, 0) self.social_layout.setObjectName("social_layout") - self.info_layout.addWidget(self.social_group, 7, 0, 1, 2) - self.image_info_layout.addLayout(self.info_layout) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.image_info_layout.addItem(spacerItem1) - self.shop_game_info_layout.addLayout(self.image_info_layout) - self.requirements_group = QtWidgets.QGroupBox(ShopGameInfo) + self.info_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.social_group) + self.actions_label = QtWidgets.QLabel(ShopInfo) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.actions_label.setFont(font) + self.actions_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.actions_label.setObjectName("actions_label") + self.info_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.actions_label) + self.buttons_widget = QtWidgets.QWidget(ShopInfo) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttons_widget.sizePolicy().hasHeightForWidth()) + self.buttons_widget.setSizePolicy(sizePolicy) + self.buttons_widget.setMinimumSize(QtCore.QSize(250, 0)) + self.buttons_widget.setMaximumSize(QtCore.QSize(250, 16777215)) + self.buttons_widget.setObjectName("buttons_widget") + self.button_layout = QtWidgets.QVBoxLayout(self.buttons_widget) + self.button_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.button_layout.setContentsMargins(0, 0, 0, 0) + self.button_layout.setObjectName("button_layout") + self.open_store_button = QtWidgets.QPushButton(self.buttons_widget) + self.open_store_button.setObjectName("open_store_button") + self.button_layout.addWidget(self.open_store_button) + self.wishlist_button = QtWidgets.QPushButton(self.buttons_widget) + self.wishlist_button.setObjectName("wishlist_button") + self.button_layout.addWidget(self.wishlist_button) + self.info_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.buttons_widget) + self.right_layout.addLayout(self.info_layout) + self.requirements_group = QtWidgets.QFrame(ShopInfo) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.requirements_group.sizePolicy().hasHeightForWidth()) self.requirements_group.setSizePolicy(sizePolicy) + self.requirements_group.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.requirements_group.setFrameShadow(QtWidgets.QFrame.Sunken) self.requirements_group.setObjectName("requirements_group") self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_group) self.requirements_layout.setContentsMargins(0, 0, 0, 0) self.requirements_layout.setObjectName("requirements_layout") - self.shop_game_info_layout.addWidget(self.requirements_group) - spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.shop_game_info_layout.addItem(spacerItem2) + self.right_layout.addWidget(self.requirements_group) + self.main_layout.addLayout(self.right_layout) + self.main_layout.setStretch(1, 1) - self.retranslateUi(ShopGameInfo) + self.retranslateUi(ShopInfo) - def retranslateUi(self, ShopGameInfo): + def retranslateUi(self, ShopInfo): _translate = QtCore.QCoreApplication.translate - self.open_store_button.setText(_translate("ShopGameInfo", "Buy in Epic Games Store")) - self.wishlist_button.setText(_translate("ShopGameInfo", "Add to wishlist")) - self.owned_label.setText(_translate("ShopGameInfo", "You already own this game")) - self.social_group.setTitle(_translate("ShopGameInfo", "Links")) - self.requirements_group.setTitle(_translate("ShopGameInfo", "System requirements")) + self.title_label.setText(_translate("ShopInfo", "Title")) + self.developer_label.setText(_translate("ShopInfo", "Developer")) + self.status_label.setText(_translate("ShopInfo", "Status")) + self.owned_label.setText(_translate("ShopInfo", "You already own this game")) + self.price_label.setText(_translate("ShopInfo", "Price")) + self.tags_label.setText(_translate("ShopInfo", "Tags")) + self.links_label.setText(_translate("ShopInfo", "Links")) + self.actions_label.setText(_translate("ShopInfo", "Actions")) + self.open_store_button.setText(_translate("ShopInfo", "Buy in Epic Games Store")) + self.wishlist_button.setText(_translate("ShopInfo", "Add to wishlist")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - ShopGameInfo = QtWidgets.QWidget() - ui = Ui_ShopGameInfo() - ui.setupUi(ShopGameInfo) - ShopGameInfo.show() + ShopInfo = QtWidgets.QWidget() + ui = Ui_ShopInfo() + ui.setupUi(ShopInfo) + ShopInfo.show() sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/shop_game_info.ui index 5c2223c6..382dcf92 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -1,57 +1,65 @@ - ShopGameInfo - + ShopInfo + 0 0 - 702 - 468 + 434 + 250 ShopGameInfo - + - + + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - tags - - - - - - - discount - - - - - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 12 + + + 12 + + + 6 + + + 6 + + + 6 + + + 6 + + + - 18 + 75 + true + + Title + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + title @@ -60,34 +68,24 @@ - - - - QLayout::SetFixedSize - - - - - Buy in Epic Games Store - - - - - - - Add to wishlist - - - - - - - + + - 14 + 75 + true + + Developer + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + dev @@ -96,94 +94,252 @@ - + + + + + 75 + true + + + + Status + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + You already own this game - - - - price + + + + + 75 + true + - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + Price + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + price + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + discount + + + + + + + + + + + 75 + true + + + + Tags + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + tags + + + + + + + + 75 + true + + + + Links + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 250 + 0 + + + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 75 + true + + + + Actions + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + - + 0 0 - - Links + + + 250 + 0 + - + + + 250 + 16777215 + + + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Buy in Epic Games Store + + + + + + + Add to wishlist + + + + - - - Qt::Horizontal + + + + 0 + 0 + - - - 40 - 20 - + + QFrame::StyledPanel - + + QFrame::Sunken + + + + 0 + + + 0 + + + 0 + + + 0 + + + - - - - - 0 - 0 - - - - System requirements - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - From f6396f488ae9e47c56b628b6bc99adf294be7bd6 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:46:02 +0300 Subject: [PATCH 09/21] Store: Exploratory changes to the store page Important changes: * Refactored QtRequests to accept parameters for `GET` operations * Infer response data type from content-type header * Support caching to disk, a manager with this set prefers the cache * Support multiple handlers for a single request (unused, possibly pointeless) * Subclass `ShopImageWidget` for all widgets used in the shop * Request a resized image instead of the original one * Fix the search and browse functions --- rare/components/tabs/store/__init__.py | 8 +- rare/components/tabs/store/__main__.py | 3 + rare/components/tabs/store/game_info.py | 6 +- rare/components/tabs/store/game_widgets.py | 66 +++--- rare/components/tabs/store/image_widget.py | 93 ++++---- rare/components/tabs/store/search_results.py | 39 ++-- rare/components/tabs/store/shop_api_core.py | 82 +++---- rare/components/tabs/store/shop_models.py | 20 +- rare/components/tabs/store/shop_widget.py | 211 ++++++++++-------- rare/components/tabs/store/wishlist.py | 78 +++---- .../components/tabs/store/shop_game_info.py | 2 +- .../components/tabs/store/shop_game_info.ui | 4 +- rare/ui/components/tabs/store/store.py | 34 ++- rare/ui/components/tabs/store/store.ui | 42 ++-- rare/ui/components/tabs/store/wishlist.py | 41 ++-- rare/ui/components/tabs/store/wishlist.ui | 45 ++-- .../components/tabs/store/wishlist_widget.py | 48 ++-- .../components/tabs/store/wishlist_widget.ui | 43 ++-- 18 files changed, 441 insertions(+), 424 deletions(-) diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index 3941be00..817e1acd 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -25,12 +25,12 @@ class StoreTab(SideTabWidget): ) self.shop = ShopWidget(cache_dir(), self.core, self.api_core, parent=self) - self.shop_index = self.addTab(self.shop, self.tr("Games")) + self.shop_index = self.addTab(self.shop, self.tr("Store")) self.shop.show_game.connect(self.show_game) self.shop.show_info.connect(self.show_search) self.search = SearchResults(self.api_core, parent=self) - self.search_index = self.addTab(self.search, self.tr("Search")) + self.search_index = self.addTab(self.search, self.tr("Search"), self.tr("Results")) self.search.show_info.connect(self.show_game) # self.search.back_button.clicked.connect(lambda: self.setCurrentIndex(self.shop_index)) @@ -40,11 +40,11 @@ class StoreTab(SideTabWidget): self.api_core, parent=self ) - self.info_index = self.addTab(self.info, self.tr("Information")) + self.info_index = self.addTab(self.info, self.tr("Information"), self.tr("Information")) # self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(self.previous_index)) self.wishlist = Wishlist(self.api_core, parent=self) - self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist")) + self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist"), self.tr("Wishlist")) self.wishlist.update_wishlist_signal.connect(self.update_wishlist) self.wishlist.show_game_info.connect(self.show_game) diff --git a/rare/components/tabs/store/__main__.py b/rare/components/tabs/store/__main__.py index 1f62692c..29d9acfd 100644 --- a/rare/components/tabs/store/__main__.py +++ b/rare/components/tabs/store/__main__.py @@ -27,9 +27,12 @@ if __name__ == "__main__": import rare.resources.stylesheets.RareStyle app = QApplication(sys.argv) + app.setApplicationName("Rare") + app.setOrganizationName("Rare") set_style_sheet("RareStyle") window = StoreWindow() + window.setWindowTitle(f"{app.applicationName()} - Store") window.resize(QSize(1280, 800)) window.show() app.exec() \ No newline at end of file diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/game_info.py index b42dccdc..2d399b17 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/game_info.py @@ -33,7 +33,7 @@ class ShopGameInfo(QWidget, SideTabContents): self.api_core = api_core self.installed = installed_titles self.ui.open_store_button.clicked.connect(self.button_clicked) - self.image = ShopImageWidget(self) + self.image = ShopImageWidget(api_core.cached_manager, self) self.image.setFixedSize(ImageSize.Normal) self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) @@ -139,7 +139,7 @@ class ShopGameInfo(QWidget, SideTabContents): "VaultClosed", "ProductLogo", ]: - self.image.fetchPixmap(img["url"], self.id_str, self.title_str) + self.image.fetchPixmap(img["url"]) break self.price.setText("") self.discount_price.setText("") @@ -207,7 +207,7 @@ class ShopGameInfo(QWidget, SideTabContents): img_url = self.game.image_urls.product_logo else: img_url = "" - self.image.fetchPixmap(img_url, self.game.id, self.game.title) + self.image.fetchPixmap(img_url) # self.image_stack.setCurrentIndex(0) try: diff --git a/rare/components/tabs/store/game_widgets.py b/rare/components/tabs/store/game_widgets.py index 85e16c72..09ba8873 100644 --- a/rare/components/tabs/store/game_widgets.py +++ b/rare/components/tabs/store/game_widgets.py @@ -1,15 +1,13 @@ import logging -from PyQt5 import QtGui from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QFont, QMouseEvent -from PyQt5.QtWidgets import QWidget +from PyQt5.QtGui import QMouseEvent +from PyQt5.QtWidgets import QPushButton from rare.components.tabs.store.shop_models import ImageUrlModel from rare.shared.image_manager import ImageSize -from rare.ui.components.tabs.store.wishlist_widget import Ui_WishlistWidget -from rare.utils.extra_widgets import ImageLabel from rare.utils.misc import qta_icon +from rare.utils.qt_requests import QtRequestManager from .image_widget import ShopImageWidget logger = logging.getLogger("GameWidgets") @@ -18,11 +16,10 @@ logger = logging.getLogger("GameWidgets") class GameWidget(ShopImageWidget): show_info = pyqtSignal(dict) - def __init__(self, path, json_info=None, parent=None): - super(GameWidget, self).__init__(parent=parent) + def __init__(self, manager: QtRequestManager, json_info=None, parent=None): + super(GameWidget, self).__init__(manager, parent=parent) self.setFixedSize(ImageSize.Wide) self.ui.setupUi(self) - self.path = path self.json_info = json_info if json_info: self.init_ui(json_info) @@ -33,6 +30,13 @@ class GameWidget(ShopImageWidget): return self.ui.title_label.setText(json_info.get("title")) + for attr in json_info["customAttributes"]: + if attr["key"] == "developerName": + developer = attr["value"] + break + else: + developer = json_info["seller"]["name"] + self.ui.developer_label.setText(developer) price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"] self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') @@ -51,7 +55,7 @@ class GameWidget(ShopImageWidget): if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo",]: if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game": continue - self.fetchPixmap(img["url"], json_info["id"], json_info["title"]) + self.fetchPixmap(img["url"]) break else: logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) @@ -62,52 +66,52 @@ class GameWidget(ShopImageWidget): self.show_info.emit(self.json_info) -class WishlistWidget(QWidget, Ui_WishlistWidget): +class WishlistWidget(ShopImageWidget): open_game = pyqtSignal(dict) delete_from_wishlist = pyqtSignal(dict) - def __init__(self, game: dict): - super(WishlistWidget, self).__init__() - self.setupUi(self) + def __init__(self, manager: QtRequestManager, game: dict, parent=None): + super(WishlistWidget, self).__init__(manager, parent=parent) + self.setFixedSize(ImageSize.Wide) + self.ui.setupUi(self) self.game = game - self.title_label.setText(game.get("title")) for attr in game["customAttributes"]: if attr["key"] == "developerName": - self.developer.setText(attr["value"]) + developer = attr["value"] break else: - self.developer.setText(game["seller"]["name"]) + developer = game["seller"]["name"] original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] - self.price.setText(original_price if original_price != "0" else self.tr("Free")) - # if discount + self.ui.title_label.setText(game.get("title")) + self.ui.developer_label.setText(developer) + self.ui.price_label.setText(f'{original_price if original_price != "0" else self.tr("Free")}') if original_price != discount_price: - self.discount = True - font = QFont() + font = self.ui.price_label.font() font.setStrikeOut(True) - self.price.setFont(font) - self.discount_price.setText(discount_price) + self.ui.price_label.setFont(font) + self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') else: - self.discount = False - self.discount_price.setVisible(False) - - self.image = ImageLabel() - self.layout().insertWidget(0, self.image) + self.ui.discount_label.setVisible(False) image_model = ImageUrlModel.from_json(game["keyImages"]) url = image_model.front_wide if not url: url = image_model.offer_image_wide - self.image.update_image(url, game.get("title"), (240, 135)) + self.fetchPixmap(url) + + self.delete_button = QPushButton(self) self.delete_button.setIcon(icon("mdi.delete", color="white")) self.delete_button.clicked.connect( lambda: self.delete_from_wishlist.emit(self.game) ) + self.layout().insertWidget(0, self.delete_button, alignment=Qt.AlignRight) - def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: + def mousePressEvent(self, a0: QMouseEvent) -> None: # left button - if e.button() == 1: + if a0.button() == Qt.LeftButton: + a0.accept() self.open_game.emit(self.game) # right - elif e.button() == 2: + elif a0.button() == Qt.RightButton: pass # self.showMenu(e) diff --git a/rare/components/tabs/store/image_widget.py b/rare/components/tabs/store/image_widget.py index a89621cf..ec925943 100644 --- a/rare/components/tabs/store/image_widget.py +++ b/rare/components/tabs/store/image_widget.py @@ -1,10 +1,10 @@ -from typing import Dict - -from PyQt5.QtCore import QRect +from PyQt5.QtCore import QEvent, QObject from PyQt5.QtCore import Qt from PyQt5.QtGui import ( QPixmap, - QImage, QMovie, + QImage, + QMovie, + QShowEvent, ) from PyQt5.QtWidgets import ( QWidget, @@ -24,19 +24,43 @@ class WaitingSpinner(QLabel): super(WaitingSpinner, self).__init__(parent=parent) self.setObjectName(type(self).__name__) self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - self.movie = QMovie(":/images/loader.gif") + self.movie = QMovie(":/images/loader.gif", parent=self) + self.setFixedSize(128, 128) self.setMovie(self.movie) + if self.parent() is not None: + self.parent().installEventFilter(self) if autostart: self.movie.start() - def setGeometry(self, a0: QRect) -> None: - self.rect().moveCenter(self.parent().rect().center()) - super(WaitingSpinner, self).setGeometry(self.rect()) + def __center_on_parent(self): + rect = self.rect() + rect.moveCenter(self.parent().contentsRect().center()) + self.setGeometry(rect) + + def event(self, e: QEvent) -> bool: + if e.type() == QEvent.ParentAboutToChange: + if self.parent() is not None: + self.parent().removeEventFilter(self) + if e.type() == QEvent.ParentChange: + if self.parent() is not None: + self.parent().installEventFilter(self) + return super().event(e) + + def showEvent(self, a0: QShowEvent) -> None: + self.__center_on_parent() + + def eventFilter(self, a0: QObject, a1: QEvent) -> bool: + if a0 is self.parent() and a1.type() == QEvent.Resize: + self.__center_on_parent() + return a0.event(a1) + return False def start(self): + self.setVisible(True) self.movie.start() def stop(self): + self.setVisible(False) self.movie.stop() @@ -44,6 +68,7 @@ class IconWidget(object): def __init__(self): self.mini_widget: QWidget = None self.title_label: QLabel = None + self.developer_label: QLabel = None self.price_label: QLabel = None self.discount_label: QLabel = None @@ -57,19 +82,24 @@ class IconWidget(object): self.title_label = QLabel(parent=self.mini_widget) self.title_label.setObjectName(f"{type(self).__name__}TitleLabel") self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.title_label.setAlignment(Qt.AlignVCenter) + self.title_label.setAlignment(Qt.AlignTop) self.title_label.setAutoFillBackground(False) self.title_label.setWordWrap(True) # information below title + self.developer_label = QLabel(parent=self.mini_widget) + self.developer_label.setObjectName(f"{type(self).__name__}TooltipLabel") + self.developer_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.developer_label.setAutoFillBackground(False) + self.price_label = QLabel(parent=self.mini_widget) self.price_label.setObjectName(f"{type(self).__name__}TooltipLabel") - self.price_label.setAlignment(Qt.AlignRight) + self.price_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.price_label.setAutoFillBackground(False) self.discount_label = QLabel(parent=self.mini_widget) self.discount_label.setObjectName(f"{type(self).__name__}TooltipLabel") - self.discount_label.setAlignment(Qt.AlignRight) + self.discount_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.discount_label.setAutoFillBackground(False) # Create layouts @@ -88,7 +118,8 @@ class IconWidget(object): # Layout the widgets # (from inner to outer) - row_layout.addWidget(self.price_label, stretch=2) + row_layout.addWidget(self.developer_label, stretch=2) + row_layout.addWidget(self.price_label) row_layout.addWidget(self.discount_label) mini_layout.addWidget(self.title_label) mini_layout.addLayout(row_layout) @@ -100,45 +131,29 @@ class IconWidget(object): class ShopImageWidget(ImageWidget): - __image_cache: Dict[str, Dict[str, QPixmap]] = {} - - def __init__(self, parent=None): + def __init__(self, manager: QtRequestManager, parent=None): super(ShopImageWidget, self).__init__(parent=parent) self.ui = IconWidget() self.spinner = WaitingSpinner(parent=self) self.spinner.setVisible(False) - self.manager = QtRequestManager("bytes") - self.app_id = "" - self.orientation = "" + self.manager = manager - def fetchPixmap(self, url, app_id: str, title: str = ""): + def fetchPixmap(self, url): self.setPixmap(QPixmap()) - self.app_id = app_id - if self._image_size.size.width() > self._image_size.size.height(): - self.orientation = "wide" - else: - self.orientation = "tall" - - if ShopImageWidget.__image_cache.get(self.app_id, None) is not None: - if pixmap := ShopImageWidget.__image_cache[self.app_id].get(self.orientation, None): - self.setPixmap(pixmap) - return self.spinner.setFixedSize(self._image_size.size) - self.spinner.setVisible(True) self.spinner.start() - self.manager.get( - url, self.__on_image_ready, payload={ - "resize": 1, "w": self._image_size.size.width(), "h": self._image_size.size.height() - } - ) + self.manager.get(url, self.__on_image_ready, params={ + "resize": 1, + "w": self._image_size.base.size.width(), + "h": self._image_size.base.size.height(), + }) def __on_image_ready(self, data): cover = QImage() cover.loadFromData(data) - cover = cover.scaled(self._image_size.size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + # cover = cover.scaled(self._image_size.size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + cover.setDevicePixelRatio(self._image_size.base.pixel_ratio) cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied) cover = QPixmap(cover) - ShopImageWidget.__image_cache.update({self.app_id: {self.orientation: cover}}) - super(ShopImageWidget, self).setPixmap(cover) + self.setPixmap(cover) self.spinner.stop() - self.spinner.setVisible(False) diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py index 5e34e889..9860781e 100644 --- a/rare/components/tabs/store/search_results.py +++ b/rare/components/tabs/store/search_results.py @@ -2,36 +2,36 @@ from PyQt5.QtCore import Qt from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import ( - QFrame, QWidget, - QVBoxLayout, QSizePolicy, - QLabel, + QLabel, QScrollArea, ) from rare.shared.image_manager import ImageSize +from rare.utils.qt_requests import QtRequestManager from rare.widgets.flow_layout import FlowLayout +from rare.widgets.side_tab import SideTabContents from .image_widget import ShopImageWidget -class SearchResults(QWidget): +class SearchResults(QScrollArea, SideTabContents): show_info = pyqtSignal(dict) def __init__(self, api_core, parent=None): super(SearchResults, self).__init__(parent=parent) + self.implements_scrollarea = True self.api_core = api_core - self.results_frame = QFrame(self) - self.results_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.results_frame.setFrameStyle(QFrame.StyledPanel) - self.results_layout = FlowLayout(self.results_frame) - self.results_frame.setLayout(self.results_layout) + self.results_container = QWidget(self) + self.results_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.results_layout = FlowLayout(self.results_container) + self.setWidget(self.results_container) + self.setWidgetResizable(True) - self.main_layout = QVBoxLayout() - self.main_layout.setContentsMargins(0, 0, 0, 0) - self.main_layout.addWidget(self.results_frame) + # self.main_layout = QVBoxLayout(self) + # self.main_layout.setContentsMargins(0, 0, 0, 0) + # self.main_layout.addWidget(self.results_scrollarea) - self.setLayout(self.main_layout) self.setEnabled(False) def load_results(self, text: str): @@ -40,10 +40,10 @@ class SearchResults(QWidget): self.api_core.search_game(text, self.show_results) def show_results(self, results: dict): - for w in self.results_frame.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): + for w in self.results_container.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): self.results_layout.removeWidget(w) w.deleteLater() - for w in self.results_frame.findChildren(SearchResultItem, options=Qt.FindDirectChildrenOnly): + for w in self.results_container.findChildren(SearchResultItem, options=Qt.FindDirectChildrenOnly): self.results_layout.removeWidget(w) w.deleteLater() @@ -51,22 +51,23 @@ class SearchResults(QWidget): self.results_layout.addWidget(QLabel(self.tr("No results found"))) else: for res in results: - w = SearchResultItem(res, parent=self.results_frame) + w = SearchResultItem(self.api_core.cached_manager, res, parent=self.results_container) w.show_info.connect(self.show_info.emit) self.results_layout.addWidget(w) + self.results_layout.update() self.setEnabled(True) class SearchResultItem(ShopImageWidget): show_info = pyqtSignal(dict) - def __init__(self, result: dict, parent=None): - super(SearchResultItem, self).__init__(parent=parent) + def __init__(self, manager: QtRequestManager, result: dict, parent=None): + super(SearchResultItem, self).__init__(manager, parent=parent) self.setFixedSize(ImageSize.Normal) self.ui.setupUi(self) for img in result["keyImages"]: if img["type"] in ["DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo"]: - self.fetchPixmap(img["url"], result["id"], result["title"]) + self.fetchPixmap(img["url"]) break else: print("No image found") diff --git a/rare/components/tabs/store/shop_api_core.py b/rare/components/tabs/store/shop_api_core.py index 906ef814..5d411b04 100644 --- a/rare/components/tabs/store/shop_api_core.py +++ b/rare/components/tabs/store/shop_api_core.py @@ -1,4 +1,3 @@ -import urllib.parse from logging import getLogger from PyQt5.QtCore import pyqtSignal, QObject @@ -10,31 +9,37 @@ from rare.components.tabs.store.constants import ( remove_from_wishlist_query, ) from rare.components.tabs.store.shop_models import BrowseModel +from rare.utils.paths import cache_dir from rare.utils.qt_requests import QtRequests logger = getLogger("ShopAPICore") -graphql_url = "https://www.epicgames.com/graphql" +graphql_url = "https://graphql.epicgames.com/graphql" class ShopApiCore(QObject): update_wishlist = pyqtSignal() - def __init__(self, auth_token, lc: str, cc: str): + def __init__(self, token, language: str, country: str): super(ShopApiCore, self).__init__() - self.token = auth_token - self.language_code: str = lc - self.country_code: str = cc + self.token = token + self.language_code: str = language + self.country_code: str = country self.locale = f"{self.language_code}-{self.country_code}" self.manager = QtRequests(parent=self) - self.auth_manager = QtRequests(token=auth_token, parent=self) + self.authed_manager = QtRequests(token=token, parent=self) + self.cached_manager = QtRequests(cache=str(cache_dir().joinpath("store")), parent=self) self.browse_active = False self.next_browse_request = tuple(()) def get_free_games(self, handle_func: callable): - url = f"https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale={self.language_code}&country={self.country_code}&allowCountries={self.country_code}" - - self.manager.get(url, lambda data: self._handle_free_games(data, handle_func)) + url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions" + params = { + "locale": self.locale, + "country": self.country_code, + "allowCountries": self.country_code, + } + self.manager.get(url, lambda data: self._handle_free_games(data, handle_func), params=params) def _handle_free_games(self, data, handle_func): try: @@ -50,14 +55,14 @@ class ShopApiCore(QObject): handle_func(results) def get_wishlist(self, handle_func): - self.auth_manager.post( + self.authed_manager.post( graphql_url, lambda data: self._handle_wishlist(data, handle_func), { "query": wishlist_query, "variables": { "country": self.country_code, - "locale": f"{self.language_code}-{self.country_code}", + "locale": self.locale, }, }, ) @@ -94,9 +99,7 @@ class ShopApiCore(QObject): }, } - self.manager.post( - graphql_url, lambda data: self._handle_search(data, handle_func), payload, - ) + self.manager.post(graphql_url, lambda data: self._handle_search(data, handle_func), payload) def _handle_search(self, data, handle_func): try: @@ -114,37 +117,11 @@ class ShopApiCore(QObject): self.next_browse_request = (browse_model, handle_func) return self.browse_active = True - url = "https://www.epicgames.com/graphql?operationName=searchStoreQuery&variables={}&extensions={}" - variables = urllib.parse.quote_plus(str( - dict(browse_model.__dict__)) - ) - extensions = urllib.parse.quote_plus(str( - dict( - persistedQuery=dict( - version=1, - sha256Hash="6e7c4dd0177150eb9a47d624be221929582df8648e7ec271c821838ff4ee148e" - ) - ) - ) - ) - - for old, new in [ - ("%26", "&"), - ("%27", "%22"), - ("+", ""), - ("%3A", ":"), - ("%2C", ","), - ("%5B", "["), - ("%5D", "]"), - ("True", "true"), - ]: - variables = variables.replace(old, new) - extensions = extensions.replace(old, new) - - url = url.format(variables, extensions) - self.auth_manager.get( - url, lambda data: self._handle_browse_games(data, handle_func) - ) + payload = { + "query": search_query, + "variables": browse_model.__dict__ + } + self.manager.post(graphql_url, lambda data: self._handle_browse_games(data, handle_func), payload) def _handle_browse_games(self, data, handle_func): self.browse_active = False @@ -188,11 +165,7 @@ class ShopApiCore(QObject): }, "query": add_to_wishlist_query, } - self.auth_manager.post( - graphql_url, - lambda data: self._handle_add_to_wishlist(data, handle_func), - payload, - ) + self.authed_manager.post(graphql_url, lambda data: self._handle_add_to_wishlist(data, handle_func), payload) def _handle_add_to_wishlist(self, data, handle_func): try: @@ -215,11 +188,8 @@ class ShopApiCore(QObject): }, "query": remove_from_wishlist_query, } - self.auth_manager.post( - graphql_url, - lambda data: self._handle_remove_from_wishlist(data, handle_func), - payload, - ) + self.authed_manager.post(graphql_url, lambda data: self._handle_remove_from_wishlist(data, handle_func), + payload) def _handle_remove_from_wishlist(self, data, handle_func): try: diff --git a/rare/components/tabs/store/shop_models.py b/rare/components/tabs/store/shop_models.py index d6bb0054..f124c317 100644 --- a/rare/components/tabs/store/shop_models.py +++ b/rare/components/tabs/store/shop_models.py @@ -1,6 +1,7 @@ import datetime from dataclasses import dataclass from typing import List, Dict +import epicstore_api.queries as egs_query class ImageUrlModel: @@ -158,22 +159,26 @@ class BrowseModel: price: str = "" onSale: bool = False + def __post_init__(self): + self.locale = f"{self.language_code}-{self.country_code}" + @property def __dict__(self): payload = { - "allowCountries": self.country_code, - "category": self.category, "count": self.count, - "country": self.country_code, - "keywords": self.keywords, - "locale": self.language_code, - "priceRange": self.price, - "releaseDate": self.date, + "category": self.category, + "allowCountries": self.country_code, + "namespace": "", "sortBy": "releaseDate", "sortDir": self.sortDir, "start": self.start, + "keywords": self.keywords, "tag": self.tag, + "priceRange": self.price, + "releaseDate": self.date, "withPrice": self.withPrice, + "locale": self.locale, + "country": self.country_code, } if self.price == "free": payload["freeGame"] = True @@ -187,5 +192,4 @@ class BrowseModel: payload["effectiveDate"] = self.date else: payload.pop("priceRange") - return payload diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index 20b3903b..f8cf9a85 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -9,15 +9,17 @@ from PyQt5.QtWidgets import ( QLabel, QPushButton, QHBoxLayout, - QWidget, QSizePolicy, + QWidget, QSizePolicy, QStackedLayout, ) from legendary.core import LegendaryCore from rare.ui.components.tabs.store.store import Ui_ShopWidget -from rare.utils.extra_widgets import WaitingSpinner, ButtonLineEdit +from rare.utils.extra_widgets import ButtonLineEdit from rare.widgets.flow_layout import FlowLayout +from rare.widgets.side_tab import SideTabContents from .constants import Constants from .game_widgets import GameWidget +from .image_widget import WaitingSpinner from .shop_api_core import ShopApiCore from .shop_models import BrowseModel @@ -25,14 +27,16 @@ logger = logging.getLogger("Shop") # noinspection PyAttributeOutsideInit,PyBroadException -class ShopWidget(QWidget, Ui_ShopWidget): +class ShopWidget(QWidget, SideTabContents): show_info = pyqtSignal(str) show_game = pyqtSignal(dict) - def __init__(self, path, core: LegendaryCore, shop_api: ShopApiCore, parent=None): + def __init__(self, cache_dir, core: LegendaryCore, shop_api: ShopApiCore, parent=None): super(ShopWidget, self).__init__(parent=parent) - self.setupUi(self) - self.path = path + self.implements_scrollarea = True + self.ui = Ui_ShopWidget() + self.ui.setupUi(self) + self.cache_dir = cache_dir self.core = core self.api_core = shop_api self.price = "" @@ -40,25 +44,39 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.types = [] self.update_games_allowed = True - self.free_scrollarea.setDisabled(True) + self.ui.free_scrollarea.setDisabled(True) self.free_game_widgets = [] self.active_search_request = False self.next_search = "" self.wishlist: List = [] - self.discount_widget.setLayout(FlowLayout(self.discount_widget)) - self.discount_stack.addWidget(WaitingSpinner(self.discount_stack)) - self.discount_stack.setCurrentIndex(1) + self.discounts_layout = QStackedLayout(self.ui.discounts_group) + self.discounts_spinner = WaitingSpinner(self.ui.discounts_group) + self.discounts_flow = QWidget(self.ui.discounts_group) + self.discounts_flow.setLayout(FlowLayout(self.discounts_flow)) + self.discounts_flow.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.discounts_layout.addWidget(self.discounts_spinner) + self.discounts_layout.addWidget(self.discounts_flow) - self.game_widget.setLayout(FlowLayout(self.game_widget)) - self.game_stack.addWidget(WaitingSpinner(self.game_stack)) - self.game_stack.setCurrentIndex(1) + self.discounts_spinner.start() + self.discounts_layout.setCurrentWidget(self.discounts_spinner) + + self.games_layout = QStackedLayout(self.ui.games_group) + self.games_spinner = WaitingSpinner(self.ui.games_group) + self.games_flow = QWidget(self.ui.games_group) + self.games_flow.setLayout(FlowLayout(self.games_flow)) + self.games_flow.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.games_layout.addWidget(self.games_spinner) + self.games_layout.addWidget(self.games_flow) + + self.games_spinner.start() + self.games_layout.setCurrentWidget(self.games_spinner) self.search_bar = ButtonLineEdit( "fa.search", placeholder_text=self.tr("Search Games") ) - self.left_layout.insertWidget(0, self.search_bar) + self.ui.left_layout.insertWidget(0, self.search_bar) # self.search_bar.textChanged.connect(self.search_games) @@ -85,22 +103,21 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.api_core.get_wishlist(self.add_wishlist_items) def add_wishlist_items(self, wishlist): - for i in range(self.discount_widget.layout().count()): - item = self.discount_widget.layout().itemAt(i) - if item: - item.widget().deleteLater() + for w in self.discounts_flow.findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): + self.discounts_flow.layout().removeWidget(w) + w.deleteLater() - if wishlist and wishlist[0] == "error": - self.discount_widget.layout().addWidget( - QLabel(self.tr("Failed to get wishlist: {}").format(wishlist[1])) - ) - btn = QPushButton(self.tr("Reload")) - self.discount_widget.layout().addWidget(btn) - btn.clicked.connect( - lambda: self.api_core.get_wishlist(self.add_wishlist_items) - ) - self.discount_stack.setCurrentIndex(0) - return + # if wishlist and wishlist[0] == "error": + # self.discounts_group.layout().addWidget( + # QLabel(self.tr("Failed to get wishlist: {}").format(wishlist[1])) + # ) + # btn = QPushButton(self.tr("Reload")) + # self.discount_widget.layout().addWidget(btn) + # btn.clicked.connect( + # lambda: self.api_core.get_wishlist(self.add_wishlist_items) + # ) + # self.discount_stack.setCurrentIndex(0) + # return discounts = 0 for game in wishlist: @@ -108,48 +125,48 @@ class ShopWidget(QWidget, Ui_ShopWidget): continue try: if game["offer"]["price"]["totalPrice"]["discount"] > 0: - w = GameWidget(self.path, game["offer"]) + w = GameWidget(self.api_core.cached_manager, game["offer"]) w.show_info.connect(self.show_game) - self.discount_widget.layout().addWidget(w) + self.discounts_flow.layout().addWidget(w) discounts += 1 except Exception as e: logger.warning(f"{game} {e}") continue - self.discounts_group.setVisible(discounts > 0) - self.discount_stack.setCurrentIndex(0) - # fix widget overlay - self.discount_widget.layout().update() + self.ui.discounts_group.setVisible(discounts > 0) + self.discounts_layout.setCurrentWidget(self.discounts_flow) + # FIXME: FlowLayout doesn't update on adding widget + self.discounts_flow.layout().update() def add_free_games(self, free_games: list): - for w in self.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): - self.free_container.layout().removeWidget(w) + for w in self.ui.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): + self.ui.free_container.layout().removeWidget(w) w.deleteLater() if free_games and free_games[0] == "error": - self.free_container.layout().addWidget( + self.ui.free_container.layout().addWidget( QLabel(self.tr("Failed to fetch free games: {}").format(free_games[1])) ) btn = QPushButton(self.tr("Reload")) - self.free_container.layout().addWidget(btn) + self.ui.free_container.layout().addWidget(btn) btn.clicked.connect( lambda: self.api_core.get_free_games(self.add_free_games) ) - self.free_container.setEnabled(True) + self.ui.free_container.setEnabled(True) return - self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.free_container) + self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.ui.free_container) free_games_now_layout = QHBoxLayout(self.free_games_now) # free_games_now_layout.setContentsMargins(0, 0, 0, 0) - self.free_games_now.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.free_games_now.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.free_games_now.setLayout(free_games_now_layout) - self.free_container.layout().addWidget(self.free_games_now) + self.ui.free_container.layout().addWidget(self.free_games_now) - self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.free_container) + self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.ui.free_container) free_games_next_layout = QHBoxLayout(self.free_games_next) # free_games_next_layout.setContentsMargins(0, 0, 0, 0) - self.free_games_next.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.free_games_next.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.free_games_next.setLayout(free_games_next_layout) - self.free_container.layout().addWidget(self.free_games_next) + self.ui.free_container.layout().addWidget(self.free_games_next) date = datetime.datetime.now() free_games_now = [] @@ -200,7 +217,7 @@ class ShopWidget(QWidget, Ui_ShopWidget): # free games now now_free = 0 for free_game in free_games_now: - w = GameWidget(self.path, free_game) + w = GameWidget(self.api_core.cached_manager, free_game) w.show_info.connect(self.show_game) self.free_games_now.layout().addWidget(w) self.free_game_widgets.append(w) @@ -212,64 +229,64 @@ class ShopWidget(QWidget, Ui_ShopWidget): # free games next week for free_game in coming_free_games: - w = GameWidget(self.path, free_game) + w = GameWidget(self.api_core.cached_manager, free_game) if free_game["title"] != "Mystery Game": w.show_info.connect(self.show_game) self.free_games_next.layout().addWidget(w) # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) - self.free_scrollarea.setMinimumHeight( + self.ui.free_scrollarea.setMinimumHeight( self.free_games_now.sizeHint().height() - + self.free_container.layout().contentsMargins().top() - + self.free_container.layout().contentsMargins().bottom() - + self.free_scrollarea.horizontalScrollBar().sizeHint().height() + + self.ui.free_container.layout().contentsMargins().top() + + self.ui.free_container.layout().contentsMargins().bottom() + + self.ui.free_scrollarea.horizontalScrollBar().sizeHint().height() ) - self.free_scrollarea.setEnabled(True) + self.ui.free_scrollarea.setEnabled(True) def show_search_results(self): if self.search_bar.text(): self.show_info.emit(self.search_bar.text()) def init_filter(self): - self.none_price.toggled.connect( - lambda: self.prepare_request("") if self.none_price.isChecked() else None + self.ui.none_price.toggled.connect( + lambda: self.prepare_request("") if self.ui.none_price.isChecked() else None ) - self.free_button.toggled.connect( + self.ui.free_button.toggled.connect( lambda: self.prepare_request("free") - if self.free_button.isChecked() + if self.ui.free_button.isChecked() else None ) - self.under10.toggled.connect( + self.ui.under10.toggled.connect( lambda: self.prepare_request("[0, 1000)") - if self.under10.isChecked() + if self.ui.under10.isChecked() else None ) - self.under20.toggled.connect( + self.ui.under20.toggled.connect( lambda: self.prepare_request("[0, 2000)") - if self.under20.isChecked() + if self.ui.under20.isChecked() else None ) - self.under30.toggled.connect( + self.ui.under30.toggled.connect( lambda: self.prepare_request("[0, 3000)") - if self.under30.isChecked() + if self.ui.under30.isChecked() else None ) - self.above.toggled.connect( + self.ui.above.toggled.connect( lambda: self.prepare_request("[1499,]") - if self.above.isChecked() + if self.ui.above.isChecked() else None ) # self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None) - self.on_discount.toggled.connect(lambda: self.prepare_request()) + self.ui.on_discount.toggled.connect(lambda: self.prepare_request()) constants = Constants() self.checkboxes = [] for groupbox, variables in [ - (self.genre_group, constants.categories), - (self.platform_group, constants.platforms), - (self.others_group, constants.others), - (self.type_group, constants.types), + (self.ui.genre_group, constants.categories), + (self.ui.platform_group, constants.platforms), + (self.ui.others_group, constants.others), + (self.ui.type_group, constants.types), ]: for text, tag in variables: @@ -280,26 +297,26 @@ class ShopWidget(QWidget, Ui_ShopWidget): ) groupbox.layout().addWidget(checkbox) self.checkboxes.append(checkbox) - self.reset_button.clicked.connect(self.reset_filters) - self.filter_scrollarea.setMinimumWidth( - self.filter_container.sizeHint().width() - + self.filter_container.layout().contentsMargins().left() - + self.filter_container.layout().contentsMargins().right() - + self.filter_scrollarea.verticalScrollBar().sizeHint().width() + self.ui.reset_button.clicked.connect(self.reset_filters) + self.ui.filter_scrollarea.setMinimumWidth( + self.ui.filter_container.sizeHint().width() + + self.ui.filter_container.layout().contentsMargins().left() + + self.ui.filter_container.layout().contentsMargins().right() + + self.ui.filter_scrollarea.verticalScrollBar().sizeHint().width() ) def reset_filters(self): self.update_games_allowed = False for cb in self.checkboxes: cb.setChecked(False) - self.none_price.setChecked(True) + self.ui.none_price.setChecked(True) self.tags = [] self.types = [] self.update_games_allowed = True self.prepare_request("") - self.on_discount.setChecked(False) + self.ui.on_discount.setChecked(False) def prepare_request( self, @@ -323,22 +340,22 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.types.append(added_type) if removed_type and removed_type in self.types: self.types.remove(removed_type) - if (self.types or self.price) or self.tags or self.on_discount.isChecked(): - self.free_scrollarea.setVisible(False) - self.discounts_group.setVisible(False) + if (self.types or self.price) or self.tags or self.ui.on_discount.isChecked(): + self.ui.free_scrollarea.setVisible(False) + self.ui.discounts_group.setVisible(False) else: - self.free_scrollarea.setVisible(True) - if len(self.discounts_group.layout().children()) > 0: - self.discounts_group.setVisible(True) + self.ui.free_scrollarea.setVisible(True) + if len(self.ui.discounts_group.layout().children()) > 0: + self.ui.discounts_group.setVisible(True) - self.game_stack.setCurrentIndex(1) + self.games_layout.setCurrentWidget(self.games_spinner) browse_model = BrowseModel( language_code=self.core.language_code, country_code=self.core.country_code, count=20, price=self.price, - onSale=self.on_discount.isChecked(), + onSale=self.ui.on_discount.isChecked(), ) browse_model.tag = "|".join(self.tags) @@ -347,24 +364,22 @@ class ShopWidget(QWidget, Ui_ShopWidget): self.api_core.browse_games(browse_model, self.show_games) def show_games(self, data): - for item in ( - self.game_widget.layout().itemAt(i) - for i in range(self.game_widget.layout().count()) - ): - item.widget().deleteLater() + for w in self.games_flow.layout().findChildren(GameWidget, options=Qt.FindDirectChildrenOnly): + self.games_flow.layout().removeWidget(w) + w.deleteLater() + if data: for game in data: - w = GameWidget(self.path, game) - self.game_widget.layout().addWidget(w) + w = GameWidget(self.api_core.cached_manager, game) w.show_info.connect(self.show_game.emit) - + self.games_flow.layout().addWidget(w) else: - self.game_widget.layout().addWidget( + self.games_flow.layout().addWidget( QLabel(self.tr("Could not get games matching the filter")) ) - self.game_stack.setCurrentIndex(0) - - self.game_widget.layout().update() + self.games_layout.setCurrentWidget(self.games_flow) + # FIXME: FlowLayout doesn't update on adding widget + self.games_flow.layout().update() class CheckBox(QCheckBox): diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 06a2d41d..0da63f44 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -1,9 +1,10 @@ -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QMessageBox, QWidget from rare.ui.components.tabs.store.wishlist import Ui_Wishlist from rare.utils.misc import icon from rare.widgets.side_tab import SideTabContents +from rare.widgets.flow_layout import FlowLayout from .shop_api_core import ShopApiCore from .game_widgets import WishlistWidget @@ -14,6 +15,7 @@ class Wishlist(QWidget, SideTabContents): def __init__(self, api_core: ShopApiCore, parent=None): super(Wishlist, self).__init__(parent=parent) + self.implements_scrollarea = True self.api_core = api_core self.ui = Ui_Wishlist() self.ui.setupUi(self) @@ -21,20 +23,19 @@ class Wishlist(QWidget, SideTabContents): self.wishlist = [] self.widgets = [] - self.ui.sort_cb.currentIndexChanged.connect( - lambda i: self.set_wishlist(self.wishlist, i) - ) + self.list_layout = FlowLayout(self.ui.list_container) + + self.ui.sort_cb.currentIndexChanged.connect(self.sort_wishlist) self.ui.filter_cb.currentIndexChanged.connect(self.set_filter) self.ui.reload_button.clicked.connect(self.update_wishlist) self.ui.reload_button.setIcon(icon("fa.refresh", color="white")) self.ui.reverse.stateChanged.connect( - lambda: self.set_wishlist(sort=self.ui.sort_cb.currentIndex()) + lambda: self.sort_wishlist(sort=self.ui.sort_cb.currentIndex()) ) def update_wishlist(self): self.setEnabled(False) - self.set_title.emit("Wishlist") self.api_core.get_wishlist(self.set_wishlist) def delete_from_wishlist(self, game): @@ -66,6 +67,32 @@ class Wishlist(QWidget, SideTabContents): else: self.ui.no_games_label.setVisible(False) + def sort_wishlist(self, sort=0): + widgets = self.ui.list_container.findChildren(WishlistWidget, options=Qt.FindDirectChildrenOnly) + for w in widgets: + self.ui.list_container.layout().removeWidget(w) + + if sort == 0: + func = lambda x: x.game["title"] + reverse = self.ui.reverse.isChecked() + elif sort == 1: + func = lambda x: x.game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + reverse = self.ui.reverse.isChecked() + elif sort == 2: + func = lambda x: x.game["seller"]["name"] + reverse = self.ui.reverse.isChecked() + elif sort == 3: + func = lambda x: 1 - (x.game["price"]["totalPrice"]["discountPrice"] / x.game["price"]["totalPrice"]["originalPrice"]) + reverse = not self.ui.reverse.isChecked() + else: + func = lambda x: x.game["title"] + reverse = self.ui.reverse.isChecked() + + widgets = sorted(widgets, key=func, reverse=reverse) + for w in widgets: + self.ui.list_container.layout().addWidget(w) + + def set_wishlist(self, wishlist=None, sort=0): if wishlist and wishlist[0] == "error": return @@ -76,45 +103,18 @@ class Wishlist(QWidget, SideTabContents): for i in self.widgets: i.deleteLater() - if sort == 0: - sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["title"]) - elif sort == 1: - sorted_list = sorted( - self.wishlist, - key=lambda x: x["offer"]["price"]["totalPrice"]["fmtPrice"][ - "discountPrice" - ], - ) - elif sort == 2: - sorted_list = sorted( - self.wishlist, key=lambda x: x["offer"]["seller"]["name"] - ) - elif sort == 3: - sorted_list = sorted( - self.wishlist, - reverse=True, - key=lambda x: 1 - - ( - x["offer"]["price"]["totalPrice"]["discountPrice"] - / x["offer"]["price"]["totalPrice"]["originalPrice"] - ), - ) - else: - sorted_list = self.wishlist self.widgets.clear() - if len(sorted_list) == 0: + if len(wishlist) == 0: self.ui.no_games_label.setVisible(True) else: self.ui.no_games_label.setVisible(False) - if self.ui.reverse.isChecked(): - sorted_list.reverse() - - for game in sorted_list: - w = WishlistWidget(game["offer"]) - self.widgets.append(w) - self.ui.list_layout.addWidget(w) + for game in wishlist: + w = WishlistWidget(self.api_core.cached_manager, game["offer"], self.ui.list_container) w.open_game.connect(self.show_game_info.emit) w.delete_from_wishlist.connect(self.delete_from_wishlist) + self.widgets.append(w) + self.list_layout.addWidget(w) + self.list_layout.update() self.setEnabled(True) diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py index 839506e1..f0c8b720 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopInfo(object): def setupUi(self, ShopInfo): ShopInfo.setObjectName("ShopInfo") - ShopInfo.resize(434, 250) + ShopInfo.resize(747, 442) ShopInfo.setWindowTitle("ShopGameInfo") self.main_layout = QtWidgets.QHBoxLayout(ShopInfo) self.main_layout.setObjectName("main_layout") diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/shop_game_info.ui index 382dcf92..802e6fd2 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -6,8 +6,8 @@ 0 0 - 434 - 250 + 747 + 442 diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py index 3fe0fa68..90c13f3b 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/store.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopWidget(object): def setupUi(self, ShopWidget): ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(750, 588) + ShopWidget.resize(350, 382) ShopWidget.setWindowTitle("Store") self.shop_layout = QtWidgets.QHBoxLayout(ShopWidget) self.shop_layout.setObjectName("shop_layout") @@ -27,7 +27,7 @@ class Ui_ShopWidget(object): self.games_scrollarea.setWidgetResizable(True) self.games_scrollarea.setObjectName("games_scrollarea") self.games_container = QtWidgets.QWidget() - self.games_container.setGeometry(QtCore.QRect(0, 0, 583, 574)) + self.games_container.setGeometry(QtCore.QRect(0, 0, 186, 368)) self.games_container.setObjectName("games_container") self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) self.games_container_layout.setContentsMargins(0, 0, 3, 0) @@ -44,7 +44,12 @@ class Ui_ShopWidget(object): self.free_scrollarea.setWidgetResizable(True) self.free_scrollarea.setObjectName("free_scrollarea") self.free_container = QtWidgets.QWidget() - self.free_container.setGeometry(QtCore.QRect(0, 0, 580, 83)) + self.free_container.setGeometry(QtCore.QRect(0, 0, 16, 16)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.free_container.sizePolicy().hasHeightForWidth()) + self.free_container.setSizePolicy(sizePolicy) self.free_container.setObjectName("free_container") self.free_container_layout = QtWidgets.QHBoxLayout(self.free_container) self.free_container_layout.setContentsMargins(0, 0, 0, 3) @@ -53,25 +58,9 @@ class Ui_ShopWidget(object): self.games_container_layout.addWidget(self.free_scrollarea) self.discounts_group = QtWidgets.QGroupBox(self.games_container) self.discounts_group.setObjectName("discounts_group") - self.discounts_layout = QtWidgets.QVBoxLayout(self.discounts_group) - self.discounts_layout.setObjectName("discounts_layout") - self.discount_stack = QtWidgets.QStackedWidget(self.discounts_group) - self.discount_stack.setObjectName("discount_stack") - self.discount_widget = QtWidgets.QWidget() - self.discount_widget.setObjectName("discount_widget") - self.discount_stack.addWidget(self.discount_widget) - self.discounts_layout.addWidget(self.discount_stack) self.games_container_layout.addWidget(self.discounts_group) self.games_group = QtWidgets.QGroupBox(self.games_container) self.games_group.setObjectName("games_group") - self.games_layout = QtWidgets.QVBoxLayout(self.games_group) - self.games_layout.setObjectName("games_layout") - self.game_stack = QtWidgets.QStackedWidget(self.games_group) - self.game_stack.setObjectName("game_stack") - self.game_widget = QtWidgets.QWidget() - self.game_widget.setObjectName("game_widget") - self.game_stack.addWidget(self.game_widget) - self.games_layout.addWidget(self.game_stack) self.games_container_layout.addWidget(self.games_group) self.games_scrollarea.setWidget(self.games_container) self.left_layout.addWidget(self.games_scrollarea) @@ -94,7 +83,12 @@ class Ui_ShopWidget(object): self.filter_scrollarea.setWidgetResizable(True) self.filter_scrollarea.setObjectName("filter_scrollarea") self.filter_container = QtWidgets.QWidget() - self.filter_container.setGeometry(QtCore.QRect(0, 0, 145, 542)) + self.filter_container.setGeometry(QtCore.QRect(0, 0, 142, 390)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.filter_container.sizePolicy().hasHeightForWidth()) + self.filter_container.setSizePolicy(sizePolicy) self.filter_container.setObjectName("filter_container") self.filter_container_layout = QtWidgets.QVBoxLayout(self.filter_container) self.filter_container_layout.setContentsMargins(0, 0, 3, 0) diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui index c5350030..90ccdfa8 100644 --- a/rare/ui/components/tabs/store/store.ui +++ b/rare/ui/components/tabs/store/store.ui @@ -6,8 +6,8 @@ 0 0 - 750 - 588 + 679 + 329 @@ -35,8 +35,8 @@ 0 0 - 583 - 574 + 515 + 315 @@ -77,10 +77,16 @@ 0 0 - 580 - 83 + 16 + 16 + + + 0 + 0 + + 0 @@ -103,13 +109,6 @@ Discounts from your wishlist - - - - - - - @@ -117,13 +116,6 @@ Games - - - - - - - @@ -169,10 +161,16 @@ 0 0 - 145 - 542 + 142 + 390 + + + 0 + 0 + + 0 diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index 4a0fb0a2..66c042f6 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -14,12 +14,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Wishlist(object): def setupUi(self, Wishlist): Wishlist.setObjectName("Wishlist") - Wishlist.resize(527, 328) + Wishlist.resize(423, 153) Wishlist.setWindowTitle("Wishlist") self.verticalLayout = QtWidgets.QVBoxLayout(Wishlist) self.verticalLayout.setObjectName("verticalLayout") - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.tool_layout = QtWidgets.QHBoxLayout() + self.tool_layout.setObjectName("tool_layout") self.sort_label = QtWidgets.QLabel(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) @@ -27,29 +27,29 @@ class Ui_Wishlist(object): sizePolicy.setHeightForWidth(self.sort_label.sizePolicy().hasHeightForWidth()) self.sort_label.setSizePolicy(sizePolicy) self.sort_label.setObjectName("sort_label") - self.horizontalLayout.addWidget(self.sort_label) + self.tool_layout.addWidget(self.sort_label) self.sort_cb = QtWidgets.QComboBox(Wishlist) self.sort_cb.setObjectName("sort_cb") self.sort_cb.addItem("") self.sort_cb.addItem("") self.sort_cb.addItem("") self.sort_cb.addItem("") - self.horizontalLayout.addWidget(self.sort_cb) + self.tool_layout.addWidget(self.sort_cb) self.reverse = QtWidgets.QCheckBox(Wishlist) self.reverse.setObjectName("reverse") - self.horizontalLayout.addWidget(self.reverse) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem) + self.tool_layout.addWidget(self.reverse) + spacerItem = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.tool_layout.addItem(spacerItem) self.filter_label = QtWidgets.QLabel(Wishlist) self.filter_label.setObjectName("filter_label") - self.horizontalLayout.addWidget(self.filter_label) + self.tool_layout.addWidget(self.filter_label) self.filter_cb = QtWidgets.QComboBox(Wishlist) self.filter_cb.setObjectName("filter_cb") self.filter_cb.addItem("") self.filter_cb.addItem("") - self.horizontalLayout.addWidget(self.filter_cb) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem1) + self.tool_layout.addWidget(self.filter_cb) + spacerItem1 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.tool_layout.addItem(spacerItem1) self.reload_button = QtWidgets.QPushButton(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -58,16 +58,19 @@ class Ui_Wishlist(object): self.reload_button.setSizePolicy(sizePolicy) self.reload_button.setText("") self.reload_button.setObjectName("reload_button") - self.horizontalLayout.addWidget(self.reload_button) - self.verticalLayout.addLayout(self.horizontalLayout) - self.list_layout = QtWidgets.QVBoxLayout() - self.list_layout.setObjectName("list_layout") - self.verticalLayout.addLayout(self.list_layout) + self.tool_layout.addWidget(self.reload_button) + self.verticalLayout.addLayout(self.tool_layout) self.no_games_label = QtWidgets.QLabel(Wishlist) self.no_games_label.setObjectName("no_games_label") self.verticalLayout.addWidget(self.no_games_label) - spacerItem2 = QtWidgets.QSpacerItem(379, 218, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem2) + self.list_scrollarea = QtWidgets.QScrollArea(Wishlist) + self.list_scrollarea.setWidgetResizable(True) + self.list_scrollarea.setObjectName("list_scrollarea") + self.list_container = QtWidgets.QWidget() + self.list_container.setGeometry(QtCore.QRect(0, 0, 407, 83)) + self.list_container.setObjectName("list_container") + self.list_scrollarea.setWidget(self.list_container) + self.verticalLayout.addWidget(self.list_scrollarea) self.retranslateUi(Wishlist) diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index a9560ec0..b28a8ce0 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -6,8 +6,8 @@ 0 0 - 527 - 328 + 423 + 153 @@ -15,7 +15,7 @@ - + @@ -61,14 +61,14 @@ - + Qt::Horizontal - 40 - 20 + 0 + 0 @@ -95,14 +95,14 @@ - + Qt::Horizontal - 40 - 20 + 0 + 0 @@ -122,9 +122,6 @@ - - - @@ -133,17 +130,21 @@ - - - Qt::Vertical + + + true - - - 379 - 218 - - - + + + + 0 + 0 + 407 + 83 + + + + diff --git a/rare/ui/components/tabs/store/wishlist_widget.py b/rare/ui/components/tabs/store/wishlist_widget.py index 47119608..7ea93faf 100644 --- a/rare/ui/components/tabs/store/wishlist_widget.py +++ b/rare/ui/components/tabs/store/wishlist_widget.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist_widget.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,20 +14,26 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_WishlistWidget(object): def setupUi(self, WishlistWidget): WishlistWidget.setObjectName("WishlistWidget") - WishlistWidget.resize(523, 172) + WishlistWidget.resize(202, 94) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(WishlistWidget.sizePolicy().hasHeightForWidth()) + WishlistWidget.setSizePolicy(sizePolicy) WishlistWidget.setWindowTitle("WishlistWIdget") - self.horizontalLayout = QtWidgets.QHBoxLayout(WishlistWidget) - self.horizontalLayout.setObjectName("horizontalLayout") - self.widget = QtWidgets.QWidget(WishlistWidget) + self.main_layout = QtWidgets.QHBoxLayout(WishlistWidget) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setObjectName("main_layout") + self.info_widget = QtWidgets.QWidget(WishlistWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) - self.widget.setSizePolicy(sizePolicy) - self.widget.setObjectName("widget") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget) + sizePolicy.setHeightForWidth(self.info_widget.sizePolicy().hasHeightForWidth()) + self.info_widget.setSizePolicy(sizePolicy) + self.info_widget.setObjectName("info_widget") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.info_widget) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.title_label = QtWidgets.QLabel(self.widget) + self.title_label = QtWidgets.QLabel(self.info_widget) font = QtGui.QFont() font.setPointSize(16) self.title_label.setFont(font) @@ -35,16 +41,16 @@ class Ui_WishlistWidget(object): self.title_label.setWordWrap(True) self.title_label.setObjectName("title_label") self.verticalLayout_2.addWidget(self.title_label) - self.developer = QtWidgets.QLabel(self.widget) + self.developer = QtWidgets.QLabel(self.info_widget) font = QtGui.QFont() font.setPointSize(12) self.developer.setFont(font) self.developer.setText("TextLabel") self.developer.setObjectName("developer") self.verticalLayout_2.addWidget(self.developer) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.discount_price = QtWidgets.QLabel(self.widget) + self.price_layout = QtWidgets.QHBoxLayout() + self.price_layout.setObjectName("price_layout") + self.discount_price = QtWidgets.QLabel(self.info_widget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -52,15 +58,13 @@ class Ui_WishlistWidget(object): self.discount_price.setSizePolicy(sizePolicy) self.discount_price.setText("TextLabel") self.discount_price.setObjectName("discount_price") - self.horizontalLayout_2.addWidget(self.discount_price) - self.price = QtWidgets.QLabel(self.widget) + self.price_layout.addWidget(self.discount_price) + self.price = QtWidgets.QLabel(self.info_widget) self.price.setText("TextLabel") self.price.setObjectName("price") - self.horizontalLayout_2.addWidget(self.price) - self.verticalLayout_2.addLayout(self.horizontalLayout_2) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_2.addItem(spacerItem) - self.horizontalLayout.addWidget(self.widget) + self.price_layout.addWidget(self.price) + self.verticalLayout_2.addLayout(self.price_layout) + self.main_layout.addWidget(self.info_widget, 0, QtCore.Qt.AlignTop) self.delete_button = QtWidgets.QPushButton(WishlistWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) @@ -69,7 +73,7 @@ class Ui_WishlistWidget(object): self.delete_button.setSizePolicy(sizePolicy) self.delete_button.setText("") self.delete_button.setObjectName("delete_button") - self.horizontalLayout.addWidget(self.delete_button) + self.main_layout.addWidget(self.delete_button) self.retranslateUi(WishlistWidget) diff --git a/rare/ui/components/tabs/store/wishlist_widget.ui b/rare/ui/components/tabs/store/wishlist_widget.ui index 63dc3640..5acbac6f 100644 --- a/rare/ui/components/tabs/store/wishlist_widget.ui +++ b/rare/ui/components/tabs/store/wishlist_widget.ui @@ -6,16 +6,34 @@ 0 0 - 523 - 172 + 202 + 94 + + + 0 + 0 + + WishlistWIdget - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 @@ -51,7 +69,7 @@ - + @@ -74,19 +92,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - From 91af16b76d2f1a77c94e73e50b5369d5a712a1c7 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:03:45 +0300 Subject: [PATCH 10/21] Store: Exploratory changes for GraphQL API --- rare/components/tabs/store/__init__.py | 3 +- rare/components/tabs/store/__main__.py | 2 +- rare/components/tabs/store/api/__init__.py | 0 .../tabs/store/api/constants/__init__.py | 0 .../tabs/store/api/constants/queries.py | 568 +++++++++++++ rare/components/tabs/store/api/debug.py | 29 + .../tabs/store/api/graphql/.graphqlconfig | 15 + .../tabs/store/api/graphql/schema.graphql | 41 + .../tabs/store/api/models/__init__.py | 0 .../tabs/store/api/models/diesel.py | 164 ++++ .../components/tabs/store/api/models/query.py | 80 ++ .../tabs/store/api/models/response.py | 589 +++++++++++++ .../components/tabs/store/api/models/utils.py | 5 + rare/components/tabs/store/constants.py | 786 ++++++++---------- rare/components/tabs/store/game_info.py | 166 ++-- rare/components/tabs/store/game_widgets.py | 88 +- rare/components/tabs/store/search_results.py | 34 +- rare/components/tabs/store/shop_api_core.py | 136 ++- rare/components/tabs/store/shop_models.py | 195 ----- rare/components/tabs/store/shop_widget.py | 43 +- rare/components/tabs/store/wishlist.py | 28 +- .../components/tabs/store/shop_game_info.py | 45 +- .../components/tabs/store/shop_game_info.ui | 51 +- rare/utils/qt_requests.py | 2 +- requirements-dev.txt | 1 + requirements-full.txt | 1 + requirements.txt | 1 + 27 files changed, 2230 insertions(+), 843 deletions(-) create mode 100644 rare/components/tabs/store/api/__init__.py create mode 100644 rare/components/tabs/store/api/constants/__init__.py create mode 100644 rare/components/tabs/store/api/constants/queries.py create mode 100644 rare/components/tabs/store/api/debug.py create mode 100644 rare/components/tabs/store/api/graphql/.graphqlconfig create mode 100644 rare/components/tabs/store/api/graphql/schema.graphql create mode 100644 rare/components/tabs/store/api/models/__init__.py create mode 100644 rare/components/tabs/store/api/models/diesel.py create mode 100644 rare/components/tabs/store/api/models/query.py create mode 100644 rare/components/tabs/store/api/models/response.py create mode 100644 rare/components/tabs/store/api/models/utils.py delete mode 100644 rare/components/tabs/store/shop_models.py diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index 817e1acd..35b5b6a2 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -6,6 +6,7 @@ from rare.widgets.side_tab import SideTabWidget from .game_info import ShopGameInfo from .search_results import SearchResults from .shop_api_core import ShopApiCore +from .api.models.response import CatalogOfferModel from .shop_widget import ShopWidget from .wishlist import WishlistWidget, Wishlist @@ -69,7 +70,7 @@ class StoreTab(SideTabWidget): def update_wishlist(self): self.shop.update_wishlist() - def show_game(self, data): + def show_game(self, data: CatalogOfferModel): self.previous_index = self.currentIndex() self.info.update_game(data) self.setCurrentIndex(self.info_index) diff --git a/rare/components/tabs/store/__main__.py b/rare/components/tabs/store/__main__.py index 29d9acfd..a7366a13 100644 --- a/rare/components/tabs/store/__main__.py +++ b/rare/components/tabs/store/__main__.py @@ -35,4 +35,4 @@ if __name__ == "__main__": window.setWindowTitle(f"{app.applicationName()} - Store") window.resize(QSize(1280, 800)) window.show() - app.exec() \ No newline at end of file + app.exec() diff --git a/rare/components/tabs/store/api/__init__.py b/rare/components/tabs/store/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/components/tabs/store/api/constants/__init__.py b/rare/components/tabs/store/api/constants/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/components/tabs/store/api/constants/queries.py b/rare/components/tabs/store/api/constants/queries.py new file mode 100644 index 00000000..5e5d9919 --- /dev/null +++ b/rare/components/tabs/store/api/constants/queries.py @@ -0,0 +1,568 @@ + +FEED_QUERY = ''' +query feedQuery( + $locale: String! + $countryCode: String + $offset: Int + $postsPerPage: Int + $category: String +) { + TransientStream { + myTransientFeed(countryCode: $countryCode, locale: $locale) { + id + activity { + ... on LinkAccountActivity { + type + created_at + platforms + } + ... on SuggestedFriendsActivity { + type + created_at + platform + suggestions { + epicId + epicDisplayName + platformFullName + platformAvatar + } + } + ... on IncomingInvitesActivity { + type + created_at + invites { + epicId + epicDisplayName + } + } + ... on RecentPlayersActivity { + type + created_at + players { + epicId + epicDisplayName + playedGameName + } + } + } + } + } + Blog { + dieselBlogPosts: getPosts( + locale: $locale + offset: $offset + postsPerPage: $postsPerPage + category: $category + ) { + blogList { + _id + author + category + content + urlPattern + slug + sticky + title + date + image + shareImage + trendingImage + url + featured + link + externalLink + } + } + } +} +''' + +REVIEWS_QUERY = ''' +query productReviewsQuery($sku: String!) { + OpenCritic { + productReviews(sku: $sku) { + id + name + openCriticScore + reviewCount + percentRecommended + openCriticUrl + award + topReviews { + publishedDate + externalUrl + snippet + language + score + author + ScoreFormat { + id + description + } + OutletId + outletName + displayScore + } + } + } +} +''' + +MEDIA_QUERY = ''' +query fetchMediaRef($mediaRefId: String!) { + Media { + getMediaRef(mediaRefId: $mediaRefId) { + accountId + outputs { + duration + url + width + height + key + contentType + } + namespace + } + } +} +''' + +ADDONS_QUERY = ''' +query getAddonsByNamespace( + $categories: String! + $count: Int! + $country: String! + $locale: String! + $namespace: String! + $sortBy: String! + $sortDir: String! +) { + Catalog { + catalogOffers( + namespace: $namespace + locale: $locale + params: { + category: $categories + count: $count + country: $country + sortBy: $sortBy + sortDir: $sortDir + } + ) { + elements { + countriesBlacklist + customAttributes { + key + value + } + description + developer + effectiveDate + id + isFeatured + keyImages { + type + url + } + lastModifiedDate + longDescription + namespace + offerType + productSlug + releaseDate + status + technicalDetails + title + urlSlug + } + } + } +} +''' + +CATALOG_QUERY = ''' +query catalogQuery( + $category: String + $count: Int + $country: String! + $keywords: String + $locale: String + $namespace: String! + $sortBy: String + $sortDir: String + $start: Int + $tag: String +) { + Catalog { + catalogOffers( + namespace: $namespace + locale: $locale + params: { + count: $count + country: $country + category: $category + keywords: $keywords + sortBy: $sortBy + sortDir: $sortDir + start: $start + tag: $tag + } + ) { + elements { + isFeatured + collectionOfferIds + title + id + namespace + description + keyImages { + type + url + } + seller { + id + name + } + productSlug + urlSlug + items { + id + namespace + } + customAttributes { + key + value + } + categories { + path + } + price(country: $country) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + } + } + } + linkedOfferId + linkedOffer { + effectiveDate + customAttributes { + key + value + } + } + } + paging { + count + total + } + } + } +} +''' + +CATALOG_TAGS_QUERY = ''' +query catalogTags($namespace: String!) { + Catalog { + tags(namespace: $namespace, start: 0, count: 999) { + elements { + aliases + id + name + referenceCount + status + } + } + } +} +''' + +PREREQUISITES_QUERY = ''' +query fetchPrerequisites($offerParams: [OfferParams]) { + Launcher { + prerequisites(offerParams: $offerParams) { + namespace + offerId + missingPrerequisiteItems + satisfiesPrerequisites + } + } +} +''' + +PROMOTIONS_QUERY = ''' +query promotionsQuery( + $namespace: String! + $country: String! + $locale: String! +) { + Catalog { + catalogOffers( + namespace: $namespace + locale: $locale + params: { + category: "freegames" + country: $country + sortBy: "effectiveDate" + sortDir: "asc" + } + ) { + elements { + title + description + id + namespace + categories { + path + } + linkedOfferNs + linkedOfferId + keyImages { + type + url + } + productSlug + promotions { + promotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + upcomingPromotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + } + } + } + } +} +''' + +OFFERS_QUERY = ''' +query catalogQuery( + $productNamespace: String! + $offerId: String! + $locale: String + $country: String! + $includeSubItems: Boolean! +) { + Catalog { + catalogOffer(namespace: $productNamespace, id: $offerId, locale: $locale) { + title + id + namespace + description + effectiveDate + expiryDate + isCodeRedemptionOnly + keyImages { + type + url + } + seller { + id + name + } + productSlug + urlSlug + url + tags { + id + } + items { + id + namespace + } + customAttributes { + key + value + } + categories { + path + } + price(country: $country) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + currencyCode + currencyInfo { + decimals + } + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + discountSetting { + discountType + } + } + } + } + } + offerSubItems(namespace: $productNamespace, id: $offerId) + @include(if: $includeSubItems) { + namespace + id + releaseInfo { + appId + platform + } + } + } +} +''' + +SEARCH_STORE_QUERY = ''' +query searchStoreQuery( + $allowCountries: String + $category: String + $count: Int + $country: String! + $keywords: String + $locale: String + $namespace: String + $itemNs: String + $sortBy: String + $sortDir: String + $start: Int + $tag: String + $releaseDate: String + $withPrice: Boolean = false + $withPromotions: Boolean = false +) { + Catalog { + searchStore( + allowCountries: $allowCountries + category: $category + count: $count + country: $country + keywords: $keywords + locale: $locale + namespace: $namespace + itemNs: $itemNs + sortBy: $sortBy + sortDir: $sortDir + releaseDate: $releaseDate + start: $start + tag: $tag + ) { + elements { + title + id + namespace + description + effectiveDate + keyImages { + type + url + } + seller { + id + name + } + productSlug + urlSlug + url + tags { + id + } + items { + id + namespace + } + customAttributes { + key + value + } + categories { + path + } + price(country: $country) @include(if: $withPrice) { + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + currencyCode + currencyInfo { + decimals + } + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + discountSetting { + discountType + } + } + } + } + promotions(category: $category) @include(if: $withPromotions) { + promotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + upcomingPromotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } + } + } + } + paging { + count + total + } + } + } +} +''' \ No newline at end of file diff --git a/rare/components/tabs/store/api/debug.py b/rare/components/tabs/store/api/debug.py new file mode 100644 index 00000000..083fcca2 --- /dev/null +++ b/rare/components/tabs/store/api/debug.py @@ -0,0 +1,29 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QTreeView, QDialog, QVBoxLayout + +from utils.json_formatter import QJsonModel + + +class DebugView(QTreeView): + def __init__(self, data, parent=None): + super(DebugView, self).__init__(parent=parent) + self.setColumnWidth(0, 300) + self.setWordWrap(True) + self.model = QJsonModel(self) + self.setModel(self.model) + self.setContextMenuPolicy(Qt.ActionsContextMenu) + try: + self.model.load(data) + except Exception as e: + pass + self.resizeColumnToContents(0) + + +class DebugDialog(QDialog): + def __init__(self, data, parent=None): + super().__init__(parent=parent) + self.resize(800, 600) + + layout = QVBoxLayout(self) + view = DebugView(data, self) + layout.addWidget(view) diff --git a/rare/components/tabs/store/api/graphql/.graphqlconfig b/rare/components/tabs/store/api/graphql/.graphqlconfig new file mode 100644 index 00000000..54fc8e5f --- /dev/null +++ b/rare/components/tabs/store/api/graphql/.graphqlconfig @@ -0,0 +1,15 @@ +{ + "name": "EGS GraphQL Schema", + "schemaPath": "schema.graphql", + "extensions": { + "endpoints": { + "Default GraphQL Endpoint": { + "url": "http://localhost:8080/graphql", + "headers": { + "user-agent": "JS GraphQL" + }, + "introspect": false + } + } + } +} \ No newline at end of file diff --git a/rare/components/tabs/store/api/graphql/schema.graphql b/rare/components/tabs/store/api/graphql/schema.graphql new file mode 100644 index 00000000..6f3edfda --- /dev/null +++ b/rare/components/tabs/store/api/graphql/schema.graphql @@ -0,0 +1,41 @@ +scalar Date + +type Currency { + decimals: Int + symbol: String +} + +type FormattedPrice { + originalPrice: String + discountPrice: String + intermediatePrice: String +} + +type TotalPrice { + discountPrice: Int + originalPrice: Int + voucherDiscount: Int + discount: Int + currencyCode: String + currencyInfo: Currency + fmtPrice(locale: String): FormattedPrice +} + +type DiscountSetting { + discountType: String +} + +type AppliedRuled { + id: ID + endDate: Date + discountSetting: DiscountSetting +} + +type LineOfferRes { + appliedRules: [AppliedRuled] +} + +type GetPriceRes { + totalPrice: TotalPrice + lineOffers: [LineOfferRes] +} \ No newline at end of file diff --git a/rare/components/tabs/store/api/models/__init__.py b/rare/components/tabs/store/api/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/components/tabs/store/api/models/diesel.py b/rare/components/tabs/store/api/models/diesel.py new file mode 100644 index 00000000..b9629343 --- /dev/null +++ b/rare/components/tabs/store/api/models/diesel.py @@ -0,0 +1,164 @@ +import logging +from dataclasses import dataclass, field +from typing import List, Dict, Any, Type, Optional + +logger = logging.getLogger("DieselModels") + +# lk: Typing overloads for unimplemented types +DieselSocialLinks = Dict + + +@dataclass +class DieselSystemDetailItem: + p_type: Optional[str] = None + minimum: Optional[str] = None + recommended: Optional[str] = None + title: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem": + d = src.copy() + tmp = cls( + p_type=d.pop("_type", ""), + minimum=d.pop("minimum", ""), + recommended=d.pop("recommended", ""), + title=d.pop("title", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselSystemDetail: + p_type: Optional[str] = None + details: Optional[List[DieselSystemDetailItem]] = None + system_type: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetail"], src: Dict[str, Any]) -> "DieselSystemDetail": + d = src.copy() + _details = d.pop("details", []) + details = [] if _details else None + for item in _details: + detail = DieselSystemDetailItem.from_dict(item) + details.append(detail) + tmp = cls( + p_type=d.pop("_type", ""), + details=details, + system_type=d.pop("systemType", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselSystemDetails: + p_type: Optional[str] = None + languages: Optional[List[str]] = None + rating: Optional[Dict] = None + systems: Optional[List[DieselSystemDetail]] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetails"], src: Dict[str, Any]) -> "DieselSystemDetails": + d = src.copy() + _systems = d.pop("systems", []) + systems = [] if _systems else None + for item in _systems: + system = DieselSystemDetail.from_dict(item) + systems.append(system) + tmp = cls( + p_type=d.pop("_type", ""), + languages=d.pop("languages", []), + rating=d.pop("rating", {}), + systems=systems, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProductAbout: + p_type: Optional[str] = None + desciption: Optional[str] = None + developer_attribution: Optional[str] = None + publisher_attribution: Optional[str] = None + short_description: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout": + d = src.copy() + tmp = cls( + p_type=d.pop("_type", ""), + desciption=d.pop("description", ""), + developer_attribution=d.pop("developerAttribution", ""), + publisher_attribution=d.pop("publisherAttribution", ""), + short_description=d.pop("shortDescription", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProductDetail: + p_type: Optional[str] = None + about: Optional[DieselProductAbout] = None + requirements: Optional[DieselSystemDetails] = None + social_links: Optional[DieselSocialLinks] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProductDetail"], src: Dict[str, Any]) -> "DieselProductDetail": + d = src.copy() + about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None + requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None + tmp = cls( + p_type=d.pop("_type", ""), + about=about, + requirements=requirements, + social_links=d.pop("socialLinks", {}), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProduct: + p_id: Optional[str] = None + p_images_: Optional[List[str]] = None + p_locale: Optional[str] = None + p_slug: Optional[str] = None + p_title: Optional[str] = None + p_url_pattern: Optional[str] = None + namespace: Optional[str] = None + pages: Optional[List["DieselProduct"]] = None + data: Optional[DieselProductDetail] = None + product_name: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProduct"], src: Dict[str, Any]) -> "DieselProduct": + d = src.copy() + _pages = d.pop("pages", []) + pages = [] if _pages else None + for item in _pages: + page = DieselProduct.from_dict(item) + pages.append(page) + data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None + tmp = cls( + p_id=d.pop("_id", ""), + p_images_=d.pop("_images_", []), + p_locale=d.pop("_locale", ""), + p_slug=d.pop("_slug", ""), + p_title=d.pop("_title", ""), + p_url_pattern=d.pop("_urlPattern", ""), + namespace=d.pop("namespace", ""), + pages=pages, + data=data, + product_name=d.pop("productName", ""), + ) + tmp.unmapped = d + return tmp diff --git a/rare/components/tabs/store/api/models/query.py b/rare/components/tabs/store/api/models/query.py new file mode 100644 index 00000000..925cc500 --- /dev/null +++ b/rare/components/tabs/store/api/models/query.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import List + + +@dataclass +class SearchDateRange: + start_date: datetime = datetime(year=1990, month=1, day=1, tzinfo=timezone.utc) + end_date: datetime = datetime.utcnow() + + def __str__(self): + def fmt_date(date: datetime) -> str: + # lk: The formatting accepted by the GraphQL API is either '%Y-%m-%dT%H:%M:%S.000Z' or '%Y-%m-%dT' + return datetime.strftime(date, '%Y-%m-%dT%H:%M:%S.000Z') + return f"[{fmt_date(self.start_date)},{fmt_date(self.end_date)}]" + + +@dataclass +class SearchStoreQuery: + country: str = "US" + category: str = "games/edition/base|bundles/games|editors|software/edition/base" + count: int = 30 + keywords: str = "" + language: str = "en" + namespace: str = "" + with_mapping: bool = True + item_ns: str = "" + sort_by: str = "releaseDate" + sort_dir: str = "DESC" + start: int = 0 + tag: List[str] = "" + release_date: SearchDateRange = field(default_factory=SearchDateRange) + with_price: bool = True + with_promotions: bool = True + price_range: str = "" + free_game: bool = None + on_sale: bool = None + effective_date: SearchDateRange = field(default_factory=SearchDateRange) + + def __post_init__(self): + self.locale = f"{self.language}-{self.country}" + + def to_dict(self): + payload = { + "allowCountries": self.country, + "category": self.category, + "count": self.count, + "country": self.country, + "keywords": self.keywords, + "locale": self.locale, + "namespace": self.namespace, + "withMapping": self.with_mapping, + "itemNs": self.item_ns, + "sortBy": self.sort_by, + "sortDir": self.sort_dir, + "start": self.start, + "tag": self.tag, + "releaseDate": str(self.release_date), + "withPrice": self.with_price, + "withPromotions": self.with_promotions, + "priceRange": self.price_range, + "freeGame": self.free_game, + "onSale": self.on_sale, + "effectiveDate": str(self.effective_date), + } + # payload.pop("withPromotions") + payload.pop("onSale") + if self.price_range == "free": + payload["freeGame"] = True + payload.pop("priceRange") + elif self.price_range.startswith(""): + payload["priceRange"] = self.price_range.replace("", "") + if self.on_sale: + payload["onSale"] = True + + if self.price_range: + payload["effectiveDate"] = self.effective_date + else: + payload.pop("priceRange") + return payload diff --git a/rare/components/tabs/store/api/models/response.py b/rare/components/tabs/store/api/models/response.py new file mode 100644 index 00000000..182985a1 --- /dev/null +++ b/rare/components/tabs/store/api/models/response.py @@ -0,0 +1,589 @@ +import logging +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import List, Dict, Any, Type, Optional + +logger = logging.getLogger("StoreApiModels") + +# lk: Typing overloads for unimplemented types +DieselSocialLinks = Dict + +CatalogNamespaceModel = Dict +CategoryModel = Dict +CustomAttributeModel = Dict +ItemModel = Dict +SellerModel = Dict +OfferMappingModel = Dict +TagModel = Dict +PromotionsModel = Dict + + +def parse_date(date: str): + return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc) + + +@dataclass +class DieselSystemDetailItem: + p_type: Optional[str] = None + minimum: Optional[str] = None + recommended: Optional[str] = None + title: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem": + d = src.copy() + tmp = cls( + p_type=d.pop("_type", ""), + minimum=d.pop("minimum", ""), + recommended=d.pop("recommended", ""), + title=d.pop("title", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselSystemDetail: + p_type: Optional[str] = None + details: Optional[List[DieselSystemDetailItem]] = None + system_type: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetail"], src: Dict[str, Any]) -> "DieselSystemDetail": + d = src.copy() + _details = d.pop("details", []) + details = [] if _details else None + for item in _details: + detail = DieselSystemDetailItem.from_dict(item) + details.append(detail) + tmp = cls( + p_type=d.pop("_type", ""), + details=details, + system_type=d.pop("systemType", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselSystemDetails: + p_type: Optional[str] = None + languages: Optional[List[str]] = None + rating: Optional[Dict] = None + systems: Optional[List[DieselSystemDetail]] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselSystemDetails"], src: Dict[str, Any]) -> "DieselSystemDetails": + d = src.copy() + _systems = d.pop("systems", []) + systems = [] if _systems else None + for item in _systems: + system = DieselSystemDetail.from_dict(item) + systems.append(system) + tmp = cls( + p_type=d.pop("_type", ""), + languages=d.pop("languages", []), + rating=d.pop("rating", {}), + systems=systems, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProductAbout: + p_type: Optional[str] = None + desciption: Optional[str] = None + developer_attribution: Optional[str] = None + publisher_attribution: Optional[str] = None + short_description: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout": + d = src.copy() + tmp = cls( + p_type=d.pop("_type", ""), + desciption=d.pop("description", ""), + developer_attribution=d.pop("developerAttribution", ""), + publisher_attribution=d.pop("publisherAttribution", ""), + short_description=d.pop("shortDescription", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProductDetail: + p_type: Optional[str] = None + about: Optional[DieselProductAbout] = None + requirements: Optional[DieselSystemDetails] = None + social_links: Optional[DieselSocialLinks] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProductDetail"], src: Dict[str, Any]) -> "DieselProductDetail": + d = src.copy() + about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None + requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None + tmp = cls( + p_type=d.pop("_type", ""), + about=about, + requirements=requirements, + social_links=d.pop("socialLinks", {}), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class DieselProduct: + p_id: Optional[str] = None + p_images_: Optional[List[str]] = None + p_locale: Optional[str] = None + p_slug: Optional[str] = None + p_title: Optional[str] = None + p_url_pattern: Optional[str] = None + namespace: Optional[str] = None + pages: Optional[List["DieselProduct"]] = None + data: Optional[DieselProductDetail] = None + product_name: Optional[str] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DieselProduct"], src: Dict[str, Any]) -> "DieselProduct": + d = src.copy() + _pages = d.pop("pages", []) + pages = [] if _pages else None + for item in _pages: + page = DieselProduct.from_dict(item) + pages.append(page) + data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None + tmp = cls( + p_id=d.pop("_id", ""), + p_images_=d.pop("_images_", []), + p_locale=d.pop("_locale", ""), + p_slug=d.pop("_slug", ""), + p_title=d.pop("_title", ""), + p_url_pattern=d.pop("_urlPattern", ""), + namespace=d.pop("namespace", ""), + pages=pages, + data=data, + product_name=d.pop("productName", ""), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class ImageUrlModel: + type: Optional[str] = None + url: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + tmp: Dict[str, Any] = {} + tmp.update({}) + if self.type is not None: + tmp["type"] = self.type + if self.url is not None: + tmp["url"] = self.url + return tmp + + @classmethod + def from_dict(cls: Type["ImageUrlModel"], src: Dict[str, Any]) -> "ImageUrlModel": + d = src.copy() + type = d.pop("type", None) + url = d.pop("url", None) + tmp = cls( + type=type, + url=url, + ) + return tmp + + +@dataclass +class KeyImagesModel: + key_images: Optional[List[ImageUrlModel]] = None + + def __getitem__(self, item): + return self.key_images[item] + + def __bool__(self): + return bool(self.key_images) + + def to_list(self) -> List[Dict[str, Any]]: + items: Optional[List[Dict[str, Any]]] = None + if self.key_images is not None: + items = [] + for image_url in self.key_images: + item = image_url.to_dict() + items.append(item) + return items + + @classmethod + def from_list(cls: Type["KeyImagesModel"], src: List[Dict]): + d = src.copy() + key_images = [] + for item in d: + image_url = ImageUrlModel.from_dict(item) + key_images.append(image_url) + tmp = cls(key_images) + return tmp + + def available_tall(self) -> List[ImageUrlModel]: + tall_types = [ + "DieselStoreFrontTall", + "OfferImageTall", + "Thumbnail", + "ProductLogo", + "DieselGameBoxLogo", + ] + tall_images = filter(lambda img: img.type in tall_types, self.key_images) + tall_images = sorted(tall_images, key=lambda x: tall_types.index(x.type)) + return tall_images + + def available_wide(self) -> List[ImageUrlModel]: + wide_types = ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"] + wide_images = filter(lambda img: img.type in wide_types, self.key_images) + wide_images = sorted(wide_images, key=lambda x: wide_types.index(x.type)) + return wide_images + + def for_dimensions(self, w: int, h: int) -> ImageUrlModel: + try: + if w > h: + model = self.available_wide()[0] + else: + model = self.available_tall()[0] + _ = model.url + except Exception as e: + logger.error(e) + logger.error(self.to_list()) + else: + return model + + +TotalPriceModel = Dict +FmtPriceModel = Dict +LineOffersModel = Dict + + +@dataclass +class PriceModel: + total_price: Optional[TotalPriceModel] = None + fmt_price: Optional[FmtPriceModel] = None + line_offers: Optional[LineOffersModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["PriceModel"], src: Dict[str, Any]) -> "PriceModel": + d = src.copy() + tmp = cls( + total_price=d.pop("totalPrice", {}), + fmt_price=d.pop("fmtPrice", {}), + line_offers=d.pop("lineOffers", {}), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class CatalogOfferModel: + catalog_ns: Optional[CatalogNamespaceModel] = None + categories: Optional[List[CategoryModel]] = None + custom_attributes: Optional[List[CustomAttributeModel]] = None + description: Optional[str] = None + effective_date: Optional[datetime] = None + expiry_date: Optional[datetime] = None + id: Optional[str] = None + is_code_redemption_only: Optional[bool] = None + items: Optional[List[ItemModel]] = None + key_images: Optional[KeyImagesModel] = None + namespace: Optional[str] = None + offer_mappings: Optional[List[OfferMappingModel]] = None + offer_type: Optional[str] = None + price: Optional[PriceModel] = None + product_slug: Optional[str] = None + promotions: Optional[PromotionsModel] = None + seller: Optional[SellerModel] = None + status: Optional[str] = None + tags: Optional[List[TagModel]] = None + title: Optional[str] = None + url: Optional[str] = None + url_slug: Optional[str] = None + viewable_date: Optional[datetime] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["CatalogOfferModel"], src: Dict[str, Any]) -> "CatalogOfferModel": + d = src.copy() + effective_date = parse_date(x) if (x := d.pop("effectiveDate", "")) else None + expiry_date = parse_date(x) if (x := d.pop("expiryDate", "")) else None + key_images = KeyImagesModel.from_list(d.pop("keyImages", [])) + price = PriceModel.from_dict(x) if (x := d.pop("price", {})) else None + viewable_date = parse_date(x) if (x := d.pop("viewableDate", "")) else None + tmp = cls( + catalog_ns=d.pop("catalogNs", {}), + categories=d.pop("categories", []), + custom_attributes=d.pop("customAttributes", []), + description=d.pop("description", ""), + effective_date=effective_date, + expiry_date=expiry_date, + id=d.pop("id", ""), + is_code_redemption_only=d.pop("isCodeRedemptionOnly", None), + items=d.pop("items", []), + key_images=key_images, + namespace=d.pop("namespace", ""), + offer_mappings=d.pop("offerMappings", []), + offer_type=d.pop("offerType", ""), + price=price, + product_slug=d.pop("productSlug", ""), + promotions=d.pop("promotions", {}), + seller=d.pop("seller", {}), + status=d.pop("status", ""), + tags=d.pop("tags", []), + title=d.pop("title", ""), + url=d.pop("url", ""), + url_slug=d.pop("urlSlug", ""), + viewable_date=viewable_date, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class WishlistItemModel: + created: Optional[datetime] = None + id: Optional[str] = None + namespace: Optional[str] = None + is_first_time: Optional[bool] = None + offer_id: Optional[str] = None + order: Optional[Any] = None + updated: Optional[datetime] = None + offer: Optional[CatalogOfferModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["WishlistItemModel"], src: Dict[str, Any]) -> "WishlistItemModel": + d = src.copy() + created = parse_date(x) if (x := d.pop("created", "")) else None + offer = CatalogOfferModel.from_dict(x) if (x := d.pop("offer", {})) else None + updated = parse_date(x) if (x := d.pop("updated", "")) else None + tmp = cls( + created=created, + id=d.pop("id", ""), + namespace=d.pop("namespace", ""), + is_first_time=d.pop("isFirstTime", None), + offer_id=d.pop("offerId", ""), + order=d.pop("order", ""), + updated=updated, + offer=offer, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class PagingModel: + count: Optional[int] = None + total: Optional[int] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["PagingModel"], src: Dict[str, Any]) -> "PagingModel": + d = src.copy() + count = d.pop("count", None) + total = d.pop("total", None) + tmp = cls( + count=count, + total=total, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class SearchStoreModel: + elements: Optional[List[CatalogOfferModel]] = None + paging: Optional[PagingModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["SearchStoreModel"], src: Dict[str, Any]) -> "SearchStoreModel": + d = src.copy() + _elements = d.pop("elements", []) + elements = [] if _elements else None + for item in _elements: + elem = CatalogOfferModel.from_dict(item) + elements.append(elem) + paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None + tmp = cls( + elements=elements, + paging=paging, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class CatalogModel: + search_store: Optional[SearchStoreModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["CatalogModel"], src: Dict[str, Any]) -> "CatalogModel": + d = src.copy() + search_store = SearchStoreModel.from_dict(x) if (x := d.pop("searchStore", {})) else None + tmp = cls( + search_store=search_store, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class WishlistItemsModel: + elements: Optional[List[WishlistItemModel]] = None + paging: Optional[PagingModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["WishlistItemsModel"], src: Dict[str, Any]) -> "WishlistItemsModel": + d = src.copy() + _elements = d.pop("elements", []) + elements = [] if _elements else None + for item in _elements: + elem = WishlistItemModel.from_dict(item) + elements.append(elem) + paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None + tmp = cls( + elements=elements, + paging=paging, + ) + tmp.unmapped = d + return tmp + + +@dataclass +class RemoveFromWishlistModel: + success: Optional[bool] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["RemoveFromWishlistModel"], src: Dict[str, Any]) -> "RemoveFromWishlistModel": + d = src.copy() + tmp = cls( + success=d.pop("success", None), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class AddToWishlistModel: + wishlist_item: Optional[WishlistItemModel] = None + success: Optional[bool] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["AddToWishlistModel"], src: Dict[str, Any]) -> "AddToWishlistModel": + d = src.copy() + wishlist_item = WishlistItemModel.from_dict(x) if (x := d.pop("wishlistItem", {})) else None + tmp = cls( + wishlist_item=wishlist_item, + success=d.pop("success", None), + ) + tmp.unmapped = d + return tmp + + +@dataclass +class WishlistModel: + wishlist_items: Optional[WishlistItemsModel] = None + remove_from_wishlist: Optional[RemoveFromWishlistModel] = None + add_to_wishlist: Optional[AddToWishlistModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["WishlistModel"], src: Dict[str, Any]) -> "WishlistModel": + d = src.copy() + wishlist_items = WishlistItemsModel.from_dict(x) if (x := d.pop("wishlistItems", {})) else None + remove_from_wishlist = ( + RemoveFromWishlistModel.from_dict(x) if (x := d.pop("removeFromWishlist", {})) else None + ) + add_to_wishlist = AddToWishlistModel.from_dict(x) if (x := d.pop("addToWishlist", {})) else None + tmp = cls( + wishlist_items=wishlist_items, + remove_from_wishlist=remove_from_wishlist, + add_to_wishlist=add_to_wishlist, + ) + tmp.unmapped = d + return tmp + + +ProductModel = Dict + + +@dataclass +class DataModel: + product: Optional[ProductModel] = None + catalog: Optional[CatalogModel] = None + wishlist: Optional[WishlistModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["DataModel"], src: Dict[str, Any]) -> "DataModel": + d = src.copy() + catalog = CatalogModel.from_dict(x) if (x := d.pop("Catalog", {})) else None + wishlist = WishlistModel.from_dict(x) if (x := d.pop("Wishlist", {})) else None + tmp = cls(product=d.pop("Product", {}), catalog=catalog, wishlist=wishlist) + tmp.unmapped = d + return tmp + + +@dataclass +class ErrorModel: + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["ErrorModel"], src: Dict[str, Any]) -> "ErrorModel": + d = src.copy() + tmp = cls() + tmp.unmapped = d + return tmp + + +@dataclass +class ExtensionsModel: + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["ExtensionsModel"], src: Dict[str, Any]) -> "ExtensionsModel": + d = src.copy() + tmp = cls() + tmp.unmapped = d + return tmp + + +@dataclass +class ResponseModel: + data: Optional[DataModel] = None + errors: Optional[List[ErrorModel]] = None + extensions: Optional[ExtensionsModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["ResponseModel"], src: Dict[str, Any]) -> "ResponseModel": + d = src.copy() + data = DataModel.from_dict(x) if (x := d.pop("data", {})) else None + _errors = d.pop("errors", []) + errors = [] if _errors else None + for item in _errors: + error = ErrorModel.from_dict(item) + errors.append(error) + extensions = ExtensionsModel.from_dict(x) if (x := d.pop("extensions", {})) else None + tmp = cls(data=data, errors=errors, extensions=extensions) + tmp.unmapped = d + return tmp diff --git a/rare/components/tabs/store/api/models/utils.py b/rare/components/tabs/store/api/models/utils.py new file mode 100644 index 00000000..92c6ebf2 --- /dev/null +++ b/rare/components/tabs/store/api/models/utils.py @@ -0,0 +1,5 @@ +from datetime import datetime, timezone + + +def parse_date(date: str): + return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc) \ No newline at end of file diff --git a/rare/components/tabs/store/constants.py b/rare/components/tabs/store/constants.py index e254dbd3..8a839b93 100644 --- a/rare/components/tabs/store/constants.py +++ b/rare/components/tabs/store/constants.py @@ -44,8 +44,187 @@ class Constants(QObject): ] -game_query = """ -query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, $sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, $effectiveDate: String) { +__Image = ''' +type +url +alt +''' + +__StorePageMapping = ''' +cmsSlug +offerId +prePurchaseOfferId +''' + +__PageSandboxModel = ''' +pageSlug +pageType +productId +sandboxId +createdDate +updatedDate +deletedDate +mappings { + %s +} +''' % (__StorePageMapping) + +__CatalogNamespace = ''' +parent +displayName +store +home: mappings(pageType: "productHome") { + %s +} +addons: mappings(pageType: "addon--cms-hybrid") { + %s +} +offers: mappings(pageType: "offer") { + %s +} +''' % (__PageSandboxModel, __PageSandboxModel, __PageSandboxModel) + +__CatalogItem = ''' +id +namespace +''' + +__GetPriceRes = ''' + totalPrice { + discountPrice + originalPrice + voucherDiscount + discount + currencyCode + currencyInfo { + decimals + symbol + } + fmtPrice(locale: $locale) { + originalPrice + discountPrice + intermediatePrice + } + } + lineOffers { + appliedRules { + id + endDate + discountSetting { + discountType + } + } + } +''' + +__Promotions = ''' +promotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } +} +upcomingPromotionalOffers { + promotionalOffers { + startDate + endDate + discountSetting { + discountType + discountPercentage + } + } +} +''' + +__CatalogOffer = ''' +title +id +namespace +offerType +expiryDate +status +isCodeRedemptionOnly +description +effectiveDate +keyImages { + %(image)s +} +currentPrice +seller { + id + name +} +productSlug +urlSlug +url +tags { + id + name + groupName +} +items { + %(catalog_item)s +} +customAttributes { + key + value +} +categories { + path +} +catalogNs @include(if: $withMapping) { + %(catalog_namespace)s +} +offerMappings @include(if: $withMapping) { + %(page_sandbox_model)s +} +price(country: $country) @include(if: $withPrice) { + %(get_price_res)s +} +promotions(category: $category) @include(if: $withPromotions) { + %(promotions)s +} +''' % { + "image": __Image, + "catalog_item": __CatalogItem, + "catalog_namespace": __CatalogNamespace, + "page_sandbox_model": __PageSandboxModel, + "get_price_res": __GetPriceRes, + "promotions": __Promotions, +} + +__Pagination = ''' +count +total +''' + +SEARCH_STORE_QUERY = ''' +query searchStoreQuery( + $allowCountries: String + $category: String + $count: Int + $country: String! + $keywords: String + $locale: String + $namespace: String + $withMapping: Boolean = false + $itemNs: String + $sortBy: String + $sortDir: String + $start: Int + $tag: String + $releaseDate: String + $withPrice: Boolean = false + $withPromotions: Boolean = false + $priceRange: String + $freeGame: Boolean + $onSale: Boolean + $effectiveDate: String +) { Catalog { searchStore( allowCountries: $allowCountries @@ -67,452 +246,209 @@ query searchStoreQuery($allowCountries: String, $category: String, $count: Int, effectiveDate: $effectiveDate ) { elements { - title - id - namespace - description - effectiveDate - keyImages { - type - url - } - currentPrice - seller { - id - name - } - productSlug - urlSlug - url - tags { - id - } - items { - id - namespace - } - customAttributes { - key - value - } - categories { - path - } - catalogNs @include(if: $withMapping) { - mappings(pageType: "productHome") { - pageSlug - pageType - } - } - offerMappings @include(if: $withMapping) { - pageSlug - pageType - } - price(country: $country) @include(if: $withPrice) { - totalPrice { - discountPrice - originalPrice - voucherDiscount - discount - currencyCode - currencyInfo { - decimals - } - fmtPrice(locale: $locale) { - originalPrice - discountPrice - intermediatePrice - } - } - lineOffers { - appliedRules { - id - endDate - discountSetting { - discountType - } - } - } - } - promotions(category: $category) @include(if: $withPromotions) { - promotionalOffers { - promotionalOffers { - startDate - endDate - discountSetting { - discountType - discountPercentage - } - } - } - upcomingPromotionalOffers { - promotionalOffers { - startDate - endDate - discountSetting { - discountType - discountPercentage - } - } - } - } + %s } paging { - count - total + %s } } } } -""" +''' % (__CatalogOffer, __Pagination) -search_query = """ -query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $itemNs: String, $sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, $effectiveDate: String) { - Catalog { - searchStore( - allowCountries: $allowCountries - category: $category - count: $count - country: $country - keywords: $keywords - locale: $locale - namespace: $namespace - itemNs: $itemNs - sortBy: $sortBy - sortDir: $sortDir - releaseDate: $releaseDate - start: $start - tag: $tag - priceRange: $priceRange - freeGame: $freeGame - onSale: $onSale - effectiveDate: $effectiveDate - ) { - elements { - title - id - namespace - description - effectiveDate - keyImages { - type - url - } - currentPrice - seller { - id - name - } - productSlug - urlSlug - url - tags { - id - } - items { - id - namespace - } - customAttributes { - key - value - } - categories { - path - } - catalogNs { - mappings(pageType: "productHome") { - pageSlug - pageType - } - } - offerMappings { - pageSlug - pageType - } - price(country: $country) @include(if: $withPrice) { - totalPrice { - discountPrice - originalPrice - voucherDiscount - discount - currencyCode - currencyInfo { - decimals - } - fmtPrice(locale: $locale) { - originalPrice - discountPrice - intermediatePrice - } - } - lineOffers { - appliedRules { - id - endDate - discountSetting { - discountType - } - } - } - } - promotions(category: $category) @include(if: $withPromotions) { - promotionalOffers { - promotionalOffers { - startDate - endDate - discountSetting { - discountType - discountPercentage - } - } - } - upcomingPromotionalOffers { - promotionalOffers { - startDate - endDate - discountSetting { - discountType - discountPercentage - } - } - } - } - } - paging { - count - total - } - } - } +__WISHLIST_ITEM = ''' +id +order +created +offerId +updated +namespace +isFirstTime +offer(locale: $locale) { + %s } -""" +''' % __CatalogOffer -wishlist_query = """ -query wishlistQuery($country:String!, $locale:String) { +WISHLIST_QUERY = ''' +query wishlistQuery( + $country: String! + $locale: String + $category: String + $withMapping: Boolean = false + $withPrice: Boolean = false + $withPromotions: Boolean = false +) { Wishlist { wishlistItems { elements { - id - order - created - offerId - updated - namespace - offer(locale: $locale) { - productSlug - urlSlug - title - id - namespace - offerType - expiryDate - status - isCodeRedemptionOnly - description - effectiveDate - keyImages { - type - url - } - seller { - id - name - } - productSlug - urlSlug - items { - id - namespace - } - customAttributes { - key - value - } - catalogNs { - mappings(pageType: "productHome") { - pageSlug - pageType + %s + } + } + } +} +''' % __WISHLIST_ITEM + +WISHLIST_ADD_QUERY = ''' +mutation addWishlistMutation( + $namespace: String! + $offerId: String! + $country: String! + $locale: String + $category: String + $withMapping: Boolean = false + $withPrice: Boolean = false + $withPromotions: Boolean = false +) { + Wishlist { + addToWishlist( + namespace: $namespace + offerId: $offerId + ) { + wishlistItem { + %s + } + success + } + } +} +''' % __WISHLIST_ITEM + +WISHLIST_REMOVE_QUERY = ''' +mutation removeFromWishlistMutation( + $namespace: String! + $offerId: String! + $operation: RemoveOperation! +) { + Wishlist { + removeFromWishlist( + namespace: $namespace + offerId: $offerId + operation: $operation + ) { + success + } + } +} +''' + +COUPONS_QUERY = ''' +query getCoupons( + $currencyCountry: String! + $identityId: String! + $locale: String +) { + CodeRedemption { + coupons( + currencyCountry: $currencyCountry + identityId: $identityId + includeSalesEventInfo: true + ) { + code + codeStatus + codeType + consumptionMetadata { + amountDisplay { + amount + currency + placement + symbol + } + minSalesPriceDisplay { + amount + currency + placement + symbol + } + } + endDate + namespace + salesEvent(locale: $locale) { + eventName + eventSlug + voucherImages { + type + url + } + voucherLink + } + startDate + } + } +} +''' + +STORE_CONFIG_QUERY = ''' +query getStoreConfig( + $includeCriticReviews: Boolean = false + $locale: String! + $sandboxId: String! + $templateId: String +) { + Product { + sandbox(sandboxId: $sandboxId) { + configuration(locale: $locale, templateId: $templateId) { + ... on StoreConfiguration { + configs { + shortDescription + criticReviews @include(if: $includeCriticReviews) { + openCritic } - } - offerMappings { - pageSlug - pageType - } - categories { - path - } - price(country: $country) { - totalPrice { - discountPrice - originalPrice - voucherDiscount - discount - fmtPrice(locale: $locale) { - originalPrice - discountPrice - intermediatePrice + socialLinks { + platform + url + } + supportedAudio + supportedText + tags(locale: $locale) { + id + name + groupName + } + technicalRequirements { + macos { + minimum + recommended + title } - currencyCode - currencyInfo { - decimals - symbol + windows { + minimum + recommended + title } } - lineOffers { - appliedRules { - id - endDate + } + } + ... on HomeConfiguration { + configs { + keyImages { + ... on KeyImage { + type + url + alt } } + longDescription } } } } } } -""" - -add_to_wishlist_query = """ -mutation addWishlistMutation($namespace: String!, $offerId: String!, $country:String!, $locale:String) { - Wishlist { - addToWishlist(namespace: $namespace, offerId: $offerId) { - wishlistItem { - id, - order, - created, - offerId, - updated, - namespace, - isFirstTime - offer { - productSlug - urlSlug - title - id - namespace - offerType - expiryDate - status - isCodeRedemptionOnly - description - effectiveDate - keyImages { - type - url - } - seller { - id - name - } - productSlug - urlSlug - items { - id - namespace - } - customAttributes { - key - value - } - catalogNs { - mappings(pageType: "productHome") { - pageSlug - pageType - } - } - offerMappings { - pageSlug - pageType - } - categories { - path - } - price(country: $country) { - totalPrice { - discountPrice - originalPrice - voucherDiscount - discount - fmtPrice(locale: $locale) { - originalPrice - discountPrice - intermediatePrice - } - currencyCode - currencyInfo { - decimals - symbol - } - } - lineOffers { - appliedRules { - id - endDate - } - } - } - } - - } - success - } - } -} -""" - -remove_from_wishlist_query = """ -mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) { - Wishlist { - removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) { - success - } - } -} -""" - -coupon_query = """ -query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) { - CodeRedemption { - coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) { - code - codeStatus - codeType - consumptionMetadata { - amountDisplay { - amount - currency - placement - symbol - } - minSalesPriceDisplay { - amount - currency - placement - symbol - } - } - endDate - namespace - salesEvent(locale: $locale) { - eventName - eventSlug - voucherImages { - type - url - } - voucherLink - } - startDate - } - } -} -""" +''' -# if __name__ == "__main__": -# from sgqlc import introspection, codegen -# -# coupon = codegen.operation.parse_graphql(coupon_query) -# codegen.schema. -# print(coupon.) +def compress_query(query: str) -> str: + return query.replace(" ", "").replace("\n", " ") + + +game_query = compress_query(SEARCH_STORE_QUERY) +search_query = compress_query(SEARCH_STORE_QUERY) +wishlist_query = compress_query(WISHLIST_QUERY) +wishlist_add_query = compress_query(WISHLIST_ADD_QUERY) +wishlist_remove_query = compress_query(WISHLIST_REMOVE_QUERY) +coupons_query = compress_query(COUPONS_QUERY) +store_config_query = compress_query(STORE_CONFIG_QUERY) + + +if __name__ == "__main__": + print(SEARCH_STORE_QUERY) diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/game_info.py index 2d399b17..19a27524 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/game_info.py @@ -1,4 +1,6 @@ import logging +from pprint import pprint +from typing import List from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QFont, QDesktopServices, QFontMetrics @@ -10,13 +12,14 @@ from PyQt5.QtWidgets import ( QSizePolicy, ) -from rare.components.tabs.store.shop_models import ShopGame +from rare.components.tabs.store.api.models.response import CatalogOfferModel, DieselProduct, DieselProductDetail from rare.shared import LegendaryCoreSingleton from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.store.shop_game_info import Ui_ShopInfo from rare.utils.misc import icon from rare.widgets.side_tab import SideTabWidget, SideTabContents from rare.widgets.elide_label import ElideLabel +from .api.debug import DebugDialog from .image_widget import ShopImageWidget logger = logging.getLogger("ShopInfo") @@ -37,45 +40,46 @@ class ShopGameInfo(QWidget, SideTabContents): self.image.setFixedSize(ImageSize.Normal) self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) - self.game: ShopGame = None + self.offer: CatalogOfferModel = None self.data: dict = {} self.ui.wishlist_button.clicked.connect(self.add_to_wishlist) + self.ui.wishlist_button.setVisible(True) self.in_wishlist = False self.wishlist = [] - self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_group) + self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_frame) self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ui.requirements_layout.addWidget(self.requirements_tabs) self.setDisabled(True) - def handle_wishlist_update(self, data): - if data and data[0] == "error": + def handle_wishlist_update(self, wishlist: List[CatalogOfferModel]): + if wishlist and wishlist[0] == "error": return - self.wishlist = [i["offer"]["title"] for i in data] - if self.title_str in self.wishlist: + self.wishlist = [game.id for game in wishlist] + if self.id_str in self.wishlist: self.in_wishlist = True - self.ui.wishlist_button.setVisible(True) self.ui.wishlist_button.setText(self.tr("Remove from Wishlist")) else: self.in_wishlist = False - self.ui.wishlist_button.setVisible(False) - def update_game(self, data: dict): - self.set_title.emit(data["title"]) - self.ui.title.setText(data["title"]) - self.title_str = data["title"] - self.id_str = data["id"] + def update_game(self, offer: CatalogOfferModel): + debug = DebugDialog(offer.__dict__, None) + debug.exec() + self.set_title.emit(offer.title) + self.ui.title.setText(offer.title) + self.title_str = offer.title + self.id_str = offer.id self.api_core.get_wishlist(self.handle_wishlist_update) - # lk: delete tabs in inverse order because indices are updated on deletion + # lk: delete tabs in reverse order because indices are updated on deletion while self.requirements_tabs.count(): self.requirements_tabs.widget(0).deleteLater() self.requirements_tabs.removeTab(0) self.requirements_tabs.clear() - slug = data["productSlug"] + slug = offer.product_slug if not slug: - for mapping in data["offerMappings"]: + for mapping in offer.offer_mappings: if mapping["pageType"] == "productHome": slug = mapping["pageSlug"] break @@ -86,7 +90,7 @@ class ShopGameInfo(QWidget, SideTabContents): slug = slug.replace("/home", "") self.slug = slug - if data["namespace"] in self.installed: + if offer.namespace in self.installed: self.ui.open_store_button.setText(self.tr("Show Game on Epic Page")) self.ui.owned_label.setVisible(True) else: @@ -94,39 +98,44 @@ class ShopGameInfo(QWidget, SideTabContents): self.ui.owned_label.setVisible(False) self.ui.price.setText(self.tr("Loading")) - self.ui.wishlist_button.setVisible(False) # self.title.setText(self.tr("Loading")) # self.image.setPixmap(QPixmap()) - self.data = data is_bundle = False - for i in data["categories"]: + for i in offer.categories: if "bundles" in i.get("path", ""): is_bundle = True # init API request if slug: - self.api_core.get_game(slug, is_bundle, self.data_received) + self.api_core.get_game(offer.product_slug, is_bundle, self.data_received) # else: # self.data_received({}) + self.offer = offer def add_to_wishlist(self): if not self.in_wishlist: - return - # self.api_core.add_to_wishlist(self.game.namespace, self.game.offer_id, - # lambda success: self.wishlist_button.setText(self.tr("Remove from wishlist")) - # if success else self.wishlist_button.setText("Something goes wrong")) + self.api_core.add_to_wishlist( + self.offer.namespace, + self.offer.id, + lambda success: self.ui.wishlist_button.setText(self.tr("Remove from wishlist")) + if success + else self.ui.wishlist_button.setText("Something went wrong") + ) else: self.api_core.remove_from_wishlist( - self.game.namespace, - self.game.offer_id, - lambda success: self.ui.wishlist_button.setVisible(False) + self.offer.namespace, + self.offer.id, + lambda success: self.ui.wishlist_button.setText(self.tr("Add to wishlist")) if success - else self.ui.wishlist_button.setText("Something goes wrong"), + else self.ui.wishlist_button.setText("Something went wrong"), ) - def data_received(self, game): + def data_received(self, product: DieselProduct): try: - self.game = ShopGame.from_json(game, self.data) + if product.pages: + product_data: DieselProductDetail = product.pages[0].data + else: + product_data: DieselProductDetail = product.data except Exception as e: raise e logger.error(str(e)) @@ -150,17 +159,19 @@ class ShopGameInfo(QWidget, SideTabContents): # self.title.setText(self.game.title) self.ui.price.setFont(QFont()) - if self.game.price == "0" or self.game.price == 0: + price = self.offer.price.total_price["fmtPrice"]["originalPrice"] + discount_price = self.offer.price.total_price["fmtPrice"]["discountPrice"] + if price == "0" or price == 0: self.ui.price.setText(self.tr("Free")) else: - self.ui.price.setText(self.game.price) - if self.game.price != self.game.discount_price: + self.ui.price.setText(price) + if price != discount_price: font = QFont() font.setStrikeOut(True) self.ui.price.setFont(font) self.ui.discount_price.setText( - self.game.discount_price - if self.game.discount_price != "0" + discount_price + if discount_price != "0" else self.tr("Free") ) self.ui.discount_price.setVisible(True) @@ -171,8 +182,9 @@ class ShopGameInfo(QWidget, SideTabContents): bold_font.setBold(True) fm = QFontMetrics(self.font()) - if self.game.reqs: - for system in self.game.reqs: + requirements = product_data.requirements + if requirements and requirements.systems: + for system in requirements.systems: req_widget = QWidget(self.requirements_tabs) req_layout = QGridLayout(req_widget) req_widget.layout().setAlignment(Qt.AlignTop) @@ -185,53 +197,57 @@ class ShopGameInfo(QWidget, SideTabContents): req_layout.addWidget(rec_label, 0, 2) req_layout.setColumnStretch(1, 2) req_layout.setColumnStretch(2, 2) - for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()): - req_layout.addWidget(QLabel(key, parent=req_widget), i + 1, 0) - min_label = ElideLabel(value[0], parent=req_widget) + for i, detail in enumerate(system.details): + req_layout.addWidget(QLabel(detail.title, parent=req_widget), i + 1, 0) + min_label = ElideLabel(detail.minimum, parent=req_widget) req_layout.addWidget(min_label, i + 1, 1) - rec_label = ElideLabel(value[1], parent=req_widget) + rec_label = ElideLabel(detail.recommended, parent=req_widget) req_layout.addWidget(rec_label, i + 1, 2) - self.requirements_tabs.addTab(req_widget, system) - # self.req_group_box.layout().addWidget(req_tabs) - # self.req_group_box.layout().setAlignment(Qt.AlignTop) - # else: - # self.req_group_box.layout().addWidget( - # QLabel(self.tr("Could not get requirements")) - # ) - self.requirements_tabs.setEnabled(True) - if self.game.image_urls.front_tall: - img_url = self.game.image_urls.front_tall - elif self.game.image_urls.offer_image_tall: - img_url = self.game.image_urls.offer_image_tall - elif self.game.image_urls.product_logo: - img_url = self.game.image_urls.product_logo + self.requirements_tabs.addTab(req_widget, system.system_type) + # self.req_group_box.layout().addWidget(req_tabs) + # self.req_group_box.layout().setAlignment(Qt.AlignTop) + # else: + # self.req_group_box.layout().addWidget( + # QLabel(self.tr("Could not get requirements")) + # ) + self.ui.requirements_frame.setVisible(True) else: - img_url = "" - self.image.fetchPixmap(img_url) + self.ui.requirements_frame.setVisible(False) + + key_images = self.offer.key_images + img_url = key_images.for_dimensions(self.image.size().width(), self.image.size().height()) + self.image.fetchPixmap(img_url.url) # self.image_stack.setCurrentIndex(0) - try: - if isinstance(self.game.developer, list): - self.ui.dev.setText(", ".join(self.game.developer)) - else: - self.ui.dev.setText(self.game.developer) - except KeyError: - pass - self.ui.tags.setText(", ".join(self.game.tags)) + about = product_data.about + self.ui.description_label.setText(about.desciption) + self.ui.dev.setText(about.developer_attribution) + # try: + # if isinstance(aboudeveloper, list): + # self.ui.dev.setText(", ".join(self.game.developer)) + # else: + # self.ui.dev.setText(self.game.developer) + # except KeyError: + # pass + tags = product_data.unmapped["meta"].get("tags", []) + self.ui.tags.setText(", ".join(tags)) # clear Layout for b in self.ui.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): self.ui.social_layout.removeWidget(b) b.deleteLater() + links = product_data.social_links link_count = 0 - for name, url in self.game.links: - - if name.lower() == "homepage": + for name, url in links.items(): + if name == "_type": + continue + name = name.replace("link", "").lower() + if name == "homepage": icn = icon("mdi.web", "fa.search", scale_factor=1.5) else: try: - icn = icon(f"mdi.{name.lower()}", f"fa.{name.lower()}", scale_factor=1.5) + icn = icon(f"mdi.{name}", f"fa.{name}", scale_factor=1.5) except Exception as e: logger.error(str(e)) continue @@ -244,10 +260,10 @@ class ShopGameInfo(QWidget, SideTabContents): self.setEnabled(True) - def add_wishlist_items(self, wishlist): - wishlist = wishlist["data"]["Wishlist"]["wishlistItems"]["elements"] - for game in wishlist: - self.wishlist.append(game["offer"]["title"]) + # def add_wishlist_items(self, wishlist: List[CatalogGameModel]): + # wishlist = wishlist["data"]["Wishlist"]["wishlistItems"]["elements"] + # for game in wishlist: + # self.wishlist.append(game["offer"]["title"]) def button_clicked(self): return diff --git a/rare/components/tabs/store/game_widgets.py b/rare/components/tabs/store/game_widgets.py index 09ba8873..9b453969 100644 --- a/rare/components/tabs/store/game_widgets.py +++ b/rare/components/tabs/store/game_widgets.py @@ -3,42 +3,44 @@ import logging from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QPushButton +from orjson import orjson -from rare.components.tabs.store.shop_models import ImageUrlModel +from rare.components.tabs.store.api.models.response import CatalogOfferModel from rare.shared.image_manager import ImageSize from rare.utils.misc import qta_icon from rare.utils.qt_requests import QtRequestManager +from .api.debug import DebugDialog from .image_widget import ShopImageWidget logger = logging.getLogger("GameWidgets") class GameWidget(ShopImageWidget): - show_info = pyqtSignal(dict) + show_info = pyqtSignal(CatalogOfferModel) - def __init__(self, manager: QtRequestManager, json_info=None, parent=None): + def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel = None, parent=None): super(GameWidget, self).__init__(manager, parent=parent) self.setFixedSize(ImageSize.Wide) self.ui.setupUi(self) - self.json_info = json_info - if json_info: - self.init_ui(json_info) + self.catalog_game = catalog_game + if catalog_game: + self.init_ui(catalog_game) - def init_ui(self, json_info): - if not json_info: + def init_ui(self, game: CatalogOfferModel): + if not game: self.ui.title_label.setText(self.tr("An error occurred")) return - self.ui.title_label.setText(json_info.get("title")) - for attr in json_info["customAttributes"]: + self.ui.title_label.setText(game.title) + for attr in game.custom_attributes: if attr["key"] == "developerName": developer = attr["value"] break else: - developer = json_info["seller"]["name"] + developer = game.seller["name"] self.ui.developer_label.setText(developer) - price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + price = game.price.total_price["fmtPrice"]["originalPrice"] + discount_price = game.price.total_price["fmtPrice"]["discountPrice"] self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: font = self.ui.price_label.font() @@ -48,43 +50,48 @@ class GameWidget(ShopImageWidget): else: self.ui.discount_label.setVisible(False) - for c in r'<>?":|\/*': - json_info["title"] = json_info["title"].replace(c, "") + key_images = game.key_images + self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) - for img in json_info["keyImages"]: - if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo",]: - if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game": - continue - self.fetchPixmap(img["url"]) - break - else: - logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) + # for img in json_info["keyImages"]: + # if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"]: + # if img["type"] == "VaultClosed" and json_info["title"] != "Mystery Game": + # continue + # self.fetchPixmap(img["url"]) + # break + # else: + # logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) def mousePressEvent(self, a0: QMouseEvent) -> None: if a0.button() == Qt.LeftButton: a0.accept() - self.show_info.emit(self.json_info) + self.show_info.emit(self.catalog_game) + if a0.button() == Qt.RightButton: + a0.accept() + print(self.catalog_game.__dict__) + dialog = DebugDialog(self.catalog_game.__dict__, self) + dialog.show() class WishlistWidget(ShopImageWidget): - open_game = pyqtSignal(dict) - delete_from_wishlist = pyqtSignal(dict) + open_game = pyqtSignal(CatalogOfferModel) + delete_from_wishlist = pyqtSignal(CatalogOfferModel) - def __init__(self, manager: QtRequestManager, game: dict, parent=None): + def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None): super(WishlistWidget, self).__init__(manager, parent=parent) self.setFixedSize(ImageSize.Wide) self.ui.setupUi(self) - self.game = game - for attr in game["customAttributes"]: + self.game = catalog_game + for attr in catalog_game.custom_attributes: if attr["key"] == "developerName": developer = attr["value"] break else: - developer = game["seller"]["name"] - original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + developer = catalog_game.seller["name"] + original_price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] + discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] - self.ui.title_label.setText(game.get("title")) + self.ui.title_label.setText(catalog_game.title) self.ui.developer_label.setText(developer) self.ui.price_label.setText(f'{original_price if original_price != "0" else self.tr("Free")}') if original_price != discount_price: @@ -94,11 +101,10 @@ class WishlistWidget(ShopImageWidget): self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') else: self.ui.discount_label.setVisible(False) - image_model = ImageUrlModel.from_json(game["keyImages"]) - url = image_model.front_wide - if not url: - url = image_model.offer_image_wide - self.fetchPixmap(url) + key_images = catalog_game.key_images + self.fetchPixmap( + key_images.for_dimensions(self.width(), self.height()).url + ) self.delete_button = QPushButton(self) self.delete_button.setIcon(icon("mdi.delete", color="white")) @@ -113,5 +119,7 @@ class WishlistWidget(ShopImageWidget): a0.accept() self.open_game.emit(self.game) # right - elif a0.button() == Qt.RightButton: - pass # self.showMenu(e) + if a0.button() == Qt.RightButton: + a0.accept() + dialog = DebugDialog(self.game.__dict__, self) + dialog.show() diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py index 9860781e..24ec3fd1 100644 --- a/rare/components/tabs/store/search_results.py +++ b/rare/components/tabs/store/search_results.py @@ -13,9 +13,12 @@ from rare.widgets.flow_layout import FlowLayout from rare.widgets.side_tab import SideTabContents from .image_widget import ShopImageWidget +from .api.debug import DebugDialog +from .api.models.response import CatalogOfferModel + class SearchResults(QScrollArea, SideTabContents): - show_info = pyqtSignal(dict) + show_info = pyqtSignal(CatalogOfferModel) def __init__(self, api_core, parent=None): super(SearchResults, self).__init__(parent=parent) @@ -59,22 +62,20 @@ class SearchResults(QScrollArea, SideTabContents): class SearchResultItem(ShopImageWidget): - show_info = pyqtSignal(dict) + show_info = pyqtSignal(CatalogOfferModel) - def __init__(self, manager: QtRequestManager, result: dict, parent=None): + def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None): super(SearchResultItem, self).__init__(manager, parent=parent) self.setFixedSize(ImageSize.Normal) self.ui.setupUi(self) - for img in result["keyImages"]: - if img["type"] in ["DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo"]: - self.fetchPixmap(img["url"]) - break - else: - print("No image found") - self.ui.title_label.setText(result["title"]) - price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + key_images = catalog_game.key_images + self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) + + self.ui.title_label.setText(catalog_game.title) + + price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] + discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: font = self.ui.price_label.font() @@ -84,9 +85,14 @@ class SearchResultItem(ShopImageWidget): else: self.ui.discount_label.setVisible(False) - self.res = result + self.catalog_game = catalog_game def mousePressEvent(self, a0: QMouseEvent) -> None: if a0.button() == Qt.LeftButton: a0.accept() - self.show_info.emit(self.res) + self.show_info.emit(self.catalog_game) + if a0.button() == Qt.RightButton: + a0.accept() + dialog = DebugDialog(self.catalog_game.__dict__, self) + dialog.show() + diff --git a/rare/components/tabs/store/shop_api_core.py b/rare/components/tabs/store/shop_api_core.py index 5d411b04..662df6d0 100644 --- a/rare/components/tabs/store/shop_api_core.py +++ b/rare/components/tabs/store/shop_api_core.py @@ -1,21 +1,33 @@ from logging import getLogger +from typing import List, Callable from PyQt5.QtCore import pyqtSignal, QObject +from PyQt5.QtWidgets import QApplication +from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.constants import ( wishlist_query, search_query, - add_to_wishlist_query, - remove_from_wishlist_query, + wishlist_add_query, + wishlist_remove_query, ) from rare.components.tabs.store.shop_models import BrowseModel from rare.utils.paths import cache_dir from rare.utils.qt_requests import QtRequests +from .api.models.query import SearchStoreQuery +from .api.models.response import ( + DieselProduct, + ResponseModel, + CatalogOfferModel, +) logger = getLogger("ShopAPICore") graphql_url = "https://graphql.epicgames.com/graphql" +DEBUG: Callable[[], bool] = lambda: "--debug" in QApplication.arguments() + + class ShopApiCore(QObject): update_wishlist = pyqtSignal() @@ -25,6 +37,7 @@ class ShopApiCore(QObject): self.language_code: str = language self.country_code: str = country self.locale = f"{self.language_code}-{self.country_code}" + self.locale = "en-US" self.manager = QtRequests(parent=self) self.authed_manager = QtRequests(token=token, parent=self) self.cached_manager = QtRequests(cache=str(cache_dir().joinpath("store")), parent=self) @@ -39,54 +52,67 @@ class ShopApiCore(QObject): "country": self.country_code, "allowCountries": self.country_code, } - self.manager.get(url, lambda data: self._handle_free_games(data, handle_func), params=params) + self.manager.get(url, lambda data: self.__handle_free_games(data, handle_func), params=params) - def _handle_free_games(self, data, handle_func): + @staticmethod + def __handle_free_games(data, handle_func): try: - results: dict = data["data"]["Catalog"]["searchStore"]["elements"] - except KeyError: + response = ResponseModel.from_dict(data) + results: List[CatalogOfferModel] = response.data.catalog.search_store.elements + handle_func(results) + except KeyError as e: + if DEBUG(): + raise e logger.error("Free games Api request failed") handle_func(["error", "Key error"]) return except Exception as e: + if DEBUG(): + raise e logger.error(f"Free games Api request failed: {e}") handle_func(["error", e]) return - handle_func(results) def get_wishlist(self, handle_func): self.authed_manager.post( graphql_url, - lambda data: self._handle_wishlist(data, handle_func), + lambda data: self.__handle_wishlist(data, handle_func), { "query": wishlist_query, "variables": { "country": self.country_code, "locale": self.locale, + "withPrice": True, }, }, ) - def _handle_wishlist(self, data, handle_func): + @staticmethod + def __handle_wishlist(data, handle_func): try: - results: list = data["data"]["Wishlist"]["wishlistItems"]["elements"] - except KeyError: + response = ResponseModel.from_dict(data) + if response.errors: + logger.error(response.errors) + handle_func(response.data.wishlist.wishlist_items.elements) + except KeyError as e: + if DEBUG(): + raise e logger.error("Free games Api request failed") handle_func(["error", "Key error"]) return except Exception as e: + if DEBUG(): + raise e logger.error(f"Free games Api request failed: {e}") handle_func(["error", e]) return - handle_func(results) - - def search_game(self, name, handle_func): + def search_game(self, name, handler): payload = { "query": search_query, "variables": { "category": "games/edition/base|bundles/games|editors|software/edition/base", - "count": 10, + "count": 20, "country": self.country_code, "keywords": name, "locale": self.locale, @@ -99,42 +125,56 @@ class ShopApiCore(QObject): }, } - self.manager.post(graphql_url, lambda data: self._handle_search(data, handle_func), payload) + self.manager.post(graphql_url, lambda data: self.__handle_search(data, handler), payload) - def _handle_search(self, data, handle_func): + @staticmethod + def __handle_search(data, handler): try: - handle_func(data["data"]["Catalog"]["searchStore"]["elements"]) + response = ResponseModel.from_dict(data) + handler(response.data.catalog.search_store.elements) except KeyError as e: logger.error(str(e)) - handle_func([]) + if DEBUG(): + raise e + handler([]) except Exception as e: logger.error(f"Search Api request failed: {e}") - handle_func([]) + if DEBUG(): + raise e + handler([]) return - def browse_games(self, browse_model: BrowseModel, handle_func): + def browse_games(self, browse_model: SearchStoreQuery, handle_func): if self.browse_active: self.next_browse_request = (browse_model, handle_func) return self.browse_active = True payload = { "query": search_query, - "variables": browse_model.__dict__ + "variables": browse_model.to_dict() } - self.manager.post(graphql_url, lambda data: self._handle_browse_games(data, handle_func), payload) + debug = DebugDialog(payload["variables"], None) + debug.exec() + self.manager.post(graphql_url, lambda data: self.__handle_browse_games(data, handle_func), payload) - def _handle_browse_games(self, data, handle_func): + def __handle_browse_games(self, data, handle_func): + debug = DebugDialog(data, None) + debug.exec() self.browse_active = False if data is None: data = {} if not self.next_browse_request: - try: - handle_func(data["data"]["Catalog"]["searchStore"]["elements"]) + response = ResponseModel.from_dict(data) + handle_func(response.data.catalog.search_store.elements) except KeyError as e: + if DEBUG(): + raise e logger.error(str(e)) handle_func([]) except Exception as e: + if DEBUG(): + raise e logger.error(f"Browse games Api request failed: {e}") handle_func([]) return @@ -143,62 +183,72 @@ class ShopApiCore(QObject): self.next_browse_request = tuple(()) def get_game(self, slug: str, is_bundle: bool, handle_func): - url = f"https://store-content.ak.epicgames.com/api/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}" - self.manager.get(url, lambda data: self._handle_get_game(data, handle_func)) + url = "https://store-content.ak.epicgames.com/api" + url += f"/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}" + self.manager.get(url, lambda data: self.__handle_get_game(data, handle_func)) - def _handle_get_game(self, data, handle_func): + @staticmethod + def __handle_get_game(data, handle_func): + debug = DebugDialog(data, None) + debug.exec() try: - handle_func(data) + product = DieselProduct.from_dict(data) + handle_func(product) except Exception as e: - raise e + if DEBUG(): + raise e logger.error(str(e)) # handle_func({}) # needs a captcha def add_to_wishlist(self, namespace, offer_id, handle_func: callable): payload = { + "query": wishlist_add_query, "variables": { "offerId": offer_id, "namespace": namespace, "country": self.country_code, "locale": self.locale, }, - "query": add_to_wishlist_query, } self.authed_manager.post(graphql_url, lambda data: self._handle_add_to_wishlist(data, handle_func), payload) def _handle_add_to_wishlist(self, data, handle_func): + debug = DebugDialog(data, None) + debug.exec() try: - data = data["data"]["Wishlist"]["addToWishlist"] - if data["success"]: - handle_func(True) - else: - handle_func(False) + response = ResponseModel.from_dict(data) + data = response.data.wishlist.add_to_wishlist + handle_func(data.success) except Exception as e: + if DEBUG(): + raise e logger.error(str(e)) handle_func(False) self.update_wishlist.emit() def remove_from_wishlist(self, namespace, offer_id, handle_func: callable): payload = { + "query": wishlist_remove_query, "variables": { "offerId": offer_id, "namespace": namespace, "operation": "REMOVE", }, - "query": remove_from_wishlist_query, } self.authed_manager.post(graphql_url, lambda data: self._handle_remove_from_wishlist(data, handle_func), payload) def _handle_remove_from_wishlist(self, data, handle_func): + debug = DebugDialog(data, None) + debug.exec() try: - data = data["data"]["Wishlist"]["removeFromWishlist"] - if data["success"]: - handle_func(True) - else: - handle_func(False) + response = ResponseModel.from_dict(data) + data = response.data.wishlist.remove_from_wishlist + handle_func(data.success) except Exception as e: + if DEBUG(): + raise e logger.error(str(e)) handle_func(False) self.update_wishlist.emit() diff --git a/rare/components/tabs/store/shop_models.py b/rare/components/tabs/store/shop_models.py deleted file mode 100644 index f124c317..00000000 --- a/rare/components/tabs/store/shop_models.py +++ /dev/null @@ -1,195 +0,0 @@ -import datetime -from dataclasses import dataclass -from typing import List, Dict -import epicstore_api.queries as egs_query - - -class ImageUrlModel: - def __init__( - self, - front_tall: str = "", - offer_image_tall: str = "", - thumbnail: str = "", - front_wide: str = "", - offer_image_wide: str = "", - product_logo: str = "", - ): - self.front_tall = front_tall - self.offer_image_tall = offer_image_tall - self.thumbnail = thumbnail - self.front_wide = front_wide - self.offer_image_wide = offer_image_wide - self.product_logo = product_logo - - @classmethod - def from_json(cls, json_data: list): - tmp = cls() - for item in json_data: - if item["type"] == "Thumbnail": - tmp.thumbnail = item["url"] - elif item["type"] == "DieselStoreFrontTall": - tmp.front_tall = item["url"] - elif item["type"] == "DieselStoreFrontWide": - tmp.front_wide = item["url"] - elif item["type"] == "OfferImageTall": - tmp.offer_image_tall = item["url"] - elif item["type"] == "OfferImageWide": - tmp.offer_image_wide = item["url"] - elif item["type"] == "ProductLogo": - tmp.product_logo = item["url"] - return tmp - - -class ShopGame: - # TODO: Copyrights etc - def __init__( - self, - title: str = "", - id: str = "", - image_urls: ImageUrlModel = None, - social_links: Dict = None, - langs: Dict = None, - reqs: Dict = None, - publisher: str = "", - developer: str = "", - original_price: str = "", - discount_price: str = "", - tags: List = None, - namespace: str = "", - offer_id: str = "", - ): - self.title = title - self.id = id - self.image_urls = image_urls - self.links = [] - if social_links: - for item in social_links: - if item.startswith("link"): - self.links.append( - tuple((item.replace("link", ""), social_links[item])) - ) - else: - self.links = [] - self.languages = langs if langs is not None else {} - self.reqs = reqs if reqs is not None else {} - self.publisher = publisher - self.developer = developer - self.price = original_price - self.discount_price = discount_price - self.tags = tags if tags is not None else [] - self.namespace = namespace - self.offer_id = offer_id - - @classmethod - def from_json(cls, api_data: dict, search_data: dict): - if isinstance(api_data, list): - for product in api_data: - if product["_title"] == "home": - api_data = product - break - if "pages" in api_data.keys(): - for page in api_data["pages"]: - if page["_slug"] == "home": - api_data = page - break - tmp = cls() - if search_data: - tmp.title = search_data.get("title", "Fail") - tmp.id = search_data.get("id") - tmp.image_urls = ImageUrlModel.from_json(search_data["keyImages"]) - if not tmp.developer: - for i in search_data["customAttributes"]: - if i["key"] == "developerName": - tmp.developer = i["value"] - tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][ - "discountPrice" - ] - tmp.namespace = search_data["namespace"] - tmp.offer_id = search_data["id"] - - if api_data: - links = api_data["data"]["socialLinks"] - tmp.links = [] - for item in links: - if item.startswith("link"): - tmp.links.append(tuple((item.replace("link", ""), links[item]))) - tmp.available_voice_langs = api_data["data"]["requirements"].get( - "languages", "Failed" - ) - tmp.reqs = {} - for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])): - try: - tmp.reqs[system["systemType"]] = {} - except KeyError: - continue - for req in system["details"]: - try: - tmp.reqs[system["systemType"]][req["title"]] = ( - req["minimum"], - req["recommended"], - ) - except KeyError: - pass - tmp.publisher = api_data["data"]["meta"].get("publisher", "") - tmp.developer = api_data["data"]["meta"].get("developer", "") - tmp.tags = [ - i.replace("_", " ").capitalize() - for i in api_data["data"]["meta"].get("tags", []) - ] - - return tmp - - -@dataclass -class BrowseModel: - category: str = "games/edition/base|bundles/games|editors|software/edition/base" - count: int = 30 - language_code: str = "en" - country_code: str = "US" - keywords: str = "" - sortDir: str = "DESC" - start: int = 0 - tag: str = "" - withMapping: bool = True - withPrice: bool = True - date: str = ( - f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%S')}.420Z]" - ) - price: str = "" - onSale: bool = False - - def __post_init__(self): - self.locale = f"{self.language_code}-{self.country_code}" - - @property - def __dict__(self): - payload = { - "count": self.count, - "category": self.category, - "allowCountries": self.country_code, - "namespace": "", - "sortBy": "releaseDate", - "sortDir": self.sortDir, - "start": self.start, - "keywords": self.keywords, - "tag": self.tag, - "priceRange": self.price, - "releaseDate": self.date, - "withPrice": self.withPrice, - "locale": self.locale, - "country": self.country_code, - } - if self.price == "free": - payload["freeGame"] = True - payload.pop("priceRange") - elif self.price.startswith(""): - payload["priceRange"] = self.price.replace("", "") - if self.onSale: - payload["onSale"] = True - - if self.price: - payload["effectiveDate"] = self.date - else: - payload.pop("priceRange") - return payload diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index f8cf9a85..d22b51eb 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -11,17 +11,18 @@ from PyQt5.QtWidgets import ( QHBoxLayout, QWidget, QSizePolicy, QStackedLayout, ) - from legendary.core import LegendaryCore + from rare.ui.components.tabs.store.store import Ui_ShopWidget from rare.utils.extra_widgets import ButtonLineEdit from rare.widgets.flow_layout import FlowLayout from rare.widgets.side_tab import SideTabContents +from .api.models.query import SearchStoreQuery +from .api.models.response import CatalogOfferModel, WishlistItemModel from .constants import Constants from .game_widgets import GameWidget from .image_widget import WaitingSpinner from .shop_api_core import ShopApiCore -from .shop_models import BrowseModel logger = logging.getLogger("Shop") @@ -102,8 +103,8 @@ class ShopWidget(QWidget, SideTabContents): def update_wishlist(self): self.api_core.get_wishlist(self.add_wishlist_items) - def add_wishlist_items(self, wishlist): - for w in self.discounts_flow.findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): + def add_wishlist_items(self, wishlist: List[WishlistItemModel]): + for w in self.discounts_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly): self.discounts_flow.layout().removeWidget(w) w.deleteLater() @@ -124,8 +125,8 @@ class ShopWidget(QWidget, SideTabContents): if not game: continue try: - if game["offer"]["price"]["totalPrice"]["discount"] > 0: - w = GameWidget(self.api_core.cached_manager, game["offer"]) + if game.offer.price.total_price["discount"] > 0: + w = GameWidget(self.api_core.cached_manager, game.offer) w.show_info.connect(self.show_game) self.discounts_flow.layout().addWidget(w) discounts += 1 @@ -137,7 +138,7 @@ class ShopWidget(QWidget, SideTabContents): # FIXME: FlowLayout doesn't update on adding widget self.discounts_flow.layout().update() - def add_free_games(self, free_games: list): + def add_free_games(self, free_games: List[CatalogOfferModel]): for w in self.ui.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): self.ui.free_container.layout().removeWidget(w) w.deleteLater() @@ -174,14 +175,14 @@ class ShopWidget(QWidget, SideTabContents): for game in free_games: try: if ( - game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] == "0" - and game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - != game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + game.price.total_price["fmtPrice"]["discountPrice"] == "0" + and game.price.total_price["fmtPrice"]["originalPrice"] + != game.price.total_price["fmtPrice"]["discountPrice"] ): free_games_now.append(game) continue - if game["title"] == "Mystery Game": + if game.title == "Mystery Game": coming_free_games.append(game) continue except KeyError as e: @@ -191,7 +192,7 @@ class ShopWidget(QWidget, SideTabContents): # parse datetime to check if game is next week or now try: start_date = datetime.datetime.strptime( - game["promotions"]["upcomingPromotionalOffers"][0][ + game.promotions["upcomingPromotionalOffers"][0][ "promotionalOffers" ][0]["startDate"], "%Y-%m-%dT%H:%M:%S.%fZ", @@ -199,7 +200,7 @@ class ShopWidget(QWidget, SideTabContents): except Exception: try: start_date = datetime.datetime.strptime( - game["promotions"]["promotionalOffers"][0][ + game.promotions["promotionalOffers"][0][ "promotionalOffers" ][0]["startDate"], "%Y-%m-%dT%H:%M:%S.%fZ", @@ -230,7 +231,7 @@ class ShopWidget(QWidget, SideTabContents): # free games next week for free_game in coming_free_games: w = GameWidget(self.api_core.cached_manager, free_game) - if free_game["title"] != "Mystery Game": + if free_game.title != "Mystery Game": w.show_info.connect(self.show_game) self.free_games_next.layout().addWidget(w) # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) @@ -350,12 +351,12 @@ class ShopWidget(QWidget, SideTabContents): self.games_layout.setCurrentWidget(self.games_spinner) - browse_model = BrowseModel( - language_code=self.core.language_code, - country_code=self.core.country_code, + browse_model = SearchStoreQuery( + language=self.core.language_code, + country=self.core.country_code, count=20, - price=self.price, - onSale=self.ui.on_discount.isChecked(), + price_range=self.price, + on_sale=self.ui.on_discount.isChecked(), ) browse_model.tag = "|".join(self.tags) @@ -364,14 +365,14 @@ class ShopWidget(QWidget, SideTabContents): self.api_core.browse_games(browse_model, self.show_games) def show_games(self, data): - for w in self.games_flow.layout().findChildren(GameWidget, options=Qt.FindDirectChildrenOnly): + for w in self.games_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly): self.games_flow.layout().removeWidget(w) w.deleteLater() if data: for game in data: w = GameWidget(self.api_core.cached_manager, game) - w.show_info.connect(self.show_game.emit) + w.show_info.connect(self.show_game) self.games_flow.layout().addWidget(w) else: self.games_flow.layout().addWidget( diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 0da63f44..b8fe7277 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -1,3 +1,5 @@ +from typing import List + from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QMessageBox, QWidget @@ -7,10 +9,11 @@ from rare.widgets.side_tab import SideTabContents from rare.widgets.flow_layout import FlowLayout from .shop_api_core import ShopApiCore from .game_widgets import WishlistWidget +from .api.models.response import WishlistItemModel, CatalogOfferModel class Wishlist(QWidget, SideTabContents): - show_game_info = pyqtSignal(dict) + show_game_info = pyqtSignal(CatalogOfferModel) update_wishlist_signal = pyqtSignal() def __init__(self, api_core: ShopApiCore, parent=None): @@ -38,10 +41,10 @@ class Wishlist(QWidget, SideTabContents): self.setEnabled(False) self.api_core.get_wishlist(self.set_wishlist) - def delete_from_wishlist(self, game): + def delete_from_wishlist(self, game: CatalogOfferModel): self.api_core.remove_from_wishlist( - game["namespace"], - game["id"], + game.namespace, + game.id, lambda success: self.update_wishlist() if success else QMessageBox.warning( @@ -73,27 +76,26 @@ class Wishlist(QWidget, SideTabContents): self.ui.list_container.layout().removeWidget(w) if sort == 0: - func = lambda x: x.game["title"] + func = lambda x: x.game.title reverse = self.ui.reverse.isChecked() elif sort == 1: - func = lambda x: x.game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + func = lambda x: x.game.price.total_price["fmtPrice"]["discountPrice"] reverse = self.ui.reverse.isChecked() elif sort == 2: - func = lambda x: x.game["seller"]["name"] + func = lambda x: x.game.seller["name"] reverse = self.ui.reverse.isChecked() elif sort == 3: - func = lambda x: 1 - (x.game["price"]["totalPrice"]["discountPrice"] / x.game["price"]["totalPrice"]["originalPrice"]) + func = lambda x: 1 - (x.game.price.total_price["discountPrice"] / x.game.price.total_price["originalPrice"]) reverse = not self.ui.reverse.isChecked() else: - func = lambda x: x.game["title"] + func = lambda x: x.game.title reverse = self.ui.reverse.isChecked() widgets = sorted(widgets, key=func, reverse=reverse) for w in widgets: self.ui.list_container.layout().addWidget(w) - - def set_wishlist(self, wishlist=None, sort=0): + def set_wishlist(self, wishlist: List[WishlistItemModel] = None, sort=0): if wishlist and wishlist[0] == "error": return @@ -111,8 +113,8 @@ class Wishlist(QWidget, SideTabContents): self.ui.no_games_label.setVisible(False) for game in wishlist: - w = WishlistWidget(self.api_core.cached_manager, game["offer"], self.ui.list_container) - w.open_game.connect(self.show_game_info.emit) + w = WishlistWidget(self.api_core.cached_manager, game.offer, self.ui.list_container) + w.open_game.connect(self.show_game_info) w.delete_from_wishlist.connect(self.delete_from_wishlist) self.widgets.append(w) self.list_layout.addWidget(w) diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py index f0c8b720..e827d9d5 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopInfo(object): def setupUi(self, ShopInfo): ShopInfo.setObjectName("ShopInfo") - ShopInfo.resize(747, 442) + ShopInfo.resize(443, 347) ShopInfo.setWindowTitle("ShopGameInfo") self.main_layout = QtWidgets.QHBoxLayout(ShopInfo) self.main_layout.setObjectName("main_layout") @@ -146,19 +146,43 @@ class Ui_ShopInfo(object): self.button_layout.addWidget(self.wishlist_button) self.info_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.buttons_widget) self.right_layout.addLayout(self.info_layout) - self.requirements_group = QtWidgets.QFrame(ShopInfo) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + self.requirements_frame = QtWidgets.QFrame(ShopInfo) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.requirements_group.sizePolicy().hasHeightForWidth()) - self.requirements_group.setSizePolicy(sizePolicy) - self.requirements_group.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.requirements_group.setFrameShadow(QtWidgets.QFrame.Sunken) - self.requirements_group.setObjectName("requirements_group") - self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_group) + sizePolicy.setHeightForWidth(self.requirements_frame.sizePolicy().hasHeightForWidth()) + self.requirements_frame.setSizePolicy(sizePolicy) + self.requirements_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.requirements_frame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.requirements_frame.setObjectName("requirements_frame") + self.requirements_layout = QtWidgets.QHBoxLayout(self.requirements_frame) self.requirements_layout.setContentsMargins(0, 0, 0, 0) self.requirements_layout.setObjectName("requirements_layout") - self.right_layout.addWidget(self.requirements_group) + self.right_layout.addWidget(self.requirements_frame) + self.description_group = QtWidgets.QGroupBox(ShopInfo) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.description_group.sizePolicy().hasHeightForWidth()) + self.description_group.setSizePolicy(sizePolicy) + self.description_group.setFlat(False) + self.description_group.setObjectName("description_group") + self.description_layout = QtWidgets.QVBoxLayout(self.description_group) + self.description_layout.setObjectName("description_layout") + self.description_label = QtWidgets.QLabel(self.description_group) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth()) + self.description_label.setSizePolicy(sizePolicy) + self.description_label.setText("error") + self.description_label.setTextFormat(QtCore.Qt.MarkdownText) + self.description_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.description_label.setWordWrap(True) + self.description_label.setOpenExternalLinks(True) + self.description_label.setObjectName("description_label") + self.description_layout.addWidget(self.description_label) + self.right_layout.addWidget(self.description_group) self.main_layout.addLayout(self.right_layout) self.main_layout.setStretch(1, 1) @@ -176,6 +200,7 @@ class Ui_ShopInfo(object): self.actions_label.setText(_translate("ShopInfo", "Actions")) self.open_store_button.setText(_translate("ShopInfo", "Buy in Epic Games Store")) self.wishlist_button.setText(_translate("ShopInfo", "Add to wishlist")) + self.description_group.setTitle(_translate("ShopInfo", "Description")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/shop_game_info.ui index 802e6fd2..9813c0cb 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -6,8 +6,8 @@ 0 0 - 747 - 442 + 443 + 347 @@ -309,9 +309,9 @@ - + - + 0 0 @@ -338,6 +338,49 @@ + + + + + 0 + 0 + + + + Description + + + false + + + + + + + 0 + 0 + + + + error + + + Qt::MarkdownText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + + + + diff --git a/rare/utils/qt_requests.py b/rare/utils/qt_requests.py index ebd1003f..2a7d8bbd 100644 --- a/rare/utils/qt_requests.py +++ b/rare/utils/qt_requests.py @@ -66,7 +66,7 @@ class QtRequests(QObject): def __post(self, item: RequestQueueItem): request = self.__prepare_request(item) - payload = orjson.dumps(item.payload) # pylint: disable=maybe-no-member + payload = orjson.dumps(item.payload) reply = self.manager.post(request, payload) reply.errorOccurred.connect(self.__on_error) self.__active_requests[reply] = item diff --git a/requirements-dev.txt b/requirements-dev.txt index 96eb2180..d2647125 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ nuitka ordered-set PyQt5-stubs qstylizer + diff --git a/requirements-full.txt b/requirements-full.txt index 55b8e26f..a2754f33 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -13,3 +13,4 @@ pythonnet>=3.0.0rc4; platform_system == "Windows" cefpython3; platform_system == "Windows" pywebview[cef]; platform_system == "Windows" pypresence + diff --git a/requirements.txt b/requirements.txt index 4a9abb8f..a83fe85c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ legendary-gl @ git+https://github.com/derrod/legendary@96e07ff ; platform_system orjson vdf; platform_system == "Linux" or platform_system == "FreeBSD" pywin32; platform_system == "Windows" + From fda82b17cfd3fb979421b3c6e24b97ce74ed1c55 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 12 Apr 2023 14:08:10 +0300 Subject: [PATCH 11/21] Shop: Use a single QGridLayout instead of left and right VBoxLayouts --- rare/components/tabs/store/shop_widget.py | 20 +- rare/ui/components/tabs/store/store.py | 115 +++-- rare/ui/components/tabs/store/store.ui | 492 +++++++++++----------- 3 files changed, 304 insertions(+), 323 deletions(-) diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py index d22b51eb..43e0342e 100644 --- a/rare/components/tabs/store/shop_widget.py +++ b/rare/components/tabs/store/shop_widget.py @@ -24,6 +24,8 @@ from .game_widgets import GameWidget from .image_widget import WaitingSpinner from .shop_api_core import ShopApiCore +from .api.models.utils import parse_date + logger = logging.getLogger("Shop") @@ -77,7 +79,7 @@ class ShopWidget(QWidget, SideTabContents): self.search_bar = ButtonLineEdit( "fa.search", placeholder_text=self.tr("Search Games") ) - self.ui.left_layout.insertWidget(0, self.search_bar) + self.ui.main_layout.addWidget(self.search_bar, 0, 0) # self.search_bar.textChanged.connect(self.search_games) @@ -169,7 +171,7 @@ class ShopWidget(QWidget, SideTabContents): self.free_games_next.setLayout(free_games_next_layout) self.ui.free_container.layout().addWidget(self.free_games_next) - date = datetime.datetime.now() + date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) free_games_now = [] coming_free_games = [] for game in free_games: @@ -191,19 +193,13 @@ class ShopWidget(QWidget, SideTabContents): try: # parse datetime to check if game is next week or now try: - start_date = datetime.datetime.strptime( - game.promotions["upcomingPromotionalOffers"][0][ - "promotionalOffers" - ][0]["startDate"], - "%Y-%m-%dT%H:%M:%S.%fZ", + start_date = parse_date( + game.promotions["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"] ) except Exception: try: - start_date = datetime.datetime.strptime( - game.promotions["promotionalOffers"][0][ - "promotionalOffers" - ][0]["startDate"], - "%Y-%m-%dT%H:%M:%S.%fZ", + start_date = parse_date( + game.promotions["promotionalOffers"][0]["promotionalOffers"][0]["startDate"] ) except Exception as e: diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py index 90c13f3b..14ea03df 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/store.py @@ -14,62 +14,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ShopWidget(object): def setupUi(self, ShopWidget): ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(350, 382) + ShopWidget.resize(788, 662) ShopWidget.setWindowTitle("Store") - self.shop_layout = QtWidgets.QHBoxLayout(ShopWidget) - self.shop_layout.setObjectName("shop_layout") - self.left_layout = QtWidgets.QVBoxLayout() - self.left_layout.setObjectName("left_layout") - self.games_scrollarea = QtWidgets.QScrollArea(ShopWidget) - self.games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) - self.games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) - self.games_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - self.games_scrollarea.setWidgetResizable(True) - self.games_scrollarea.setObjectName("games_scrollarea") - self.games_container = QtWidgets.QWidget() - self.games_container.setGeometry(QtCore.QRect(0, 0, 186, 368)) - self.games_container.setObjectName("games_container") - self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) - self.games_container_layout.setContentsMargins(0, 0, 3, 0) - self.games_container_layout.setObjectName("games_container_layout") - self.free_scrollarea = QtWidgets.QScrollArea(self.games_container) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.free_scrollarea.sizePolicy().hasHeightForWidth()) - self.free_scrollarea.setSizePolicy(sizePolicy) - self.free_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) - self.free_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) - self.free_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.free_scrollarea.setWidgetResizable(True) - self.free_scrollarea.setObjectName("free_scrollarea") - self.free_container = QtWidgets.QWidget() - self.free_container.setGeometry(QtCore.QRect(0, 0, 16, 16)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.free_container.sizePolicy().hasHeightForWidth()) - self.free_container.setSizePolicy(sizePolicy) - self.free_container.setObjectName("free_container") - self.free_container_layout = QtWidgets.QHBoxLayout(self.free_container) - self.free_container_layout.setContentsMargins(0, 0, 0, 3) - self.free_container_layout.setObjectName("free_container_layout") - self.free_scrollarea.setWidget(self.free_container) - self.games_container_layout.addWidget(self.free_scrollarea) - self.discounts_group = QtWidgets.QGroupBox(self.games_container) - self.discounts_group.setObjectName("discounts_group") - self.games_container_layout.addWidget(self.discounts_group) - self.games_group = QtWidgets.QGroupBox(self.games_container) - self.games_group.setObjectName("games_group") - self.games_container_layout.addWidget(self.games_group) - self.games_scrollarea.setWidget(self.games_container) - self.left_layout.addWidget(self.games_scrollarea) - self.shop_layout.addLayout(self.left_layout) - self.right_layout = QtWidgets.QVBoxLayout() - self.right_layout.setObjectName("right_layout") - self.reset_button = QtWidgets.QPushButton(ShopWidget) - self.reset_button.setObjectName("reset_button") - self.right_layout.addWidget(self.reset_button) + self.main_layout = QtWidgets.QGridLayout(ShopWidget) + self.main_layout.setObjectName("main_layout") self.filter_scrollarea = QtWidgets.QScrollArea(ShopWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) @@ -141,17 +89,59 @@ class Ui_ShopWidget(object): self.others_layout.setObjectName("others_layout") self.filter_container_layout.addWidget(self.others_group) self.filter_scrollarea.setWidget(self.filter_container) - self.right_layout.addWidget(self.filter_scrollarea) - self.shop_layout.addLayout(self.right_layout) - self.shop_layout.setStretch(0, 1) + self.main_layout.addWidget(self.filter_scrollarea, 1, 1, 1, 1) + self.reset_button = QtWidgets.QPushButton(ShopWidget) + self.reset_button.setObjectName("reset_button") + self.main_layout.addWidget(self.reset_button, 0, 1, 1, 1) + self.games_scrollarea = QtWidgets.QScrollArea(ShopWidget) + self.games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.games_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.games_scrollarea.setWidgetResizable(True) + self.games_scrollarea.setObjectName("games_scrollarea") + self.games_container = QtWidgets.QWidget() + self.games_container.setGeometry(QtCore.QRect(0, 0, 628, 618)) + self.games_container.setObjectName("games_container") + self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) + self.games_container_layout.setContentsMargins(0, 0, 3, 0) + self.games_container_layout.setObjectName("games_container_layout") + self.free_scrollarea = QtWidgets.QScrollArea(self.games_container) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.free_scrollarea.sizePolicy().hasHeightForWidth()) + self.free_scrollarea.setSizePolicy(sizePolicy) + self.free_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.free_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.free_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.free_scrollarea.setWidgetResizable(True) + self.free_scrollarea.setObjectName("free_scrollarea") + self.free_container = QtWidgets.QWidget() + self.free_container.setGeometry(QtCore.QRect(0, 0, 16, 16)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.free_container.sizePolicy().hasHeightForWidth()) + self.free_container.setSizePolicy(sizePolicy) + self.free_container.setObjectName("free_container") + self.free_container_layout = QtWidgets.QHBoxLayout(self.free_container) + self.free_container_layout.setContentsMargins(0, 0, 0, 3) + self.free_container_layout.setObjectName("free_container_layout") + self.free_scrollarea.setWidget(self.free_container) + self.games_container_layout.addWidget(self.free_scrollarea) + self.discounts_group = QtWidgets.QGroupBox(self.games_container) + self.discounts_group.setObjectName("discounts_group") + self.games_container_layout.addWidget(self.discounts_group) + self.games_group = QtWidgets.QGroupBox(self.games_container) + self.games_group.setObjectName("games_group") + self.games_container_layout.addWidget(self.games_group) + self.games_scrollarea.setWidget(self.games_container) + self.main_layout.addWidget(self.games_scrollarea, 1, 0, 1, 1) self.retranslateUi(ShopWidget) def retranslateUi(self, ShopWidget): _translate = QtCore.QCoreApplication.translate - self.discounts_group.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) - self.games_group.setTitle(_translate("ShopWidget", "Games")) - self.reset_button.setText(_translate("ShopWidget", "Reset filters")) self.price_group.setTitle(_translate("ShopWidget", "Price")) self.none_price.setText(_translate("ShopWidget", "None")) self.free_button.setText(_translate("ShopWidget", "Free")) @@ -164,6 +154,9 @@ class Ui_ShopWidget(object): self.genre_group.setTitle(_translate("ShopWidget", "Genre")) self.type_group.setTitle(_translate("ShopWidget", "Type")) self.others_group.setTitle(_translate("ShopWidget", "Other tags")) + self.reset_button.setText(_translate("ShopWidget", "Reset filters")) + self.discounts_group.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) + self.games_group.setTitle(_translate("ShopWidget", "Games")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui index 90ccdfa8..c46a8bf1 100644 --- a/rare/ui/components/tabs/store/store.ui +++ b/rare/ui/components/tabs/store/store.ui @@ -6,282 +6,274 @@ 0 0 - 679 - 329 + 788 + 662 Store - - - - - - - QFrame::NoFrame + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 142 + 390 + + + + + 0 + 0 + + + + + 0 - - QFrame::Plain + + 0 - - QAbstractScrollArea::AdjustToContents + + 3 - - true + + 0 - - - - 0 - 0 - 515 - 315 - - - - - 0 + + + + Price - - 0 - - - 3 - - - 0 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 16 - 16 - + + + + + None - - - 0 - 0 - + + true - - - 0 - - - 0 - - - 0 - - - 3 - - - - - - - - Discounts from your wishlist - - - - - - - Games - - - - - - - - + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + + + Genre + + + + + + + + Type + + + + + + + + Other tags + + + + + + + - - - - - - Reset filters + + + + Reset filters + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 628 + 618 + + + + + 0 - - - - - - - 0 - 0 - + + 0 - - QFrame::NoFrame + + 3 - - QFrame::Plain + + 0 - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - 0 - 0 - 142 - 390 - - - - - 0 - 0 - - - - - 0 + + + + + 0 + 0 + - - 0 + + QFrame::NoFrame - - 3 + + QFrame::Plain - - 0 + + Qt::ScrollBarAlwaysOff - - - - Price + + true + + + + + 0 + 0 + 16 + 16 + + + + + 0 + 0 + + + + + 0 - - - - - None - - - true - - - - - - - Free - - - - - - - Under 10 - - - - - - - Under 20 - - - - - - - Under 30 - - - - - - - 14.99 and above - - - - - - - Discount - - - - - - - - - - Platform + + 0 - - - - - - - Genre + + 0 - - - - - - - Type + + 3 - - - - - - - Other tags - - - - - - - - - + + + + + + + + Discounts from your wishlist + + + + + + + Games + + + + + + From b4a26b59329a3d7ccea8398082488d7db9bbf5f9 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 23 Sep 2023 20:24:28 +0300 Subject: [PATCH 12/21] Store: Finalize store design --- rare/components/tabs/store/__init__.py | 62 +-- rare/components/tabs/store/__main__.py | 4 +- rare/components/tabs/store/api/debug.py | 2 +- .../tabs/store/api/graphql/schema.graphql | 35 ++ .../components/tabs/store/api/models/query.py | 4 +- .../tabs/store/api/models/response.py | 176 +------- rare/components/tabs/store/landing.py | 212 ++++++++++ rare/components/tabs/store/results.py | 57 +++ rare/components/tabs/store/search.py | 223 ++++++++++ rare/components/tabs/store/search_results.py | 98 ----- rare/components/tabs/store/shop_widget.py | 396 ------------------ .../store/{shop_api_core.py => store_api.py} | 47 ++- .../components/tabs/store/widgets/__init__.py | 0 .../{game_info.py => widgets/details.py} | 48 ++- rare/components/tabs/store/widgets/groups.py | 18 + .../{image_widget.py => widgets/image.py} | 61 +-- .../{game_widgets.py => widgets/items.py} | 86 ++-- rare/components/tabs/store/wishlist.py | 72 +++- .../store/{shop_game_info.py => details.py} | 108 ++--- .../store/{shop_game_info.ui => details.ui} | 65 +-- .../tabs/store/{store.py => landing.py} | 117 ++---- rare/ui/components/tabs/store/landing.ui | 183 ++++++++ rare/ui/components/tabs/store/search.py | 130 ++++++ rare/ui/components/tabs/store/search.ui | 183 ++++++++ rare/ui/components/tabs/store/store.ui | 282 ------------- .../components/tabs/store/widgets/__init__.py | 0 .../store/{ => widgets}/wishlist_widget.py | 4 +- .../store/{ => widgets}/wishlist_widget.ui | 0 rare/ui/components/tabs/store/wishlist.py | 12 +- rare/ui/components/tabs/store/wishlist.ui | 2 +- 30 files changed, 1360 insertions(+), 1327 deletions(-) create mode 100644 rare/components/tabs/store/landing.py create mode 100644 rare/components/tabs/store/results.py create mode 100644 rare/components/tabs/store/search.py delete mode 100644 rare/components/tabs/store/search_results.py delete mode 100644 rare/components/tabs/store/shop_widget.py rename rare/components/tabs/store/{shop_api_core.py => store_api.py} (89%) create mode 100644 rare/components/tabs/store/widgets/__init__.py rename rare/components/tabs/store/{game_info.py => widgets/details.py} (90%) create mode 100644 rare/components/tabs/store/widgets/groups.py rename rare/components/tabs/store/{image_widget.py => widgets/image.py} (67%) rename rare/components/tabs/store/{game_widgets.py => widgets/items.py} (64%) rename rare/ui/components/tabs/store/{shop_game_info.py => details.py} (69%) rename rare/ui/components/tabs/store/{shop_game_info.ui => details.ui} (87%) rename rare/ui/components/tabs/store/{store.py => landing.py} (51%) create mode 100644 rare/ui/components/tabs/store/landing.ui create mode 100644 rare/ui/components/tabs/store/search.py create mode 100644 rare/ui/components/tabs/store/search.ui delete mode 100644 rare/ui/components/tabs/store/store.ui create mode 100644 rare/ui/components/tabs/store/widgets/__init__.py rename rare/ui/components/tabs/store/{ => widgets}/wishlist_widget.py (97%) rename rare/ui/components/tabs/store/{ => widgets}/wishlist_widget.ui (100%) diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index 35b5b6a2..273d2293 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -1,14 +1,13 @@ +from PyQt5.QtGui import QShowEvent, QHideEvent from legendary.core import LegendaryCore -from rare.shared import RareCore -from rare.utils.paths import cache_dir from rare.widgets.side_tab import SideTabWidget -from .game_info import ShopGameInfo -from .search_results import SearchResults -from .shop_api_core import ShopApiCore from .api.models.response import CatalogOfferModel -from .shop_widget import ShopWidget -from .wishlist import WishlistWidget, Wishlist +from .landing import LandingWidget, LandingPage +from .search import SearchPage +from .store_api import StoreAPI +from .widgets.details import DetailsWidget +from .wishlist import WishlistPage class StoreTab(SideTabWidget): @@ -19,45 +18,31 @@ class StoreTab(SideTabWidget): self.core = core # self.rcore = RareCore.instance() - self.api_core = ShopApiCore( + self.api = StoreAPI( self.core.egs.session.headers["Authorization"], self.core.language_code, self.core.country_code, + [] # [i.asset_infos["Windows"].namespace for i in self.rcore.game_list if bool(i.asset_infos)] ) - self.shop = ShopWidget(cache_dir(), self.core, self.api_core, parent=self) - self.shop_index = self.addTab(self.shop, self.tr("Store")) - self.shop.show_game.connect(self.show_game) - self.shop.show_info.connect(self.show_search) + self.landing = LandingPage(self.api, parent=self) + self.landing_index = self.addTab(self.landing, self.tr("Store")) - self.search = SearchResults(self.api_core, parent=self) - self.search_index = self.addTab(self.search, self.tr("Search"), self.tr("Results")) - self.search.show_info.connect(self.show_game) - # self.search.back_button.clicked.connect(lambda: self.setCurrentIndex(self.shop_index)) + self.search = SearchPage(self.core, self.api, parent=self) + self.search_index = self.addTab(self.search, self.tr("Search")) - self.info = ShopGameInfo( - # [i.asset_infos["Windows"].namespace for i in self.rcore.game_list if bool(i.asset_infos)], - [], - self.api_core, - parent=self - ) - self.info_index = self.addTab(self.info, self.tr("Information"), self.tr("Information")) - # self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(self.previous_index)) + self.wishlist = WishlistPage(self.api, parent=self) + self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist")) - self.wishlist = Wishlist(self.api_core, parent=self) - self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist"), self.tr("Wishlist")) - self.wishlist.update_wishlist_signal.connect(self.update_wishlist) - self.wishlist.show_game_info.connect(self.show_game) + self.api.update_wishlist.connect(self.update_wishlist) - self.api_core.update_wishlist.connect(self.update_wishlist) - - self.previous_index = self.shop_index + self.previous_index = self.landing_index def showEvent(self, a0: QShowEvent) -> None: if a0.spontaneous() or self.init: return super().showEvent(a0) - self.shop.load() - self.wishlist_widget.update_wishlist() + # self.landing.load() + # self.wishlist.update_wishlist() self.init = True return super().showEvent(a0) @@ -68,13 +53,4 @@ class StoreTab(SideTabWidget): return super().hideEvent(a0) def update_wishlist(self): - self.shop.update_wishlist() - - def show_game(self, data: CatalogOfferModel): - self.previous_index = self.currentIndex() - self.info.update_game(data) - self.setCurrentIndex(self.info_index) - - def show_search(self, text: str): - self.search.load_results(text) - self.setCurrentIndex(self.search_index) + self.landing.update_wishlist() diff --git a/rare/components/tabs/store/__main__.py b/rare/components/tabs/store/__main__.py index a7366a13..ad7c13f4 100644 --- a/rare/components/tabs/store/__main__.py +++ b/rare/components/tabs/store/__main__.py @@ -18,13 +18,13 @@ class StoreWindow(QDialog): layout = QVBoxLayout(self) layout.addWidget(self.store_tab) - self.store_tab.load() + self.store_tab.show() if __name__ == "__main__": - from rare.utils.misc import set_style_sheet import rare.resources.static_css import rare.resources.stylesheets.RareStyle + from rare.utils.misc import set_style_sheet app = QApplication(sys.argv) app.setApplicationName("Rare") diff --git a/rare/components/tabs/store/api/debug.py b/rare/components/tabs/store/api/debug.py index 083fcca2..24a20016 100644 --- a/rare/components/tabs/store/api/debug.py +++ b/rare/components/tabs/store/api/debug.py @@ -1,7 +1,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QTreeView, QDialog, QVBoxLayout -from utils.json_formatter import QJsonModel +from rare.utils.json_formatter import QJsonModel class DebugView(QTreeView): diff --git a/rare/components/tabs/store/api/graphql/schema.graphql b/rare/components/tabs/store/api/graphql/schema.graphql index 6f3edfda..676fcfe5 100644 --- a/rare/components/tabs/store/api/graphql/schema.graphql +++ b/rare/components/tabs/store/api/graphql/schema.graphql @@ -38,4 +38,39 @@ type LineOfferRes { type GetPriceRes { totalPrice: TotalPrice lineOffers: [LineOfferRes] +} + +type Image { + type: String + url: String + alt: String +} + +type StorePageMapping { + cmsSlug: String + offerId: ID + prePurchaseOfferId: ID +} + +type PageSandboxModel { + pageSlug: String + pageType: String + productId: ID + sandboxId: ID + createdDate: Date + updatedDate: Date + deletedDate: Date + mappings: [StorePageMapping] +} + +type CatalogNamespace { + parent: ID + displayName: String + store: String + mappings: [PageSandboxModel] +} + +type CatalogItem { + id: ID + namespace: ID } \ No newline at end of file diff --git a/rare/components/tabs/store/api/models/query.py b/rare/components/tabs/store/api/models/query.py index 925cc500..f3f21ca8 100644 --- a/rare/components/tabs/store/api/models/query.py +++ b/rare/components/tabs/store/api/models/query.py @@ -6,11 +6,11 @@ from typing import List @dataclass class SearchDateRange: start_date: datetime = datetime(year=1990, month=1, day=1, tzinfo=timezone.utc) - end_date: datetime = datetime.utcnow() + end_date: datetime = datetime.utcnow().replace(tzinfo=timezone.utc) def __str__(self): def fmt_date(date: datetime) -> str: - # lk: The formatting accepted by the GraphQL API is either '%Y-%m-%dT%H:%M:%S.000Z' or '%Y-%m-%dT' + # lk: The formatting accepted by the GraphQL API is either '%Y-%m-%dT%H:%M:%S.000Z' or '%Y-%m-%d' return datetime.strftime(date, '%Y-%m-%dT%H:%M:%S.000Z') return f"[{fmt_date(self.start_date)},{fmt_date(self.end_date)}]" diff --git a/rare/components/tabs/store/api/models/response.py b/rare/components/tabs/store/api/models/response.py index 182985a1..6a7672f2 100644 --- a/rare/components/tabs/store/api/models/response.py +++ b/rare/components/tabs/store/api/models/response.py @@ -1,8 +1,10 @@ import logging from dataclasses import dataclass, field -from datetime import datetime, timezone +from datetime import datetime from typing import List, Dict, Any, Type, Optional +from .utils import parse_date + logger = logging.getLogger("StoreApiModels") # lk: Typing overloads for unimplemented types @@ -13,171 +15,11 @@ CategoryModel = Dict CustomAttributeModel = Dict ItemModel = Dict SellerModel = Dict -OfferMappingModel = Dict +PageSandboxModel = Dict TagModel = Dict PromotionsModel = Dict -def parse_date(date: str): - return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc) - - -@dataclass -class DieselSystemDetailItem: - p_type: Optional[str] = None - minimum: Optional[str] = None - recommended: Optional[str] = None - title: Optional[str] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem": - d = src.copy() - tmp = cls( - p_type=d.pop("_type", ""), - minimum=d.pop("minimum", ""), - recommended=d.pop("recommended", ""), - title=d.pop("title", ""), - ) - tmp.unmapped = d - return tmp - - -@dataclass -class DieselSystemDetail: - p_type: Optional[str] = None - details: Optional[List[DieselSystemDetailItem]] = None - system_type: Optional[str] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselSystemDetail"], src: Dict[str, Any]) -> "DieselSystemDetail": - d = src.copy() - _details = d.pop("details", []) - details = [] if _details else None - for item in _details: - detail = DieselSystemDetailItem.from_dict(item) - details.append(detail) - tmp = cls( - p_type=d.pop("_type", ""), - details=details, - system_type=d.pop("systemType", ""), - ) - tmp.unmapped = d - return tmp - - -@dataclass -class DieselSystemDetails: - p_type: Optional[str] = None - languages: Optional[List[str]] = None - rating: Optional[Dict] = None - systems: Optional[List[DieselSystemDetail]] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselSystemDetails"], src: Dict[str, Any]) -> "DieselSystemDetails": - d = src.copy() - _systems = d.pop("systems", []) - systems = [] if _systems else None - for item in _systems: - system = DieselSystemDetail.from_dict(item) - systems.append(system) - tmp = cls( - p_type=d.pop("_type", ""), - languages=d.pop("languages", []), - rating=d.pop("rating", {}), - systems=systems, - ) - tmp.unmapped = d - return tmp - - -@dataclass -class DieselProductAbout: - p_type: Optional[str] = None - desciption: Optional[str] = None - developer_attribution: Optional[str] = None - publisher_attribution: Optional[str] = None - short_description: Optional[str] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout": - d = src.copy() - tmp = cls( - p_type=d.pop("_type", ""), - desciption=d.pop("description", ""), - developer_attribution=d.pop("developerAttribution", ""), - publisher_attribution=d.pop("publisherAttribution", ""), - short_description=d.pop("shortDescription", ""), - ) - tmp.unmapped = d - return tmp - - -@dataclass -class DieselProductDetail: - p_type: Optional[str] = None - about: Optional[DieselProductAbout] = None - requirements: Optional[DieselSystemDetails] = None - social_links: Optional[DieselSocialLinks] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselProductDetail"], src: Dict[str, Any]) -> "DieselProductDetail": - d = src.copy() - about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None - requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None - tmp = cls( - p_type=d.pop("_type", ""), - about=about, - requirements=requirements, - social_links=d.pop("socialLinks", {}), - ) - tmp.unmapped = d - return tmp - - -@dataclass -class DieselProduct: - p_id: Optional[str] = None - p_images_: Optional[List[str]] = None - p_locale: Optional[str] = None - p_slug: Optional[str] = None - p_title: Optional[str] = None - p_url_pattern: Optional[str] = None - namespace: Optional[str] = None - pages: Optional[List["DieselProduct"]] = None - data: Optional[DieselProductDetail] = None - product_name: Optional[str] = None - unmapped: Dict[str, Any] = field(default_factory=dict) - - @classmethod - def from_dict(cls: Type["DieselProduct"], src: Dict[str, Any]) -> "DieselProduct": - d = src.copy() - _pages = d.pop("pages", []) - pages = [] if _pages else None - for item in _pages: - page = DieselProduct.from_dict(item) - pages.append(page) - data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None - tmp = cls( - p_id=d.pop("_id", ""), - p_images_=d.pop("_images_", []), - p_locale=d.pop("_locale", ""), - p_slug=d.pop("_slug", ""), - p_title=d.pop("_title", ""), - p_url_pattern=d.pop("_urlPattern", ""), - namespace=d.pop("namespace", ""), - pages=pages, - data=data, - product_name=d.pop("productName", ""), - ) - tmp.unmapped = d - return tmp - - @dataclass class ImageUrlModel: type: Optional[str] = None @@ -271,14 +113,14 @@ LineOffersModel = Dict @dataclass -class PriceModel: +class GetPriceResModel: total_price: Optional[TotalPriceModel] = None fmt_price: Optional[FmtPriceModel] = None line_offers: Optional[LineOffersModel] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: Type["PriceModel"], src: Dict[str, Any]) -> "PriceModel": + def from_dict(cls: Type["GetPriceResModel"], src: Dict[str, Any]) -> "GetPriceResModel": d = src.copy() tmp = cls( total_price=d.pop("totalPrice", {}), @@ -302,9 +144,9 @@ class CatalogOfferModel: items: Optional[List[ItemModel]] = None key_images: Optional[KeyImagesModel] = None namespace: Optional[str] = None - offer_mappings: Optional[List[OfferMappingModel]] = None + offer_mappings: Optional[List[PageSandboxModel]] = None offer_type: Optional[str] = None - price: Optional[PriceModel] = None + price: Optional[GetPriceResModel] = None product_slug: Optional[str] = None promotions: Optional[PromotionsModel] = None seller: Optional[SellerModel] = None @@ -322,7 +164,7 @@ class CatalogOfferModel: effective_date = parse_date(x) if (x := d.pop("effectiveDate", "")) else None expiry_date = parse_date(x) if (x := d.pop("expiryDate", "")) else None key_images = KeyImagesModel.from_list(d.pop("keyImages", [])) - price = PriceModel.from_dict(x) if (x := d.pop("price", {})) else None + price = GetPriceResModel.from_dict(x) if (x := d.pop("price", {})) else None viewable_date = parse_date(x) if (x := d.pop("viewableDate", "")) else None tmp = cls( catalog_ns=d.pop("catalogNs", {}), diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py new file mode 100644 index 00000000..6f570a75 --- /dev/null +++ b/rare/components/tabs/store/landing.py @@ -0,0 +1,212 @@ +import datetime +import logging +from typing import List + +from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal +from PyQt5.QtGui import QShowEvent, QHideEvent +from PyQt5.QtWidgets import ( + QHBoxLayout, + QWidget, + QSizePolicy, + QVBoxLayout, + QSpacerItem, + QScrollArea, + QFrame, +) + +from rare.widgets.flow_layout import FlowLayout +from rare.widgets.side_tab import SideTabContents +from rare.widgets.sliding_stack import SlidingStackedWidget +from rare.components.tabs.store.api.models.response import CatalogOfferModel, WishlistItemModel +from .api.models.utils import parse_date +from .store_api import StoreAPI +from .widgets.details import DetailsWidget +from .widgets.items import StoreItemWidget +from .widgets.groups import StoreGroup + +logger = logging.getLogger("StoreLanding") + + +class LandingPage(SlidingStackedWidget, SideTabContents): + + def __init__(self, api: StoreAPI, parent=None): + super(LandingPage, self).__init__(parent=parent) + self.implements_scrollarea = True + + self.landing_widget = LandingWidget(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.details_widget = DetailsWidget([], 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.details_scroll = QScrollArea(self) + self.details_scroll.setWidgetResizable(True) + self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) + self.details_scroll.setWidget(self.details_widget) + + self.setDirection(Qt.Horizontal) + self.addWidget(self.landing_scroll) + self.addWidget(self.details_scroll) + + @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_scroll) + + +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.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + self.free_games_next = StoreGroup(self.tr("Free next week"), layout=QHBoxLayout, parent=self) + self.free_games_next.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + 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("Games"), FlowLayout, self) + self.games_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.games_group.loading(False) + self.games_group.setVisible(True) + + layout.addWidget(self.free_games_now, alignment=Qt.AlignTop) + layout.addWidget(self.free_games_next, 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_games(self.__add_free) + self.api.get_wishlist(self.__add_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 __add_discounts(self, wishlist: List[WishlistItemModel]): + for w in self.discounts_group.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly): + self.discounts_group.layout().removeWidget(w) + w.deleteLater() + + discounts = 0 + for game in wishlist: + if not game: + continue + try: + if game.offer.price.total_price["discount"] > 0: + w = StoreItemWidget(self.api.cached_manager, game.offer) + w.show_details.connect(self.show_details) + self.discounts_group.layout().addWidget(w) + discounts += 1 + except Exception as e: + logger.warning(f"{game} {e}") + continue + # self.discounts_group.setVisible(discounts > 0) + self.discounts_group.loading(False) + + def __add_free(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 game in free_games: + try: + if ( + game.price.total_price["fmtPrice"]["discountPrice"] == "0" + and game.price.total_price["fmtPrice"]["originalPrice"] + != game.price.total_price["fmtPrice"]["discountPrice"] + ): + free_now.append(game) + continue + + if game.title == "Mystery Game": + free_next.append(game) + continue + except KeyError as e: + logger.warning(str(e)) + + try: + # parse datetime to check if game is next week or now + try: + start_date = parse_date( + game.promotions["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"] + ) + except Exception: + try: + start_date = parse_date( + game.promotions["promotionalOffers"][0]["promotionalOffers"][0]["startDate"] + ) + except Exception as e: + continue + + except TypeError: + print("type error") + continue + + if start_date > date: + free_next.append(game) + + # free games now + self.free_games_now.setVisible(bool(len(free_now))) + for game in free_now: + w = StoreItemWidget(self.api.cached_manager, game) + 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(len(free_next))) + for game in free_next: + w = StoreItemWidget(self.api.cached_manager, game) + if game.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): + for w in self.games_group.findChildren(StoreItemWidget, options=Qt.FindDirectChildrenOnly): + self.games_group.layout().removeWidget(w) + w.deleteLater() + + if data: + 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) diff --git a/rare/components/tabs/store/results.py b/rare/components/tabs/store/results.py new file mode 100644 index 00000000..f837a103 --- /dev/null +++ b/rare/components/tabs/store/results.py @@ -0,0 +1,57 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtWidgets import ( + QWidget, + QSizePolicy, + QLabel, + QScrollArea, +) + +from rare.widgets.flow_layout import FlowLayout +from .api.models.response import CatalogOfferModel +from .widgets.items import ResultsItemWidget + + +class ResultsWidget(QScrollArea): + show_details = pyqtSignal(CatalogOfferModel) + + def __init__(self, store_api, parent=None): + super(ResultsWidget, self).__init__(parent=parent) + self.implements_scrollarea = True + self.store_api = store_api + + self.results_container = QWidget(self) + self.results_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.results_layout = FlowLayout(self.results_container) + self.setWidget(self.results_container) + self.setWidgetResizable(True) + + # self.main_layout = QVBoxLayout(self) + # self.main_layout.setContentsMargins(0, 0, 0, 0) + # self.main_layout.addWidget(self.results_scrollarea) + + self.setEnabled(False) + + def load_results(self, text: str): + self.setEnabled(False) + if text != "": + self.store_api.search_game(text, self.show_results) + + def show_results(self, results: dict): + for w in self.results_container.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): + self.results_layout.removeWidget(w) + w.deleteLater() + for w in self.results_container.findChildren(ResultsItemWidget, options=Qt.FindDirectChildrenOnly): + self.results_layout.removeWidget(w) + w.deleteLater() + + if not results: + self.results_layout.addWidget(QLabel(self.tr("No results found"))) + else: + for res in results: + w = ResultsItemWidget(self.store_api.cached_manager, res, parent=self.results_container) + w.show_details.connect(self.show_details.emit) + self.results_layout.addWidget(w) + self.results_layout.update() + self.setEnabled(True) + diff --git a/rare/components/tabs/store/search.py b/rare/components/tabs/store/search.py new file mode 100644 index 00000000..870e9daf --- /dev/null +++ b/rare/components/tabs/store/search.py @@ -0,0 +1,223 @@ +import logging +from typing import List + +from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from PyQt5.QtWidgets import ( + QCheckBox, + QWidget, + QSizePolicy, + QScrollArea, + QFrame, +) +from legendary.core import LegendaryCore + +from rare.ui.components.tabs.store.search import Ui_SearchWidget +from rare.utils.extra_widgets import ButtonLineEdit +from rare.widgets.side_tab import SideTabContents +from rare.widgets.sliding_stack import SlidingStackedWidget +from .api.models.query import SearchStoreQuery +from .api.models.response import CatalogOfferModel +from .constants import Constants +from .results import ResultsWidget +from .store_api import StoreAPI +from .widgets.details import DetailsWidget + +logger = logging.getLogger("Shop") + + +class SearchPage(SlidingStackedWidget, SideTabContents): + def __init__(self, core, api: StoreAPI, parent=None): + super(SearchPage, self).__init__(parent=parent) + self.implements_scrollarea = True + + self.search_widget = SearchWidget(core, api, parent=self) + self.search_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.search_widget.set_title.connect(self.set_title) + self.search_widget.show_details.connect(self.show_details) + + self.details_widget = DetailsWidget([], api, parent=self) + self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.details_widget.set_title.connect(self.set_title) + self.details_widget.back_clicked.connect(self.show_main) + + self.details_scroll = QScrollArea(self) + self.details_scroll.setWidgetResizable(True) + self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) + self.details_scroll.setWidget(self.details_widget) + + self.setDirection(Qt.Horizontal) + self.addWidget(self.search_widget) + self.addWidget(self.details_scroll) + + @pyqtSlot() + def show_main(self): + self.slideInWidget(self.search_widget) + + @pyqtSlot(object) + def show_details(self, game: CatalogOfferModel): + self.details_widget.update_game(game) + self.slideInWidget(self.details_scroll) + + +# noinspection PyAttributeOutsideInit,PyBroadException +class SearchWidget(QWidget, SideTabContents): + show_details = pyqtSignal(CatalogOfferModel) + + def __init__(self, core: LegendaryCore, api: StoreAPI, parent=None): + super(SearchWidget, self).__init__(parent=parent) + self.implements_scrollarea = True + self.ui = Ui_SearchWidget() + self.ui.setupUi(self) + self.ui.main_layout.setContentsMargins(0, 0, 3, 0) + + self.core = core + self.api_core = api + self.price = "" + self.tags = [] + self.types = [] + self.update_games_allowed = True + + self.free_game_widgets = [] + self.active_search_request = False + self.next_search = "" + self.wishlist: List = [] + + self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games")) + self.results_scrollarea = ResultsWidget(self.api_core, self) + self.results_scrollarea.show_details.connect(self.show_details) + + self.ui.left_layout.addWidget(self.search_bar) + self.ui.left_layout.addWidget(self.results_scrollarea) + + self.search_bar.returnPressed.connect(self.show_search_results) + self.search_bar.buttonClicked.connect(self.show_search_results) + + # self.init_filter() + + def load(self): + # load browse games + self.prepare_request() + + def show_search_results(self): + if text := self.search_bar.text(): + self.results_scrollarea.load_results(text) + # self.show_info.emit(self.search_bar.text()) + + def init_filter(self): + self.ui.none_price.toggled.connect( + lambda: self.prepare_request("") if self.ui.none_price.isChecked() else None + ) + self.ui.free_button.toggled.connect( + lambda: self.prepare_request("free") if self.ui.free_button.isChecked() else None + ) + self.ui.under10.toggled.connect( + lambda: self.prepare_request("[0, 1000)") if self.ui.under10.isChecked() else None + ) + self.ui.under20.toggled.connect( + lambda: self.prepare_request("[0, 2000)") if self.ui.under20.isChecked() else None + ) + self.ui.under30.toggled.connect( + lambda: self.prepare_request("[0, 3000)") if self.ui.under30.isChecked() else None + ) + self.ui.above.toggled.connect( + lambda: self.prepare_request("[1499,]") if self.ui.above.isChecked() else None + ) + # self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None) + self.ui.on_discount.toggled.connect(lambda: self.prepare_request()) + constants = Constants() + + self.checkboxes = [] + + for groupbox, variables in [ + (self.ui.genre_group, constants.categories), + (self.ui.platform_group, constants.platforms), + (self.ui.others_group, constants.others), + (self.ui.type_group, constants.types), + ]: + for text, tag in variables: + checkbox = CheckBox(text, tag) + checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x)) + checkbox.deactivated.connect(lambda x: self.prepare_request(removed_tag=x)) + groupbox.layout().addWidget(checkbox) + self.checkboxes.append(checkbox) + self.ui.reset_button.clicked.connect(self.reset_filters) + self.ui.filter_scrollarea.setMinimumWidth( + self.ui.filter_container.sizeHint().width() + + self.ui.filter_container.layout().contentsMargins().left() + + self.ui.filter_container.layout().contentsMargins().right() + + self.ui.filter_scrollarea.verticalScrollBar().sizeHint().width() + ) + + def reset_filters(self): + self.update_games_allowed = False + for cb in self.checkboxes: + cb.setChecked(False) + self.ui.none_price.setChecked(True) + + self.tags = [] + self.types = [] + self.update_games_allowed = True + + self.ui.on_discount.setChecked(False) + + def prepare_request( + self, + price: str = None, + added_tag: int = 0, + removed_tag: int = 0, + added_type: str = "", + removed_type: str = "", + ): + if not self.update_games_allowed: + return + if price is not None: + self.price = price + + if added_tag != 0: + self.tags.append(added_tag) + if removed_tag != 0 and removed_tag in self.tags: + self.tags.remove(removed_tag) + + if added_type: + self.types.append(added_type) + if removed_type and removed_type in self.types: + self.types.remove(removed_type) + if (self.types or self.price) or self.tags or self.ui.on_discount.isChecked(): + # self.free_scrollarea.setVisible(False) + self.discounts_group.setVisible(False) + else: + # self.free_scrollarea.setVisible(True) + if len(self.discounts_group.layout().children()) > 0: + self.discounts_group.setVisible(True) + + self.games_group.loading(True) + + browse_model = SearchStoreQuery( + language=self.core.language_code, + country=self.core.country_code, + count=20, + price_range=self.price, + on_sale=self.ui.on_discount.isChecked(), + ) + browse_model.tag = "|".join(self.tags) + + if self.types: + browse_model.category = "|".join(self.types) + self.api_core.browse_games(browse_model, self.show_games) + + +class CheckBox(QCheckBox): + activated = pyqtSignal(str) + deactivated = pyqtSignal(str) + + def __init__(self, text, tag): + super(CheckBox, self).__init__(text) + self.tag = tag + + self.toggled.connect(self.handle_toggle) + + def handle_toggle(self): + if self.isChecked(): + self.activated.emit(self.tag) + else: + self.deactivated.emit(self.tag) diff --git a/rare/components/tabs/store/search_results.py b/rare/components/tabs/store/search_results.py deleted file mode 100644 index 24ec3fd1..00000000 --- a/rare/components/tabs/store/search_results.py +++ /dev/null @@ -1,98 +0,0 @@ -from PyQt5.QtCore import Qt -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtGui import QMouseEvent -from PyQt5.QtWidgets import ( - QWidget, - QSizePolicy, - QLabel, QScrollArea, -) - -from rare.shared.image_manager import ImageSize -from rare.utils.qt_requests import QtRequestManager -from rare.widgets.flow_layout import FlowLayout -from rare.widgets.side_tab import SideTabContents -from .image_widget import ShopImageWidget - -from .api.debug import DebugDialog -from .api.models.response import CatalogOfferModel - - -class SearchResults(QScrollArea, SideTabContents): - show_info = pyqtSignal(CatalogOfferModel) - - def __init__(self, api_core, parent=None): - super(SearchResults, self).__init__(parent=parent) - self.implements_scrollarea = True - self.api_core = api_core - - self.results_container = QWidget(self) - self.results_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.results_layout = FlowLayout(self.results_container) - self.setWidget(self.results_container) - self.setWidgetResizable(True) - - # self.main_layout = QVBoxLayout(self) - # self.main_layout.setContentsMargins(0, 0, 0, 0) - # self.main_layout.addWidget(self.results_scrollarea) - - self.setEnabled(False) - - def load_results(self, text: str): - self.setEnabled(False) - if text != "": - self.api_core.search_game(text, self.show_results) - - def show_results(self, results: dict): - for w in self.results_container.findChildren(QLabel, options=Qt.FindDirectChildrenOnly): - self.results_layout.removeWidget(w) - w.deleteLater() - for w in self.results_container.findChildren(SearchResultItem, options=Qt.FindDirectChildrenOnly): - self.results_layout.removeWidget(w) - w.deleteLater() - - if not results: - self.results_layout.addWidget(QLabel(self.tr("No results found"))) - else: - for res in results: - w = SearchResultItem(self.api_core.cached_manager, res, parent=self.results_container) - w.show_info.connect(self.show_info.emit) - self.results_layout.addWidget(w) - self.results_layout.update() - self.setEnabled(True) - - -class SearchResultItem(ShopImageWidget): - show_info = pyqtSignal(CatalogOfferModel) - - def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None): - super(SearchResultItem, self).__init__(manager, parent=parent) - self.setFixedSize(ImageSize.Normal) - self.ui.setupUi(self) - - key_images = catalog_game.key_images - self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) - - self.ui.title_label.setText(catalog_game.title) - - price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] - discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] - self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') - if price != discount_price: - font = self.ui.price_label.font() - font.setStrikeOut(True) - self.ui.price_label.setFont(font) - self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') - else: - self.ui.discount_label.setVisible(False) - - self.catalog_game = catalog_game - - def mousePressEvent(self, a0: QMouseEvent) -> None: - if a0.button() == Qt.LeftButton: - a0.accept() - self.show_info.emit(self.catalog_game) - if a0.button() == Qt.RightButton: - a0.accept() - dialog = DebugDialog(self.catalog_game.__dict__, self) - dialog.show() - diff --git a/rare/components/tabs/store/shop_widget.py b/rare/components/tabs/store/shop_widget.py deleted file mode 100644 index 43e0342e..00000000 --- a/rare/components/tabs/store/shop_widget.py +++ /dev/null @@ -1,396 +0,0 @@ -import datetime -import logging -from typing import List - -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtWidgets import ( - QGroupBox, - QCheckBox, - QLabel, - QPushButton, - QHBoxLayout, - QWidget, QSizePolicy, QStackedLayout, -) -from legendary.core import LegendaryCore - -from rare.ui.components.tabs.store.store import Ui_ShopWidget -from rare.utils.extra_widgets import ButtonLineEdit -from rare.widgets.flow_layout import FlowLayout -from rare.widgets.side_tab import SideTabContents -from .api.models.query import SearchStoreQuery -from .api.models.response import CatalogOfferModel, WishlistItemModel -from .constants import Constants -from .game_widgets import GameWidget -from .image_widget import WaitingSpinner -from .shop_api_core import ShopApiCore - -from .api.models.utils import parse_date - -logger = logging.getLogger("Shop") - - -# noinspection PyAttributeOutsideInit,PyBroadException -class ShopWidget(QWidget, SideTabContents): - show_info = pyqtSignal(str) - show_game = pyqtSignal(dict) - - def __init__(self, cache_dir, core: LegendaryCore, shop_api: ShopApiCore, parent=None): - super(ShopWidget, self).__init__(parent=parent) - self.implements_scrollarea = True - self.ui = Ui_ShopWidget() - self.ui.setupUi(self) - self.cache_dir = cache_dir - self.core = core - self.api_core = shop_api - self.price = "" - self.tags = [] - self.types = [] - self.update_games_allowed = True - - self.ui.free_scrollarea.setDisabled(True) - - self.free_game_widgets = [] - self.active_search_request = False - self.next_search = "" - self.wishlist: List = [] - - self.discounts_layout = QStackedLayout(self.ui.discounts_group) - self.discounts_spinner = WaitingSpinner(self.ui.discounts_group) - self.discounts_flow = QWidget(self.ui.discounts_group) - self.discounts_flow.setLayout(FlowLayout(self.discounts_flow)) - self.discounts_flow.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.discounts_layout.addWidget(self.discounts_spinner) - self.discounts_layout.addWidget(self.discounts_flow) - - self.discounts_spinner.start() - self.discounts_layout.setCurrentWidget(self.discounts_spinner) - - self.games_layout = QStackedLayout(self.ui.games_group) - self.games_spinner = WaitingSpinner(self.ui.games_group) - self.games_flow = QWidget(self.ui.games_group) - self.games_flow.setLayout(FlowLayout(self.games_flow)) - self.games_flow.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.games_layout.addWidget(self.games_spinner) - self.games_layout.addWidget(self.games_flow) - - self.games_spinner.start() - self.games_layout.setCurrentWidget(self.games_spinner) - - self.search_bar = ButtonLineEdit( - "fa.search", placeholder_text=self.tr("Search Games") - ) - self.ui.main_layout.addWidget(self.search_bar, 0, 0) - - # self.search_bar.textChanged.connect(self.search_games) - - self.search_bar.returnPressed.connect(self.show_search_results) - self.search_bar.buttonClicked.connect(self.show_search_results) - - self.init_filter() - - self.search_bar.setHidden(True) - self.filter_gb.setHidden(True) - self.filter_game_gb.setHidden(True) - - # self.search_bar.textChanged.connect(self.load_completer) - - def load(self): - # load free games - self.api_core.get_free_games(self.add_free_games) - # load wishlist - self.api_core.get_wishlist(self.add_wishlist_items) - # load browse games - self.prepare_request() - - def update_wishlist(self): - self.api_core.get_wishlist(self.add_wishlist_items) - - def add_wishlist_items(self, wishlist: List[WishlistItemModel]): - for w in self.discounts_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly): - self.discounts_flow.layout().removeWidget(w) - w.deleteLater() - - # if wishlist and wishlist[0] == "error": - # self.discounts_group.layout().addWidget( - # QLabel(self.tr("Failed to get wishlist: {}").format(wishlist[1])) - # ) - # btn = QPushButton(self.tr("Reload")) - # self.discount_widget.layout().addWidget(btn) - # btn.clicked.connect( - # lambda: self.api_core.get_wishlist(self.add_wishlist_items) - # ) - # self.discount_stack.setCurrentIndex(0) - # return - - discounts = 0 - for game in wishlist: - if not game: - continue - try: - if game.offer.price.total_price["discount"] > 0: - w = GameWidget(self.api_core.cached_manager, game.offer) - w.show_info.connect(self.show_game) - self.discounts_flow.layout().addWidget(w) - discounts += 1 - except Exception as e: - logger.warning(f"{game} {e}") - continue - self.ui.discounts_group.setVisible(discounts > 0) - self.discounts_layout.setCurrentWidget(self.discounts_flow) - # FIXME: FlowLayout doesn't update on adding widget - self.discounts_flow.layout().update() - - def add_free_games(self, free_games: List[CatalogOfferModel]): - for w in self.ui.free_container.layout().findChildren(QGroupBox, options=Qt.FindDirectChildrenOnly): - self.ui.free_container.layout().removeWidget(w) - w.deleteLater() - - if free_games and free_games[0] == "error": - self.ui.free_container.layout().addWidget( - QLabel(self.tr("Failed to fetch free games: {}").format(free_games[1])) - ) - btn = QPushButton(self.tr("Reload")) - self.ui.free_container.layout().addWidget(btn) - btn.clicked.connect( - lambda: self.api_core.get_free_games(self.add_free_games) - ) - self.ui.free_container.setEnabled(True) - return - - self.free_games_now = QGroupBox(self.tr("Free now"), parent=self.ui.free_container) - free_games_now_layout = QHBoxLayout(self.free_games_now) - # free_games_now_layout.setContentsMargins(0, 0, 0, 0) - self.free_games_now.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.free_games_now.setLayout(free_games_now_layout) - self.ui.free_container.layout().addWidget(self.free_games_now) - - self.free_games_next = QGroupBox(self.tr("Free next week"), parent=self.ui.free_container) - free_games_next_layout = QHBoxLayout(self.free_games_next) - # free_games_next_layout.setContentsMargins(0, 0, 0, 0) - self.free_games_next.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.free_games_next.setLayout(free_games_next_layout) - self.ui.free_container.layout().addWidget(self.free_games_next) - - date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) - free_games_now = [] - coming_free_games = [] - for game in free_games: - try: - if ( - game.price.total_price["fmtPrice"]["discountPrice"] == "0" - and game.price.total_price["fmtPrice"]["originalPrice"] - != game.price.total_price["fmtPrice"]["discountPrice"] - ): - free_games_now.append(game) - continue - - if game.title == "Mystery Game": - coming_free_games.append(game) - continue - except KeyError as e: - logger.warning(str(e)) - - try: - # parse datetime to check if game is next week or now - try: - start_date = parse_date( - game.promotions["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"] - ) - except Exception: - try: - start_date = parse_date( - game.promotions["promotionalOffers"][0]["promotionalOffers"][0]["startDate"] - ) - except Exception as e: - - continue - - except TypeError: - print("type error") - continue - - if start_date > date: - coming_free_games.append(game) - # free games now - now_free = 0 - for free_game in free_games_now: - w = GameWidget(self.api_core.cached_manager, free_game) - w.show_info.connect(self.show_game) - self.free_games_now.layout().addWidget(w) - self.free_game_widgets.append(w) - now_free += 1 - if now_free == 0: - self.free_games_now.layout().addWidget( - QLabel(self.tr("Could not find current free game")) - ) - - # free games next week - for free_game in coming_free_games: - w = GameWidget(self.api_core.cached_manager, free_game) - if free_game.title != "Mystery Game": - w.show_info.connect(self.show_game) - self.free_games_next.layout().addWidget(w) - # self.coming_free_games.setFixedWidth(int(40 + len(coming_free_games) * 300)) - - self.ui.free_scrollarea.setMinimumHeight( - self.free_games_now.sizeHint().height() - + self.ui.free_container.layout().contentsMargins().top() - + self.ui.free_container.layout().contentsMargins().bottom() - + self.ui.free_scrollarea.horizontalScrollBar().sizeHint().height() - ) - self.ui.free_scrollarea.setEnabled(True) - - def show_search_results(self): - if self.search_bar.text(): - self.show_info.emit(self.search_bar.text()) - - def init_filter(self): - self.ui.none_price.toggled.connect( - lambda: self.prepare_request("") if self.ui.none_price.isChecked() else None - ) - self.ui.free_button.toggled.connect( - lambda: self.prepare_request("free") - if self.ui.free_button.isChecked() - else None - ) - self.ui.under10.toggled.connect( - lambda: self.prepare_request("[0, 1000)") - if self.ui.under10.isChecked() - else None - ) - self.ui.under20.toggled.connect( - lambda: self.prepare_request("[0, 2000)") - if self.ui.under20.isChecked() - else None - ) - self.ui.under30.toggled.connect( - lambda: self.prepare_request("[0, 3000)") - if self.ui.under30.isChecked() - else None - ) - self.ui.above.toggled.connect( - lambda: self.prepare_request("[1499,]") - if self.ui.above.isChecked() - else None - ) - # self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None) - self.ui.on_discount.toggled.connect(lambda: self.prepare_request()) - constants = Constants() - - self.checkboxes = [] - - for groupbox, variables in [ - (self.ui.genre_group, constants.categories), - (self.ui.platform_group, constants.platforms), - (self.ui.others_group, constants.others), - (self.ui.type_group, constants.types), - ]: - - for text, tag in variables: - checkbox = CheckBox(text, tag) - checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x)) - checkbox.deactivated.connect( - lambda x: self.prepare_request(removed_tag=x) - ) - groupbox.layout().addWidget(checkbox) - self.checkboxes.append(checkbox) - self.ui.reset_button.clicked.connect(self.reset_filters) - self.ui.filter_scrollarea.setMinimumWidth( - self.ui.filter_container.sizeHint().width() - + self.ui.filter_container.layout().contentsMargins().left() - + self.ui.filter_container.layout().contentsMargins().right() - + self.ui.filter_scrollarea.verticalScrollBar().sizeHint().width() - ) - - def reset_filters(self): - self.update_games_allowed = False - for cb in self.checkboxes: - cb.setChecked(False) - self.ui.none_price.setChecked(True) - - self.tags = [] - self.types = [] - self.update_games_allowed = True - self.prepare_request("") - - self.ui.on_discount.setChecked(False) - - def prepare_request( - self, - price: str = None, - added_tag: int = 0, - removed_tag: int = 0, - added_type: str = "", - removed_type: str = "", - ): - if not self.update_games_allowed: - return - if price is not None: - self.price = price - - if added_tag != 0: - self.tags.append(added_tag) - if removed_tag != 0 and removed_tag in self.tags: - self.tags.remove(removed_tag) - - if added_type: - self.types.append(added_type) - if removed_type and removed_type in self.types: - self.types.remove(removed_type) - if (self.types or self.price) or self.tags or self.ui.on_discount.isChecked(): - self.ui.free_scrollarea.setVisible(False) - self.ui.discounts_group.setVisible(False) - else: - self.ui.free_scrollarea.setVisible(True) - if len(self.ui.discounts_group.layout().children()) > 0: - self.ui.discounts_group.setVisible(True) - - self.games_layout.setCurrentWidget(self.games_spinner) - - browse_model = SearchStoreQuery( - language=self.core.language_code, - country=self.core.country_code, - count=20, - price_range=self.price, - on_sale=self.ui.on_discount.isChecked(), - ) - browse_model.tag = "|".join(self.tags) - - if self.types: - browse_model.category = "|".join(self.types) - self.api_core.browse_games(browse_model, self.show_games) - - def show_games(self, data): - for w in self.games_flow.findChildren(QWidget, options=Qt.FindDirectChildrenOnly): - self.games_flow.layout().removeWidget(w) - w.deleteLater() - - if data: - for game in data: - w = GameWidget(self.api_core.cached_manager, game) - w.show_info.connect(self.show_game) - self.games_flow.layout().addWidget(w) - else: - self.games_flow.layout().addWidget( - QLabel(self.tr("Could not get games matching the filter")) - ) - self.games_layout.setCurrentWidget(self.games_flow) - # FIXME: FlowLayout doesn't update on adding widget - self.games_flow.layout().update() - - -class CheckBox(QCheckBox): - activated = pyqtSignal(str) - deactivated = pyqtSignal(str) - - def __init__(self, text, tag): - super(CheckBox, self).__init__(text) - self.tag = tag - - self.toggled.connect(self.handle_toggle) - - def handle_toggle(self): - if self.isChecked(): - self.activated.emit(self.tag) - else: - self.deactivated.emit(self.tag) diff --git a/rare/components/tabs/store/shop_api_core.py b/rare/components/tabs/store/store_api.py similarity index 89% rename from rare/components/tabs/store/shop_api_core.py rename to rare/components/tabs/store/store_api.py index 662df6d0..90ac2b7e 100644 --- a/rare/components/tabs/store/shop_api_core.py +++ b/rare/components/tabs/store/store_api.py @@ -11,12 +11,11 @@ from rare.components.tabs.store.constants import ( wishlist_add_query, wishlist_remove_query, ) -from rare.components.tabs.store.shop_models import BrowseModel from rare.utils.paths import cache_dir from rare.utils.qt_requests import QtRequests from .api.models.query import SearchStoreQuery +from .api.models.diesel import DieselProduct from .api.models.response import ( - DieselProduct, ResponseModel, CatalogOfferModel, ) @@ -28,11 +27,11 @@ graphql_url = "https://graphql.epicgames.com/graphql" DEBUG: Callable[[], bool] = lambda: "--debug" in QApplication.arguments() -class ShopApiCore(QObject): +class StoreAPI(QObject): update_wishlist = pyqtSignal() - def __init__(self, token, language: str, country: str): - super(ShopApiCore, self).__init__() + def __init__(self, token, language: str, country: str, installed): + super(StoreAPI, self).__init__() self.token = token self.language_code: str = language self.country_code: str = country @@ -42,6 +41,8 @@ class ShopApiCore(QObject): self.authed_manager = QtRequests(token=token, parent=self) self.cached_manager = QtRequests(cache=str(cache_dir().joinpath("store")), parent=self) + self.installed = installed + self.browse_active = False self.next_browse_request = tuple(()) @@ -153,13 +154,13 @@ class ShopApiCore(QObject): "query": search_query, "variables": browse_model.to_dict() } - debug = DebugDialog(payload["variables"], None) - debug.exec() + # debug = DebugDialog(payload["variables"], None) + # debug.exec() self.manager.post(graphql_url, lambda data: self.__handle_browse_games(data, handle_func), payload) def __handle_browse_games(self, data, handle_func): - debug = DebugDialog(data, None) - debug.exec() + # debug = DebugDialog(data, None) + # debug.exec() self.browse_active = False if data is None: data = {} @@ -182,15 +183,29 @@ class ShopApiCore(QObject): self.browse_games(*self.next_browse_request) # pylint: disable=E1120 self.next_browse_request = tuple(()) - def get_game(self, slug: str, is_bundle: bool, handle_func): + def get_game_config_graphql(self, namespace: str, handle_func): + payload = { + "query": config_query, + "variables": { + "namespace": namespace + } + } + + def __make_graphql_query(self): + pass + + def __make_api_query(self): + pass + + def get_game_config_cms(self, slug: str, is_bundle: bool, handle_func): url = "https://store-content.ak.epicgames.com/api" url += f"/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}" self.manager.get(url, lambda data: self.__handle_get_game(data, handle_func)) @staticmethod def __handle_get_game(data, handle_func): - debug = DebugDialog(data, None) - debug.exec() + # debug = DebugDialog(data, None) + # debug.exec() try: product = DieselProduct.from_dict(data) handle_func(product) @@ -214,8 +229,8 @@ class ShopApiCore(QObject): self.authed_manager.post(graphql_url, lambda data: self._handle_add_to_wishlist(data, handle_func), payload) def _handle_add_to_wishlist(self, data, handle_func): - debug = DebugDialog(data, None) - debug.exec() + # debug = DebugDialog(data, None) + # debug.exec() try: response = ResponseModel.from_dict(data) data = response.data.wishlist.add_to_wishlist @@ -240,8 +255,8 @@ class ShopApiCore(QObject): payload) def _handle_remove_from_wishlist(self, data, handle_func): - debug = DebugDialog(data, None) - debug.exec() + # debug = DebugDialog(data, None) + # debug.exec() try: response = ResponseModel.from_dict(data) data = response.data.wishlist.remove_from_wishlist diff --git a/rare/components/tabs/store/widgets/__init__.py b/rare/components/tabs/store/widgets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/components/tabs/store/game_info.py b/rare/components/tabs/store/widgets/details.py similarity index 90% rename from rare/components/tabs/store/game_info.py rename to rare/components/tabs/store/widgets/details.py index 19a27524..07f8c84c 100644 --- a/rare/components/tabs/store/game_info.py +++ b/rare/components/tabs/store/widgets/details.py @@ -1,8 +1,7 @@ import logging -from pprint import pprint from typing import List -from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtCore import Qt, QUrl, pyqtSignal from PyQt5.QtGui import QFont, QDesktopServices, QFontMetrics from PyQt5.QtWidgets import ( QWidget, @@ -12,38 +11,42 @@ from PyQt5.QtWidgets import ( QSizePolicy, ) -from rare.components.tabs.store.api.models.response import CatalogOfferModel, DieselProduct, DieselProductDetail -from rare.shared import LegendaryCoreSingleton -from rare.shared.image_manager import ImageSize -from rare.ui.components.tabs.store.shop_game_info import Ui_ShopInfo +from rare.components.tabs.store.api.debug import DebugDialog +from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail +from rare.components.tabs.store.api.models.response import CatalogOfferModel +from rare.models.image import ImageSize +from rare.ui.components.tabs.store.details import Ui_DetailsWidget from rare.utils.misc import icon -from rare.widgets.side_tab import SideTabWidget, SideTabContents from rare.widgets.elide_label import ElideLabel -from .api.debug import DebugDialog -from .image_widget import ShopImageWidget +from rare.widgets.side_tab import SideTabWidget, SideTabContents +from .image import LoadingImageWidget -logger = logging.getLogger("ShopInfo") +logger = logging.getLogger("StoreDetails") -class ShopGameInfo(QWidget, SideTabContents): +class DetailsWidget(QWidget, SideTabContents): + back_clicked: pyqtSignal = pyqtSignal() # TODO Design def __init__(self, installed_titles: list, api_core, parent=None): - super(ShopGameInfo, self).__init__(parent=parent) - self.ui = Ui_ShopInfo() + super(DetailsWidget, self).__init__(parent=parent) + self.ui = Ui_DetailsWidget() self.ui.setupUi(self) + self.ui.main_layout.setContentsMargins(0, 0, 3, 0) + # self.core = LegendaryCoreSingleton() self.api_core = api_core self.installed = installed_titles - self.ui.open_store_button.clicked.connect(self.button_clicked) - self.image = ShopImageWidget(api_core.cached_manager, self) - self.image.setFixedSize(ImageSize.Normal) - self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) - self.offer: CatalogOfferModel = None self.data: dict = {} + self.image = LoadingImageWidget(api_core.cached_manager, self) + self.image.setFixedSize(ImageSize.Normal) + self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) + self.ui.left_layout.setAlignment(Qt.AlignTop) + self.ui.wishlist_button.clicked.connect(self.add_to_wishlist) + self.ui.open_store_button.clicked.connect(self.button_clicked) self.ui.wishlist_button.setVisible(True) self.in_wishlist = False self.wishlist = [] @@ -52,7 +55,9 @@ class ShopGameInfo(QWidget, SideTabContents): self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ui.requirements_layout.addWidget(self.requirements_tabs) - self.setDisabled(True) + self.ui.back_button.clicked.connect(self.back_clicked) + + self.setDisabled(False) def handle_wishlist_update(self, wishlist: List[CatalogOfferModel]): if wishlist and wishlist[0] == "error": @@ -67,7 +72,6 @@ class ShopGameInfo(QWidget, SideTabContents): def update_game(self, offer: CatalogOfferModel): debug = DebugDialog(offer.__dict__, None) debug.exec() - self.set_title.emit(offer.title) self.ui.title.setText(offer.title) self.title_str = offer.title self.id_str = offer.id @@ -107,7 +111,7 @@ class ShopGameInfo(QWidget, SideTabContents): # init API request if slug: - self.api_core.get_game(offer.product_slug, is_bundle, self.data_received) + self.api_core.get_game_config_cms(offer.product_slug, is_bundle, self.data_received) # else: # self.data_received({}) self.offer = offer @@ -220,7 +224,7 @@ class ShopGameInfo(QWidget, SideTabContents): # self.image_stack.setCurrentIndex(0) about = product_data.about - self.ui.description_label.setText(about.desciption) + self.ui.description_label.setMarkdown(about.desciption) self.ui.dev.setText(about.developer_attribution) # try: # if isinstance(aboudeveloper, list): diff --git a/rare/components/tabs/store/widgets/groups.py b/rare/components/tabs/store/widgets/groups.py new file mode 100644 index 00000000..3675ecc7 --- /dev/null +++ b/rare/components/tabs/store/widgets/groups.py @@ -0,0 +1,18 @@ +from PyQt5.QtWidgets import QGroupBox, QLayout + +from rare.widgets.loading_widget import LoadingWidget + + +class StoreGroup(QGroupBox): + def __init__(self, title: str, layout: type[QLayout], parent=None): + super().__init__(parent=parent) + self.setTitle(title) + self.main_layout = layout(self) + self.loading_widget = LoadingWidget(autostart=True, parent=self) + + def loading(self, state: bool) -> None: + if state: + self.loading_widget.start() + else: + self.loading_widget.stop() + diff --git a/rare/components/tabs/store/image_widget.py b/rare/components/tabs/store/widgets/image.py similarity index 67% rename from rare/components/tabs/store/image_widget.py rename to rare/components/tabs/store/widgets/image.py index ec925943..22bd0140 100644 --- a/rare/components/tabs/store/image_widget.py +++ b/rare/components/tabs/store/widgets/image.py @@ -1,10 +1,7 @@ -from PyQt5.QtCore import QEvent, QObject from PyQt5.QtCore import Qt from PyQt5.QtGui import ( QPixmap, QImage, - QMovie, - QShowEvent, ) from PyQt5.QtWidgets import ( QWidget, @@ -15,53 +12,9 @@ from PyQt5.QtWidgets import ( QLabel, ) -from rare.utils.qt_requests import QtRequestManager +from rare.utils.qt_requests import QtRequests from rare.widgets.image_widget import ImageWidget - - -class WaitingSpinner(QLabel): - def __init__(self, autostart=False, parent=None): - super(WaitingSpinner, self).__init__(parent=parent) - self.setObjectName(type(self).__name__) - self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - self.movie = QMovie(":/images/loader.gif", parent=self) - self.setFixedSize(128, 128) - self.setMovie(self.movie) - if self.parent() is not None: - self.parent().installEventFilter(self) - if autostart: - self.movie.start() - - def __center_on_parent(self): - rect = self.rect() - rect.moveCenter(self.parent().contentsRect().center()) - self.setGeometry(rect) - - def event(self, e: QEvent) -> bool: - if e.type() == QEvent.ParentAboutToChange: - if self.parent() is not None: - self.parent().removeEventFilter(self) - if e.type() == QEvent.ParentChange: - if self.parent() is not None: - self.parent().installEventFilter(self) - return super().event(e) - - def showEvent(self, a0: QShowEvent) -> None: - self.__center_on_parent() - - def eventFilter(self, a0: QObject, a1: QEvent) -> bool: - if a0 is self.parent() and a1.type() == QEvent.Resize: - self.__center_on_parent() - return a0.event(a1) - return False - - def start(self): - self.setVisible(True) - self.movie.start() - - def stop(self): - self.setVisible(False) - self.movie.stop() +from rare.widgets.loading_widget import LoadingWidget class IconWidget(object): @@ -76,7 +29,7 @@ class IconWidget(object): # on-hover popup self.mini_widget = QWidget(parent=widget) self.mini_widget.setObjectName(f"{type(self).__name__}MiniWidget") - self.mini_widget.setFixedHeight(widget.height() // 4) + self.mini_widget.setFixedHeight(int(widget.height() // 3)) # game title self.title_label = QLabel(parent=self.mini_widget) @@ -130,11 +83,11 @@ class IconWidget(object): widget.setLayout(image_layout) -class ShopImageWidget(ImageWidget): - def __init__(self, manager: QtRequestManager, parent=None): - super(ShopImageWidget, self).__init__(parent=parent) +class LoadingImageWidget(ImageWidget): + def __init__(self, manager: QtRequests, parent=None): + super(LoadingImageWidget, self).__init__(parent=parent) self.ui = IconWidget() - self.spinner = WaitingSpinner(parent=self) + self.spinner = LoadingWidget(parent=self) self.spinner.setVisible(False) self.manager = manager diff --git a/rare/components/tabs/store/game_widgets.py b/rare/components/tabs/store/widgets/items.py similarity index 64% rename from rare/components/tabs/store/game_widgets.py rename to rare/components/tabs/store/widgets/items.py index 9b453969..96427501 100644 --- a/rare/components/tabs/store/game_widgets.py +++ b/rare/components/tabs/store/widgets/items.py @@ -3,26 +3,40 @@ import logging from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QPushButton -from orjson import orjson +from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.api.models.response import CatalogOfferModel -from rare.shared.image_manager import ImageSize +from rare.models.image import ImageSize from rare.utils.misc import qta_icon -from rare.utils.qt_requests import QtRequestManager -from .api.debug import DebugDialog -from .image_widget import ShopImageWidget +from rare.utils.qt_requests import QtRequests +from .image import LoadingImageWidget logger = logging.getLogger("GameWidgets") -class GameWidget(ShopImageWidget): - show_info = pyqtSignal(CatalogOfferModel) +class ItemWidget(LoadingImageWidget): + show_details = pyqtSignal(CatalogOfferModel) - def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel = None, parent=None): - super(GameWidget, self).__init__(manager, parent=parent) + def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel = None, parent=None): + super(ItemWidget, self).__init__(manager, parent=parent) + self.catalog_game = catalog_game + + def mousePressEvent(self, a0: QMouseEvent) -> None: + if a0.button() == Qt.LeftButton: + a0.accept() + self.show_details.emit(self.catalog_game) + if a0.button() == Qt.RightButton: + a0.accept() + print(self.catalog_game.__dict__) + dialog = DebugDialog(self.catalog_game.__dict__, self) + dialog.show() + + +class StoreItemWidget(ItemWidget): + def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel = None, parent=None): + super(StoreItemWidget, self).__init__(manager, catalog_game, parent=parent) self.setFixedSize(ImageSize.Wide) self.ui.setupUi(self) - self.catalog_game = catalog_game if catalog_game: self.init_ui(catalog_game) @@ -62,26 +76,38 @@ class GameWidget(ShopImageWidget): # else: # logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) - def mousePressEvent(self, a0: QMouseEvent) -> None: - if a0.button() == Qt.LeftButton: - a0.accept() - self.show_info.emit(self.catalog_game) - if a0.button() == Qt.RightButton: - a0.accept() - print(self.catalog_game.__dict__) - dialog = DebugDialog(self.catalog_game.__dict__, self) - dialog.show() + +class ResultsItemWidget(ItemWidget): + def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None): + super(ResultsItemWidget, self).__init__(manager, catalog_game, parent=parent) + self.setFixedSize(ImageSize.Normal) + self.ui.setupUi(self) + + key_images = catalog_game.key_images + self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) + + self.ui.title_label.setText(catalog_game.title) + + price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] + discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] + self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') + if price != discount_price: + font = self.ui.price_label.font() + font.setStrikeOut(True) + self.ui.price_label.setFont(font) + self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') + else: + self.ui.discount_label.setVisible(False) -class WishlistWidget(ShopImageWidget): - open_game = pyqtSignal(CatalogOfferModel) +class WishlistItemWidget(ItemWidget): + show_details = pyqtSignal(CatalogOfferModel) delete_from_wishlist = pyqtSignal(CatalogOfferModel) - def __init__(self, manager: QtRequestManager, catalog_game: CatalogOfferModel, parent=None): - super(WishlistWidget, self).__init__(manager, parent=parent) + def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None): + super(WishlistItemWidget, self).__init__(manager, catalog_game, parent=parent) self.setFixedSize(ImageSize.Wide) self.ui.setupUi(self) - self.game = catalog_game for attr in catalog_game.custom_attributes: if attr["key"] == "developerName": developer = attr["value"] @@ -109,17 +135,7 @@ class WishlistWidget(ShopImageWidget): self.delete_button = QPushButton(self) self.delete_button.setIcon(icon("mdi.delete", color="white")) self.delete_button.clicked.connect( - lambda: self.delete_from_wishlist.emit(self.game) + lambda: self.delete_from_wishlist.emit(self.catalog_game) ) self.layout().insertWidget(0, self.delete_button, alignment=Qt.AlignRight) - def mousePressEvent(self, a0: QMouseEvent) -> None: - # left button - if a0.button() == Qt.LeftButton: - a0.accept() - self.open_game.emit(self.game) - # right - if a0.button() == Qt.RightButton: - a0.accept() - dialog = DebugDialog(self.game.__dict__, self) - dialog.show() diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index b8fe7277..3d9305fd 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -1,27 +1,65 @@ from typing import List -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtWidgets import QMessageBox, QWidget +from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from PyQt5.QtGui import QShowEvent +from PyQt5.QtWidgets import QMessageBox, QWidget, QScrollArea, QFrame, QSizePolicy from rare.ui.components.tabs.store.wishlist import Ui_Wishlist from rare.utils.misc import icon -from rare.widgets.side_tab import SideTabContents from rare.widgets.flow_layout import FlowLayout -from .shop_api_core import ShopApiCore -from .game_widgets import WishlistWidget +from rare.widgets.side_tab import SideTabContents +from rare.widgets.sliding_stack import SlidingStackedWidget from .api.models.response import WishlistItemModel, CatalogOfferModel +from .store_api import StoreAPI +from .widgets.details import DetailsWidget +from .widgets.items import WishlistItemWidget -class Wishlist(QWidget, SideTabContents): - show_game_info = pyqtSignal(CatalogOfferModel) +class WishlistPage(SlidingStackedWidget, SideTabContents): + def __init__(self, api: StoreAPI, parent=None): + super(WishlistPage, self).__init__(parent=parent) + self.implements_scrollarea = True + + self.wishlist_widget = WishlistWidget(api, parent=self) + self.wishlist_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.wishlist_widget.set_title.connect(self.set_title) + self.wishlist_widget.show_details.connect(self.show_details) + + self.details_widget = DetailsWidget([], api, parent=self) + self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.details_widget.set_title.connect(self.set_title) + self.details_widget.back_clicked.connect(self.show_main) + + self.details_scroll = QScrollArea(self) + self.details_scroll.setWidgetResizable(True) + self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) + self.details_scroll.setWidget(self.details_widget) + + self.setDirection(Qt.Horizontal) + self.addWidget(self.wishlist_widget) + self.addWidget(self.details_scroll) + + @pyqtSlot() + def show_main(self): + self.slideInWidget(self.wishlist_widget) + + @pyqtSlot(object) + def show_details(self, game: CatalogOfferModel): + self.details_widget.update_game(game) + self.slideInWidget(self.details_scroll) + + +class WishlistWidget(QWidget, SideTabContents): + show_details = pyqtSignal(CatalogOfferModel) update_wishlist_signal = pyqtSignal() - def __init__(self, api_core: ShopApiCore, parent=None): - super(Wishlist, self).__init__(parent=parent) + def __init__(self, api: StoreAPI, parent=None): + super(WishlistWidget, self).__init__(parent=parent) self.implements_scrollarea = True - self.api_core = api_core + self.api = api self.ui = Ui_Wishlist() self.ui.setupUi(self) + self.ui.main_layout.setContentsMargins(0, 0, 3, 0) self.setEnabled(False) self.wishlist = [] self.widgets = [] @@ -37,12 +75,16 @@ class Wishlist(QWidget, SideTabContents): lambda: self.sort_wishlist(sort=self.ui.sort_cb.currentIndex()) ) + def showEvent(self, a0: QShowEvent) -> None: + self.update_wishlist() + return super().showEvent(a0) + def update_wishlist(self): self.setEnabled(False) - self.api_core.get_wishlist(self.set_wishlist) + self.api.get_wishlist(self.set_wishlist) def delete_from_wishlist(self, game: CatalogOfferModel): - self.api_core.remove_from_wishlist( + self.api.remove_from_wishlist( game.namespace, game.id, lambda success: self.update_wishlist() @@ -71,7 +113,7 @@ class Wishlist(QWidget, SideTabContents): self.ui.no_games_label.setVisible(False) def sort_wishlist(self, sort=0): - widgets = self.ui.list_container.findChildren(WishlistWidget, options=Qt.FindDirectChildrenOnly) + widgets = self.ui.list_container.findChildren(WishlistItemWidget, options=Qt.FindDirectChildrenOnly) for w in widgets: self.ui.list_container.layout().removeWidget(w) @@ -113,8 +155,8 @@ class Wishlist(QWidget, SideTabContents): self.ui.no_games_label.setVisible(False) for game in wishlist: - w = WishlistWidget(self.api_core.cached_manager, game.offer, self.ui.list_container) - w.open_game.connect(self.show_game_info) + w = WishlistItemWidget(self.api.cached_manager, game.offer, self.ui.list_container) + w.show_details.connect(self.show_details) w.delete_from_wishlist.connect(self.delete_from_wishlist) self.widgets.append(w) self.list_layout.addWidget(w) diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/details.py similarity index 69% rename from rare/ui/components/tabs/store/shop_game_info.py rename to rare/ui/components/tabs/store/details.py index e827d9d5..e547df2f 100644 --- a/rare/ui/components/tabs/store/shop_game_info.py +++ b/rare/ui/components/tabs/store/details.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/shop_game_info.ui' +# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/details.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -11,24 +11,29 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_ShopInfo(object): - def setupUi(self, ShopInfo): - ShopInfo.setObjectName("ShopInfo") - ShopInfo.resize(443, 347) - ShopInfo.setWindowTitle("ShopGameInfo") - self.main_layout = QtWidgets.QHBoxLayout(ShopInfo) +class Ui_DetailsWidget(object): + def setupUi(self, DetailsWidget): + DetailsWidget.setObjectName("DetailsWidget") + DetailsWidget.resize(414, 343) + DetailsWidget.setWindowTitle("DetailsWidget") + self.main_layout = QtWidgets.QHBoxLayout(DetailsWidget) self.main_layout.setObjectName("main_layout") self.left_layout = QtWidgets.QVBoxLayout() self.left_layout.setObjectName("left_layout") + self.back_button = QtWidgets.QPushButton(DetailsWidget) + self.back_button.setText("") + self.back_button.setObjectName("back_button") + self.left_layout.addWidget(self.back_button) self.main_layout.addLayout(self.left_layout) self.right_layout = QtWidgets.QVBoxLayout() self.right_layout.setObjectName("right_layout") self.info_layout = QtWidgets.QFormLayout() + self.info_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.info_layout.setContentsMargins(6, 6, 6, 6) self.info_layout.setSpacing(12) self.info_layout.setObjectName("info_layout") - self.title_label = QtWidgets.QLabel(ShopInfo) + self.title_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -36,12 +41,12 @@ class Ui_ShopInfo(object): self.title_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.title_label.setObjectName("title_label") self.info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.title_label) - self.title = QtWidgets.QLabel(ShopInfo) + self.title = QtWidgets.QLabel(DetailsWidget) self.title.setText("title") self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.title.setObjectName("title") self.info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.title) - self.developer_label = QtWidgets.QLabel(ShopInfo) + self.developer_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -49,12 +54,12 @@ class Ui_ShopInfo(object): self.developer_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.developer_label.setObjectName("developer_label") self.info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.developer_label) - self.dev = QtWidgets.QLabel(ShopInfo) + self.dev = QtWidgets.QLabel(DetailsWidget) self.dev.setText("dev") self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.dev.setObjectName("dev") self.info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.dev) - self.status_label = QtWidgets.QLabel(ShopInfo) + self.status_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -62,10 +67,10 @@ class Ui_ShopInfo(object): self.status_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.status_label.setObjectName("status_label") self.info_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.status_label) - self.owned_label = QtWidgets.QLabel(ShopInfo) + self.owned_label = QtWidgets.QLabel(DetailsWidget) self.owned_label.setObjectName("owned_label") self.info_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.owned_label) - self.price_label = QtWidgets.QLabel(ShopInfo) + self.price_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -73,7 +78,7 @@ class Ui_ShopInfo(object): self.price_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.price_label.setObjectName("price_label") self.info_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.price_label) - self.price_widget = QtWidgets.QWidget(ShopInfo) + self.price_widget = QtWidgets.QWidget(DetailsWidget) self.price_widget.setObjectName("price_widget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.price_widget) self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) @@ -89,7 +94,7 @@ class Ui_ShopInfo(object): self.discount_price.setObjectName("discount_price") self.horizontalLayout.addWidget(self.discount_price) self.info_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.price_widget) - self.tags_label = QtWidgets.QLabel(ShopInfo) + self.tags_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -97,11 +102,11 @@ class Ui_ShopInfo(object): self.tags_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.tags_label.setObjectName("tags_label") self.info_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.tags_label) - self.tags = QtWidgets.QLabel(ShopInfo) + self.tags = QtWidgets.QLabel(DetailsWidget) self.tags.setText("tags") self.tags.setObjectName("tags") self.info_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.tags) - self.links_label = QtWidgets.QLabel(ShopInfo) + self.links_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -109,7 +114,7 @@ class Ui_ShopInfo(object): self.links_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.links_label.setObjectName("links_label") self.info_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.links_label) - self.social_group = QtWidgets.QWidget(ShopInfo) + self.social_group = QtWidgets.QWidget(DetailsWidget) self.social_group.setMinimumSize(QtCore.QSize(250, 0)) self.social_group.setObjectName("social_group") self.social_layout = QtWidgets.QHBoxLayout(self.social_group) @@ -117,7 +122,7 @@ class Ui_ShopInfo(object): self.social_layout.setContentsMargins(0, 0, 0, 0) self.social_layout.setObjectName("social_layout") self.info_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.social_group) - self.actions_label = QtWidgets.QLabel(ShopInfo) + self.actions_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) @@ -125,7 +130,7 @@ class Ui_ShopInfo(object): self.actions_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.actions_label.setObjectName("actions_label") self.info_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.actions_label) - self.buttons_widget = QtWidgets.QWidget(ShopInfo) + self.buttons_widget = QtWidgets.QWidget(DetailsWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -146,7 +151,7 @@ class Ui_ShopInfo(object): self.button_layout.addWidget(self.wishlist_button) self.info_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.buttons_widget) self.right_layout.addLayout(self.info_layout) - self.requirements_frame = QtWidgets.QFrame(ShopInfo) + self.requirements_frame = QtWidgets.QFrame(DetailsWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -159,55 +164,34 @@ class Ui_ShopInfo(object): self.requirements_layout.setContentsMargins(0, 0, 0, 0) self.requirements_layout.setObjectName("requirements_layout") self.right_layout.addWidget(self.requirements_frame) - self.description_group = QtWidgets.QGroupBox(ShopInfo) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.description_group.sizePolicy().hasHeightForWidth()) - self.description_group.setSizePolicy(sizePolicy) - self.description_group.setFlat(False) - self.description_group.setObjectName("description_group") - self.description_layout = QtWidgets.QVBoxLayout(self.description_group) - self.description_layout.setObjectName("description_layout") - self.description_label = QtWidgets.QLabel(self.description_group) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth()) - self.description_label.setSizePolicy(sizePolicy) - self.description_label.setText("error") - self.description_label.setTextFormat(QtCore.Qt.MarkdownText) - self.description_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.description_label.setWordWrap(True) + self.description_label = QtWidgets.QTextBrowser(DetailsWidget) self.description_label.setOpenExternalLinks(True) self.description_label.setObjectName("description_label") - self.description_layout.addWidget(self.description_label) - self.right_layout.addWidget(self.description_group) + self.right_layout.addWidget(self.description_label) self.main_layout.addLayout(self.right_layout) self.main_layout.setStretch(1, 1) - self.retranslateUi(ShopInfo) + self.retranslateUi(DetailsWidget) - def retranslateUi(self, ShopInfo): + def retranslateUi(self, DetailsWidget): _translate = QtCore.QCoreApplication.translate - self.title_label.setText(_translate("ShopInfo", "Title")) - self.developer_label.setText(_translate("ShopInfo", "Developer")) - self.status_label.setText(_translate("ShopInfo", "Status")) - self.owned_label.setText(_translate("ShopInfo", "You already own this game")) - self.price_label.setText(_translate("ShopInfo", "Price")) - self.tags_label.setText(_translate("ShopInfo", "Tags")) - self.links_label.setText(_translate("ShopInfo", "Links")) - self.actions_label.setText(_translate("ShopInfo", "Actions")) - self.open_store_button.setText(_translate("ShopInfo", "Buy in Epic Games Store")) - self.wishlist_button.setText(_translate("ShopInfo", "Add to wishlist")) - self.description_group.setTitle(_translate("ShopInfo", "Description")) + self.title_label.setText(_translate("DetailsWidget", "Title")) + self.developer_label.setText(_translate("DetailsWidget", "Developer")) + self.status_label.setText(_translate("DetailsWidget", "Status")) + self.owned_label.setText(_translate("DetailsWidget", "You already own this game")) + self.price_label.setText(_translate("DetailsWidget", "Price")) + self.tags_label.setText(_translate("DetailsWidget", "Tags")) + self.links_label.setText(_translate("DetailsWidget", "Links")) + self.actions_label.setText(_translate("DetailsWidget", "Actions")) + self.open_store_button.setText(_translate("DetailsWidget", "Buy in Epic Games Store")) + self.wishlist_button.setText(_translate("DetailsWidget", "Add to wishlist")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - ShopInfo = QtWidgets.QWidget() - ui = Ui_ShopInfo() - ui.setupUi(ShopInfo) - ShopInfo.show() + DetailsWidget = QtWidgets.QWidget() + ui = Ui_DetailsWidget() + ui.setupUi(DetailsWidget) + DetailsWidget.show() sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/shop_game_info.ui b/rare/ui/components/tabs/store/details.ui similarity index 87% rename from rare/ui/components/tabs/store/shop_game_info.ui rename to rare/ui/components/tabs/store/details.ui index 9813c0cb..af07ca9f 100644 --- a/rare/ui/components/tabs/store/shop_game_info.ui +++ b/rare/ui/components/tabs/store/details.ui @@ -1,26 +1,37 @@ - ShopInfo - + DetailsWidget + 0 0 - 443 - 347 + 414 + 343 - ShopGameInfo + DetailsWidget - + + + + + + + + + + + QLayout::SetFixedSize + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -339,46 +350,10 @@ - - - - 0 - 0 - + + + true - - Description - - - false - - - - - - - 0 - 0 - - - - error - - - Qt::MarkdownText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - true - - - - diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/landing.py similarity index 51% rename from rare/ui/components/tabs/store/store.py rename to rare/ui/components/tabs/store/landing.py index 14ea03df..c2aa0099 100644 --- a/rare/ui/components/tabs/store/store.py +++ b/rare/ui/components/tabs/store/landing.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/store.ui' +# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/landing.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -11,14 +11,22 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_ShopWidget(object): - def setupUi(self, ShopWidget): - ShopWidget.setObjectName("ShopWidget") - ShopWidget.resize(788, 662) - ShopWidget.setWindowTitle("Store") - self.main_layout = QtWidgets.QGridLayout(ShopWidget) +class Ui_LandingWidget(object): + def setupUi(self, LandingWidget): + LandingWidget.setObjectName("LandingWidget") + LandingWidget.resize(788, 662) + LandingWidget.setWindowTitle("LandingWidget") + self.main_layout = QtWidgets.QHBoxLayout(LandingWidget) self.main_layout.setObjectName("main_layout") - self.filter_scrollarea = QtWidgets.QScrollArea(ShopWidget) + self.left_layout = QtWidgets.QVBoxLayout() + self.left_layout.setObjectName("left_layout") + self.main_layout.addLayout(self.left_layout) + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.setObjectName("right_layout") + self.reset_button = QtWidgets.QPushButton(LandingWidget) + self.reset_button.setObjectName("reset_button") + self.right_layout.addWidget(self.reset_button) + self.filter_scrollarea = QtWidgets.QScrollArea(LandingWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -89,81 +97,34 @@ class Ui_ShopWidget(object): self.others_layout.setObjectName("others_layout") self.filter_container_layout.addWidget(self.others_group) self.filter_scrollarea.setWidget(self.filter_container) - self.main_layout.addWidget(self.filter_scrollarea, 1, 1, 1, 1) - self.reset_button = QtWidgets.QPushButton(ShopWidget) - self.reset_button.setObjectName("reset_button") - self.main_layout.addWidget(self.reset_button, 0, 1, 1, 1) - self.games_scrollarea = QtWidgets.QScrollArea(ShopWidget) - self.games_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) - self.games_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) - self.games_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - self.games_scrollarea.setWidgetResizable(True) - self.games_scrollarea.setObjectName("games_scrollarea") - self.games_container = QtWidgets.QWidget() - self.games_container.setGeometry(QtCore.QRect(0, 0, 628, 618)) - self.games_container.setObjectName("games_container") - self.games_container_layout = QtWidgets.QVBoxLayout(self.games_container) - self.games_container_layout.setContentsMargins(0, 0, 3, 0) - self.games_container_layout.setObjectName("games_container_layout") - self.free_scrollarea = QtWidgets.QScrollArea(self.games_container) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.free_scrollarea.sizePolicy().hasHeightForWidth()) - self.free_scrollarea.setSizePolicy(sizePolicy) - self.free_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) - self.free_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) - self.free_scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.free_scrollarea.setWidgetResizable(True) - self.free_scrollarea.setObjectName("free_scrollarea") - self.free_container = QtWidgets.QWidget() - self.free_container.setGeometry(QtCore.QRect(0, 0, 16, 16)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.free_container.sizePolicy().hasHeightForWidth()) - self.free_container.setSizePolicy(sizePolicy) - self.free_container.setObjectName("free_container") - self.free_container_layout = QtWidgets.QHBoxLayout(self.free_container) - self.free_container_layout.setContentsMargins(0, 0, 0, 3) - self.free_container_layout.setObjectName("free_container_layout") - self.free_scrollarea.setWidget(self.free_container) - self.games_container_layout.addWidget(self.free_scrollarea) - self.discounts_group = QtWidgets.QGroupBox(self.games_container) - self.discounts_group.setObjectName("discounts_group") - self.games_container_layout.addWidget(self.discounts_group) - self.games_group = QtWidgets.QGroupBox(self.games_container) - self.games_group.setObjectName("games_group") - self.games_container_layout.addWidget(self.games_group) - self.games_scrollarea.setWidget(self.games_container) - self.main_layout.addWidget(self.games_scrollarea, 1, 0, 1, 1) + self.right_layout.addWidget(self.filter_scrollarea) + self.main_layout.addLayout(self.right_layout) + self.main_layout.setStretch(0, 1) - self.retranslateUi(ShopWidget) + self.retranslateUi(LandingWidget) - def retranslateUi(self, ShopWidget): + def retranslateUi(self, LandingWidget): _translate = QtCore.QCoreApplication.translate - self.price_group.setTitle(_translate("ShopWidget", "Price")) - self.none_price.setText(_translate("ShopWidget", "None")) - self.free_button.setText(_translate("ShopWidget", "Free")) - self.under10.setText(_translate("ShopWidget", "Under 10")) - self.under20.setText(_translate("ShopWidget", "Under 20")) - self.under30.setText(_translate("ShopWidget", "Under 30")) - self.above.setText(_translate("ShopWidget", "14.99 and above")) - self.on_discount.setText(_translate("ShopWidget", "Discount")) - self.platform_group.setTitle(_translate("ShopWidget", "Platform")) - self.genre_group.setTitle(_translate("ShopWidget", "Genre")) - self.type_group.setTitle(_translate("ShopWidget", "Type")) - self.others_group.setTitle(_translate("ShopWidget", "Other tags")) - self.reset_button.setText(_translate("ShopWidget", "Reset filters")) - self.discounts_group.setTitle(_translate("ShopWidget", "Discounts from your wishlist")) - self.games_group.setTitle(_translate("ShopWidget", "Games")) + self.reset_button.setText(_translate("LandingWidget", "Reset filters")) + self.price_group.setTitle(_translate("LandingWidget", "Price")) + self.none_price.setText(_translate("LandingWidget", "None")) + self.free_button.setText(_translate("LandingWidget", "Free")) + self.under10.setText(_translate("LandingWidget", "Under 10")) + self.under20.setText(_translate("LandingWidget", "Under 20")) + self.under30.setText(_translate("LandingWidget", "Under 30")) + self.above.setText(_translate("LandingWidget", "14.99 and above")) + self.on_discount.setText(_translate("LandingWidget", "Discount")) + self.platform_group.setTitle(_translate("LandingWidget", "Platform")) + self.genre_group.setTitle(_translate("LandingWidget", "Genre")) + self.type_group.setTitle(_translate("LandingWidget", "Type")) + self.others_group.setTitle(_translate("LandingWidget", "Other tags")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) - ShopWidget = QtWidgets.QWidget() - ui = Ui_ShopWidget() - ui.setupUi(ShopWidget) - ShopWidget.show() + LandingWidget = QtWidgets.QWidget() + ui = Ui_LandingWidget() + ui.setupUi(LandingWidget) + LandingWidget.show() sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/landing.ui b/rare/ui/components/tabs/store/landing.ui new file mode 100644 index 00000000..2e3b75b4 --- /dev/null +++ b/rare/ui/components/tabs/store/landing.ui @@ -0,0 +1,183 @@ + + + LandingWidget + + + + 0 + 0 + 788 + 662 + + + + LandingWidget + + + + + + + + + + + Reset filters + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 142 + 390 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 3 + + + 0 + + + + + Price + + + + + + None + + + true + + + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + + + Genre + + + + + + + + Type + + + + + + + + Other tags + + + + + + + + + + + + + + + diff --git a/rare/ui/components/tabs/store/search.py b/rare/ui/components/tabs/store/search.py new file mode 100644 index 00000000..d7b78c95 --- /dev/null +++ b/rare/ui/components/tabs/store/search.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/search.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SearchWidget(object): + def setupUi(self, SearchWidget): + SearchWidget.setObjectName("SearchWidget") + SearchWidget.resize(491, 382) + SearchWidget.setWindowTitle("SearchWidget") + self.main_layout = QtWidgets.QHBoxLayout(SearchWidget) + self.main_layout.setObjectName("main_layout") + self.left_layout = QtWidgets.QVBoxLayout() + self.left_layout.setObjectName("left_layout") + self.main_layout.addLayout(self.left_layout) + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.setObjectName("right_layout") + self.reset_button = QtWidgets.QPushButton(SearchWidget) + self.reset_button.setObjectName("reset_button") + self.right_layout.addWidget(self.reset_button) + self.filter_scrollarea = QtWidgets.QScrollArea(SearchWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.filter_scrollarea.sizePolicy().hasHeightForWidth()) + self.filter_scrollarea.setSizePolicy(sizePolicy) + self.filter_scrollarea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.filter_scrollarea.setFrameShadow(QtWidgets.QFrame.Plain) + self.filter_scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.filter_scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.filter_scrollarea.setWidgetResizable(True) + self.filter_scrollarea.setObjectName("filter_scrollarea") + self.filter_container = QtWidgets.QWidget() + self.filter_container.setGeometry(QtCore.QRect(0, 0, 142, 390)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.filter_container.sizePolicy().hasHeightForWidth()) + self.filter_container.setSizePolicy(sizePolicy) + self.filter_container.setObjectName("filter_container") + self.filter_container_layout = QtWidgets.QVBoxLayout(self.filter_container) + self.filter_container_layout.setContentsMargins(0, 0, 3, 0) + self.filter_container_layout.setObjectName("filter_container_layout") + self.price_group = QtWidgets.QGroupBox(self.filter_container) + self.price_group.setObjectName("price_group") + self.price_layout = QtWidgets.QVBoxLayout(self.price_group) + self.price_layout.setObjectName("price_layout") + self.none_price = QtWidgets.QRadioButton(self.price_group) + self.none_price.setChecked(True) + self.none_price.setObjectName("none_price") + self.price_layout.addWidget(self.none_price) + self.free_button = QtWidgets.QRadioButton(self.price_group) + self.free_button.setObjectName("free_button") + self.price_layout.addWidget(self.free_button) + self.under10 = QtWidgets.QRadioButton(self.price_group) + self.under10.setObjectName("under10") + self.price_layout.addWidget(self.under10) + self.under20 = QtWidgets.QRadioButton(self.price_group) + self.under20.setObjectName("under20") + self.price_layout.addWidget(self.under20) + self.under30 = QtWidgets.QRadioButton(self.price_group) + self.under30.setObjectName("under30") + self.price_layout.addWidget(self.under30) + self.above = QtWidgets.QRadioButton(self.price_group) + self.above.setObjectName("above") + self.price_layout.addWidget(self.above) + self.on_discount = QtWidgets.QCheckBox(self.price_group) + self.on_discount.setObjectName("on_discount") + self.price_layout.addWidget(self.on_discount) + self.filter_container_layout.addWidget(self.price_group) + self.platform_group = QtWidgets.QGroupBox(self.filter_container) + self.platform_group.setObjectName("platform_group") + self.platfrom_layout = QtWidgets.QVBoxLayout(self.platform_group) + self.platfrom_layout.setObjectName("platfrom_layout") + self.filter_container_layout.addWidget(self.platform_group) + self.genre_group = QtWidgets.QGroupBox(self.filter_container) + self.genre_group.setObjectName("genre_group") + self.genre_layout = QtWidgets.QVBoxLayout(self.genre_group) + self.genre_layout.setObjectName("genre_layout") + self.filter_container_layout.addWidget(self.genre_group) + self.type_group = QtWidgets.QGroupBox(self.filter_container) + self.type_group.setObjectName("type_group") + self.type_layout = QtWidgets.QVBoxLayout(self.type_group) + self.type_layout.setObjectName("type_layout") + self.filter_container_layout.addWidget(self.type_group) + self.others_group = QtWidgets.QGroupBox(self.filter_container) + self.others_group.setObjectName("others_group") + self.others_layout = QtWidgets.QVBoxLayout(self.others_group) + self.others_layout.setObjectName("others_layout") + self.filter_container_layout.addWidget(self.others_group) + self.filter_scrollarea.setWidget(self.filter_container) + self.right_layout.addWidget(self.filter_scrollarea) + self.main_layout.addLayout(self.right_layout) + self.main_layout.setStretch(0, 1) + + self.retranslateUi(SearchWidget) + + def retranslateUi(self, SearchWidget): + _translate = QtCore.QCoreApplication.translate + self.reset_button.setText(_translate("SearchWidget", "Reset filters")) + self.price_group.setTitle(_translate("SearchWidget", "Price")) + self.none_price.setText(_translate("SearchWidget", "None")) + self.free_button.setText(_translate("SearchWidget", "Free")) + self.under10.setText(_translate("SearchWidget", "Under 10")) + self.under20.setText(_translate("SearchWidget", "Under 20")) + self.under30.setText(_translate("SearchWidget", "Under 30")) + self.above.setText(_translate("SearchWidget", "14.99 and above")) + self.on_discount.setText(_translate("SearchWidget", "Discount")) + self.platform_group.setTitle(_translate("SearchWidget", "Platform")) + self.genre_group.setTitle(_translate("SearchWidget", "Genre")) + self.type_group.setTitle(_translate("SearchWidget", "Type")) + self.others_group.setTitle(_translate("SearchWidget", "Other tags")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + SearchWidget = QtWidgets.QWidget() + ui = Ui_SearchWidget() + ui.setupUi(SearchWidget) + SearchWidget.show() + sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/search.ui b/rare/ui/components/tabs/store/search.ui new file mode 100644 index 00000000..516433f4 --- /dev/null +++ b/rare/ui/components/tabs/store/search.ui @@ -0,0 +1,183 @@ + + + SearchWidget + + + + 0 + 0 + 491 + 382 + + + + SearchWidget + + + + + + + + + + + Reset filters + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 142 + 390 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 3 + + + 0 + + + + + Price + + + + + + None + + + true + + + + + + + Free + + + + + + + Under 10 + + + + + + + Under 20 + + + + + + + Under 30 + + + + + + + 14.99 and above + + + + + + + Discount + + + + + + + + + + Platform + + + + + + + + Genre + + + + + + + + Type + + + + + + + + Other tags + + + + + + + + + + + + + + + diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui deleted file mode 100644 index c46a8bf1..00000000 --- a/rare/ui/components/tabs/store/store.ui +++ /dev/null @@ -1,282 +0,0 @@ - - - ShopWidget - - - - 0 - 0 - 788 - 662 - - - - Store - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - 0 - 0 - 142 - 390 - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 3 - - - 0 - - - - - Price - - - - - - None - - - true - - - - - - - Free - - - - - - - Under 10 - - - - - - - Under 20 - - - - - - - Under 30 - - - - - - - 14.99 and above - - - - - - - Discount - - - - - - - - - - Platform - - - - - - - - Genre - - - - - - - - Type - - - - - - - - Other tags - - - - - - - - - - - - Reset filters - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - 0 - 0 - 628 - 618 - - - - - 0 - - - 0 - - - 3 - - - 0 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 16 - 16 - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 3 - - - - - - - - - Discounts from your wishlist - - - - - - - Games - - - - - - - - - - - - diff --git a/rare/ui/components/tabs/store/widgets/__init__.py b/rare/ui/components/tabs/store/widgets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/ui/components/tabs/store/wishlist_widget.py b/rare/ui/components/tabs/store/widgets/wishlist_widget.py similarity index 97% rename from rare/ui/components/tabs/store/wishlist_widget.py rename to rare/ui/components/tabs/store/widgets/wishlist_widget.py index 7ea93faf..57fb2850 100644 --- a/rare/ui/components/tabs/store/wishlist_widget.py +++ b/rare/ui/components/tabs/store/widgets/wishlist_widget.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist_widget.ui' +# Form implementation generated from reading ui file 'rare/ui/components/tabs/store/widgets/wishlist_widget.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. diff --git a/rare/ui/components/tabs/store/wishlist_widget.ui b/rare/ui/components/tabs/store/widgets/wishlist_widget.ui similarity index 100% rename from rare/ui/components/tabs/store/wishlist_widget.ui rename to rare/ui/components/tabs/store/widgets/wishlist_widget.ui diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index 66c042f6..2bea5fa8 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/store/wishlist.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -16,8 +16,8 @@ class Ui_Wishlist(object): Wishlist.setObjectName("Wishlist") Wishlist.resize(423, 153) Wishlist.setWindowTitle("Wishlist") - self.verticalLayout = QtWidgets.QVBoxLayout(Wishlist) - self.verticalLayout.setObjectName("verticalLayout") + self.main_layout = QtWidgets.QVBoxLayout(Wishlist) + self.main_layout.setObjectName("main_layout") self.tool_layout = QtWidgets.QHBoxLayout() self.tool_layout.setObjectName("tool_layout") self.sort_label = QtWidgets.QLabel(Wishlist) @@ -59,10 +59,10 @@ class Ui_Wishlist(object): self.reload_button.setText("") self.reload_button.setObjectName("reload_button") self.tool_layout.addWidget(self.reload_button) - self.verticalLayout.addLayout(self.tool_layout) + self.main_layout.addLayout(self.tool_layout) self.no_games_label = QtWidgets.QLabel(Wishlist) self.no_games_label.setObjectName("no_games_label") - self.verticalLayout.addWidget(self.no_games_label) + self.main_layout.addWidget(self.no_games_label) self.list_scrollarea = QtWidgets.QScrollArea(Wishlist) self.list_scrollarea.setWidgetResizable(True) self.list_scrollarea.setObjectName("list_scrollarea") @@ -70,7 +70,7 @@ class Ui_Wishlist(object): self.list_container.setGeometry(QtCore.QRect(0, 0, 407, 83)) self.list_container.setObjectName("list_container") self.list_scrollarea.setWidget(self.list_container) - self.verticalLayout.addWidget(self.list_scrollarea) + self.main_layout.addWidget(self.list_scrollarea) self.retranslateUi(Wishlist) diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index b28a8ce0..1d2fe6bd 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -13,7 +13,7 @@ Wishlist - + From 816c5f3de9a31287077d8e40bf369e5f1bb57a4d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:08:08 +0200 Subject: [PATCH 13/21] Store: adapt image sized --- rare/components/tabs/store/widgets/details.py | 2 +- rare/components/tabs/store/widgets/items.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index 07f8c84c..f3a168fd 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -41,7 +41,7 @@ class DetailsWidget(QWidget, SideTabContents): self.data: dict = {} self.image = LoadingImageWidget(api_core.cached_manager, self) - self.image.setFixedSize(ImageSize.Normal) + self.image.setFixedSize(ImageSize.Display) self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) self.ui.left_layout.setAlignment(Qt.AlignTop) diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py index 96427501..02b6b500 100644 --- a/rare/components/tabs/store/widgets/items.py +++ b/rare/components/tabs/store/widgets/items.py @@ -35,7 +35,7 @@ class ItemWidget(LoadingImageWidget): class StoreItemWidget(ItemWidget): def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel = None, parent=None): super(StoreItemWidget, self).__init__(manager, catalog_game, parent=parent) - self.setFixedSize(ImageSize.Wide) + self.setFixedSize(ImageSize.DisplayWide) self.ui.setupUi(self) if catalog_game: self.init_ui(catalog_game) @@ -80,7 +80,7 @@ class StoreItemWidget(ItemWidget): class ResultsItemWidget(ItemWidget): def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None): super(ResultsItemWidget, self).__init__(manager, catalog_game, parent=parent) - self.setFixedSize(ImageSize.Normal) + self.setFixedSize(ImageSize.Display) self.ui.setupUi(self) key_images = catalog_game.key_images @@ -106,7 +106,7 @@ class WishlistItemWidget(ItemWidget): def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None): super(WishlistItemWidget, self).__init__(manager, catalog_game, parent=parent) - self.setFixedSize(ImageSize.Wide) + self.setFixedSize(ImageSize.DisplayWide) self.ui.setupUi(self) for attr in catalog_game.custom_attributes: if attr["key"] == "developerName": From 2a2458bacbc657cde271cea83ac75453e9b297b7 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 3 Feb 2024 02:28:55 +0200 Subject: [PATCH 14/21] Store: Update details page * Add a big back button in details page. * Add static CSS to render QPushButtons as flat when the `flat` property is set * Remove outer scroll areas from details page since the page is already adjustable * Remove scroll area from the requirements widget because ElideLabels are already used in it. * Fix crash when sorting the wishlist --- rare/components/tabs/store/__main__.py | 3 +- rare/components/tabs/store/landing.py | 9 +-- rare/components/tabs/store/results.py | 1 - rare/components/tabs/store/search.py | 9 +-- rare/components/tabs/store/widgets/details.py | 61 +++++++++++-------- rare/components/tabs/store/wishlist.py | 19 +++--- rare/resources/static_css/stylesheet.py | 8 +++ rare/ui/components/tabs/store/details.py | 19 ++++-- rare/ui/components/tabs/store/details.ui | 38 ++++++++---- 9 files changed, 97 insertions(+), 70 deletions(-) diff --git a/rare/components/tabs/store/__main__.py b/rare/components/tabs/store/__main__.py index ad7c13f4..c14f5874 100644 --- a/rare/components/tabs/store/__main__.py +++ b/rare/components/tabs/store/__main__.py @@ -23,13 +23,14 @@ class StoreWindow(QDialog): if __name__ == "__main__": import rare.resources.static_css - import rare.resources.stylesheets.RareStyle + # import rare.resources.stylesheets.RareStyle from rare.utils.misc import set_style_sheet app = QApplication(sys.argv) app.setApplicationName("Rare") app.setOrganizationName("Rare") + set_style_sheet("") set_style_sheet("RareStyle") window = StoreWindow() window.setWindowTitle(f"{app.applicationName()} - Store") diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py index 6f570a75..a55e5124 100644 --- a/rare/components/tabs/store/landing.py +++ b/rare/components/tabs/store/landing.py @@ -48,14 +48,9 @@ class LandingPage(SlidingStackedWidget, SideTabContents): self.details_widget.set_title.connect(self.set_title) self.details_widget.back_clicked.connect(self.show_main) - self.details_scroll = QScrollArea(self) - self.details_scroll.setWidgetResizable(True) - self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) - self.details_scroll.setWidget(self.details_widget) - self.setDirection(Qt.Horizontal) self.addWidget(self.landing_scroll) - self.addWidget(self.details_scroll) + self.addWidget(self.details_widget) @pyqtSlot() def show_main(self): @@ -64,7 +59,7 @@ class LandingPage(SlidingStackedWidget, SideTabContents): @pyqtSlot(object) def show_details(self, game: CatalogOfferModel): self.details_widget.update_game(game) - self.slideInWidget(self.details_scroll) + self.slideInWidget(self.details_widget) class LandingWidget(QWidget, SideTabContents): diff --git a/rare/components/tabs/store/results.py b/rare/components/tabs/store/results.py index f837a103..6ae53088 100644 --- a/rare/components/tabs/store/results.py +++ b/rare/components/tabs/store/results.py @@ -17,7 +17,6 @@ class ResultsWidget(QScrollArea): def __init__(self, store_api, parent=None): super(ResultsWidget, self).__init__(parent=parent) - self.implements_scrollarea = True self.store_api = store_api self.results_container = QWidget(self) diff --git a/rare/components/tabs/store/search.py b/rare/components/tabs/store/search.py index 870e9daf..7e5d6d8d 100644 --- a/rare/components/tabs/store/search.py +++ b/rare/components/tabs/store/search.py @@ -40,14 +40,9 @@ class SearchPage(SlidingStackedWidget, SideTabContents): self.details_widget.set_title.connect(self.set_title) self.details_widget.back_clicked.connect(self.show_main) - self.details_scroll = QScrollArea(self) - self.details_scroll.setWidgetResizable(True) - self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) - self.details_scroll.setWidget(self.details_widget) - self.setDirection(Qt.Horizontal) self.addWidget(self.search_widget) - self.addWidget(self.details_scroll) + self.addWidget(self.details_widget) @pyqtSlot() def show_main(self): @@ -56,7 +51,7 @@ class SearchPage(SlidingStackedWidget, SideTabContents): @pyqtSlot(object) def show_details(self, game: CatalogOfferModel): self.details_widget.update_game(game) - self.slideInWidget(self.details_scroll) + self.slideInWidget(self.details_widget) # noinspection PyAttributeOutsideInit,PyBroadException diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index f3a168fd..98f8eaf0 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -2,7 +2,7 @@ import logging from typing import List from PyQt5.QtCore import Qt, QUrl, pyqtSignal -from PyQt5.QtGui import QFont, QDesktopServices, QFontMetrics +from PyQt5.QtGui import QFont, QDesktopServices, QKeyEvent from PyQt5.QtWidgets import ( QWidget, QLabel, @@ -12,7 +12,7 @@ from PyQt5.QtWidgets import ( ) from rare.components.tabs.store.api.debug import DebugDialog -from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail +from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail, DieselSystemDetail from rare.components.tabs.store.api.models.response import CatalogOfferModel from rare.models.image import ImageSize from rare.ui.components.tabs.store.details import Ui_DetailsWidget @@ -30,6 +30,8 @@ class DetailsWidget(QWidget, SideTabContents): # TODO Design def __init__(self, installed_titles: list, api_core, parent=None): super(DetailsWidget, self).__init__(parent=parent) + self.implements_scrollarea = True + self.ui = Ui_DetailsWidget() self.ui.setupUi(self) self.ui.main_layout.setContentsMargins(0, 0, 3, 0) @@ -55,6 +57,7 @@ class DetailsWidget(QWidget, SideTabContents): self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ui.requirements_layout.addWidget(self.requirements_tabs) + self.ui.back_button.setIcon(icon("fa.chevron-left")) self.ui.back_button.clicked.connect(self.back_clicked) self.setDisabled(False) @@ -182,31 +185,10 @@ class DetailsWidget(QWidget, SideTabContents): else: self.ui.discount_price.setVisible(False) - bold_font = QFont() - bold_font.setBold(True) - - fm = QFontMetrics(self.font()) requirements = product_data.requirements if requirements and requirements.systems: for system in requirements.systems: - req_widget = QWidget(self.requirements_tabs) - req_layout = QGridLayout(req_widget) - req_widget.layout().setAlignment(Qt.AlignTop) - req_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - min_label = QLabel(self.tr("Minimum"), parent=req_widget) - min_label.setFont(bold_font) - rec_label = QLabel(self.tr("Recommend"), parent=req_widget) - rec_label.setFont(bold_font) - req_layout.addWidget(min_label, 0, 1) - req_layout.addWidget(rec_label, 0, 2) - req_layout.setColumnStretch(1, 2) - req_layout.setColumnStretch(2, 2) - for i, detail in enumerate(system.details): - req_layout.addWidget(QLabel(detail.title, parent=req_widget), i + 1, 0) - min_label = ElideLabel(detail.minimum, parent=req_widget) - req_layout.addWidget(min_label, i + 1, 1) - rec_label = ElideLabel(detail.recommended, parent=req_widget) - req_layout.addWidget(rec_label, i + 1, 2) + req_widget = RequirementsWidget(system, self.requirements_tabs) self.requirements_tabs.addTab(req_widget, system.system_type) # self.req_group_box.layout().addWidget(req_tabs) # self.req_group_box.layout().setAlignment(Qt.AlignTop) @@ -273,6 +255,10 @@ class DetailsWidget(QWidget, SideTabContents): return QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.core.language_code}/p/{self.slug}")) + def keyPressEvent(self, a0: QKeyEvent): + if a0.key() == Qt.Key_Escape: + self.back_clicked.emit() + class SocialButton(QPushButton): def __init__(self, icn, url, parent=None): @@ -280,3 +266,30 @@ class SocialButton(QPushButton): self.url = url self.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url))) self.setToolTip(url) + + +class RequirementsWidget(QWidget, SideTabContents): + def __init__(self, system: DieselSystemDetail, parent=None): + super().__init__(parent=parent) + self.implements_scrollarea = True + + bold_font = self.font() + bold_font.setBold(True) + + req_layout = QGridLayout(self) + min_label = QLabel(self.tr("Minimum"), parent=self) + min_label.setFont(bold_font) + rec_label = QLabel(self.tr("Recommend"), parent=self) + rec_label.setFont(bold_font) + req_layout.addWidget(min_label, 0, 1) + req_layout.addWidget(rec_label, 0, 2) + req_layout.setColumnStretch(1, 2) + req_layout.setColumnStretch(2, 2) + for i, detail in enumerate(system.details): + req_layout.addWidget(QLabel(detail.title, parent=self), i + 1, 0) + min_label = ElideLabel(detail.minimum, parent=self) + req_layout.addWidget(min_label, i + 1, 1) + rec_label = ElideLabel(detail.recommended, parent=self) + req_layout.addWidget(rec_label, i + 1, 2) + req_layout.setAlignment(Qt.AlignTop) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 3d9305fd..bedc2ca4 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -30,14 +30,9 @@ class WishlistPage(SlidingStackedWidget, SideTabContents): self.details_widget.set_title.connect(self.set_title) self.details_widget.back_clicked.connect(self.show_main) - self.details_scroll = QScrollArea(self) - self.details_scroll.setWidgetResizable(True) - self.details_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) - self.details_scroll.setWidget(self.details_widget) - self.setDirection(Qt.Horizontal) self.addWidget(self.wishlist_widget) - self.addWidget(self.details_scroll) + self.addWidget(self.details_widget) @pyqtSlot() def show_main(self): @@ -46,7 +41,7 @@ class WishlistPage(SlidingStackedWidget, SideTabContents): @pyqtSlot(object) def show_details(self, game: CatalogOfferModel): self.details_widget.update_game(game) - self.slideInWidget(self.details_scroll) + self.slideInWidget(self.details_widget) class WishlistWidget(QWidget, SideTabContents): @@ -118,19 +113,19 @@ class WishlistWidget(QWidget, SideTabContents): self.ui.list_container.layout().removeWidget(w) if sort == 0: - func = lambda x: x.game.title + func = lambda x: x.catalog_game.title reverse = self.ui.reverse.isChecked() elif sort == 1: - func = lambda x: x.game.price.total_price["fmtPrice"]["discountPrice"] + func = lambda x: x.catalog_game.price.total_price["fmtPrice"]["discountPrice"] reverse = self.ui.reverse.isChecked() elif sort == 2: - func = lambda x: x.game.seller["name"] + func = lambda x: x.catalog_game.seller["name"] reverse = self.ui.reverse.isChecked() elif sort == 3: - func = lambda x: 1 - (x.game.price.total_price["discountPrice"] / x.game.price.total_price["originalPrice"]) + func = lambda x: 1 - (x.catalog_game.price.total_price["discountPrice"] / x.catalog_game.price.total_price["originalPrice"]) reverse = not self.ui.reverse.isChecked() else: - func = lambda x: x.game.title + func = lambda x: x.catalog_game.title reverse = self.ui.reverse.isChecked() widgets = sorted(widgets, key=func, reverse=reverse) diff --git a/rare/resources/static_css/stylesheet.py b/rare/resources/static_css/stylesheet.py index 551826e7..43a7aece 100644 --- a/rare/resources/static_css/stylesheet.py +++ b/rare/resources/static_css/stylesheet.py @@ -64,6 +64,14 @@ def css_name(widget: Union[wrappertype, QObject, Type], subwidget: str = ""): css = qstylizer.style.StyleSheet() +# Generic flat button +css['QPushButton[flat="true"]'].setValues( + border="0px", + borderRadius="5px", + backgroundColor="rgba(255, 255, 255, 5%)", +) + + # InfoLabel css.QLabel["#InfoLabel"].setValues( color="#999", diff --git a/rare/ui/components/tabs/store/details.py b/rare/ui/components/tabs/store/details.py index e547df2f..5c570cb2 100644 --- a/rare/ui/components/tabs/store/details.py +++ b/rare/ui/components/tabs/store/details.py @@ -14,16 +14,23 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_DetailsWidget(object): def setupUi(self, DetailsWidget): DetailsWidget.setObjectName("DetailsWidget") - DetailsWidget.resize(414, 343) + DetailsWidget.resize(702, 414) DetailsWidget.setWindowTitle("DetailsWidget") self.main_layout = QtWidgets.QHBoxLayout(DetailsWidget) self.main_layout.setObjectName("main_layout") + self.back_button = QtWidgets.QPushButton(DetailsWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.back_button.sizePolicy().hasHeightForWidth()) + self.back_button.setSizePolicy(sizePolicy) + self.back_button.setText("") + self.back_button.setIconSize(QtCore.QSize(32, 32)) + self.back_button.setFlat(True) + self.back_button.setObjectName("back_button") + self.main_layout.addWidget(self.back_button) self.left_layout = QtWidgets.QVBoxLayout() self.left_layout.setObjectName("left_layout") - self.back_button = QtWidgets.QPushButton(DetailsWidget) - self.back_button.setText("") - self.back_button.setObjectName("back_button") - self.left_layout.addWidget(self.back_button) self.main_layout.addLayout(self.left_layout) self.right_layout = QtWidgets.QVBoxLayout() self.right_layout.setObjectName("right_layout") @@ -169,7 +176,7 @@ class Ui_DetailsWidget(object): self.description_label.setObjectName("description_label") self.right_layout.addWidget(self.description_label) self.main_layout.addLayout(self.right_layout) - self.main_layout.setStretch(1, 1) + self.main_layout.setStretch(2, 1) self.retranslateUi(DetailsWidget) diff --git a/rare/ui/components/tabs/store/details.ui b/rare/ui/components/tabs/store/details.ui index af07ca9f..20b21038 100644 --- a/rare/ui/components/tabs/store/details.ui +++ b/rare/ui/components/tabs/store/details.ui @@ -6,24 +6,38 @@ 0 0 - 414 - 343 + 702 + 414 DetailsWidget - + - - - - - - - - - + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + true + + + + + From 9ec349e2d1608fcfc63b6f1a2cdbf864e4f7d50d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:29:39 +0200 Subject: [PATCH 15/21] WIP --- rare/components/tabs/store/__init__.py | 11 +- .../tabs/store/api/graphql/schema.graphql | 4 +- .../tabs/store/api/models/diesel.py | 12 +- .../tabs/store/api/models/response.py | 225 +++++++++++------- .../components/tabs/store/api/models/utils.py | 2 +- rare/components/tabs/store/landing.py | 93 +++----- rare/components/tabs/store/search.py | 20 +- rare/components/tabs/store/store_api.py | 16 +- rare/components/tabs/store/widgets/details.py | 83 +++---- rare/components/tabs/store/widgets/items.py | 24 +- rare/components/tabs/store/wishlist.py | 4 +- rare/ui/components/tabs/store/details.py | 8 +- rare/ui/components/tabs/store/details.ui | 49 ++-- rare/utils/qt_requests.py | 2 +- 14 files changed, 270 insertions(+), 283 deletions(-) diff --git a/rare/components/tabs/store/__init__.py b/rare/components/tabs/store/__init__.py index 273d2293..c011ae45 100644 --- a/rare/components/tabs/store/__init__.py +++ b/rare/components/tabs/store/__init__.py @@ -28,21 +28,15 @@ class StoreTab(SideTabWidget): self.landing = LandingPage(self.api, parent=self) self.landing_index = self.addTab(self.landing, self.tr("Store")) - self.search = SearchPage(self.core, self.api, parent=self) + self.search = SearchPage(self.api, parent=self) self.search_index = self.addTab(self.search, self.tr("Search")) self.wishlist = WishlistPage(self.api, parent=self) self.wishlist_index = self.addTab(self.wishlist, self.tr("Wishlist")) - self.api.update_wishlist.connect(self.update_wishlist) - - self.previous_index = self.landing_index - def showEvent(self, a0: QShowEvent) -> None: if a0.spontaneous() or self.init: return super().showEvent(a0) - # self.landing.load() - # self.wishlist.update_wishlist() self.init = True return super().showEvent(a0) @@ -51,6 +45,3 @@ class StoreTab(SideTabWidget): return super().hideEvent(a0) # TODO: Implement store unloading return super().hideEvent(a0) - - def update_wishlist(self): - self.landing.update_wishlist() diff --git a/rare/components/tabs/store/api/graphql/schema.graphql b/rare/components/tabs/store/api/graphql/schema.graphql index 676fcfe5..6ccfe7a7 100644 --- a/rare/components/tabs/store/api/graphql/schema.graphql +++ b/rare/components/tabs/store/api/graphql/schema.graphql @@ -25,14 +25,14 @@ type DiscountSetting { discountType: String } -type AppliedRuled { +type AppliedRules { id: ID endDate: Date discountSetting: DiscountSetting } type LineOfferRes { - appliedRules: [AppliedRuled] + appliedRules: [AppliedRules] } type GetPriceRes { diff --git a/rare/components/tabs/store/api/models/diesel.py b/rare/components/tabs/store/api/models/diesel.py index b9629343..437c8b8c 100644 --- a/rare/components/tabs/store/api/models/diesel.py +++ b/rare/components/tabs/store/api/models/diesel.py @@ -33,7 +33,7 @@ class DieselSystemDetailItem: class DieselSystemDetail: p_type: Optional[str] = None details: Optional[List[DieselSystemDetailItem]] = None - system_type: Optional[str] = None + systemType: Optional[str] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod @@ -47,7 +47,7 @@ class DieselSystemDetail: tmp = cls( p_type=d.pop("_type", ""), details=details, - system_type=d.pop("systemType", ""), + systemType=d.pop("systemType", ""), ) tmp.unmapped = d return tmp @@ -107,7 +107,7 @@ class DieselProductDetail: p_type: Optional[str] = None about: Optional[DieselProductAbout] = None requirements: Optional[DieselSystemDetails] = None - social_links: Optional[DieselSocialLinks] = None + socialLinks: Optional[DieselSocialLinks] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod @@ -119,7 +119,7 @@ class DieselProductDetail: p_type=d.pop("_type", ""), about=about, requirements=requirements, - social_links=d.pop("socialLinks", {}), + socialLinks=d.pop("socialLinks", {}), ) tmp.unmapped = d return tmp @@ -136,7 +136,7 @@ class DieselProduct: namespace: Optional[str] = None pages: Optional[List["DieselProduct"]] = None data: Optional[DieselProductDetail] = None - product_name: Optional[str] = None + productName: Optional[str] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod @@ -158,7 +158,7 @@ class DieselProduct: namespace=d.pop("namespace", ""), pages=pages, data=data, - product_name=d.pop("productName", ""), + productName=d.pop("productName", ""), ) tmp.unmapped = d return tmp diff --git a/rare/components/tabs/store/api/models/response.py b/rare/components/tabs/store/api/models/response.py index 6a7672f2..23332d51 100644 --- a/rare/components/tabs/store/api/models/response.py +++ b/rare/components/tabs/store/api/models/response.py @@ -1,7 +1,7 @@ import logging from dataclasses import dataclass, field from datetime import datetime -from typing import List, Dict, Any, Type, Optional +from typing import List, Dict, Any, Type, Optional, Tuple from .utils import parse_date @@ -17,7 +17,6 @@ ItemModel = Dict SellerModel = Dict PageSandboxModel = Dict TagModel = Dict -PromotionsModel = Dict @dataclass @@ -39,16 +38,15 @@ class ImageUrlModel: d = src.copy() type = d.pop("type", None) url = d.pop("url", None) - tmp = cls( - type=type, - url=url, - ) + tmp = cls(type=type, url=url) return tmp @dataclass class KeyImagesModel: key_images: Optional[List[ImageUrlModel]] = None + tall_types = ("DieselStoreFrontTall", "OfferImageTall", "Thumbnail", "ProductLogo", "DieselGameBoxLogo") + wide_types = ("DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo") def __getitem__(self, item): return self.key_images[item] @@ -76,21 +74,13 @@ class KeyImagesModel: return tmp def available_tall(self) -> List[ImageUrlModel]: - tall_types = [ - "DieselStoreFrontTall", - "OfferImageTall", - "Thumbnail", - "ProductLogo", - "DieselGameBoxLogo", - ] - tall_images = filter(lambda img: img.type in tall_types, self.key_images) - tall_images = sorted(tall_images, key=lambda x: tall_types.index(x.type)) + tall_images = filter(lambda img: img.type in KeyImagesModel.tall_types, self.key_images) + tall_images = sorted(tall_images, key=lambda x: KeyImagesModel.tall_types.index(x.type)) return tall_images def available_wide(self) -> List[ImageUrlModel]: - wide_types = ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"] - wide_images = filter(lambda img: img.type in wide_types, self.key_images) - wide_images = sorted(wide_images, key=lambda x: wide_types.index(x.type)) + wide_images = filter(lambda img: img.type in KeyImagesModel.wide_types, self.key_images) + wide_images = sorted(wide_images, key=lambda x: KeyImagesModel.wide_types.index(x.type)) return wide_images def for_dimensions(self, w: int, h: int) -> ImageUrlModel: @@ -107,55 +97,133 @@ class KeyImagesModel: return model -TotalPriceModel = Dict -FmtPriceModel = Dict +CurrencyModel = Dict +FormattedPriceModel = Dict LineOffersModel = Dict @dataclass -class GetPriceResModel: - total_price: Optional[TotalPriceModel] = None - fmt_price: Optional[FmtPriceModel] = None - line_offers: Optional[LineOffersModel] = None +class TotalPriceModel: + discountPrice: Optional[int] = None + originalPrice: Optional[int] = None + voucherDiscount: Optional[int] = None + discount: Optional[int] = None + currencyCode: Optional[str] = None + currencyInfo: Optional[CurrencyModel] = None + fmtPrice: Optional[FormattedPriceModel] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: Type["GetPriceResModel"], src: Dict[str, Any]) -> "GetPriceResModel": + def from_dict(cls: Type["TotalPriceModel"], src: Dict[str, Any]) -> "TotalPriceModel": d = src.copy() tmp = cls( - total_price=d.pop("totalPrice", {}), - fmt_price=d.pop("fmtPrice", {}), - line_offers=d.pop("lineOffers", {}), + discountPrice=d.pop("discountPrice", None), + originalPrice=d.pop("originalPrice", None), + voucherDiscount=d.pop("voucherDiscount", None), + discount=d.pop("discount", None), + currencyCode=d.pop("currencyCode", None), + currencyInfo=d.pop("currrencyInfo", {}), + fmtPrice=d.pop("fmtPrice", {}), ) tmp.unmapped = d return tmp +@dataclass +class GetPriceResModel: + totalPrice: Optional[TotalPriceModel] = None + lineOffers: Optional[LineOffersModel] = None + unmapped: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls: Type["GetPriceResModel"], src: Dict[str, Any]) -> "GetPriceResModel": + d = src.copy() + total_price = TotalPriceModel.from_dict(x) if (x := d.pop("totalPrice", {})) else None + tmp = cls(totalPrice=total_price, lineOffers=d.pop("lineOffers", {})) + tmp.unmapped = d + return tmp + + +DiscountSettingModel = Dict + + +@dataclass +class PromotionalOfferModel: + startDate: Optional[datetime] = None + endDate: Optional[datetime] = None + discountSetting: Optional[DiscountSettingModel] = None + + @classmethod + def from_dict(cls: Type["PromotionalOfferModel"], src: Dict[str, Any]) -> "PromotionalOfferModel": + d = src.copy() + start_date = parse_date(x) if (x := d.pop("startDate", "")) else None + end_date = parse_date(x) if (x := d.pop("endDate", "")) else None + tmp = cls(startDate=start_date, endDate=end_date, discountSetting=d.pop("discountSetting", {})) + tmp.unmapped = d + return tmp + + +@dataclass +class PromotionalOffersModel: + promotionalOffers: Optional[Tuple[PromotionalOfferModel]] = None + + @classmethod + def from_list(cls: Type["PromotionalOffersModel"], src: Dict[str, List]) -> "PromotionalOffersModel": + d = src.copy() + promotional_offers = ( + tuple([PromotionalOfferModel.from_dict(y) for y in x]) if (x := d.pop("promotionalOffers", [])) else None + ) + tmp = cls(promotionalOffers=promotional_offers) + tmp.unmapped = d + return tmp + + +@dataclass +class PromotionsModel: + promotionalOffers: Optional[Tuple[PromotionalOffersModel]] = None + upcomingPromotionalOffers: Optional[Tuple[PromotionalOffersModel]] = None + + @classmethod + def from_dict(cls: Type["PromotionsModel"], src: Dict[str, Any]) -> "PromotionsModel": + d = src.copy() + promotional_offers = ( + tuple([PromotionalOffersModel.from_list(y) for y in x]) if (x := d.pop("promotionalOffers", [])) else None + ) + upcoming_promotional_offers = ( + tuple([PromotionalOffersModel.from_list(y) for y in x]) + if (x := d.pop("upcomingPromotionalOffers", [])) + else None + ) + tmp = cls(promotionalOffers=promotional_offers, upcomingPromotionalOffers=upcoming_promotional_offers) + tmp.unmapped = d + return tmp + + @dataclass class CatalogOfferModel: - catalog_ns: Optional[CatalogNamespaceModel] = None + catalogNs: Optional[CatalogNamespaceModel] = None categories: Optional[List[CategoryModel]] = None - custom_attributes: Optional[List[CustomAttributeModel]] = None + customAttributes: Optional[List[CustomAttributeModel]] = None description: Optional[str] = None - effective_date: Optional[datetime] = None - expiry_date: Optional[datetime] = None + effectiveDate: Optional[datetime] = None + expiryDate: Optional[datetime] = None id: Optional[str] = None - is_code_redemption_only: Optional[bool] = None + isCodeRedemptionOnly: Optional[bool] = None items: Optional[List[ItemModel]] = None - key_images: Optional[KeyImagesModel] = None + keyImages: Optional[KeyImagesModel] = None namespace: Optional[str] = None - offer_mappings: Optional[List[PageSandboxModel]] = None - offer_type: Optional[str] = None + offerMappings: Optional[List[PageSandboxModel]] = None + offerType: Optional[str] = None price: Optional[GetPriceResModel] = None - product_slug: Optional[str] = None + productSlug: Optional[str] = None promotions: Optional[PromotionsModel] = None seller: Optional[SellerModel] = None status: Optional[str] = None tags: Optional[List[TagModel]] = None title: Optional[str] = None url: Optional[str] = None - url_slug: Optional[str] = None - viewable_date: Optional[datetime] = None + urlSlug: Optional[str] = None + viewableDate: Optional[datetime] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod @@ -165,31 +233,32 @@ class CatalogOfferModel: expiry_date = parse_date(x) if (x := d.pop("expiryDate", "")) else None key_images = KeyImagesModel.from_list(d.pop("keyImages", [])) price = GetPriceResModel.from_dict(x) if (x := d.pop("price", {})) else None + promotions = PromotionsModel.from_dict(x) if (x := d.pop("promotions", {})) else None viewable_date = parse_date(x) if (x := d.pop("viewableDate", "")) else None tmp = cls( - catalog_ns=d.pop("catalogNs", {}), + catalogNs=d.pop("catalogNs", {}), categories=d.pop("categories", []), - custom_attributes=d.pop("customAttributes", []), + customAttributes=d.pop("customAttributes", []), description=d.pop("description", ""), - effective_date=effective_date, - expiry_date=expiry_date, + effectiveDate=effective_date, + expiryDate=expiry_date, id=d.pop("id", ""), - is_code_redemption_only=d.pop("isCodeRedemptionOnly", None), + isCodeRedemptionOnly=d.pop("isCodeRedemptionOnly", None), items=d.pop("items", []), - key_images=key_images, + keyImages=key_images, namespace=d.pop("namespace", ""), - offer_mappings=d.pop("offerMappings", []), - offer_type=d.pop("offerType", ""), + offerMappings=d.pop("offerMappings", []), + offerType=d.pop("offerType", ""), price=price, - product_slug=d.pop("productSlug", ""), - promotions=d.pop("promotions", {}), + productSlug=d.pop("productSlug", ""), + promotions=promotions, seller=d.pop("seller", {}), status=d.pop("status", ""), tags=d.pop("tags", []), title=d.pop("title", ""), url=d.pop("url", ""), - url_slug=d.pop("urlSlug", ""), - viewable_date=viewable_date, + urlSlug=d.pop("urlSlug", ""), + viewableDate=viewable_date, ) tmp.unmapped = d return tmp @@ -200,8 +269,8 @@ class WishlistItemModel: created: Optional[datetime] = None id: Optional[str] = None namespace: Optional[str] = None - is_first_time: Optional[bool] = None - offer_id: Optional[str] = None + isFirstTime: Optional[bool] = None + offerId: Optional[str] = None order: Optional[Any] = None updated: Optional[datetime] = None offer: Optional[CatalogOfferModel] = None @@ -217,8 +286,8 @@ class WishlistItemModel: created=created, id=d.pop("id", ""), namespace=d.pop("namespace", ""), - is_first_time=d.pop("isFirstTime", None), - offer_id=d.pop("offerId", ""), + isFirstTime=d.pop("isFirstTime", None), + offerId=d.pop("offerId", ""), order=d.pop("order", ""), updated=updated, offer=offer, @@ -238,10 +307,7 @@ class PagingModel: d = src.copy() count = d.pop("count", None) total = d.pop("total", None) - tmp = cls( - count=count, - total=total, - ) + tmp = cls(count=count, total=total) tmp.unmapped = d return tmp @@ -261,26 +327,21 @@ class SearchStoreModel: elem = CatalogOfferModel.from_dict(item) elements.append(elem) paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None - tmp = cls( - elements=elements, - paging=paging, - ) + tmp = cls(elements=elements, paging=paging) tmp.unmapped = d return tmp @dataclass class CatalogModel: - search_store: Optional[SearchStoreModel] = None + searchStore: Optional[SearchStoreModel] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod def from_dict(cls: Type["CatalogModel"], src: Dict[str, Any]) -> "CatalogModel": d = src.copy() search_store = SearchStoreModel.from_dict(x) if (x := d.pop("searchStore", {})) else None - tmp = cls( - search_store=search_store, - ) + tmp = cls(searchStore=search_store) tmp.unmapped = d return tmp @@ -300,10 +361,7 @@ class WishlistItemsModel: elem = WishlistItemModel.from_dict(item) elements.append(elem) paging = PagingModel.from_dict(x) if (x := d.pop("paging", {})) else None - tmp = cls( - elements=elements, - paging=paging, - ) + tmp = cls(elements=elements, paging=paging) tmp.unmapped = d return tmp @@ -316,16 +374,14 @@ class RemoveFromWishlistModel: @classmethod def from_dict(cls: Type["RemoveFromWishlistModel"], src: Dict[str, Any]) -> "RemoveFromWishlistModel": d = src.copy() - tmp = cls( - success=d.pop("success", None), - ) + tmp = cls(success=d.pop("success", None)) tmp.unmapped = d return tmp @dataclass class AddToWishlistModel: - wishlist_item: Optional[WishlistItemModel] = None + wishlistItem: Optional[WishlistItemModel] = None success: Optional[bool] = None unmapped: Dict[str, Any] = field(default_factory=dict) @@ -333,33 +389,26 @@ class AddToWishlistModel: def from_dict(cls: Type["AddToWishlistModel"], src: Dict[str, Any]) -> "AddToWishlistModel": d = src.copy() wishlist_item = WishlistItemModel.from_dict(x) if (x := d.pop("wishlistItem", {})) else None - tmp = cls( - wishlist_item=wishlist_item, - success=d.pop("success", None), - ) + tmp = cls(wishlistItem=wishlist_item, success=d.pop("success", None)) tmp.unmapped = d return tmp @dataclass class WishlistModel: - wishlist_items: Optional[WishlistItemsModel] = None - remove_from_wishlist: Optional[RemoveFromWishlistModel] = None - add_to_wishlist: Optional[AddToWishlistModel] = None + wishlistItems: Optional[WishlistItemsModel] = None + removeFromWishlist: Optional[RemoveFromWishlistModel] = None + addToWishlist: Optional[AddToWishlistModel] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod def from_dict(cls: Type["WishlistModel"], src: Dict[str, Any]) -> "WishlistModel": d = src.copy() wishlist_items = WishlistItemsModel.from_dict(x) if (x := d.pop("wishlistItems", {})) else None - remove_from_wishlist = ( - RemoveFromWishlistModel.from_dict(x) if (x := d.pop("removeFromWishlist", {})) else None - ) + remove_from_wishlist = RemoveFromWishlistModel.from_dict(x) if (x := d.pop("removeFromWishlist", {})) else None add_to_wishlist = AddToWishlistModel.from_dict(x) if (x := d.pop("addToWishlist", {})) else None tmp = cls( - wishlist_items=wishlist_items, - remove_from_wishlist=remove_from_wishlist, - add_to_wishlist=add_to_wishlist, + wishlistItems=wishlist_items, removeFromWishlist=remove_from_wishlist, addToWishlist=add_to_wishlist ) tmp.unmapped = d return tmp diff --git a/rare/components/tabs/store/api/models/utils.py b/rare/components/tabs/store/api/models/utils.py index 92c6ebf2..06f79c67 100644 --- a/rare/components/tabs/store/api/models/utils.py +++ b/rare/components/tabs/store/api/models/utils.py @@ -2,4 +2,4 @@ from datetime import datetime, timezone def parse_date(date: str): - return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc) \ No newline at end of file + return datetime.fromisoformat(date[:-1]).replace(tzinfo=timezone.utc) diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py index a55e5124..a8582697 100644 --- a/rare/components/tabs/store/landing.py +++ b/rare/components/tabs/store/landing.py @@ -14,26 +14,25 @@ from PyQt5.QtWidgets import ( 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 rare.components.tabs.store.api.models.response import CatalogOfferModel, WishlistItemModel -from .api.models.utils import parse_date from .store_api import StoreAPI from .widgets.details import DetailsWidget -from .widgets.items import StoreItemWidget from .widgets.groups import StoreGroup +from .widgets.items import StoreItemWidget logger = logging.getLogger("StoreLanding") class LandingPage(SlidingStackedWidget, SideTabContents): - def __init__(self, api: StoreAPI, parent=None): + def __init__(self, store_api: StoreAPI, parent=None): super(LandingPage, self).__init__(parent=parent) self.implements_scrollarea = True - self.landing_widget = LandingWidget(api, parent=self) + 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) @@ -43,7 +42,7 @@ class LandingPage(SlidingStackedWidget, SideTabContents): self.landing_scroll.setFrameStyle(QFrame.NoFrame | QFrame.Plain) self.landing_scroll.setWidget(self.landing_widget) - self.details_widget = DetailsWidget([], api, parent=self) + self.details_widget = DetailsWidget([], 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) @@ -83,7 +82,7 @@ class LandingWidget(QWidget, SideTabContents): 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("Games"), FlowLayout, self) + 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(True) @@ -97,8 +96,8 @@ class LandingWidget(QWidget, SideTabContents): def showEvent(self, a0: QShowEvent) -> None: if a0.spontaneous(): return super().showEvent(a0) - self.api.get_free_games(self.__add_free) - self.api.get_wishlist(self.__add_discounts) + 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: @@ -107,28 +106,20 @@ class LandingWidget(QWidget, SideTabContents): # TODO: Implement tab unloading return super().hideEvent(a0) - def __add_discounts(self, wishlist: List[WishlistItemModel]): + 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() - discounts = 0 - for game in wishlist: - if not game: - continue - try: - if game.offer.price.total_price["discount"] > 0: - w = StoreItemWidget(self.api.cached_manager, game.offer) - w.show_details.connect(self.show_details) - self.discounts_group.layout().addWidget(w) - discounts += 1 - except Exception as e: - logger.warning(f"{game} {e}") - continue - # self.discounts_group.setVisible(discounts > 0) + for item in wishlist: + if item.offer.price.totalPrice.discount > 0: + w = StoreItemWidget(self.api.cached_manager, item.offer) + w.show_details.connect(self.show_details) + self.discounts_group.layout().addWidget(w) + self.discounts_group.setVisible(bool(wishlist)) self.discounts_group.loading(False) - def __add_free(self, free_games: List[CatalogOfferModel]): + 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() @@ -140,56 +131,38 @@ class LandingWidget(QWidget, SideTabContents): date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) free_now = [] free_next = [] - for game in free_games: + for item in free_games: try: - if ( - game.price.total_price["fmtPrice"]["discountPrice"] == "0" - and game.price.total_price["fmtPrice"]["originalPrice"] - != game.price.total_price["fmtPrice"]["discountPrice"] - ): - free_now.append(game) + if item.price.totalPrice.discountPrice == 0: + free_now.append(item) continue - - if game.title == "Mystery Game": - free_next.append(game) + if item.title == "Mystery Game": + free_next.append(item) continue except KeyError as e: logger.warning(str(e)) - try: - # parse datetime to check if game is next week or now - try: - start_date = parse_date( - game.promotions["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"] - ) - except Exception: - try: - start_date = parse_date( - game.promotions["promotionalOffers"][0]["promotionalOffers"][0]["startDate"] - ) - except Exception as e: - continue - - except TypeError: - print("type error") - continue + 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(game) + free_next.append(item) # free games now - self.free_games_now.setVisible(bool(len(free_now))) - for game in free_now: - w = StoreItemWidget(self.api.cached_manager, game) + 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(len(free_next))) - for game in free_next: - w = StoreItemWidget(self.api.cached_manager, game) - if game.title != "Mystery Game": + 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) diff --git a/rare/components/tabs/store/search.py b/rare/components/tabs/store/search.py index 7e5d6d8d..56456b30 100644 --- a/rare/components/tabs/store/search.py +++ b/rare/components/tabs/store/search.py @@ -26,16 +26,16 @@ logger = logging.getLogger("Shop") class SearchPage(SlidingStackedWidget, SideTabContents): - def __init__(self, core, api: StoreAPI, parent=None): + def __init__(self, store_api: StoreAPI, parent=None): super(SearchPage, self).__init__(parent=parent) self.implements_scrollarea = True - self.search_widget = SearchWidget(core, api, parent=self) + self.search_widget = SearchWidget(store_api, parent=self) self.search_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.search_widget.set_title.connect(self.set_title) self.search_widget.show_details.connect(self.show_details) - self.details_widget = DetailsWidget([], api, parent=self) + self.details_widget = DetailsWidget([], store_api, parent=self) self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.details_widget.set_title.connect(self.set_title) self.details_widget.back_clicked.connect(self.show_main) @@ -58,27 +58,25 @@ class SearchPage(SlidingStackedWidget, SideTabContents): class SearchWidget(QWidget, SideTabContents): show_details = pyqtSignal(CatalogOfferModel) - def __init__(self, core: LegendaryCore, api: StoreAPI, parent=None): + def __init__(self, store_api: StoreAPI, parent=None): super(SearchWidget, self).__init__(parent=parent) self.implements_scrollarea = True self.ui = Ui_SearchWidget() self.ui.setupUi(self) self.ui.main_layout.setContentsMargins(0, 0, 3, 0) - self.core = core - self.api_core = api + self.store_api = store_api self.price = "" self.tags = [] self.types = [] self.update_games_allowed = True - self.free_game_widgets = [] self.active_search_request = False self.next_search = "" self.wishlist: List = [] self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games")) - self.results_scrollarea = ResultsWidget(self.api_core, self) + self.results_scrollarea = ResultsWidget(self.store_api, self) self.results_scrollarea.show_details.connect(self.show_details) self.ui.left_layout.addWidget(self.search_bar) @@ -188,8 +186,8 @@ class SearchWidget(QWidget, SideTabContents): self.games_group.loading(True) browse_model = SearchStoreQuery( - language=self.core.language_code, - country=self.core.country_code, + language=self.store_api.language_code, + country=self.store_api.country_code, count=20, price_range=self.price, on_sale=self.ui.on_discount.isChecked(), @@ -198,7 +196,7 @@ class SearchWidget(QWidget, SideTabContents): if self.types: browse_model.category = "|".join(self.types) - self.api_core.browse_games(browse_model, self.show_games) + self.store_api.browse_games(browse_model, self.show_games) class CheckBox(QCheckBox): diff --git a/rare/components/tabs/store/store_api.py b/rare/components/tabs/store/store_api.py index 90ac2b7e..0f45d8e8 100644 --- a/rare/components/tabs/store/store_api.py +++ b/rare/components/tabs/store/store_api.py @@ -20,7 +20,7 @@ from .api.models.response import ( CatalogOfferModel, ) -logger = getLogger("ShopAPICore") +logger = getLogger("StoreAPI") graphql_url = "https://graphql.epicgames.com/graphql" @@ -46,7 +46,7 @@ class StoreAPI(QObject): self.browse_active = False self.next_browse_request = tuple(()) - def get_free_games(self, handle_func: callable): + def get_free(self, handle_func: callable): url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions" params = { "locale": self.locale, @@ -59,7 +59,7 @@ class StoreAPI(QObject): def __handle_free_games(data, handle_func): try: response = ResponseModel.from_dict(data) - results: List[CatalogOfferModel] = response.data.catalog.search_store.elements + results: List[CatalogOfferModel] = response.data.catalog.searchStore.elements handle_func(results) except KeyError as e: if DEBUG(): @@ -94,7 +94,7 @@ class StoreAPI(QObject): response = ResponseModel.from_dict(data) if response.errors: logger.error(response.errors) - handle_func(response.data.wishlist.wishlist_items.elements) + handle_func(response.data.wishlist.wishlistItems.elements) except KeyError as e: if DEBUG(): raise e @@ -132,7 +132,7 @@ class StoreAPI(QObject): def __handle_search(data, handler): try: response = ResponseModel.from_dict(data) - handler(response.data.catalog.search_store.elements) + handler(response.data.catalog.searchStore.elements) except KeyError as e: logger.error(str(e)) if DEBUG(): @@ -167,7 +167,7 @@ class StoreAPI(QObject): if not self.next_browse_request: try: response = ResponseModel.from_dict(data) - handle_func(response.data.catalog.search_store.elements) + handle_func(response.data.catalog.searchStore.elements) except KeyError as e: if DEBUG(): raise e @@ -233,7 +233,7 @@ class StoreAPI(QObject): # debug.exec() try: response = ResponseModel.from_dict(data) - data = response.data.wishlist.add_to_wishlist + data = response.data.wishlist.addToWishlist handle_func(data.success) except Exception as e: if DEBUG(): @@ -259,7 +259,7 @@ class StoreAPI(QObject): # debug.exec() try: response = ResponseModel.from_dict(data) - data = response.data.wishlist.remove_from_wishlist + data = response.data.wishlist.removeFromWishlist handle_func(data.success) except Exception as e: if DEBUG(): diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index 98f8eaf0..e0386bc7 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -1,5 +1,5 @@ import logging -from typing import List +from typing import List, Dict from PyQt5.QtCore import Qt, QUrl, pyqtSignal from PyQt5.QtGui import QFont, QDesktopServices, QKeyEvent @@ -14,6 +14,7 @@ from PyQt5.QtWidgets import ( from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail, DieselSystemDetail from rare.components.tabs.store.api.models.response import CatalogOfferModel +from rare.components.tabs.store.store_api import StoreAPI from rare.models.image import ImageSize from rare.ui.components.tabs.store.details import Ui_DetailsWidget from rare.utils.misc import icon @@ -28,7 +29,7 @@ class DetailsWidget(QWidget, SideTabContents): back_clicked: pyqtSignal = pyqtSignal() # TODO Design - def __init__(self, installed_titles: list, api_core, parent=None): + def __init__(self, installed: List, store_api: StoreAPI, parent=None): super(DetailsWidget, self).__init__(parent=parent) self.implements_scrollarea = True @@ -36,13 +37,11 @@ class DetailsWidget(QWidget, SideTabContents): self.ui.setupUi(self) self.ui.main_layout.setContentsMargins(0, 0, 3, 0) - # self.core = LegendaryCoreSingleton() - self.api_core = api_core - self.installed = installed_titles - self.offer: CatalogOfferModel = None - self.data: dict = {} + self.store_api = store_api + self.installed = installed + self.catalog_offer: CatalogOfferModel = None - self.image = LoadingImageWidget(api_core.cached_manager, self) + self.image = LoadingImageWidget(store_api.cached_manager, self) self.image.setFixedSize(ImageSize.Display) self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop) self.ui.left_layout.setAlignment(Qt.AlignTop) @@ -53,7 +52,7 @@ class DetailsWidget(QWidget, SideTabContents): self.in_wishlist = False self.wishlist = [] - self.requirements_tabs: SideTabWidget = SideTabWidget(parent=self.ui.requirements_frame) + self.requirements_tabs = SideTabWidget(parent=self.ui.requirements_frame) self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ui.requirements_layout.addWidget(self.requirements_tabs) @@ -78,15 +77,17 @@ class DetailsWidget(QWidget, SideTabContents): self.ui.title.setText(offer.title) self.title_str = offer.title self.id_str = offer.id - self.api_core.get_wishlist(self.handle_wishlist_update) + self.store_api.get_wishlist(self.handle_wishlist_update) + # lk: delete tabs in reverse order because indices are updated on deletion while self.requirements_tabs.count(): self.requirements_tabs.widget(0).deleteLater() self.requirements_tabs.removeTab(0) self.requirements_tabs.clear() - slug = offer.product_slug + + slug = offer.productSlug if not slug: - for mapping in offer.offer_mappings: + for mapping in offer.offerMappings: if mapping["pageType"] == "productHome": slug = mapping["pageSlug"] break @@ -114,24 +115,24 @@ class DetailsWidget(QWidget, SideTabContents): # init API request if slug: - self.api_core.get_game_config_cms(offer.product_slug, is_bundle, self.data_received) + self.store_api.get_game_config_cms(offer.productSlug, is_bundle, self.data_received) # else: # self.data_received({}) - self.offer = offer + self.catalog_offer = offer def add_to_wishlist(self): if not self.in_wishlist: - self.api_core.add_to_wishlist( - self.offer.namespace, - self.offer.id, + self.store_api.add_to_wishlist( + self.catalog_offer.namespace, + self.catalog_offer.id, lambda success: self.ui.wishlist_button.setText(self.tr("Remove from wishlist")) if success else self.ui.wishlist_button.setText("Something went wrong") ) else: - self.api_core.remove_from_wishlist( - self.offer.namespace, - self.offer.id, + self.store_api.remove_from_wishlist( + self.catalog_offer.namespace, + self.catalog_offer.id, lambda success: self.ui.wishlist_button.setText(self.tr("Add to wishlist")) if success else self.ui.wishlist_button.setText("Something went wrong"), @@ -146,34 +147,16 @@ class DetailsWidget(QWidget, SideTabContents): except Exception as e: raise e logger.error(str(e)) - self.price.setText("Error") - self.requirements_tabs.setEnabled(False) - for img in self.data.get("keyImages"): - if img["type"] in [ - "DieselStoreFrontWide", - "OfferImageTall", - "VaultClosed", - "ProductLogo", - ]: - self.image.fetchPixmap(img["url"]) - break - self.price.setText("") - self.discount_price.setText("") - self.social_group.setEnabled(False) - self.tags.setText("") - self.dev.setText(self.data.get("seller", {}).get("name", "")) - return - # self.title.setText(self.game.title) - self.ui.price.setFont(QFont()) - price = self.offer.price.total_price["fmtPrice"]["originalPrice"] - discount_price = self.offer.price.total_price["fmtPrice"]["discountPrice"] + self.ui.price.setFont(self.font()) + price = self.catalog_offer.price.totalPrice.fmtPrice["originalPrice"] + discount_price = self.catalog_offer.price.totalPrice.fmtPrice["discountPrice"] if price == "0" or price == 0: self.ui.price.setText(self.tr("Free")) else: self.ui.price.setText(price) if price != discount_price: - font = QFont() + font = self.font() font.setStrikeOut(True) self.ui.price.setFont(font) self.ui.discount_price.setText( @@ -189,18 +172,11 @@ class DetailsWidget(QWidget, SideTabContents): if requirements and requirements.systems: for system in requirements.systems: req_widget = RequirementsWidget(system, self.requirements_tabs) - self.requirements_tabs.addTab(req_widget, system.system_type) - # self.req_group_box.layout().addWidget(req_tabs) - # self.req_group_box.layout().setAlignment(Qt.AlignTop) - # else: - # self.req_group_box.layout().addWidget( - # QLabel(self.tr("Could not get requirements")) - # ) - self.ui.requirements_frame.setVisible(True) + self.requirements_tabs.addTab(req_widget, system.systemType) else: self.ui.requirements_frame.setVisible(False) - key_images = self.offer.key_images + key_images = self.catalog_offer.keyImages img_url = key_images.for_dimensions(self.image.size().width(), self.image.size().height()) self.image.fetchPixmap(img_url.url) @@ -223,7 +199,7 @@ class DetailsWidget(QWidget, SideTabContents): self.ui.social_layout.removeWidget(b) b.deleteLater() - links = product_data.social_links + links = product_data.socialLinks link_count = 0 for name, url in links.items(): if name == "_type": @@ -252,8 +228,7 @@ class DetailsWidget(QWidget, SideTabContents): # self.wishlist.append(game["offer"]["title"]) def button_clicked(self): - return - QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.core.language_code}/p/{self.slug}")) + QDesktopServices.openUrl(QUrl(f"https://www.epicgames.com/store/{self.store_api.language_code}/p/{self.slug}")) def keyPressEvent(self, a0: QKeyEvent): if a0.key() == Qt.Key_Escape: diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py index 02b6b500..3fc0363c 100644 --- a/rare/components/tabs/store/widgets/items.py +++ b/rare/components/tabs/store/widgets/items.py @@ -11,7 +11,7 @@ from rare.utils.misc import qta_icon from rare.utils.qt_requests import QtRequests from .image import LoadingImageWidget -logger = logging.getLogger("GameWidgets") +logger = logging.getLogger("StoreWidgets") class ItemWidget(LoadingImageWidget): @@ -46,15 +46,15 @@ class StoreItemWidget(ItemWidget): return self.ui.title_label.setText(game.title) - for attr in game.custom_attributes: + for attr in game.customAttributes: if attr["key"] == "developerName": developer = attr["value"] break else: developer = game.seller["name"] self.ui.developer_label.setText(developer) - price = game.price.total_price["fmtPrice"]["originalPrice"] - discount_price = game.price.total_price["fmtPrice"]["discountPrice"] + price = game.price.totalPrice.fmtPrice["originalPrice"] + discount_price = game.price.totalPrice.fmtPrice["discountPrice"] self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: font = self.ui.price_label.font() @@ -64,7 +64,7 @@ class StoreItemWidget(ItemWidget): else: self.ui.discount_label.setVisible(False) - key_images = game.key_images + key_images = game.keyImages self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) # for img in json_info["keyImages"]: @@ -83,13 +83,13 @@ class ResultsItemWidget(ItemWidget): self.setFixedSize(ImageSize.Display) self.ui.setupUi(self) - key_images = catalog_game.key_images + key_images = catalog_game.keyImages self.fetchPixmap(key_images.for_dimensions(self.width(), self.height()).url) self.ui.title_label.setText(catalog_game.title) - price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] - discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] + price = catalog_game.price.totalPrice.fmtPrice["originalPrice"] + discount_price = catalog_game.price.totalPrice.fmtPrice["discountPrice"] self.ui.price_label.setText(f'{price if price != "0" else self.tr("Free")}') if price != discount_price: font = self.ui.price_label.font() @@ -108,14 +108,14 @@ class WishlistItemWidget(ItemWidget): super(WishlistItemWidget, self).__init__(manager, catalog_game, parent=parent) self.setFixedSize(ImageSize.DisplayWide) self.ui.setupUi(self) - for attr in catalog_game.custom_attributes: + for attr in catalog_game.customAttributes: if attr["key"] == "developerName": developer = attr["value"] break else: developer = catalog_game.seller["name"] - original_price = catalog_game.price.total_price["fmtPrice"]["originalPrice"] - discount_price = catalog_game.price.total_price["fmtPrice"]["discountPrice"] + original_price = catalog_game.price.totalPrice.fmtPrice["originalPrice"] + discount_price = catalog_game.price.totalPrice.fmtPrice["discountPrice"] self.ui.title_label.setText(catalog_game.title) self.ui.developer_label.setText(developer) @@ -127,7 +127,7 @@ class WishlistItemWidget(ItemWidget): self.ui.discount_label.setText(f'{discount_price if discount_price != "0" else self.tr("Free")}') else: self.ui.discount_label.setVisible(False) - key_images = catalog_game.key_images + key_images = catalog_game.keyImages self.fetchPixmap( key_images.for_dimensions(self.width(), self.height()).url ) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index bedc2ca4..2eb173a9 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -116,13 +116,13 @@ class WishlistWidget(QWidget, SideTabContents): func = lambda x: x.catalog_game.title reverse = self.ui.reverse.isChecked() elif sort == 1: - func = lambda x: x.catalog_game.price.total_price["fmtPrice"]["discountPrice"] + func = lambda x: x.catalog_game.price.totalPrice["fmtPrice"]["discountPrice"] reverse = self.ui.reverse.isChecked() elif sort == 2: func = lambda x: x.catalog_game.seller["name"] reverse = self.ui.reverse.isChecked() elif sort == 3: - func = lambda x: 1 - (x.catalog_game.price.total_price["discountPrice"] / x.catalog_game.price.total_price["originalPrice"]) + func = lambda x: 1 - (x.catalog_game.price.totalPrice["discountPrice"] / x.catalog_game.price.totalPrice["originalPrice"]) reverse = not self.ui.reverse.isChecked() else: func = lambda x: x.catalog_game.title diff --git a/rare/ui/components/tabs/store/details.py b/rare/ui/components/tabs/store/details.py index 5c570cb2..ccb87c86 100644 --- a/rare/ui/components/tabs/store/details.py +++ b/rare/ui/components/tabs/store/details.py @@ -18,6 +18,8 @@ class Ui_DetailsWidget(object): DetailsWidget.setWindowTitle("DetailsWidget") self.main_layout = QtWidgets.QHBoxLayout(DetailsWidget) self.main_layout.setObjectName("main_layout") + self.left_layout = QtWidgets.QVBoxLayout() + self.left_layout.setObjectName("left_layout") self.back_button = QtWidgets.QPushButton(DetailsWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) @@ -28,9 +30,7 @@ class Ui_DetailsWidget(object): self.back_button.setIconSize(QtCore.QSize(32, 32)) self.back_button.setFlat(True) self.back_button.setObjectName("back_button") - self.main_layout.addWidget(self.back_button) - self.left_layout = QtWidgets.QVBoxLayout() - self.left_layout.setObjectName("left_layout") + self.left_layout.addWidget(self.back_button) self.main_layout.addLayout(self.left_layout) self.right_layout = QtWidgets.QVBoxLayout() self.right_layout.setObjectName("right_layout") @@ -176,7 +176,7 @@ class Ui_DetailsWidget(object): self.description_label.setObjectName("description_label") self.right_layout.addWidget(self.description_label) self.main_layout.addLayout(self.right_layout) - self.main_layout.setStretch(2, 1) + self.main_layout.setStretch(1, 1) self.retranslateUi(DetailsWidget) diff --git a/rare/ui/components/tabs/store/details.ui b/rare/ui/components/tabs/store/details.ui index 20b21038..b78efa4c 100644 --- a/rare/ui/components/tabs/store/details.ui +++ b/rare/ui/components/tabs/store/details.ui @@ -13,31 +13,32 @@ DetailsWidget - + - - - - 0 - 0 - - - - - - - - 32 - 32 - - - - true - - - - - + + + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + true + + + + diff --git a/rare/utils/qt_requests.py b/rare/utils/qt_requests.py index 2a7d8bbd..02a9c5a2 100644 --- a/rare/utils/qt_requests.py +++ b/rare/utils/qt_requests.py @@ -5,7 +5,7 @@ from typing import Callable, Dict, TypeVar, List, Tuple from typing import Union import orjson -from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot +from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot, QJsonDocument from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkDiskCache USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" From 7a2a6458ed17b54635d2d68036110efba7bd5359 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:45:14 +0200 Subject: [PATCH 16/21] Store: Update details page --- .../tabs/store/api/models/diesel.py | 56 ++--- rare/components/tabs/store/widgets/details.py | 34 +-- rare/components/tabs/store/widgets/items.py | 1 - rare/ui/components/tabs/store/details.py | 158 +++++++------- rare/ui/components/tabs/store/details.ui | 196 ++++++++++-------- requirements-dev.txt | 2 +- 6 files changed, 237 insertions(+), 210 deletions(-) diff --git a/rare/components/tabs/store/api/models/diesel.py b/rare/components/tabs/store/api/models/diesel.py index 437c8b8c..20a4f07a 100644 --- a/rare/components/tabs/store/api/models/diesel.py +++ b/rare/components/tabs/store/api/models/diesel.py @@ -10,7 +10,7 @@ DieselSocialLinks = Dict @dataclass class DieselSystemDetailItem: - p_type: Optional[str] = None + _type: Optional[str] = None minimum: Optional[str] = None recommended: Optional[str] = None title: Optional[str] = None @@ -20,7 +20,7 @@ class DieselSystemDetailItem: def from_dict(cls: Type["DieselSystemDetailItem"], src: Dict[str, Any]) -> "DieselSystemDetailItem": d = src.copy() tmp = cls( - p_type=d.pop("_type", ""), + _type=d.pop("_type", ""), minimum=d.pop("minimum", ""), recommended=d.pop("recommended", ""), title=d.pop("title", ""), @@ -31,7 +31,7 @@ class DieselSystemDetailItem: @dataclass class DieselSystemDetail: - p_type: Optional[str] = None + _type: Optional[str] = None details: Optional[List[DieselSystemDetailItem]] = None systemType: Optional[str] = None unmapped: Dict[str, Any] = field(default_factory=dict) @@ -45,7 +45,7 @@ class DieselSystemDetail: detail = DieselSystemDetailItem.from_dict(item) details.append(detail) tmp = cls( - p_type=d.pop("_type", ""), + _type=d.pop("_type", ""), details=details, systemType=d.pop("systemType", ""), ) @@ -55,7 +55,7 @@ class DieselSystemDetail: @dataclass class DieselSystemDetails: - p_type: Optional[str] = None + _type: Optional[str] = None languages: Optional[List[str]] = None rating: Optional[Dict] = None systems: Optional[List[DieselSystemDetail]] = None @@ -70,7 +70,7 @@ class DieselSystemDetails: system = DieselSystemDetail.from_dict(item) systems.append(system) tmp = cls( - p_type=d.pop("_type", ""), + _type=d.pop("_type", ""), languages=d.pop("languages", []), rating=d.pop("rating", {}), systems=systems, @@ -81,22 +81,22 @@ class DieselSystemDetails: @dataclass class DieselProductAbout: - p_type: Optional[str] = None + _type: Optional[str] = None desciption: Optional[str] = None - developer_attribution: Optional[str] = None - publisher_attribution: Optional[str] = None - short_description: Optional[str] = None + developerAttribution: Optional[str] = None + publisherAttribution: Optional[str] = None + shortDescription: Optional[str] = None unmapped: Dict[str, Any] = field(default_factory=dict) @classmethod def from_dict(cls: Type["DieselProductAbout"], src: Dict[str, Any]) -> "DieselProductAbout": d = src.copy() tmp = cls( - p_type=d.pop("_type", ""), + _type=d.pop("_type", ""), desciption=d.pop("description", ""), - developer_attribution=d.pop("developerAttribution", ""), - publisher_attribution=d.pop("publisherAttribution", ""), - short_description=d.pop("shortDescription", ""), + developerAttribution=d.pop("developerAttribution", ""), + publisherAttribution=d.pop("publisherAttribution", ""), + shortDescription=d.pop("shortDescription", ""), ) tmp.unmapped = d return tmp @@ -104,7 +104,7 @@ class DieselProductAbout: @dataclass class DieselProductDetail: - p_type: Optional[str] = None + _type: Optional[str] = None about: Optional[DieselProductAbout] = None requirements: Optional[DieselSystemDetails] = None socialLinks: Optional[DieselSocialLinks] = None @@ -116,7 +116,7 @@ class DieselProductDetail: about = DieselProductAbout.from_dict(x) if (x := d.pop("about"), {}) else None requirements = DieselSystemDetails.from_dict(x) if (x := d.pop("requirements", {})) else None tmp = cls( - p_type=d.pop("_type", ""), + _type=d.pop("_type", ""), about=about, requirements=requirements, socialLinks=d.pop("socialLinks", {}), @@ -127,12 +127,12 @@ class DieselProductDetail: @dataclass class DieselProduct: - p_id: Optional[str] = None - p_images_: Optional[List[str]] = None - p_locale: Optional[str] = None - p_slug: Optional[str] = None - p_title: Optional[str] = None - p_url_pattern: Optional[str] = None + _id: Optional[str] = None + _images_: Optional[List[str]] = None + _locale: Optional[str] = None + _slug: Optional[str] = None + _title: Optional[str] = None + _urlPattern: Optional[str] = None namespace: Optional[str] = None pages: Optional[List["DieselProduct"]] = None data: Optional[DieselProductDetail] = None @@ -149,12 +149,12 @@ class DieselProduct: pages.append(page) data = DieselProductDetail.from_dict(x) if (x := d.pop("data", {})) else None tmp = cls( - p_id=d.pop("_id", ""), - p_images_=d.pop("_images_", []), - p_locale=d.pop("_locale", ""), - p_slug=d.pop("_slug", ""), - p_title=d.pop("_title", ""), - p_url_pattern=d.pop("_urlPattern", ""), + _id=d.pop("_id", ""), + _images_=d.pop("_images_", []), + _locale=d.pop("_locale", ""), + _slug=d.pop("_slug", ""), + _title=d.pop("_title", ""), + _urlPattern=d.pop("_urlPattern", ""), namespace=d.pop("namespace", ""), pages=pages, data=data, diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index e0386bc7..3051845c 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -47,7 +47,7 @@ class DetailsWidget(QWidget, SideTabContents): self.ui.left_layout.setAlignment(Qt.AlignTop) self.ui.wishlist_button.clicked.connect(self.add_to_wishlist) - self.ui.open_store_button.clicked.connect(self.button_clicked) + self.ui.store_button.clicked.connect(self.button_clicked) self.ui.wishlist_button.setVisible(True) self.in_wishlist = False self.wishlist = [] @@ -99,13 +99,13 @@ class DetailsWidget(QWidget, SideTabContents): self.slug = slug if offer.namespace in self.installed: - self.ui.open_store_button.setText(self.tr("Show Game on Epic Page")) - self.ui.owned_label.setVisible(True) + self.ui.store_button.setText(self.tr("Show Game on Epic Page")) + self.ui.status.setVisible(True) else: - self.ui.open_store_button.setText(self.tr("Buy Game in Epic Games Store")) - self.ui.owned_label.setVisible(False) + self.ui.store_button.setText(self.tr("Buy Game in Epic Games Store")) + self.ui.status.setVisible(False) - self.ui.price.setText(self.tr("Loading")) + self.ui.original_price.setText(self.tr("Loading")) # self.title.setText(self.tr("Loading")) # self.image.setPixmap(QPixmap()) is_bundle = False @@ -148,17 +148,17 @@ class DetailsWidget(QWidget, SideTabContents): raise e logger.error(str(e)) - self.ui.price.setFont(self.font()) + self.ui.original_price.setFont(self.font()) price = self.catalog_offer.price.totalPrice.fmtPrice["originalPrice"] discount_price = self.catalog_offer.price.totalPrice.fmtPrice["discountPrice"] if price == "0" or price == 0: - self.ui.price.setText(self.tr("Free")) + self.ui.original_price.setText(self.tr("Free")) else: - self.ui.price.setText(price) + self.ui.original_price.setText(price) if price != discount_price: font = self.font() font.setStrikeOut(True) - self.ui.price.setFont(font) + self.ui.original_price.setFont(font) self.ui.discount_price.setText( discount_price if discount_price != "0" @@ -173,6 +173,7 @@ class DetailsWidget(QWidget, SideTabContents): for system in requirements.systems: req_widget = RequirementsWidget(system, self.requirements_tabs) self.requirements_tabs.addTab(req_widget, system.systemType) + self.ui.requirements_frame.setVisible(True) else: self.ui.requirements_frame.setVisible(False) @@ -183,7 +184,7 @@ class DetailsWidget(QWidget, SideTabContents): # self.image_stack.setCurrentIndex(0) about = product_data.about self.ui.description_label.setMarkdown(about.desciption) - self.ui.dev.setText(about.developer_attribution) + self.ui.developer.setText(about.developerAttribution) # try: # if isinstance(aboudeveloper, list): # self.ui.dev.setText(", ".join(self.game.developer)) @@ -195,8 +196,8 @@ class DetailsWidget(QWidget, SideTabContents): self.ui.tags.setText(", ".join(tags)) # clear Layout - for b in self.ui.social_group.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): - self.ui.social_layout.removeWidget(b) + for b in self.ui.social_links.findChildren(SocialButton, options=Qt.FindDirectChildrenOnly): + self.ui.social_links_layout.removeWidget(b) b.deleteLater() links = product_data.socialLinks @@ -214,11 +215,11 @@ class DetailsWidget(QWidget, SideTabContents): logger.error(str(e)) continue - button = SocialButton(icn, url, parent=self.ui.social_group) - self.ui.social_layout.addWidget(button) + button = SocialButton(icn, url, parent=self.ui.social_links) + self.ui.social_links_layout.addWidget(button) link_count += 1 - self.ui.social_group.setEnabled(bool(link_count)) + self.ui.social_links.setEnabled(bool(link_count)) self.setEnabled(True) @@ -238,6 +239,7 @@ class DetailsWidget(QWidget, SideTabContents): class SocialButton(QPushButton): def __init__(self, icn, url, parent=None): super(SocialButton, self).__init__(icn, "", parent=parent) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.url = url self.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url))) self.setToolTip(url) diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py index 3fc0363c..86e61f5e 100644 --- a/rare/components/tabs/store/widgets/items.py +++ b/rare/components/tabs/store/widgets/items.py @@ -101,7 +101,6 @@ class ResultsItemWidget(ItemWidget): class WishlistItemWidget(ItemWidget): - show_details = pyqtSignal(CatalogOfferModel) delete_from_wishlist = pyqtSignal(CatalogOfferModel) def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None): diff --git a/rare/ui/components/tabs/store/details.py b/rare/ui/components/tabs/store/details.py index ccb87c86..0862694b 100644 --- a/rare/ui/components/tabs/store/details.py +++ b/rare/ui/components/tabs/store/details.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_DetailsWidget(object): def setupUi(self, DetailsWidget): DetailsWidget.setObjectName("DetailsWidget") - DetailsWidget.resize(702, 414) + DetailsWidget.resize(630, 371) DetailsWidget.setWindowTitle("DetailsWidget") self.main_layout = QtWidgets.QHBoxLayout(DetailsWidget) self.main_layout.setObjectName("main_layout") @@ -34,12 +34,13 @@ class Ui_DetailsWidget(object): self.main_layout.addLayout(self.left_layout) self.right_layout = QtWidgets.QVBoxLayout() self.right_layout.setObjectName("right_layout") - self.info_layout = QtWidgets.QFormLayout() - self.info_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.info_layout.setContentsMargins(6, 6, 6, 6) - self.info_layout.setSpacing(12) - self.info_layout.setObjectName("info_layout") + self.details_layout = QtWidgets.QFormLayout() + self.details_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.details_layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.FieldsStayAtSizeHint) + self.details_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.details_layout.setContentsMargins(6, 6, 6, 6) + self.details_layout.setSpacing(12) + self.details_layout.setObjectName("details_layout") self.title_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -47,12 +48,12 @@ class Ui_DetailsWidget(object): self.title_label.setFont(font) self.title_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.title_label.setObjectName("title_label") - self.info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.title_label) + self.details_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.title_label) self.title = QtWidgets.QLabel(DetailsWidget) self.title.setText("title") self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.title.setObjectName("title") - self.info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.title) + self.details_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.title) self.developer_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -60,12 +61,25 @@ class Ui_DetailsWidget(object): self.developer_label.setFont(font) self.developer_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.developer_label.setObjectName("developer_label") - self.info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.developer_label) - self.dev = QtWidgets.QLabel(DetailsWidget) - self.dev.setText("dev") - self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) - self.dev.setObjectName("dev") - self.info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.dev) + self.details_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.developer_label) + self.developer = QtWidgets.QLabel(DetailsWidget) + self.developer.setText("developer") + self.developer.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.developer.setObjectName("developer") + self.details_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.developer) + self.publisher_label = QtWidgets.QLabel(DetailsWidget) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.publisher_label.setFont(font) + self.publisher_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.publisher_label.setObjectName("publisher_label") + self.details_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.publisher_label) + self.publisher = QtWidgets.QLabel(DetailsWidget) + self.publisher.setText("publisher") + self.publisher.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.publisher.setObjectName("publisher") + self.details_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.publisher) self.status_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -73,10 +87,10 @@ class Ui_DetailsWidget(object): self.status_label.setFont(font) self.status_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.status_label.setObjectName("status_label") - self.info_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.status_label) - self.owned_label = QtWidgets.QLabel(DetailsWidget) - self.owned_label.setObjectName("owned_label") - self.info_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.owned_label) + self.details_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.status_label) + self.status = QtWidgets.QLabel(DetailsWidget) + self.status.setObjectName("status") + self.details_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.status) self.price_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -84,23 +98,7 @@ class Ui_DetailsWidget(object): self.price_label.setFont(font) self.price_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.price_label.setObjectName("price_label") - self.info_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.price_label) - self.price_widget = QtWidgets.QWidget(DetailsWidget) - self.price_widget.setObjectName("price_widget") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.price_widget) - self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.price = QtWidgets.QLabel(self.price_widget) - self.price.setText("price") - self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) - self.price.setObjectName("price") - self.horizontalLayout.addWidget(self.price) - self.discount_price = QtWidgets.QLabel(self.price_widget) - self.discount_price.setText("discount") - self.discount_price.setObjectName("discount_price") - self.horizontalLayout.addWidget(self.discount_price) - self.info_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.price_widget) + self.details_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.price_label) self.tags_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -108,27 +106,19 @@ class Ui_DetailsWidget(object): self.tags_label.setFont(font) self.tags_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.tags_label.setObjectName("tags_label") - self.info_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.tags_label) + self.details_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.tags_label) self.tags = QtWidgets.QLabel(DetailsWidget) self.tags.setText("tags") self.tags.setObjectName("tags") - self.info_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.tags) - self.links_label = QtWidgets.QLabel(DetailsWidget) + self.details_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.tags) + self.social_links_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) - self.links_label.setFont(font) - self.links_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.links_label.setObjectName("links_label") - self.info_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.links_label) - self.social_group = QtWidgets.QWidget(DetailsWidget) - self.social_group.setMinimumSize(QtCore.QSize(250, 0)) - self.social_group.setObjectName("social_group") - self.social_layout = QtWidgets.QHBoxLayout(self.social_group) - self.social_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.social_layout.setContentsMargins(0, 0, 0, 0) - self.social_layout.setObjectName("social_layout") - self.info_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.social_group) + self.social_links_label.setFont(font) + self.social_links_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.social_links_label.setObjectName("social_links_label") + self.details_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.social_links_label) self.actions_label = QtWidgets.QLabel(DetailsWidget) font = QtGui.QFont() font.setBold(True) @@ -136,28 +126,49 @@ class Ui_DetailsWidget(object): self.actions_label.setFont(font) self.actions_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.actions_label.setObjectName("actions_label") - self.info_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.actions_label) - self.buttons_widget = QtWidgets.QWidget(DetailsWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + self.details_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.actions_label) + self.social_links = QtWidgets.QWidget(DetailsWidget) + self.social_links.setObjectName("social_links") + self.social_links_layout = QtWidgets.QHBoxLayout(self.social_links) + self.social_links_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.social_links_layout.setContentsMargins(0, 0, 0, 0) + self.social_links_layout.setObjectName("social_links_layout") + self.details_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.social_links) + self.actions = QtWidgets.QWidget(DetailsWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.buttons_widget.sizePolicy().hasHeightForWidth()) - self.buttons_widget.setSizePolicy(sizePolicy) - self.buttons_widget.setMinimumSize(QtCore.QSize(250, 0)) - self.buttons_widget.setMaximumSize(QtCore.QSize(250, 16777215)) - self.buttons_widget.setObjectName("buttons_widget") - self.button_layout = QtWidgets.QVBoxLayout(self.buttons_widget) - self.button_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.button_layout.setContentsMargins(0, 0, 0, 0) - self.button_layout.setObjectName("button_layout") - self.open_store_button = QtWidgets.QPushButton(self.buttons_widget) - self.open_store_button.setObjectName("open_store_button") - self.button_layout.addWidget(self.open_store_button) - self.wishlist_button = QtWidgets.QPushButton(self.buttons_widget) + sizePolicy.setHeightForWidth(self.actions.sizePolicy().hasHeightForWidth()) + self.actions.setSizePolicy(sizePolicy) + self.actions.setMinimumSize(QtCore.QSize(250, 0)) + self.actions.setObjectName("actions") + self.actions_layout = QtWidgets.QVBoxLayout(self.actions) + self.actions_layout.setContentsMargins(0, 0, 0, 0) + self.actions_layout.setObjectName("actions_layout") + self.store_button = QtWidgets.QPushButton(self.actions) + self.store_button.setObjectName("store_button") + self.actions_layout.addWidget(self.store_button) + self.wishlist_button = QtWidgets.QPushButton(self.actions) self.wishlist_button.setObjectName("wishlist_button") - self.button_layout.addWidget(self.wishlist_button) - self.info_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.buttons_widget) - self.right_layout.addLayout(self.info_layout) + self.actions_layout.addWidget(self.wishlist_button) + self.details_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.actions) + self.price = QtWidgets.QWidget(DetailsWidget) + self.price.setObjectName("price") + self.price_layout = QtWidgets.QHBoxLayout(self.price) + self.price_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.price_layout.setContentsMargins(0, 0, 0, 0) + self.price_layout.setObjectName("price_layout") + self.original_price = QtWidgets.QLabel(self.price) + self.original_price.setText("orignal") + self.original_price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.original_price.setObjectName("original_price") + self.price_layout.addWidget(self.original_price) + self.discount_price = QtWidgets.QLabel(self.price) + self.discount_price.setText("discount") + self.discount_price.setObjectName("discount_price") + self.price_layout.addWidget(self.discount_price) + self.details_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.price) + self.right_layout.addLayout(self.details_layout) self.requirements_frame = QtWidgets.QFrame(DetailsWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -184,13 +195,14 @@ class Ui_DetailsWidget(object): _translate = QtCore.QCoreApplication.translate self.title_label.setText(_translate("DetailsWidget", "Title")) self.developer_label.setText(_translate("DetailsWidget", "Developer")) + self.publisher_label.setText(_translate("DetailsWidget", "Publisher")) self.status_label.setText(_translate("DetailsWidget", "Status")) - self.owned_label.setText(_translate("DetailsWidget", "You already own this game")) + self.status.setText(_translate("DetailsWidget", "You already own this game")) self.price_label.setText(_translate("DetailsWidget", "Price")) self.tags_label.setText(_translate("DetailsWidget", "Tags")) - self.links_label.setText(_translate("DetailsWidget", "Links")) + self.social_links_label.setText(_translate("DetailsWidget", "Links")) self.actions_label.setText(_translate("DetailsWidget", "Actions")) - self.open_store_button.setText(_translate("DetailsWidget", "Buy in Epic Games Store")) + self.store_button.setText(_translate("DetailsWidget", "Buy in Epic Games Store")) self.wishlist_button.setText(_translate("DetailsWidget", "Add to wishlist")) diff --git a/rare/ui/components/tabs/store/details.ui b/rare/ui/components/tabs/store/details.ui index b78efa4c..0ccbdef5 100644 --- a/rare/ui/components/tabs/store/details.ui +++ b/rare/ui/components/tabs/store/details.ui @@ -6,8 +6,8 @@ 0 0 - 702 - 414 + 630 + 371 @@ -43,10 +43,13 @@ - + QLayout::SetFixedSize + + QFormLayout::FieldsStayAtSizeHint + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -111,9 +114,9 @@ - + - dev + developer Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse @@ -121,6 +124,32 @@ + + + + 75 + true + + + + Publisher + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + publisher + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + @@ -136,14 +165,14 @@ - - + + You already own this game - + @@ -159,45 +188,7 @@ - - - - - QLayout::SetFixedSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - price - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - discount - - - - - - - + @@ -213,15 +204,15 @@ - + tags - - + + 75 @@ -236,34 +227,7 @@ - - - - - 250 - 0 - - - - - QLayout::SetFixedSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - + @@ -280,9 +244,30 @@ - + + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + - + 0 0 @@ -293,13 +278,39 @@ 0 - - - 250 - 16777215 - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Buy in Epic Games Store + + + + + + + Add to wishlist + + + + + + + + + QLayout::SetFixedSize @@ -316,16 +327,19 @@ 0 - + - Buy in Epic Games Store + orignal + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - + - Add to wishlist + discount diff --git a/requirements-dev.txt b/requirements-dev.txt index d2647125..1435147b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,4 +7,4 @@ nuitka ordered-set PyQt5-stubs qstylizer - +graphql-query From 1fab13fd920c52a85bf66f5d0cc8882e53b133c6 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:39:33 +0200 Subject: [PATCH 17/21] Store: Fix rebase errors --- rare/components/tabs/store/landing.py | 13 +++++++------ rare/components/tabs/store/widgets/details.py | 8 ++++---- rare/components/tabs/store/widgets/items.py | 2 +- rare/components/tabs/store/wishlist.py | 6 +++--- rare/resources/resources.py | Bin 3601126 -> 3601126 bytes rare/resources/static_css/__init__.py | Bin 4331 -> 4475 bytes rare/resources/static_css/stylesheet.qss | 5 +++++ .../stylesheets/ChildOfMetropolis/__init__.py | Bin 26680 -> 26680 bytes .../stylesheets/RareStyle/__init__.py | Bin 26642 -> 26642 bytes 9 files changed, 20 insertions(+), 14 deletions(-) diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py index a8582697..75821b23 100644 --- a/rare/components/tabs/store/landing.py +++ b/rare/components/tabs/store/landing.py @@ -142,13 +142,14 @@ class LandingWidget(QWidget, SideTabContents): except KeyError as e: logger.warning(str(e)) - 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 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) + if start_date > date: + free_next.append(item) # free games now self.free_games_now.setVisible(bool(free_now)) diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index 3051845c..7b83bacf 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -17,7 +17,7 @@ from rare.components.tabs.store.api.models.response import CatalogOfferModel from rare.components.tabs.store.store_api import StoreAPI from rare.models.image import ImageSize from rare.ui.components.tabs.store.details import Ui_DetailsWidget -from rare.utils.misc import icon +from rare.utils.misc import qta_icon from rare.widgets.elide_label import ElideLabel from rare.widgets.side_tab import SideTabWidget, SideTabContents from .image import LoadingImageWidget @@ -56,7 +56,7 @@ class DetailsWidget(QWidget, SideTabContents): self.requirements_tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ui.requirements_layout.addWidget(self.requirements_tabs) - self.ui.back_button.setIcon(icon("fa.chevron-left")) + self.ui.back_button.setIcon(qta_icon("fa.chevron-left")) self.ui.back_button.clicked.connect(self.back_clicked) self.setDisabled(False) @@ -207,10 +207,10 @@ class DetailsWidget(QWidget, SideTabContents): continue name = name.replace("link", "").lower() if name == "homepage": - icn = icon("mdi.web", "fa.search", scale_factor=1.5) + icn = qta_icon("mdi.web", "fa.search", scale_factor=1.5) else: try: - icn = icon(f"mdi.{name}", f"fa.{name}", scale_factor=1.5) + icn = qta_icon(f"mdi.{name}", f"fa.{name}", scale_factor=1.5) except Exception as e: logger.error(str(e)) continue diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py index 86e61f5e..d89b47f1 100644 --- a/rare/components/tabs/store/widgets/items.py +++ b/rare/components/tabs/store/widgets/items.py @@ -132,7 +132,7 @@ class WishlistItemWidget(ItemWidget): ) self.delete_button = QPushButton(self) - self.delete_button.setIcon(icon("mdi.delete", color="white")) + self.delete_button.setIcon(qta_icon("mdi.delete", color="white")) self.delete_button.clicked.connect( lambda: self.delete_from_wishlist.emit(self.catalog_game) ) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 2eb173a9..a469a978 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -2,10 +2,10 @@ from typing import List from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot from PyQt5.QtGui import QShowEvent -from PyQt5.QtWidgets import QMessageBox, QWidget, QScrollArea, QFrame, QSizePolicy +from PyQt5.QtWidgets import QMessageBox, QWidget, QSizePolicy from rare.ui.components.tabs.store.wishlist import Ui_Wishlist -from rare.utils.misc import icon +from rare.utils.misc import qta_icon from rare.widgets.flow_layout import FlowLayout from rare.widgets.side_tab import SideTabContents from rare.widgets.sliding_stack import SlidingStackedWidget @@ -64,7 +64,7 @@ class WishlistWidget(QWidget, SideTabContents): self.ui.sort_cb.currentIndexChanged.connect(self.sort_wishlist) self.ui.filter_cb.currentIndexChanged.connect(self.set_filter) self.ui.reload_button.clicked.connect(self.update_wishlist) - self.ui.reload_button.setIcon(icon("fa.refresh", color="white")) + self.ui.reload_button.setIcon(qta_icon("fa.refresh", color="white")) self.ui.reverse.stateChanged.connect( lambda: self.sort_wishlist(sort=self.ui.sort_cb.currentIndex()) diff --git a/rare/resources/resources.py b/rare/resources/resources.py index a45a4bb1698992e1f2cc07c09ae1c162c07deb7c..f3fc0f5ba66275ce94e32ef767f9075c99f8e53c 100644 GIT binary patch delta 139 zcmWN=$qj-)06@{8qPU(wD2!GI~D~Da;=?Y%s6@ delta 139 zcmWN=Q8EGn007WgT7<0#5mBL1h_$;p%>H$WKKpn_aRD>UbeO)|zwXe!nOAH#K~Y`` z95_@FI&$np)u}V*A~kgl7cMnjxpt%F)}6L{4>}$_>FViw_TtsR(8!yyiFY49O?~+m a`;kaj+21bu%@@h69OsL9y#C*1X}AY3M=&7( diff --git a/rare/resources/static_css/__init__.py b/rare/resources/static_css/__init__.py index 0e139851eb4be14a74e8bc96bce0efb5fa4a8a78..2e4d9ccf0ad9f3d200bd21caf4da863eec19bb3e 100644 GIT binary patch literal 4475 zcmcJTO>Yxd6o&Wy6$30tDv0c{XKbUgK&e!*K?U_=ff1UIJK!p@!5&ic@B2K*c1Q?S z#R8s~sy?+@{rO4u`}_1o@qB&xVR?}@S-0L~zkPn$7um_nKD#Q;M#Wiv8i)US_Ugs+ z7r)-io?TpI&HHNgE^V{Rs!iE)mG$qJ+pGg3`)A2^owAGdw!b$Rber|%0daZxVZG_I zm;L#AlLmv2{oBof=-amHtL$l3-(L(CyW#M#^2M&1A0$V4%^vdUY_Xd+i(OqTc16i* z7rRNf*tP7BIX{}9gRDVTv5uI4eEr|p)->SNHcV1RWM0l zvs!KNltV%zheOTjDe|OUkf*%Mt*$sd2eA`j%&9b8>`E|8q%Gd$*e;RR$mLM*K4vBM zXP|{A?zbX%ddgda-3ENMMx2^sXTn~NT$CZJV(CSTYy@I86=9CeAyJv|EZNOjHA`Pd z$cJdo<`WOGcGv9vmWSXqsbvZLr_LqmnstvkhCLwky%`bReTkJmAov zmC$N9G4!`=@n|el*vrrA8R;#2th^3B5#h+ii2RrO%jv1P;9Yw&WJXYX4Kky8tl24Y z>lS`+P&3yS%N&$wYhxr^kft(M#=iV&xO6;Atbq(dR1PPHoHD-DS=54A9LtAyGef)L2njFfH-1;B?MX z-hfP$L88tU}Fxtlg0fZ@795HqY;@Wbz~&j8G|_o(-`PMXQWsnI9)oPQ{+w>auO|*{+gTlr$jUMqJ&?_dU1tXxUGFum>?f@` zw+Fo=AU z$ExzQGt1aSEM|ego0HrrXID7~%$#%3D963Q-UOT8PR>g+U+mwFgRJxqiCOjj_Fnep zvx?1$_l33U#EBaZ9!?ozq8DgKm*}s*L5uqVw3uYrw~Cw@ei1lFj8)u(abHxt#q`9c zcB2;1nqtpRGmfu@+{f1rJI;gXEI2v+tkt-y;5BB=k|m?LJJLFF(i&;sosZUtbu4e@8VF94xTBQfuiq@9 z+8a(i=apQnv{p`_#g`iBSUn(E>rQX0%Y5snUub+S5ANUoR$o<@>GrpJ)P(hAA2^ek zli(^>5_|;A>fw*7e)>oZJtH z8z-Xizis0-2TJKboXLNbGhb*PD&&sK{}-q`?%qL&4!J4)o`Bp)6Df*2kQ(=Ph)mKT=gecr}0F9(7Eu0uK>1+MCWt;? zE&JntENo9s_sB>8UZr=-ZJ#zrgfg!6o&Wt6$TcxBdB60P8>8WP)8cEK?9nP1wqIk*N{=0l*H|re?QN2pV)H{`54dH_dAO;aPIswNDFIMx#f`o3PtlZ|jh3s=q_sDHKMJ z*f`&YvI|X8eSVhw{xN(JJl|YituDegX*b*Cx6iM;JUMyYB{%t5nxAE-QTeYIZ(hB8 z_3MM=#l=NZe=OG@!Y;Win~<#5N%wKJOIje3e^zW)A-UM>x(B0CyWLz4h^x!1&9+Nk zcjudJ7>z!4@3#Zd_f6TA$@8RoxEw9}@p!nhWna#QmBV_u?2BgE&x>WhsF!_}FZ+D9 z?3;AiPXqUA*-y|5WH=3Eo3OwqwA2>8;= zqhYl|%4;G5NM18EjNXF%jJU`{YmKrok~5^l%<-$>YUD(|$3_v&DbKZ@@T{jsz5z95 zof?9}6t)3`IUdZoYTje+v9*AyRTlHbo;k{q^)9z|B7(Lu?9PoIJF({ARiR1E^)oak zNLL`svjn+@&kQdXK?I|i!$=M?op5QV5rO9lZF6IlQ$hPXd)DSYk||FGD-HPOUoW&* zfMN`yZR};pS;^EuKZi$QL}OLQF2R;nk|8-p%e4ZunOO$O>RGVX;;nbRHkw2oFQPQq zM%3(yFyoq{S$M~0!CpktBWxQ3IfYo01sPI%OdRY5cUq;;j?z3&SZ5;i6}D@5*~dBS z#0m}-nsjO3;EA;{1%GC2kT65-+~Z$H?r0TT>wHbVz9TOWNBX-H8W=i&#`ZQ%;lU%mTrP*_-BA)^YamEC-RpupaxC=egd%*S z46eYA^BShk$JqZMI%VZ%S6MIS$$4V_m_Tyi$*E$D?YW9~qvupjdAHhg-b>{`cV`4*KO^##@N8=KZb^16ToCNTJgEhE7%Kns@eK$kHSZmiBu@+-) ztQU}D|BAYw_DIEAV8{759wKWdosUM&dYOP|JhY^Jri+so4pwYQ46Ff9IZv1v544@~ z_J^61Pn^}P>zycY10mKqEN8Og@_exNtR&9}J1O>#=)-fI-#n8O5GF*&70)hX9_KOl z=sz5+dMAyivu8=cvo&hRF@->|ikTGg+%yjXDg9Q%_)I{ECo}IYcI|j)gK^nUd!u7E zty<4a?HNI5fcDQlk5jTHq)>(+<|v^|BL z2FQaT>!2jsM$vP^Ua&T-rkNI4Ggld0&8~eE^A>aA7l_zSdgI>81r77z`C#O%Y`qKm z?>lli^2{(eIE&-=lTlL(3G;5{YRlS`vAx^qKk|Ot$crc&2j27`+QZj^XR{__IZg4x z?v5c~v7-F?8socPdd%K_ zj^~dYhY1|+5Gh!Ft54;DSdiGUhcHQ>6?%v;|cf*4*0d6ly-#)uO0N<{~ zg1mkE9?f{<2(hn@SHCA9M`b~4Cdw-D{9EFQr_!dz1 zeL&f0)C7pWT(7#rANk$M=^pvu-<$AZwd=z6An*(SpHGhXKluj7yK7FByDR<&KOKyp XUxadfeRY7pXaA4&_Qx^!+V#=DJ&&LH diff --git a/rare/resources/static_css/stylesheet.qss b/rare/resources/static_css/stylesheet.qss index 051a419d..df10c32d 100644 --- a/rare/resources/static_css/stylesheet.qss +++ b/rare/resources/static_css/stylesheet.qss @@ -1,6 +1,11 @@ /* This file is auto-generated from "stylesheet.py". DO NOT EDIT!!! */ +QPushButton[flat="true"] { + border: 0px; + border-radius: 5px; + background-color: rgba(255, 255, 255, 5%); +} QLabel#InfoLabel { color: #999; font-style: italic; diff --git a/rare/resources/stylesheets/ChildOfMetropolis/__init__.py b/rare/resources/stylesheets/ChildOfMetropolis/__init__.py index effb5639b1fca9c2f44d9dbc8f891bdf0b68ea96..73407d65408148e9bfc00ec2a454221ed0bf19c9 100644 GIT binary patch delta 40 wcmdmSfpNzL#trL|c@twQQcPniER$m@EDR^#i+7(Km!djZBf)%gT*@tG0Ai*PkpKVy delta 40 wcmdmSfpNzL#trL|c`afpEK*`BjFMt1k`gE1i+7(Km!djZBf)%gT*@tG0As-ry#N3J diff --git a/rare/resources/stylesheets/RareStyle/__init__.py b/rare/resources/stylesheets/RareStyle/__init__.py index b987bee262da6f386d765ec9cc7a49f4a6adb1dc..dfc98d1937b496919b939f7c1988da284f1ea1b0 100644 GIT binary patch delta 32 ocmbPqfpO9W#tj|GyooUtDW)+MmdP;{7KW2|#Jg|4lWfKU0Nuw82LJ#7 delta 32 ocmbPqfpO9W#tj|GycRJPW(F}87M3v;$>x)H#Jg|4lWfKU0M<7Qu>b%7 From 766557924a72d799af30631a641dba19b556ea12 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 24 Feb 2024 13:48:56 +0200 Subject: [PATCH 18/21] Store: Unset autoFillBackground for some scrollareas --- rare/components/tabs/store/landing.py | 2 ++ rare/components/tabs/store/search.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py index 75821b23..7aa5263d 100644 --- a/rare/components/tabs/store/landing.py +++ b/rare/components/tabs/store/landing.py @@ -41,6 +41,8 @@ class LandingPage(SlidingStackedWidget, SideTabContents): 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 = DetailsWidget([], store_api, parent=self) self.details_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) diff --git a/rare/components/tabs/store/search.py b/rare/components/tabs/store/search.py index 56456b30..1e93b1e0 100644 --- a/rare/components/tabs/store/search.py +++ b/rare/components/tabs/store/search.py @@ -65,6 +65,9 @@ class SearchWidget(QWidget, SideTabContents): self.ui.setupUi(self) self.ui.main_layout.setContentsMargins(0, 0, 3, 0) + self.ui.filter_scrollarea.widget().setAutoFillBackground(False) + self.ui.filter_scrollarea.viewport().setAutoFillBackground(False) + self.store_api = store_api self.price = "" self.tags = [] @@ -75,7 +78,7 @@ class SearchWidget(QWidget, SideTabContents): self.next_search = "" self.wishlist: List = [] - self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games")) + self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search")) self.results_scrollarea = ResultsWidget(self.store_api, self) self.results_scrollarea.show_details.connect(self.show_details) From fb0d5bbe1003fa601819971b1be7251bfedde06f Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:30:35 +0200 Subject: [PATCH 19/21] Store: Fix various wishlist issues. * Use horizontal scrollarea for free games. Based on the same idea as WrapperSettings scrollarea. Both need some adjustments. * Remove debugging dialogs. Need a better way anyways to debug. --- rare/components/tabs/store/landing.py | 92 +++++++++--- rare/components/tabs/store/store_api.py | 11 -- rare/components/tabs/store/widgets/details.py | 3 - rare/components/tabs/store/widgets/items.py | 4 - rare/components/tabs/store/wishlist.py | 141 +++++++++++------- rare/ui/components/tabs/store/wishlist.py | 71 ++++----- rare/ui/components/tabs/store/wishlist.ui | 75 +++------- 7 files changed, 213 insertions(+), 184 deletions(-) diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py index 7aa5263d..00af2cf8 100644 --- a/rare/components/tabs/store/landing.py +++ b/rare/components/tabs/store/landing.py @@ -2,8 +2,8 @@ import datetime import logging from typing import List -from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal -from PyQt5.QtGui import QShowEvent, QHideEvent +from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QObject, QEvent +from PyQt5.QtGui import QShowEvent, QHideEvent, QResizeEvent from PyQt5.QtWidgets import ( QHBoxLayout, QWidget, @@ -63,6 +63,38 @@ class LandingPage(SlidingStackedWidget, SideTabContents): 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) @@ -76,10 +108,12 @@ class LandingWidget(QWidget, SideTabContents): self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.free_games_now = StoreGroup(self.tr("Free now"), layout=QHBoxLayout, parent=self) - self.free_games_now.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + 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.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + 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) @@ -87,10 +121,30 @@ class LandingWidget(QWidget, SideTabContents): 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(True) + self.games_group.setVisible(False) - layout.addWidget(self.free_games_now, alignment=Qt.AlignTop) - layout.addWidget(self.free_games_next, alignment=Qt.AlignTop) + 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)) @@ -113,12 +167,12 @@ class LandingWidget(QWidget, SideTabContents): self.discounts_group.layout().removeWidget(w) w.deleteLater() - for item in wishlist: - if item.offer.price.totalPrice.discount > 0: - w = StoreItemWidget(self.api.cached_manager, item.offer) - w.show_details.connect(self.show_details) - self.discounts_group.layout().addWidget(w) - self.discounts_group.setVisible(bool(wishlist)) + 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]): @@ -171,13 +225,15 @@ class LandingWidget(QWidget, SideTabContents): 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() - if data: - for game in data: - w = StoreItemWidget(self.api.cached_manager, game) - w.show_details.connect(self.show_details) - self.games_group.layout().addWidget(w) + 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) diff --git a/rare/components/tabs/store/store_api.py b/rare/components/tabs/store/store_api.py index 0f45d8e8..4c8dfdc1 100644 --- a/rare/components/tabs/store/store_api.py +++ b/rare/components/tabs/store/store_api.py @@ -4,7 +4,6 @@ from typing import List, Callable from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QApplication -from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.constants import ( wishlist_query, search_query, @@ -154,13 +153,9 @@ class StoreAPI(QObject): "query": search_query, "variables": browse_model.to_dict() } - # debug = DebugDialog(payload["variables"], None) - # debug.exec() self.manager.post(graphql_url, lambda data: self.__handle_browse_games(data, handle_func), payload) def __handle_browse_games(self, data, handle_func): - # debug = DebugDialog(data, None) - # debug.exec() self.browse_active = False if data is None: data = {} @@ -204,8 +199,6 @@ class StoreAPI(QObject): @staticmethod def __handle_get_game(data, handle_func): - # debug = DebugDialog(data, None) - # debug.exec() try: product = DieselProduct.from_dict(data) handle_func(product) @@ -229,8 +222,6 @@ class StoreAPI(QObject): self.authed_manager.post(graphql_url, lambda data: self._handle_add_to_wishlist(data, handle_func), payload) def _handle_add_to_wishlist(self, data, handle_func): - # debug = DebugDialog(data, None) - # debug.exec() try: response = ResponseModel.from_dict(data) data = response.data.wishlist.addToWishlist @@ -255,8 +246,6 @@ class StoreAPI(QObject): payload) def _handle_remove_from_wishlist(self, data, handle_func): - # debug = DebugDialog(data, None) - # debug.exec() try: response = ResponseModel.from_dict(data) data = response.data.wishlist.removeFromWishlist diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py index 7b83bacf..309856bc 100644 --- a/rare/components/tabs/store/widgets/details.py +++ b/rare/components/tabs/store/widgets/details.py @@ -11,7 +11,6 @@ from PyQt5.QtWidgets import ( QSizePolicy, ) -from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.api.models.diesel import DieselProduct, DieselProductDetail, DieselSystemDetail from rare.components.tabs.store.api.models.response import CatalogOfferModel from rare.components.tabs.store.store_api import StoreAPI @@ -72,8 +71,6 @@ class DetailsWidget(QWidget, SideTabContents): self.in_wishlist = False def update_game(self, offer: CatalogOfferModel): - debug = DebugDialog(offer.__dict__, None) - debug.exec() self.ui.title.setText(offer.title) self.title_str = offer.title self.id_str = offer.id diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py index d89b47f1..94a022ea 100644 --- a/rare/components/tabs/store/widgets/items.py +++ b/rare/components/tabs/store/widgets/items.py @@ -4,7 +4,6 @@ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QPushButton -from rare.components.tabs.store.api.debug import DebugDialog from rare.components.tabs.store.api.models.response import CatalogOfferModel from rare.models.image import ImageSize from rare.utils.misc import qta_icon @@ -27,9 +26,6 @@ class ItemWidget(LoadingImageWidget): self.show_details.emit(self.catalog_game) if a0.button() == Qt.RightButton: a0.accept() - print(self.catalog_game.__dict__) - dialog = DebugDialog(self.catalog_game.__dict__, self) - dialog.show() class StoreItemWidget(ItemWidget): diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index a469a978..04c12868 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -1,3 +1,4 @@ +from enum import IntEnum from typing import List from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot @@ -44,6 +45,18 @@ class WishlistPage(SlidingStackedWidget, SideTabContents): self.slideInWidget(self.details_widget) +class WishlistOrder(IntEnum): + NAME = 1 + PRICE = 2 + DISCOUNT = 3 + DEVELOPER = 4 + + +class WishlistFilter(IntEnum): + NONE = 0 + DISCOUNT = 1 + + class WishlistWidget(QWidget, SideTabContents): show_details = pyqtSignal(CatalogOfferModel) update_wishlist_signal = pyqtSignal() @@ -55,21 +68,37 @@ class WishlistWidget(QWidget, SideTabContents): self.ui = Ui_Wishlist() self.ui.setupUi(self) self.ui.main_layout.setContentsMargins(0, 0, 3, 0) - self.setEnabled(False) - self.wishlist = [] - self.widgets = [] - self.list_layout = FlowLayout(self.ui.list_container) + self.wishlist_layout = FlowLayout() + self.ui.container_layout.addLayout(self.wishlist_layout, stretch=1) + + sortings = { + WishlistOrder.NAME: self.tr("Name"), + WishlistOrder.PRICE: self.tr("Price"), + WishlistOrder.DISCOUNT: self.tr("Discount"), + WishlistOrder.DEVELOPER: self.tr("Developer"), + } + for data, text in sortings.items(): + self.ui.order_combo.addItem(text, data) + self.ui.order_combo.currentIndexChanged.connect(self.order_wishlist) + + filters = { + WishlistFilter.NONE: self.tr("None"), + WishlistFilter.DISCOUNT: self.tr("Discount"), + } + for data, text in filters.items(): + self.ui.filter_combo.addItem(text, data) + self.ui.filter_combo.currentIndexChanged.connect(self.filter_wishlist) - self.ui.sort_cb.currentIndexChanged.connect(self.sort_wishlist) - self.ui.filter_cb.currentIndexChanged.connect(self.set_filter) - self.ui.reload_button.clicked.connect(self.update_wishlist) self.ui.reload_button.setIcon(qta_icon("fa.refresh", color="white")) + self.ui.reload_button.clicked.connect(self.update_wishlist) - self.ui.reverse.stateChanged.connect( - lambda: self.sort_wishlist(sort=self.ui.sort_cb.currentIndex()) + self.ui.reverse_check.stateChanged.connect( + lambda: self.order_wishlist(self.ui.order_combo.currentIndex()) ) + self.setEnabled(False) + def showEvent(self, a0: QShowEvent) -> None: self.update_wishlist() return super().showEvent(a0) @@ -90,70 +119,68 @@ class WishlistWidget(QWidget, SideTabContents): ) self.update_wishlist_signal.emit() - def set_filter(self, i): - count = 0 - for w in self.widgets: - if i == 1 and not w.discount: - w.setVisible(False) + @pyqtSlot(int) + def filter_wishlist(self, index: int = int(WishlistFilter.NONE)): + list_filter = self.ui.filter_combo.itemData(index, Qt.UserRole) + widgets = self.ui.container.findChildren(WishlistItemWidget, options=Qt.FindDirectChildrenOnly) + for w in widgets: + if list_filter == WishlistFilter.NONE: + w.setVisible(True) + elif list_filter == WishlistFilter.DISCOUNT: + w.setVisible(bool(w.catalog_game.price.totalPrice.discount)) else: w.setVisible(True) - count += 1 + have_visible = any(map(lambda x: x.isVisible(), widgets)) + self.ui.no_games_label.setVisible(not have_visible) - if i == 0: - w.setVisible(True) - - if count == 0: - self.ui.no_games_label.setVisible(True) - else: - self.ui.no_games_label.setVisible(False) - - def sort_wishlist(self, sort=0): - widgets = self.ui.list_container.findChildren(WishlistItemWidget, options=Qt.FindDirectChildrenOnly) + @pyqtSlot(int) + def order_wishlist(self, index: int = int(WishlistOrder.NAME)): + list_order = self.ui.order_combo.itemData(index, Qt.UserRole) + widgets = self.ui.container.findChildren(WishlistItemWidget, options=Qt.FindDirectChildrenOnly) for w in widgets: - self.ui.list_container.layout().removeWidget(w) + self.wishlist_layout.removeWidget(w) - if sort == 0: - func = lambda x: x.catalog_game.title - reverse = self.ui.reverse.isChecked() - elif sort == 1: - func = lambda x: x.catalog_game.price.totalPrice["fmtPrice"]["discountPrice"] - reverse = self.ui.reverse.isChecked() - elif sort == 2: - func = lambda x: x.catalog_game.seller["name"] - reverse = self.ui.reverse.isChecked() - elif sort == 3: - func = lambda x: 1 - (x.catalog_game.price.totalPrice["discountPrice"] / x.catalog_game.price.totalPrice["originalPrice"]) - reverse = not self.ui.reverse.isChecked() + if list_order == WishlistOrder.NAME: + def func(x: WishlistItemWidget): + return x.catalog_game.title + elif list_order == WishlistOrder.PRICE: + def func(x: WishlistItemWidget): + return x.catalog_game.price.totalPrice.discountPrice + elif list_order == WishlistOrder.DEVELOPER: + def func(x: WishlistItemWidget): + return x.catalog_game.seller["name"] + elif list_order == WishlistOrder.DISCOUNT: + def func(x: WishlistItemWidget): + discount = x.catalog_game.price.totalPrice.discountPrice + original = x.catalog_game.price.totalPrice.originalPrice + return 1 - (discount / original) else: - func = lambda x: x.catalog_game.title - reverse = self.ui.reverse.isChecked() + def func(x: WishlistItemWidget): + return x.catalog_game.title + reverse = self.ui.reverse_check.isChecked() widgets = sorted(widgets, key=func, reverse=reverse) for w in widgets: - self.ui.list_container.layout().addWidget(w) + self.wishlist_layout.addWidget(w) - def set_wishlist(self, wishlist: List[WishlistItemModel] = None, sort=0): + def set_wishlist(self, wishlist: List[WishlistItemModel] = None): if wishlist and wishlist[0] == "error": return - if wishlist is not None: - self.wishlist = wishlist + widgets = self.ui.container.findChildren(WishlistItemWidget, options=Qt.FindDirectChildrenOnly) + for w in widgets: + self.wishlist_layout.removeWidget(w) + w.deleteLater() - for i in self.widgets: - i.deleteLater() - - self.widgets.clear() - - if len(wishlist) == 0: - self.ui.no_games_label.setVisible(True) - else: - self.ui.no_games_label.setVisible(False) + self.ui.no_games_label.setVisible(bool(wishlist)) for game in wishlist: - w = WishlistItemWidget(self.api.cached_manager, game.offer, self.ui.list_container) + w = WishlistItemWidget(self.api.cached_manager, game.offer, self.ui.container) w.show_details.connect(self.show_details) w.delete_from_wishlist.connect(self.delete_from_wishlist) - self.widgets.append(w) - self.list_layout.addWidget(w) - self.list_layout.update() + self.wishlist_layout.addWidget(w) + + self.order_wishlist(self.ui.order_combo.currentIndex()) + self.filter_wishlist(self.ui.filter_combo.currentIndex()) + self.setEnabled(True) diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index 2bea5fa8..ff93d8d5 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -14,40 +14,34 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Wishlist(object): def setupUi(self, Wishlist): Wishlist.setObjectName("Wishlist") - Wishlist.resize(423, 153) + Wishlist.resize(489, 165) Wishlist.setWindowTitle("Wishlist") self.main_layout = QtWidgets.QVBoxLayout(Wishlist) self.main_layout.setObjectName("main_layout") self.tool_layout = QtWidgets.QHBoxLayout() self.tool_layout.setObjectName("tool_layout") - self.sort_label = QtWidgets.QLabel(Wishlist) + self.order_label = QtWidgets.QLabel(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sort_label.sizePolicy().hasHeightForWidth()) - self.sort_label.setSizePolicy(sizePolicy) - self.sort_label.setObjectName("sort_label") - self.tool_layout.addWidget(self.sort_label) - self.sort_cb = QtWidgets.QComboBox(Wishlist) - self.sort_cb.setObjectName("sort_cb") - self.sort_cb.addItem("") - self.sort_cb.addItem("") - self.sort_cb.addItem("") - self.sort_cb.addItem("") - self.tool_layout.addWidget(self.sort_cb) - self.reverse = QtWidgets.QCheckBox(Wishlist) - self.reverse.setObjectName("reverse") - self.tool_layout.addWidget(self.reverse) + sizePolicy.setHeightForWidth(self.order_label.sizePolicy().hasHeightForWidth()) + self.order_label.setSizePolicy(sizePolicy) + self.order_label.setObjectName("order_label") + self.tool_layout.addWidget(self.order_label) + self.order_combo = QtWidgets.QComboBox(Wishlist) + self.order_combo.setObjectName("order_combo") + self.tool_layout.addWidget(self.order_combo) + self.reverse_check = QtWidgets.QCheckBox(Wishlist) + self.reverse_check.setObjectName("reverse_check") + self.tool_layout.addWidget(self.reverse_check) spacerItem = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.tool_layout.addItem(spacerItem) self.filter_label = QtWidgets.QLabel(Wishlist) self.filter_label.setObjectName("filter_label") self.tool_layout.addWidget(self.filter_label) - self.filter_cb = QtWidgets.QComboBox(Wishlist) - self.filter_cb.setObjectName("filter_cb") - self.filter_cb.addItem("") - self.filter_cb.addItem("") - self.tool_layout.addWidget(self.filter_cb) + self.filter_combo = QtWidgets.QComboBox(Wishlist) + self.filter_combo.setObjectName("filter_combo") + self.tool_layout.addWidget(self.filter_combo) spacerItem1 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.tool_layout.addItem(spacerItem1) self.reload_button = QtWidgets.QPushButton(Wishlist) @@ -60,31 +54,28 @@ class Ui_Wishlist(object): self.reload_button.setObjectName("reload_button") self.tool_layout.addWidget(self.reload_button) self.main_layout.addLayout(self.tool_layout) - self.no_games_label = QtWidgets.QLabel(Wishlist) + self.scrollarea = QtWidgets.QScrollArea(Wishlist) + self.scrollarea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollarea.setWidgetResizable(True) + self.scrollarea.setObjectName("scrollarea") + self.container = QtWidgets.QWidget() + self.container.setGeometry(QtCore.QRect(0, 0, 473, 115)) + self.container.setObjectName("container") + self.container_layout = QtWidgets.QVBoxLayout(self.container) + self.container_layout.setObjectName("container_layout") + self.no_games_label = QtWidgets.QLabel(self.container) self.no_games_label.setObjectName("no_games_label") - self.main_layout.addWidget(self.no_games_label) - self.list_scrollarea = QtWidgets.QScrollArea(Wishlist) - self.list_scrollarea.setWidgetResizable(True) - self.list_scrollarea.setObjectName("list_scrollarea") - self.list_container = QtWidgets.QWidget() - self.list_container.setGeometry(QtCore.QRect(0, 0, 407, 83)) - self.list_container.setObjectName("list_container") - self.list_scrollarea.setWidget(self.list_container) - self.main_layout.addWidget(self.list_scrollarea) + self.container_layout.addWidget(self.no_games_label, 0, QtCore.Qt.AlignTop) + self.scrollarea.setWidget(self.container) + self.main_layout.addWidget(self.scrollarea) self.retranslateUi(Wishlist) def retranslateUi(self, Wishlist): _translate = QtCore.QCoreApplication.translate - self.sort_label.setText(_translate("Wishlist", "Sort by")) - self.sort_cb.setItemText(0, _translate("Wishlist", "Name")) - self.sort_cb.setItemText(1, _translate("Wishlist", "Price")) - self.sort_cb.setItemText(2, _translate("Wishlist", "Developer")) - self.sort_cb.setItemText(3, _translate("Wishlist", "Discount")) - self.reverse.setText(_translate("Wishlist", "Reverse")) - self.filter_label.setText(_translate("Wishlist", "Filter:")) - self.filter_cb.setItemText(0, _translate("Wishlist", "None")) - self.filter_cb.setItemText(1, _translate("Wishlist", "Discount")) + self.order_label.setText(_translate("Wishlist", "Sort by")) + self.reverse_check.setText(_translate("Wishlist", "Reverse")) + self.filter_label.setText(_translate("Wishlist", "Filter by")) self.no_games_label.setText(_translate("Wishlist", "No games matching your filter")) diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index 1d2fe6bd..2a9e064e 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -6,8 +6,8 @@ 0 0 - 423 - 153 + 489 + 165 @@ -17,7 +17,7 @@ - + 0 @@ -30,38 +30,17 @@ - - - - Name - - - - - Price - - - - - Developer - - - - - Discount - - - + - + Reverse - + Qt::Horizontal @@ -76,26 +55,15 @@ - Filter: + Filter by - - - - None - - - - - Discount - - - + - + Qt::Horizontal @@ -123,26 +91,31 @@ - - - No games matching your filter + + + QAbstractScrollArea::AdjustToContents - - - - true - + 0 0 - 407 - 83 + 473 + 115 + + + + + No games matching your filter + + + + From 89340f331b81fed246b9c37c5884abf28de4d18f Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:37:51 +0200 Subject: [PATCH 20/21] StoreAPI: comment unused and erroneous code --- rare/components/tabs/store/store_api.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rare/components/tabs/store/store_api.py b/rare/components/tabs/store/store_api.py index 4c8dfdc1..bfdff865 100644 --- a/rare/components/tabs/store/store_api.py +++ b/rare/components/tabs/store/store_api.py @@ -178,13 +178,13 @@ class StoreAPI(QObject): self.browse_games(*self.next_browse_request) # pylint: disable=E1120 self.next_browse_request = tuple(()) - def get_game_config_graphql(self, namespace: str, handle_func): - payload = { - "query": config_query, - "variables": { - "namespace": namespace - } - } + # def get_game_config_graphql(self, namespace: str, handle_func): + # payload = { + # "query": config_query, + # "variables": { + # "namespace": namespace + # } + # } def __make_graphql_query(self): pass From 14bde0a23c7c882d6002b981be4b0f1fbbc9c8b3 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:00:40 +0200 Subject: [PATCH 21/21] WishlistWidget: re-order comboboxes to match the order in the library * Move filter combobox at the beginning of the top panel and move the order combobox after it to replicate the order in the library view. --- rare/components/tabs/store/wishlist.py | 16 +++++------ rare/ui/components/tabs/store/wishlist.py | 21 ++------------ rare/ui/components/tabs/store/wishlist.ui | 35 +---------------------- 3 files changed, 12 insertions(+), 60 deletions(-) diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py index 04c12868..c2f1bd06 100644 --- a/rare/components/tabs/store/wishlist.py +++ b/rare/components/tabs/store/wishlist.py @@ -72,6 +72,14 @@ class WishlistWidget(QWidget, SideTabContents): self.wishlist_layout = FlowLayout() self.ui.container_layout.addLayout(self.wishlist_layout, stretch=1) + filters = { + WishlistFilter.NONE: self.tr("All items"), + WishlistFilter.DISCOUNT: self.tr("Discount"), + } + for data, text in filters.items(): + self.ui.filter_combo.addItem(text, data) + self.ui.filter_combo.currentIndexChanged.connect(self.filter_wishlist) + sortings = { WishlistOrder.NAME: self.tr("Name"), WishlistOrder.PRICE: self.tr("Price"), @@ -82,14 +90,6 @@ class WishlistWidget(QWidget, SideTabContents): self.ui.order_combo.addItem(text, data) self.ui.order_combo.currentIndexChanged.connect(self.order_wishlist) - filters = { - WishlistFilter.NONE: self.tr("None"), - WishlistFilter.DISCOUNT: self.tr("Discount"), - } - for data, text in filters.items(): - self.ui.filter_combo.addItem(text, data) - self.ui.filter_combo.currentIndexChanged.connect(self.filter_wishlist) - self.ui.reload_button.setIcon(qta_icon("fa.refresh", color="white")) self.ui.reload_button.clicked.connect(self.update_wishlist) diff --git a/rare/ui/components/tabs/store/wishlist.py b/rare/ui/components/tabs/store/wishlist.py index ff93d8d5..a34293d8 100644 --- a/rare/ui/components/tabs/store/wishlist.py +++ b/rare/ui/components/tabs/store/wishlist.py @@ -20,14 +20,9 @@ class Ui_Wishlist(object): self.main_layout.setObjectName("main_layout") self.tool_layout = QtWidgets.QHBoxLayout() self.tool_layout.setObjectName("tool_layout") - self.order_label = QtWidgets.QLabel(Wishlist) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.order_label.sizePolicy().hasHeightForWidth()) - self.order_label.setSizePolicy(sizePolicy) - self.order_label.setObjectName("order_label") - self.tool_layout.addWidget(self.order_label) + self.filter_combo = QtWidgets.QComboBox(Wishlist) + self.filter_combo.setObjectName("filter_combo") + self.tool_layout.addWidget(self.filter_combo) self.order_combo = QtWidgets.QComboBox(Wishlist) self.order_combo.setObjectName("order_combo") self.tool_layout.addWidget(self.order_combo) @@ -36,14 +31,6 @@ class Ui_Wishlist(object): self.tool_layout.addWidget(self.reverse_check) spacerItem = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.tool_layout.addItem(spacerItem) - self.filter_label = QtWidgets.QLabel(Wishlist) - self.filter_label.setObjectName("filter_label") - self.tool_layout.addWidget(self.filter_label) - self.filter_combo = QtWidgets.QComboBox(Wishlist) - self.filter_combo.setObjectName("filter_combo") - self.tool_layout.addWidget(self.filter_combo) - spacerItem1 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.tool_layout.addItem(spacerItem1) self.reload_button = QtWidgets.QPushButton(Wishlist) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -73,9 +60,7 @@ class Ui_Wishlist(object): def retranslateUi(self, Wishlist): _translate = QtCore.QCoreApplication.translate - self.order_label.setText(_translate("Wishlist", "Sort by")) self.reverse_check.setText(_translate("Wishlist", "Reverse")) - self.filter_label.setText(_translate("Wishlist", "Filter by")) self.no_games_label.setText(_translate("Wishlist", "No games matching your filter")) diff --git a/rare/ui/components/tabs/store/wishlist.ui b/rare/ui/components/tabs/store/wishlist.ui index 2a9e064e..d101e023 100644 --- a/rare/ui/components/tabs/store/wishlist.ui +++ b/rare/ui/components/tabs/store/wishlist.ui @@ -17,17 +17,7 @@ - - - - 0 - 0 - - - - Sort by - - + @@ -52,29 +42,6 @@ - - - - Filter by - - - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - -