diff --git a/custom_legendary/models/game.py b/custom_legendary/models/game.py index 16e2d790..b2ea5682 100644 --- a/custom_legendary/models/game.py +++ b/custom_legendary/models/game.py @@ -144,3 +144,39 @@ class VerifyResult(Enum): HASH_MISMATCH = 1 FILE_MISSING = 2 OTHER_ERROR = 3 + + +x = {'title': 'Frostpunk', + 'id': 'b43c1e1e0ca14b6784b323c59c751136', 'namespace': 'd5241c76f178492ea1540fce45616757', + 'description': 'Frostpunk', 'effectiveDate': '2099-01-01T00:00:00.000Z', 'offerType': 'OTHERS', 'expiryDate': None, + 'status': 'ACTIVE', 'isCodeRedemptionOnly': True, 'keyImages': [{'type': 'VaultClosed', + 'url': 'https://cdn1.epicgames.com/d5241c76f178492ea1540fce45616757/offer/EpicVault_Clean_OPEN_V10_LightsON-1920x1080-75e6d0636a6083944570a1c6f94ead4f.png'}, + {'type': 'DieselStoreFrontWide', + 'url': 'https://cdn1.epicgames.com/salesEvent/salesEvent/EGS_Frostpunk_wide_2560x1440-ef2f4d458120af0839dde35b1a022828'}, + {'type': 'DieselStoreFrontTall', + 'url': 'https://cdn1.epicgames.com/salesEvent/salesEvent/EGS_Frostpunk_Tall_1200x1600-c71dc27cfe505c6c662c49011b36a0c5'}], + 'seller': {'id': 'o-ufmrk5furrrxgsp5tdngefzt5rxdcn', 'name': 'Epic Dev Test Account'}, 'productSlug': 'frostpunk', + 'urlSlug': 'free-games-06', 'url': None, + 'items': [{'id': '8341d7c7e4534db7848cc428aa4cbe5a', 'namespace': 'd5241c76f178492ea1540fce45616757'}], + 'customAttributes': [{'key': 'com.epicgames.app.freegames.vault.close', 'value': '[]'}, + {'key': 'com.epicgames.app.blacklist', 'value': '[]'}, + {'key': 'com.epicgames.app.freegames.vault.slug', + 'value': 'news/the-epic-mega-sale-returns-for-2021'}, + {'key': 'publisherName', 'value': '11 bit studios'}, {'key': 'dupe', 'value': '[]'}, + {'key': 'com.epicgames.app.freegames.vault.open', 'value': '[]'}, + {'key': 'developerName', 'value': '11 bit studios'}, + {'key': 'com.epicgames.app.productSlug', 'value': 'frostpunk'}], + 'categories': [{'path': 'freegames/vaulted'}, {'path': 'freegames'}, {'path': 'games'}, {'path': 'applications'}], + 'tags': [], 'price': {'totalPrice': {'discountPrice': 0, 'originalPrice': 0, 'voucherDiscount': 0, 'discount': 0, + 'currencyCode': 'USD', 'currencyInfo': {'decimals': 2}, + 'fmtPrice': {'originalPrice': '0', 'discountPrice': '0', + 'intermediatePrice': '0'}}, + 'lineOffers': [{'appliedRules': []}]}, 'promotions': {'promotionalOffers': [], + 'upcomingPromotionalOffers': [{ + 'promotionalOffers': [ + { + 'startDate': '2021-06-03T15:00:00.000Z', + 'endDate': '2021-06-10T15:00:00.000Z', + 'discountSetting': { + 'discountType': 'PERCENTAGE', + 'discountPercentage': 0}}]}]}} diff --git a/rare/components/tab_widget.py b/rare/components/tab_widget.py index fe0213f2..566e08bf 100644 --- a/rare/components/tab_widget.py +++ b/rare/components/tab_widget.py @@ -13,6 +13,7 @@ from rare.components.tabs.cloud_saves import SyncSaves from rare.components.tabs.downloads import DownloadTab from rare.components.tabs.games import GameTab from rare.components.tabs.settings import SettingsTab +from rare.components.tabs.shop import Shop from rare.utils import legendary_utils from rare.utils.models import InstallQueueItemModel, InstallOptionsModel @@ -22,7 +23,7 @@ class TabWidget(QTabWidget): def __init__(self, core: LegendaryCore, parent, offline): super(TabWidget, self).__init__(parent=parent) - disabled_tab = 3 if not offline else 1 + disabled_tab = 4 if not offline else 1 self.core = core self.setTabBar(TabBar(disabled_tab)) @@ -38,6 +39,9 @@ class TabWidget(QTabWidget): self.cloud_saves = SyncSaves(core, self) self.addTab(self.cloud_saves, "Cloud Saves") + self.store = Shop() + self.addTab(self.store, self.tr("Store")) + self.settings = SettingsTab(core, self) # Space Tab @@ -92,9 +96,15 @@ class TabWidget(QTabWidget): self.games_tab.default_widget.game_list.game_exited.connect(self.game_finished) # Open game list on click on Games tab button - self.tabBarClicked.connect(lambda x: self.games_tab.layout.setCurrentIndex(0) if x == 0 else None) + self.tabBarClicked.connect(self.mouse_clicked) self.setIconSize(QSize(25, 25)) + def mouse_clicked(self, tab_num): + if tab_num == 0: + self.games_tab.layout.setCurrentIndex(0) + if tab_num == 3: + self.store.load() + def install_game(self, app_name, disable_path=False): install_dialog = InstallDialog(self.core, InstallQueueItemModel(options=InstallOptionsModel(app_name=app_name)), diff --git a/rare/components/tabs/games/game_widgets/installed_icon_widget.py b/rare/components/tabs/games/game_widgets/installed_icon_widget.py index e78e753b..a52e86f0 100644 --- a/rare/components/tabs/games/game_widgets/installed_icon_widget.py +++ b/rare/components/tabs/games/game_widgets/installed_icon_widget.py @@ -8,7 +8,6 @@ from qtawesome import icon from custom_legendary.core import LegendaryCore from custom_legendary.models.game import InstalledGame from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget -from rare.utils.extra_widgets import ClickableLabel logger = getLogger("GameWidgetInstalled") @@ -36,7 +35,7 @@ class GameWidgetInstalled(BaseInstalledWidget): if self.pixmap: w = 200 self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation) - self.image = ClickableLabel() + self.image = QLabel() self.image.setObjectName("game_widget") self.image.setPixmap(self.pixmap) self.layout.addWidget(self.image) diff --git a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py b/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py index afa5bbef..bdd8b14f 100644 --- a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py +++ b/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py @@ -5,7 +5,6 @@ from PyQt5.QtWidgets import QVBoxLayout, QLabel from custom_legendary.core import LegendaryCore from custom_legendary.models.game import Game from rare.components.tabs.games.game_widgets.base_uninstalled_widget import BaseUninstalledWidget -from rare.utils.extra_widgets import ClickableLabel logger = getLogger("Uninstalled") @@ -19,7 +18,7 @@ class IconWidgetUninstalled(BaseUninstalledWidget): if self.pixmap: w = 200 self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3)) - self.image = ClickableLabel() + self.image = QLabel() self.image.setPixmap(self.pixmap) self.layout.addWidget(self.image) diff --git a/rare/components/tabs/shop/__init__.py b/rare/components/tabs/shop/__init__.py new file mode 100644 index 00000000..1b883bd7 --- /dev/null +++ b/rare/components/tabs/shop/__init__.py @@ -0,0 +1,28 @@ +from PyQt5.QtWidgets import QStackedWidget, QWidget, QVBoxLayout, QLabel + +from rare.components.tabs.shop.shop_info import ShopGameInfo +from rare.components.tabs.shop.shop_widget import ShopWidget + + +class Shop(QStackedWidget): + init = False + def __init__(self): + super(Shop, self).__init__() + + self.shop = ShopWidget() + self.addWidget(self.shop) + + self.info = ShopGameInfo() + self.addWidget(self.info) + + self.shop.show_info.connect(self.show_info) + + def load(self): + if not self.init: + self.init = True + self.shop.load() + + + def show_info(self, slug): + self.info.update_game(slug) + self.setCurrentIndex(1) diff --git a/rare/components/tabs/shop/shop_info.py b/rare/components/tabs/shop/shop_info.py new file mode 100644 index 00000000..a331a643 --- /dev/null +++ b/rare/components/tabs/shop/shop_info.py @@ -0,0 +1,35 @@ +import os +import webbrowser + +from PyQt5.QtCore import QLocale, Qt +from PyQt5.QtGui import QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager +from PyQt5.QtWidgets import QWidget + +from rare.ui.components.tabs.store.shop_game_info import Ui_shop_info +from rare.utils import api_utils + + +class ShopGameInfo(QWidget, Ui_shop_info): + slug = "" + + def __init__(self): + super(ShopGameInfo, self).__init__() + self.setupUi(self) + self.pushButton.clicked.connect(self.button_clicked) + self.manager = QNetworkAccessManager() + + def update_game(self, slug: str): + locale = QLocale.system().name().split("_")[0] + game = api_utils.get_product(slug, locale) + self.slug = slug + self.title.setText(game[0]["productName"]) + self.image.setPixmap( + QPixmap(os.path.expanduser(f"~/.cache/rare/cache/{game[0]['productName']}.png")).scaled(180, + int(180 * 9 / 16), + transformMode=Qt.SmoothTransformation)) + + self.dev.setText(game[0]["data"]["meta"]["developer"][0]) + + def button_clicked(self): + webbrowser.open("https://www.epicgames.com/store/de/p/" + self.slug) diff --git a/rare/components/tabs/shop/shop_widget.py b/rare/components/tabs/shop/shop_widget.py new file mode 100644 index 00000000..efc0ce18 --- /dev/null +++ b/rare/components/tabs/shop/shop_widget.py @@ -0,0 +1,209 @@ +import datetime +import json +import logging +import os +from json import JSONDecodeError + +import requests +from PyQt5 import QtGui +from PyQt5.QtCore import Qt, pyqtSignal, QUrl, QByteArray, QJsonDocument, QJsonParseError, QObjectCleanupHandler, \ + QStringListModel +from PyQt5.QtGui import QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QCompleter + +from rare.ui.components.tabs.store.store import Ui_ShopWidget +from rare.utils.extra_widgets import WaitingSpinner +from rare.utils.utils import get_lang + + +class ShopWidget(QWidget, Ui_ShopWidget): + show_info = pyqtSignal(str) + free_game_widgets = [] + + def __init__(self): + super(ShopWidget, self).__init__() + self.setupUi(self) + self.search_results.setVisible(False) + self.manager = QNetworkAccessManager() + self.free_games_stack.addWidget(WaitingSpinner()) + self.free_games_stack.setCurrentIndex(1) + self.search.textChanged.connect(self.search_games) + self.completer = QCompleter() + self.completer.setCaseSensitivity(Qt.CaseInsensitive) + self.search.setCompleter(self.completer) + self.search.returnPressed.connect(self.show_game) + self.data = [] + + def load(self): + if not os.path.exists(p := os.path.expanduser(f"~/.cache/rare/cache/")): + os.makedirs(p) + url = "https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions" + self.free_game_request = self.manager.get(QNetworkRequest(QUrl(url))) + self.free_game_request.readyRead.connect(self.add_free_games) + self.free_game_request.finished.connect(self.free_game_request.deleteLater if self.free_game_request else None) + + # free_games = api_utils.get_free_games() + + def add_free_games(self): + if self.free_game_request: + if self.free_game_request.error() == QNetworkReply.NoError: + try: + free_games = json.loads(self.free_game_request.readAll().data().decode()) + except JSONDecodeError: + print(self.free_game_request.readAll().data().decode()) + return + else: + return + else: + return + free_games = free_games["data"]["Catalog"]["searchStore"]["elements"] + date = datetime.datetime.now() + free_games_now = [] + coming_free_games = [] + for game in free_games: + if game["title"] == "Mystery Game": + coming_free_games.append(game) + continue + try: + # parse datetime + end_date = datetime.datetime.strptime( + game["promotions"].get("promotionalOffers", game["promotions"].get("upcomingPromotionalOffers"))[0]["promotionalOffers"][0]["endDate"], + '%Y-%m-%dT%H:%M:%S.%fZ') + start_date = datetime.datetime.strptime( + game["promotions"].get("promotionalOffers", game["promotions"].get("upcomingPromotionalOffers"))[0][ + "promotionalOffers"][0]["startDate"], + '%Y-%m-%dT%H:%M:%S.%fZ') + except IndexError: + print("index error") + continue + except KeyError: + print("keyerror") + continue + except TypeError: + print("type error") + continue + if start_date < date < end_date: + free_games_now.append(game) + elif start_date > date: + coming_free_games.append(game) + + for free_game in free_games_now: + w = GameWidget(free_game) + w.show_info.connect(self.show_info) + self.free_game_now.layout().addWidget(w) + self.free_game_widgets.append(w) + self.free_game_group_box_2.setMinimumHeight(200) + + for free_game in coming_free_games: + w = GameWidget(free_game) + if free_game["title"] != "Mystery Game": + w.show_info.connect(self.show_info) + self.comming_free_game.layout().addWidget(w) + self.free_game_widgets.append(w) + self.free_games_stack.setCurrentIndex(0) + + def search_games(self, text): + if text == "": + self.search_results.setVisible(False) + else: + locale = get_lang() + payload = QByteArray() + payload.append( + "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") + + payload = json.dumps({ + "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", + "variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 10, + "country": "DE", "keywords": text, "locale": locale, "sortDir": "DESC", + "allowCountries": locale.upper(), + "start": 0, "tag": "", "withMapping": False, "withPrice": True} + }).encode() + request = QNetworkRequest(QUrl("https://www.epicgames.com/graphql")) + request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self.search_request = self.manager.post(request, payload) + # self.search_request = self.manager.post(QNetworkRequest(QUrl("https://www.epicgames.com/graphql")), payload) + self.search_request.readyRead.connect(self.show_search_results) + self.search_request.finished.connect( + self.search_request.deleteLater if self.search_request else None) + + def show_search_results(self): + if self.search_request: + if self.search_request.error() == QNetworkReply.NoError: + error = QJsonParseError() + json_data = QJsonDocument.fromJson(self.search_request.readAll().data(), error) + if QJsonParseError.NoError == error.error: + data = json.loads(json_data.toJson().data().decode())["data"]["Catalog"]["searchStore"]["elements"] + else: + logging.error(error.errorString()) + self.search_results.setVisible(False) + return + #response = .decode(encoding="utf-8") + #print(response) + #results = json.loads(response) + else: + return + else: + return + self.data = data + titles = [i.get("title") for i in data] + model = QStringListModel() + model.setStringList(titles) + self.completer.setModel(model) + self.completer.popup() + # self.search_results.setLayout(layout) + # self.search_results.setVisible(True) + + def show_game(self): + if self.data: + slug = self.data[0].get("productSlug") + self.show_info.emit(slug) + + +class SearchResultItem(QWidget): + def __init__(self, json_info): + super(SearchResultItem, self).__init__() + self.layout = QHBoxLayout() + self.title = QLabel(json_info.get("title", "undefined")) + self.layout.addWidget(self.title) + self.slug = json_info.get("productSlug", "undefined") + + self.setLayout(self.layout) + + +class GameWidget(QWidget): + show_info = pyqtSignal(str) + + def __init__(self, json_info): + super(GameWidget, self).__init__() + self.layout = QVBoxLayout() + self.image = QLabel() + self.slug = json_info["productSlug"] + if not os.path.exists(path := os.path.expanduser(f"~/.cache/rare/cache/{json_info['title']}.png")): + for img in json_info["keyImages"]: + if json_info["title"] != "Mystery Game": + if img["type"] == "DieselStoreFrontWide": + with open(path, "wb") as img_file: + content = requests.get(img["url"]).content + img_file.write(content) + break + else: + if img["type"] == "VaultClosed": + with open(path, "wb") as img_file: + content = requests.get(img["url"]).content + img_file.write(content) + break + else: + print("No image found") + width = 300 + self.image.setPixmap(QPixmap(os.path.expanduser(f"~/.cache/rare/cache/{json_info['title']}.png")) + .scaled(width, int(width * 9 / 16), transformMode=Qt.SmoothTransformation)) + self.layout.addWidget(self.image) + + self.title = QLabel(json_info["title"]) + self.layout.addWidget(self.title) + + self.setLayout(self.layout) + + def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: + self.show_info.emit(self.slug) diff --git a/rare/ui/components/tabs/store/shop_game_info.py b/rare/ui/components/tabs/store/shop_game_info.py new file mode 100644 index 00000000..d0acab66 --- /dev/null +++ b/rare/ui/components/tabs/store/shop_game_info.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'shop_game_info.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# 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_shop_info(object): + def setupUi(self, shop_info): + shop_info.setObjectName("shop_info") + shop_info.resize(702, 468) + self.gridLayout = QtWidgets.QGridLayout(shop_info) + self.gridLayout.setObjectName("gridLayout") + self.pushButton = QtWidgets.QPushButton(shop_info) + self.pushButton.setObjectName("pushButton") + self.gridLayout.addWidget(self.pushButton, 3, 1, 1, 1) + self.price = QtWidgets.QLabel(shop_info) + self.price.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.price.setObjectName("price") + self.gridLayout.addWidget(self.price, 1, 1, 1, 1) + self.title = QtWidgets.QLabel(shop_info) + self.title.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.title.setObjectName("title") + self.gridLayout.addWidget(self.title, 0, 1, 1, 1) + self.image = QtWidgets.QLabel(shop_info) + self.image.setObjectName("image") + self.gridLayout.addWidget(self.image, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 4, 1, 1, 1) + self.dev = QtWidgets.QLabel(shop_info) + self.dev.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) + self.dev.setObjectName("dev") + self.gridLayout.addWidget(self.dev, 2, 1, 1, 1) + + self.retranslateUi(shop_info) + QtCore.QMetaObject.connectSlotsByName(shop_info) + + def retranslateUi(self, shop_info): + _translate = QtCore.QCoreApplication.translate + shop_info.setWindowTitle(_translate("shop_info", "Form")) + self.pushButton.setText(_translate("shop_info", "Buy Game in Epic Games Store")) + self.price.setText(_translate("shop_info", "TextLabel")) + self.title.setText(_translate("shop_info", "Error")) + self.image.setText(_translate("shop_info", "TextLabel")) + self.dev.setText(_translate("shop_info", "TextLabel")) + + +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() + 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 new file mode 100644 index 00000000..c3235210 --- /dev/null +++ b/rare/ui/components/tabs/store/shop_game_info.ui @@ -0,0 +1,78 @@ + + + shop_info + + + + 0 + 0 + 702 + 468 + + + + Form + + + + + + Buy Game in Epic Games Store + + + + + + + TextLabel + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Error + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + TextLabel + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + TextLabel + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + diff --git a/rare/ui/components/tabs/store/store.py b/rare/ui/components/tabs/store/store.py new file mode 100644 index 00000000..ce8adcfb --- /dev/null +++ b/rare/ui/components/tabs/store/store.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'store.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# 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_ShopWidget(object): + def setupUi(self, ShopWidget): + ShopWidget.setObjectName("ShopWidget") + ShopWidget.resize(697, 362) + self.verticalLayout = QtWidgets.QVBoxLayout(ShopWidget) + self.verticalLayout.setObjectName("verticalLayout") + self.search = QtWidgets.QLineEdit(ShopWidget) + self.search.setObjectName("search") + self.verticalLayout.addWidget(self.search) + self.search_results = QtWidgets.QGroupBox(ShopWidget) + self.search_results.setFlat(False) + self.search_results.setObjectName("search_results") + self.verticalLayout.addWidget(self.search_results) + self.free_game_group_box_2 = QtWidgets.QGroupBox(ShopWidget) + self.free_game_group_box_2.setObjectName("free_game_group_box_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.free_game_group_box_2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.free_games_stack = QtWidgets.QStackedWidget(self.free_game_group_box_2) + self.free_games_stack.setObjectName("free_games_stack") + self.free_games_page = QtWidgets.QWidget() + self.free_games_page.setObjectName("free_games_page") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.free_games_page) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.free_game_now = QtWidgets.QGroupBox(self.free_games_page) + self.free_game_now.setObjectName("free_game_now") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.free_game_now) + self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout_3.addWidget(self.free_game_now) + self.comming_free_game = QtWidgets.QGroupBox(self.free_games_page) + self.comming_free_game.setObjectName("comming_free_game") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.comming_free_game) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout_3.addWidget(self.comming_free_game) + self.free_games_stack.addWidget(self.free_games_page) + self.verticalLayout_2.addWidget(self.free_games_stack) + self.verticalLayout.addWidget(self.free_game_group_box_2) + self.games_groupbox_2 = QtWidgets.QGroupBox(ShopWidget) + self.games_groupbox_2.setObjectName("games_groupbox_2") + self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.games_groupbox_2) + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.verticalLayout.addWidget(self.games_groupbox_2) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + + self.retranslateUi(ShopWidget) + self.free_games_stack.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(ShopWidget) + + def retranslateUi(self, ShopWidget): + _translate = QtCore.QCoreApplication.translate + ShopWidget.setWindowTitle(_translate("ShopWidget", "Form")) + self.search.setPlaceholderText(_translate("ShopWidget", "Search Games")) + self.search_results.setTitle(_translate("ShopWidget", "Search results")) + self.free_game_group_box_2.setTitle(_translate("ShopWidget", "Free Games")) + self.free_game_now.setTitle(_translate("ShopWidget", "Free Game")) + self.comming_free_game.setTitle(_translate("ShopWidget", "Comming Free Game")) + self.games_groupbox_2.setTitle(_translate("ShopWidget", "Other nice games")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + ShopWidget = QtWidgets.QWidget() + ui = Ui_ShopWidget() + ui.setupUi(ShopWidget) + ShopWidget.show() + sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/store/store.ui b/rare/ui/components/tabs/store/store.ui new file mode 100644 index 00000000..1e470192 --- /dev/null +++ b/rare/ui/components/tabs/store/store.ui @@ -0,0 +1,95 @@ + + + ShopWidget + + + + 0 + 0 + 697 + 362 + + + + Form + + + + + + Search Games + + + + + + + Search results + + + false + + + + + + + Free Games + + + + + + 0 + + + + + + + Free Game + + + + + + + + Comming Free Game + + + + + + + + + + + + + + + Other nice games + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index 121ab59d..cfc66800 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -114,13 +114,6 @@ class FlowLayout(QLayout): return parent.spacing() -class ClickableLabel(QLabel): - clicked = pyqtSignal() - - def __init__(self): - super(ClickableLabel, self).__init__() - - class PathEdit(QWidget, Ui_PathEdit): def __init__(self, text: str = "", diff --git a/rare/utils/models.py b/rare/utils/models.py index 44400ddc..790868d1 100644 --- a/rare/utils/models.py +++ b/rare/utils/models.py @@ -34,3 +34,51 @@ class InstallQueueItemModel: def __bool__(self): return (self.status_q is not None) and (self.download is not None) and (self.options is not None) + + +class ShopGame: + # TODO: Copyrights etc + def __init__(self, title: str = "", image_urls: dict = None, social_links: dict = None, + langs: list = None, reqs: list = None, publisher: str = "", developer: str = ""): + self.title = title + 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 + self.reqs = reqs # {"Betriebssystem":win7, processor:i9 9900k, ram...}; Note: name from language + self.publisher = publisher + self.developer = developer + + @classmethod + def from_json(cls, data: dict): + if isinstance(data, list): + for product in data: + if product["_title"] == "home": + data = product + break + tmp = cls() + tmp.title = data.get("productName", "undefined") + tmp.img_urls = { + "DieselImage": data["data"]["about"]["image"]["src"], + "banner": data["data"]["hero"]["backgroundImageUrl"] + } + links = 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 = data["data"]["requirements"]["languages"] + tmp.reqs = {} + for system in data["data"]["requirements"]["systems"]: + tmp.reqs[system] = {} + for i in system: + tmp.reqs[i[system]["title"]] = tuple((i[system]["minimum"], i[system]["recommend"])) + + tmp.publisher = data["data"]["meta"].get("publisher", "undefined") + tmp.developer = data["data"]["meta"].get("developer", "undefined") + return tmp diff --git a/test.py b/test.py new file mode 100644 index 00000000..de073353 --- /dev/null +++ b/test.py @@ -0,0 +1,20 @@ +import sys +from PyQt5.QtCore import Qt, QStringListModel +from PyQt5.QtWidgets import QApplication, QCompleter, QLineEdit + +def get_data(model): + model.setStringList(["completion", "data", "goes", "here"]) + +if __name__ == "__main__": + + app = QApplication(sys.argv) + edit = QLineEdit() + completer = QCompleter() + edit.setCompleter(completer) + + model = QStringListModel() + completer.setModel(model) + get_data(model) + + edit.show() + sys.exit(app.exec_()) \ No newline at end of file