diff --git a/AppData/EncoderDecoder.py b/AppData/EncoderDecoder.py index e9d85f5..a6d2e3f 100644 --- a/AppData/EncoderDecoder.py +++ b/AppData/EncoderDecoder.py @@ -84,13 +84,13 @@ def encode(cls, obj: typing.Any) -> typing.Any: elif isinstance(obj, QtCore.QDateTime): return f"datetime:{obj.toString(QtCore.Qt.DateFormat.ISODateWithMs)}" elif isinstance(obj, QtCore.QTimeZone): - return f"timezone:{obj.id().data().decode()}" + return f"timezone:{obj.id().data().decode(errors='ignore')}" elif isinstance(obj, QtCore.QUrl): return f"url:{obj.toString()}" elif isinstance(obj, bytes): - return f"bytes:{obj.decode()}" + return f"bytes:{obj.decode(errors='ignore')}" elif isinstance(obj, bytearray): - return f"bytearray:{obj.decode()}" + return f"bytearray:{obj.decode(errors='ignore')}" elif isinstance(obj, tuple): return cls._encodeTuple(obj) elif isinstance(obj, list): diff --git a/AppData/Preferences.py b/AppData/Preferences.py index 60c728e..506de1e 100644 --- a/AppData/Preferences.py +++ b/AppData/Preferences.py @@ -103,8 +103,17 @@ def getClipFilename(self) -> str: class Advanced(Serializable): def __init__(self): + self._themeMode = App.ThemeManager.getThemeMode().value self._searchExternalContent = True + def __setup__(self): + App.ThemeManager.setThemeMode(App.ThemeManager.Modes.fromString(self._themeMode)) + del self._themeMode + + def __save__(self): + self._themeMode = App.ThemeManager.getThemeMode().value + return super().__save__() + def setSearchExternalContentEnabled(self, enabled: bool) -> None: self._searchExternalContent = enabled @@ -162,7 +171,7 @@ def __save__(self): self._downloadHistory = App.DownloadHistory.getHistoryList() return super().__save__() - def getDownloadOptionHistory(self, historyType: DownloadOptionHistory.BaseOptionHistory) -> None: + def getDownloadOptionHistory(self, historyType: DownloadOptionHistory.BaseOptionHistory) -> DownloadOptionHistory.BaseOptionHistory: return self._downloadOptionHistory[historyType.getId()] def updateDownloadStats(self, fileSize: int) -> None: diff --git a/AppData/Updater.py b/AppData/Updater.py index 22ff682..2a57e1f 100644 --- a/AppData/Updater.py +++ b/AppData/Updater.py @@ -138,6 +138,11 @@ def Update_3_1_0(data: dict) -> dict: } return data + @staticmethod + def Update_3_2_0(data: dict) -> dict: + data["advanced"]["_themeMode"] = "str:" + return data + @classmethod def getUpdaters(cls, versionFrom: str) -> list[typing.Callable[[dict], dict]] | None: VERSIONS = { @@ -151,7 +156,8 @@ def getUpdaters(cls, versionFrom: str) -> list[typing.Callable[[dict], dict]] | "3.1.0": cls.Update_3_1_0, "3.1.1": None, "3.1.2": None, - "3.1.3": None + "3.1.3": None, + "3.2.0": cls.Update_3_2_0 } updaters = [] versionFound = False diff --git a/Core/App.py b/Core/App.py index 4ad8325..8eca454 100644 --- a/Core/App.py +++ b/Core/App.py @@ -64,6 +64,9 @@ def restart(self) -> None: from Services.Account.TwitchAccount import TwitchAccount as _TwitchAccount Account = _TwitchAccount(parent=Instance) +from Services.Theme.ThemeManager import ThemeManager as _ThemeManager +ThemeManager = _ThemeManager(parent=Instance) + from Download.Downloader.Core.Engine.File.FileDownloadManager import FileDownloadManager as _FileDownloadManager FileDownloadManager = _FileDownloadManager(parent=Instance) diff --git a/Core/GlobalExceptions.py b/Core/GlobalExceptions.py index 0ecc28d..71d1245 100644 --- a/Core/GlobalExceptions.py +++ b/Core/GlobalExceptions.py @@ -26,7 +26,7 @@ class NetworkError(Exception): def __init__(self, reply: QtNetwork.QNetworkReply): self.reasonCode = reply.error() self.reasonText = reply.errorString() - self.responseText = "" if self.reasonCode == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError else reply.readAll().data().decode() + self.responseText = "" if self.reasonCode == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError else reply.readAll().data().decode(errors="ignore") def __str__(self): if self.responseText == "": diff --git a/Core/Meta.py b/Core/Meta.py index f62df5e..02dbc2c 100644 --- a/Core/Meta.py +++ b/Core/Meta.py @@ -4,7 +4,7 @@ class Meta: APP_NAME = "TwitchLink" - APP_VERSION = "3.1.3" + APP_VERSION = "3.2.0" AUTHOR = "DevHotteok" diff --git a/Core/Notification.py b/Core/Notification.py index 1903d62..3d44e52 100644 --- a/Core/Notification.py +++ b/Core/Notification.py @@ -14,4 +14,4 @@ def __init__(self, systemTrayIcon: QtWidgets.QSystemTrayIcon, parent: QtCore.QOb self.systemTrayIcon = systemTrayIcon def toastMessage(self, title: str, message: str, icon: QtWidgets.QSystemTrayIcon.MessageIcon | QtGui.QIcon | None = None) -> None: - self.systemTrayIcon.showMessage(title, message, icon or QtGui.QIcon(Icons.APP_LOGO_ICON)) \ No newline at end of file + self.systemTrayIcon.showMessage(title, message, icon or Icons.APP_LOGO.icon) \ No newline at end of file diff --git a/Core/Qt/QtCore/QTimeZone.py b/Core/Qt/QtCore/QTimeZone.py index d35cccd..950e1ed 100644 --- a/Core/Qt/QtCore/QTimeZone.py +++ b/Core/Qt/QtCore/QTimeZone.py @@ -3,5 +3,5 @@ class _QTimeZone_Patcher(QtCore.QTimeZone): def name(self) -> str: - return self.id().data().decode() + return self.id().data().decode(errors="ignore") QtCore.QTimeZone.name = _QTimeZone_Patcher.name #Direct Attribute Patch - [Info] Affects all embedded objects \ No newline at end of file diff --git a/Core/Qt/QtWidgets/QMessageBox.py b/Core/Qt/QtWidgets/QMessageBox.py index 5041688..9bf8425 100644 --- a/Core/Qt/QtWidgets/QMessageBox.py +++ b/Core/Qt/QtWidgets/QMessageBox.py @@ -6,6 +6,6 @@ class _QMessageBox(QtWidgets.QMessageBox): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setWindowIcon(Icons.APP_LOGO.icon) self.setIcon(QtWidgets.QMessageBox.Icon.Information) QtWidgets.QMessageBox = _QMessageBox #Direct Class Patch - [Warning] Does not affect embedded objects (Use with caution) \ No newline at end of file diff --git a/Core/Qt/QtWidgets/QProgressDialog.py b/Core/Qt/QtWidgets/QProgressDialog.py index c7ee33d..ec196f4 100644 --- a/Core/Qt/QtWidgets/QProgressDialog.py +++ b/Core/Qt/QtWidgets/QProgressDialog.py @@ -1,13 +1,13 @@ from Services.Image.Presets import Icons -from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtWidgets class _QProgressDialog(QtWidgets.QProgressDialog): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowFlag(QtCore.Qt.WindowType.WindowContextHelpButtonHint, False) - self.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setWindowIcon(Icons.APP_LOGO.icon) self.progressBar = QtWidgets.QProgressBar(parent=self) self.setMaximum = self.progressBar.setMaximum self.setMinimum = self.progressBar.setMinimum diff --git a/Core/SystemTrayIcon.py b/Core/SystemTrayIcon.py index 5cdd75d..3f25546 100644 --- a/Core/SystemTrayIcon.py +++ b/Core/SystemTrayIcon.py @@ -1,7 +1,7 @@ from Core.Config import Config from Services.Image.Presets import Icons -from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtWidgets class SystemTrayIcon(QtWidgets.QSystemTrayIcon): @@ -9,7 +9,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): def __init__(self, parent: QtCore.QObject | None = None): super().__init__(parent=parent) - self.setIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setIcon(Icons.APP_LOGO.icon) self.setContextMenu(QtWidgets.QMenu()) self.activated.connect(self._activatedHandler) self.messageClicked.connect(self._messageClickedHandler) diff --git a/Core/Ui.py b/Core/Ui.py index 44865af..4ae6712 100644 --- a/Core/Ui.py +++ b/Core/Ui.py @@ -51,7 +51,7 @@ def setupInstance(cls, instance: QtWidgets.QWidget) -> None: instance.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) if isinstance(instance, QtWidgets.QMainWindow) or isinstance(instance, QtWidgets.QDialog): instance.setWindowFlag(QtCore.Qt.WindowType.WindowContextHelpButtonHint, False) - instance.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + instance.setWindowIcon(Icons.APP_LOGO.icon) instance.setWindowTitle(instance.windowTitle() or Config.APP_NAME) cls.setPartnerContent(instance) diff --git a/Core/Updater.py b/Core/Updater.py index 4fd541a..aedf6a8 100644 --- a/Core/Updater.py +++ b/Core/Updater.py @@ -138,7 +138,7 @@ def _sendRequest(self, url: str) -> None: def _finished(self) -> None: if self._networkReply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: try: - text = self._networkReply.readAll().data().decode() + text = self._networkReply.readAll().data().decode(errors="ignore") if text.startswith("redirect:"): if self._redirectCount < Config.STATUS_UPDATE_MAX_REDIRECT_COUNT: self._redirectCount += 1 diff --git a/Download/DownloadInfo.py b/Download/DownloadInfo.py index 1f67756..29dfcbb 100644 --- a/Download/DownloadInfo.py +++ b/Download/DownloadInfo.py @@ -13,7 +13,7 @@ import os -class DownloadInfoType(Serializable): +class DownloadInfoType: class Types(enum.Enum): STREAM = "stream" VIDEO = "video" diff --git a/Download/DownloadManager.py b/Download/DownloadManager.py index 3832045..1999771 100644 --- a/Download/DownloadManager.py +++ b/Download/DownloadManager.py @@ -31,7 +31,7 @@ def onFinish(self, downloader: StreamDownloader | VideoDownloader | ClipDownload self.runningCountChangedSignal.emit(len(self.runningDownloaders)) self.completedSignal.emit(downloader.getId()) - def create(self, downloadInfo: DownloadInfo) -> None: + def create(self, downloadInfo: DownloadInfo) -> uuid.UUID: downloader = TwitchDownloader.create(downloadInfo, parent=self) downloader.started.connect(self.onStart) downloader.finished.connect(self.onFinish) diff --git a/Download/Downloader/Core/Engine/FFmpeg/FFmpeg.py b/Download/Downloader/Core/Engine/FFmpeg/FFmpeg.py index 5d97bcb..678a3e5 100644 --- a/Download/Downloader/Core/Engine/FFmpeg/FFmpeg.py +++ b/Download/Downloader/Core/Engine/FFmpeg/FFmpeg.py @@ -106,11 +106,11 @@ def _onProcessFinish(self, exitCode: int, exitStatus: QtCore.QProcess.ExitStatus self.finished.emit() def _readStandardOutput(self) -> None: - for line in self._process.readAllStandardOutput().data().decode().splitlines(): + for line in self._process.readAllStandardOutput().data().decode(errors="ignore").splitlines(): self.logger.debug(line) def _readStandardError(self) -> None: - for line in self._process.readAllStandardError().data().decode().splitlines(): + for line in self._process.readAllStandardError().data().decode(errors="ignore").splitlines(): self.logger.debug(line) def _getCodecParams(self, fileName: str, transcode: bool = False) -> tuple[str, ...]: diff --git a/Download/GlobalDownloadManager.py b/Download/GlobalDownloadManager.py index 85eac10..211d57c 100644 --- a/Download/GlobalDownloadManager.py +++ b/Download/GlobalDownloadManager.py @@ -2,7 +2,6 @@ from Core.App import T from Core.Config import Config from Core.GlobalExceptions import Exceptions -from Services.Script import Script from Download.Downloader.TwitchDownloader import TwitchDownloader from Download.Downloader.Core.StreamDownloader import StreamDownloader from Download.Downloader.Core.VideoDownloader import VideoDownloader diff --git a/Download/ScheduledDownloadManager.py b/Download/ScheduledDownloadManager.py index f9009b0..6855b47 100644 --- a/Download/ScheduledDownloadManager.py +++ b/Download/ScheduledDownloadManager.py @@ -74,7 +74,7 @@ def isError(self) -> bool: def isDownloaderError(self) -> bool: return self._status == self.DOWNLOADER_ERROR - def getError(self) -> Exception: + def getError(self) -> Exception | None: return self._error def cleanup(self) -> None: diff --git a/Search/Engine.py b/Search/Engine.py index 0425e11..256dab0 100644 --- a/Search/Engine.py +++ b/Search/Engine.py @@ -97,7 +97,7 @@ def _raiseException(self, exception: Exception) -> None: self._error = exception self._setFinished() - def getError(self) -> Exception: + def getError(self) -> Exception | None: return self._error def getData(self) -> TwitchGQLModels.Channel | TwitchGQLModels.Video | TwitchGQLModels.Clip | ExternalPlaybackGenerator.ExternalPlayback: diff --git a/Search/ExternalPlaybackGenerator.py b/Search/ExternalPlaybackGenerator.py index 89f7d33..0c76c80 100644 --- a/Search/ExternalPlaybackGenerator.py +++ b/Search/ExternalPlaybackGenerator.py @@ -47,7 +47,7 @@ def _checkUrl(self) -> None: def _replyFinished(self) -> None: if self._reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: - text = self._reply.readAll().data().decode() + text = self._reply.readAll().data().decode(errors="ignore") self._resolutions = VariantPlaylistReader.loads(text, baseUrl=self._reply.url()) if len(self._resolutions) == 0: try: @@ -69,7 +69,7 @@ def _playlistCheckFinished(self) -> None: if self._playlistCheckReply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: try: playlist = Playlist() - playlist.loads(self._playlistCheckReply.readAll().data().decode(), baseUrl=self._playlistCheckReply.url()) + playlist.loads(self._playlistCheckReply.readAll().data().decode(errors="ignore"), baseUrl=self._playlistCheckReply.url()) except Exception as e: self._raiseException(e) else: @@ -89,7 +89,7 @@ def _raiseException(self, exception: Exception) -> None: self._error = exception self._setFinished() - def getError(self) -> Exception: + def getError(self) -> Exception | None: return self._error def getData(self) -> ExternalPlayback: diff --git a/Services/Image/Presets.py b/Services/Image/Presets.py index 95b10f3..cf11d27 100644 --- a/Services/Image/Presets.py +++ b/Services/Image/Presets.py @@ -1,4 +1,6 @@ from Core.Config import Config as CoreConfig, _P +from Services.Theme.ThemedIconManager import ThemedIconManager +from Services.Theme.ThemedIcon import ThemedIcon class Images: @@ -20,39 +22,67 @@ class ImageSize: CATEGORY = (90, 120) +class IconPath: + ROOT = _P(CoreConfig.RESOURCE_ROOT, "icons") + LIGHT = _P(ROOT, "light") + DARK = _P(ROOT, "dark") + + class Icons: - ICON_ROOT = _P(CoreConfig.RESOURCE_ROOT, "icons") - - APP_LOGO_ICON = _P(ICON_ROOT, "icon.ico") - - SEARCH_ICON = _P(ICON_ROOT, "search.svg") - DOWNLOAD_ICON = _P(ICON_ROOT, "download.svg") - SCHEDULED_ICON = _P(ICON_ROOT, "scheduled.svg") - ACCOUNT_ICON = _P(ICON_ROOT, "account.svg") - SETTINGS_ICON = _P(ICON_ROOT, "settings.svg") - INFO_ICON = _P(ICON_ROOT, "info.svg") - HOME_ICON = _P(ICON_ROOT, "home.svg") - FOLDER_ICON = _P(ICON_ROOT, "folder.svg") - FILE_ICON = _P(ICON_ROOT, "file.svg") - FILE_NOT_FOUND_ICON = _P(ICON_ROOT, "file_not_found.svg") - UPDATE_FOUND_ICON = _P(ICON_ROOT, "update_found.svg") - LOADING_ICON = _P(ICON_ROOT, "loading.svg") - DOWNLOADING_FILE_ICON = _P(ICON_ROOT, "downloading_file.svg") - CREATING_FILE_ICON = _P(ICON_ROOT, "creating_file.svg") - CLOSE_ICON = _P(ICON_ROOT, "close.svg") - SAVE_ICON = _P(ICON_ROOT, "save.svg") - MOVE_ICON = _P(ICON_ROOT, "move.svg") - LOGIN_ICON = _P(ICON_ROOT, "login.svg") - ANNOUNCEMENT_ICON = _P(ICON_ROOT, "announcement.svg") - NOTICE_ICON = _P(ICON_ROOT, "notice.svg") - TEXT_FILE_ICON = _P(ICON_ROOT, "text_file.svg") - IMAGE_ICON = _P(ICON_ROOT, "image.svg") - ALERT_RED_ICON = _P(ICON_ROOT, "alert_red.svg") - TOGGLE_OFF_ICON = _P(ICON_ROOT, "toggle_off.svg") - TOGGLE_ON_ICON = _P(ICON_ROOT, "toggle_on.svg") - WEB_ICON = _P(ICON_ROOT, "web.svg") - VIEWER_ICON = _P(ICON_ROOT, "viewer.svg") - VERIFIED_ICON = _P(ICON_ROOT, "verified.svg") - CHANNEL_BACKGROUND_WHITE_ICON = _P(ICON_ROOT, "channel_background_white.svg") - STORAGE_ICON = _P(ICON_ROOT, "storage.svg") - HISTORY_ICON = _P(ICON_ROOT, "history.svg") \ No newline at end of file + @staticmethod + def I(name: str, route: bool = True) -> ThemedIcon: + if route: + return ThemedIconManager.create(_P(IconPath.LIGHT, name), _P(IconPath.DARK, name)) + else: + return ThemedIconManager.create(_P(IconPath.ROOT, name), _P(IconPath.ROOT, name)) + + + APP_LOGO = I("icon.ico", route=False) + + ACCOUNT = I("account.svg") + ALERT_RED = I("alert_red.svg") + ANNOUNCEMENT = I("announcement.svg") + BACK = I("back.svg") + CANCEL = I("cancel.svg") + CHANNEL_BACKGROUND_WHITE = I("channel_background_white.svg") + CLOSE = I("close.svg") + COPY = I("copy.svg") + CREATING_FILE = I("creating_file.svg") + DOWNLOAD = I("download.svg") + DOWNLOADING_FILE = I("downloading_file.svg") + FILE = I("file.svg") + FILE_NOT_FOUND = I("file_not_found.svg") + FOLDER = I("folder.svg") + FORWARD = I("forward.svg") + HELP = I("help.svg") + HISTORY = I("history.svg") + HOME = I("home.svg") + IMAGE = I("image.svg") + INFO = I("info.svg") + INSTANT_DOWNLOAD = I("instant_download.svg") + LAUNCH = I("launch.svg") + LIST = I("list.svg") + LOADING = I("loading.svg") + LOGIN = I("login.svg") + MOVE = I("move.svg") + NOTICE = I("notice.svg") + PLUS = I("plus.svg") + RELOAD = I("reload.svg") + RETRY = I("retry.svg") + SAVE = I("save.svg") + SCHEDULED = I("scheduled.svg") + SEARCH = I("search.svg") + SETTINGS = I("settings.svg") + STORAGE = I("storage.svg") + TEXT_FILE = I("text_file.svg") + THEME_AUTOMATIC = I("theme_automatic.svg") + THEME_DARK = I("theme_dark.svg") + THEME_LIGHT = I("theme_light.svg") + TOGGLE_OFF = I("toggle_off.svg") + TOGGLE_ON = I("toggle_on.svg") + TRASH = I("trash.svg") + UPDATE_FOUND = I("update_found.svg") + VERIFIED = I("verified.svg") + VIEWER = I("viewer.svg") + WARNING_RED = I("warning_red.svg") + WEB = I("web.svg") \ No newline at end of file diff --git a/Services/Playlist/PlaylistManager.py b/Services/Playlist/PlaylistManager.py index ed95c86..2398dfd 100644 --- a/Services/Playlist/PlaylistManager.py +++ b/Services/Playlist/PlaylistManager.py @@ -58,7 +58,7 @@ def _requestDone(self) -> None: if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: self._currentNetworkError = None try: - self.playlist.loads(reply.readAll().data().decode(), baseUrl=self.url) + self.playlist.loads(reply.readAll().data().decode(errors="ignore"), baseUrl=self.url) except Exception as e: self._raiseException(e) else: diff --git a/Services/Temp/TempManager.py b/Services/Temp/TempManager.py index 5fe16a0..44c9229 100644 --- a/Services/Temp/TempManager.py +++ b/Services/Temp/TempManager.py @@ -75,7 +75,7 @@ def cleanTempDirKeyFile(self, tempDirKeyFile: str) -> None: if OSUtils.isFile(tempDirKeyFile): file = QtCore.QFile(tempDirKeyFile, self) if file.open(QtCore.QIODevice.OpenModeFlag.ReadOnly): - tempDir = file.readAll().data().decode() + tempDir = file.readAll().data().decode(errors="ignore") if OSUtils.isDirectory(tempDir): self.logger.info(f"Removing temp directory: {tempDir}") OSUtils.removeDirectory(tempDir) diff --git a/Services/Theme/Palette.py b/Services/Theme/Palette.py new file mode 100644 index 0000000..1a79455 --- /dev/null +++ b/Services/Theme/Palette.py @@ -0,0 +1,221 @@ +from PyQt6 import QtGui + + +class Palette: + LIGHT = { + QtGui.QPalette.ColorRole.Window: { + QtGui.QPalette.ColorGroup.Disabled: (240, 240, 240), + QtGui.QPalette.ColorGroup.Active: (240, 240, 240), + QtGui.QPalette.ColorGroup.Inactive: (240, 240, 240) + }, + QtGui.QPalette.ColorRole.WindowText: { + QtGui.QPalette.ColorGroup.Disabled: (120, 120, 120), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.Base: { + QtGui.QPalette.ColorGroup.Disabled: (240, 240, 240), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.AlternateBase: { + QtGui.QPalette.ColorGroup.Disabled: (245, 245, 245), + QtGui.QPalette.ColorGroup.Active: (245, 245, 245), + QtGui.QPalette.ColorGroup.Inactive: (245, 245, 245) + }, + QtGui.QPalette.ColorRole.ToolTipBase: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 220), + QtGui.QPalette.ColorGroup.Active: (255, 255, 220), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 220) + }, + QtGui.QPalette.ColorRole.ToolTipText: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 0), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.PlaceholderText: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 0, 128), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0, 128), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0, 128) + }, + QtGui.QPalette.ColorRole.Text: { + QtGui.QPalette.ColorGroup.Disabled: (120, 120, 120), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.Button: { + QtGui.QPalette.ColorGroup.Disabled: (240, 240, 240), + QtGui.QPalette.ColorGroup.Active: (240, 240, 240), + QtGui.QPalette.ColorGroup.Inactive: (240, 240, 240) + }, + QtGui.QPalette.ColorRole.ButtonText: { + QtGui.QPalette.ColorGroup.Disabled: (120, 120, 120), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.BrightText: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 255), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.Light: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 255), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.Midlight: { + QtGui.QPalette.ColorGroup.Disabled: (247, 247, 247), + QtGui.QPalette.ColorGroup.Active: (227, 227, 227), + QtGui.QPalette.ColorGroup.Inactive: (227, 227, 227) + }, + QtGui.QPalette.ColorRole.Dark: { + QtGui.QPalette.ColorGroup.Disabled: (160, 160, 160), + QtGui.QPalette.ColorGroup.Active: (160, 160, 160), + QtGui.QPalette.ColorGroup.Inactive: (160, 160, 160) + }, + QtGui.QPalette.ColorRole.Mid: { + QtGui.QPalette.ColorGroup.Disabled: (160, 160, 160), + QtGui.QPalette.ColorGroup.Active: (160, 160, 160), + QtGui.QPalette.ColorGroup.Inactive: (160, 160, 160) + }, + QtGui.QPalette.ColorRole.Shadow: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 0), + QtGui.QPalette.ColorGroup.Active: (105, 105, 105), + QtGui.QPalette.ColorGroup.Inactive: (105, 105, 105) + }, + QtGui.QPalette.ColorRole.Highlight: { + QtGui.QPalette.ColorGroup.Disabled: (0, 120, 215), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (240, 240, 240) + }, + QtGui.QPalette.ColorRole.Accent: { + QtGui.QPalette.ColorGroup.Disabled: (120, 120, 120), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (240, 240, 240) + }, + QtGui.QPalette.ColorRole.HighlightedText: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 255), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.Link: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 255), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (0, 120, 215) + }, + QtGui.QPalette.ColorRole.LinkVisited: { + QtGui.QPalette.ColorGroup.Disabled: (255, 0, 255), + QtGui.QPalette.ColorGroup.Active: (0, 38, 66), + QtGui.QPalette.ColorGroup.Inactive: (0, 38, 66) + } + } + + + + DARK = { + QtGui.QPalette.ColorRole.Window: { + QtGui.QPalette.ColorGroup.Disabled: (60, 60, 60), + QtGui.QPalette.ColorGroup.Active: (60, 60, 60), + QtGui.QPalette.ColorGroup.Inactive: (60, 60, 60) + }, + QtGui.QPalette.ColorRole.WindowText: { + QtGui.QPalette.ColorGroup.Disabled: (157, 157, 157), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.Base: { + QtGui.QPalette.ColorGroup.Disabled: (30, 30, 30), + QtGui.QPalette.ColorGroup.Active: (45, 45, 45), + QtGui.QPalette.ColorGroup.Inactive: (45, 45, 45) + }, + QtGui.QPalette.ColorRole.AlternateBase: { + QtGui.QPalette.ColorGroup.Disabled: (52, 52, 52), + QtGui.QPalette.ColorGroup.Active: (0, 38, 66), + QtGui.QPalette.ColorGroup.Inactive: (0, 38, 66) + }, + QtGui.QPalette.ColorRole.ToolTipBase: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 220), + QtGui.QPalette.ColorGroup.Active: (60, 60, 60), + QtGui.QPalette.ColorGroup.Inactive: (60, 60, 60) + }, + QtGui.QPalette.ColorRole.ToolTipText: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 0), + QtGui.QPalette.ColorGroup.Active: (212, 212, 212), + QtGui.QPalette.ColorGroup.Inactive: (212, 212, 212) + }, + QtGui.QPalette.ColorRole.PlaceholderText: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 255, 128), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255, 128), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255, 128) + }, + QtGui.QPalette.ColorRole.Text: { + QtGui.QPalette.ColorGroup.Disabled: (157, 157, 157), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.Button: { + QtGui.QPalette.ColorGroup.Disabled: (60, 60, 60), + QtGui.QPalette.ColorGroup.Active: (60, 60, 60), + QtGui.QPalette.ColorGroup.Inactive: (60, 60, 60) + }, + QtGui.QPalette.ColorRole.ButtonText: { + QtGui.QPalette.ColorGroup.Disabled: (157, 157, 157), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.BrightText: { + QtGui.QPalette.ColorGroup.Disabled: (166, 216, 255), + QtGui.QPalette.ColorGroup.Active: (166, 216, 255), + QtGui.QPalette.ColorGroup.Inactive: (166, 216, 255) + }, + QtGui.QPalette.ColorRole.Light: { + QtGui.QPalette.ColorGroup.Disabled: (120, 120, 120), + QtGui.QPalette.ColorGroup.Active: (120, 120, 120), + QtGui.QPalette.ColorGroup.Inactive: (120, 120, 120) + }, + QtGui.QPalette.ColorRole.Midlight: { + QtGui.QPalette.ColorGroup.Disabled: (90, 90, 90), + QtGui.QPalette.ColorGroup.Active: (90, 90, 90), + QtGui.QPalette.ColorGroup.Inactive: (90, 90, 90) + }, + QtGui.QPalette.ColorRole.Dark: { + QtGui.QPalette.ColorGroup.Disabled: (30, 30, 30), + QtGui.QPalette.ColorGroup.Active: (30, 30, 30), + QtGui.QPalette.ColorGroup.Inactive: (30, 30, 30) + }, + QtGui.QPalette.ColorRole.Mid: { + QtGui.QPalette.ColorGroup.Disabled: (40, 40, 40), + QtGui.QPalette.ColorGroup.Active: (40, 40, 40), + QtGui.QPalette.ColorGroup.Inactive: (40, 40, 40) + }, + QtGui.QPalette.ColorRole.Shadow: { + QtGui.QPalette.ColorGroup.Disabled: (0, 0, 0), + QtGui.QPalette.ColorGroup.Active: (0, 0, 0), + QtGui.QPalette.ColorGroup.Inactive: (0, 0, 0) + }, + QtGui.QPalette.ColorRole.Highlight: { + QtGui.QPalette.ColorGroup.Disabled: (0, 120, 215), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (30, 30, 30) + }, + QtGui.QPalette.ColorRole.Accent: { + QtGui.QPalette.ColorGroup.Disabled: (157, 157, 157), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (30, 30, 30) + }, + QtGui.QPalette.ColorRole.HighlightedText: { + QtGui.QPalette.ColorGroup.Disabled: (255, 255, 255), + QtGui.QPalette.ColorGroup.Active: (255, 255, 255), + QtGui.QPalette.ColorGroup.Inactive: (255, 255, 255) + }, + QtGui.QPalette.ColorRole.Link: { + QtGui.QPalette.ColorGroup.Disabled: (48, 140, 198), + QtGui.QPalette.ColorGroup.Active: (0, 120, 215), + QtGui.QPalette.ColorGroup.Inactive: (0, 120, 215) + }, + QtGui.QPalette.ColorRole.LinkVisited: { + QtGui.QPalette.ColorGroup.Disabled: (255, 0, 255), + QtGui.QPalette.ColorGroup.Active: (0, 38, 66), + QtGui.QPalette.ColorGroup.Inactive: (0, 38, 66) + } + } \ No newline at end of file diff --git a/Services/Theme/ThemeManager.py b/Services/Theme/ThemeManager.py new file mode 100644 index 0000000..04b9d1c --- /dev/null +++ b/Services/Theme/ThemeManager.py @@ -0,0 +1,71 @@ +from .Palette import Palette +from .ThemedIconManager import ThemedIconManager + +from Core import App + +from PyQt6 import QtCore, QtGui, QtWidgets + +import typing +import enum + + +class ThemeManager(QtCore.QObject): + themeUpdated = QtCore.pyqtSignal() + + class Modes(enum.Enum): + AUTO = "" + LIGHT = "light" + DARK = "dark" + + def isAuto(self) -> bool: + return self == self.AUTO + + def isLight(self) -> bool: + return self == self.LIGHT + + def isDark(self) -> bool: + return self == self.DARK + + def toString(self) -> str: + return self.value + + @classmethod + def fromString(cls, value: str) -> typing.Self | None: + for member in cls: + if member.value == value: + return member + return None + + + def __init__(self, parent: QtCore.QObject | None = None): + super().__init__(parent=parent) + self._themeMode: ThemeManager.Modes = self.Modes.AUTO + self._currentTheme = "" + App.Instance.styleHints().colorSchemeChanged.connect(self._colorSchemeChanged) + + def getThemeMode(self) -> Modes: + return self._themeMode + + def setThemeMode(self, themeMode: Modes) -> None: + self._themeMode = themeMode + self.updateTheme() + + def isDarkModeEnabled(self) -> bool: + return self._themeMode.isDark() or (self._themeMode.isAuto() and App.Instance.styleHints().colorScheme() == QtCore.Qt.ColorScheme.Dark) + + def _colorSchemeChanged(self, colorScheme: QtCore.Qt.ColorScheme) -> None: + self.updateTheme() + + def updateTheme(self) -> None: + newTheme = "Fusion" if self.isDarkModeEnabled() else "windowsvista" + if newTheme != self._currentTheme: + self._currentTheme = newTheme + ThemedIconManager.setDarkModeEnabled(self.isDarkModeEnabled()) + palette = QtGui.QPalette() + paletteData = Palette.DARK if self.isDarkModeEnabled() else Palette.LIGHT + for role, roleData in paletteData.items(): + for group, color in roleData.items(): + palette.setColor(group, role, QtGui.QColor(*color)) + App.Instance.setPalette(palette) + App.Instance.setStyle(QtWidgets.QStyleFactory.create(self._currentTheme)) + self.themeUpdated.emit() \ No newline at end of file diff --git a/Services/Theme/ThemedIcon.py b/Services/Theme/ThemedIcon.py new file mode 100644 index 0000000..3612b15 --- /dev/null +++ b/Services/Theme/ThemedIcon.py @@ -0,0 +1,29 @@ +from PyQt6 import QtGui + + +class ThemedIcon: + def __init__(self, lightModePath: str, darkModePath: str): + self.lightModePath = lightModePath + self.darkModePath = darkModePath + self._darkModeEnabled = False + self._icon: QtGui.QIcon | None = None + + @property + def icon(self) -> QtGui.QIcon: + if self._icon == None: + self._icon = QtGui.QIcon(self.path) + return self._icon + + @property + def path(self) -> str: + return self.darkModePath if self._darkModeEnabled else self.lightModePath + + def setDarkModeEnabled(self, enabled: bool) -> None: + if self._darkModeEnabled == enabled: + return + self._darkModeEnabled = enabled + newIcon = QtGui.QIcon(self.path) + if self._icon == None: + self._icon = newIcon + else: + self._icon.swap(newIcon) \ No newline at end of file diff --git a/Services/Theme/ThemedIconManager.py b/Services/Theme/ThemedIconManager.py new file mode 100644 index 0000000..33d31c4 --- /dev/null +++ b/Services/Theme/ThemedIconManager.py @@ -0,0 +1,25 @@ +from .ThemedIcon import ThemedIcon + + +class ThemedIconManager: + icons: list[ThemedIcon] = [] + darkModeEnabled: bool = False + + @classmethod + def create(cls, lightModePath: str, darkModePath: str) -> ThemedIcon: + themedIcon = ThemedIcon(lightModePath, darkModePath) + cls.icons.append(themedIcon) + return themedIcon + + @classmethod + def remove(cls, themedIcon: ThemedIcon) -> None: + if themedIcon in cls.icons: + cls.icons.remove(themedIcon) + + @classmethod + def setDarkModeEnabled(cls, enabled: bool) -> None: + if cls.darkModeEnabled == enabled: + return + cls.darkModeEnabled = enabled + for themedIcon in cls.icons: + themedIcon.setDarkModeEnabled(cls.darkModeEnabled) \ No newline at end of file diff --git a/Services/Theme/ThemedIconViewer.py b/Services/Theme/ThemedIconViewer.py new file mode 100644 index 0000000..8930914 --- /dev/null +++ b/Services/Theme/ThemedIconViewer.py @@ -0,0 +1,32 @@ +from .ThemedIcon import ThemedIcon + +from Core.App import ThemeManager + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class ThemedIconViewer(QtCore.QObject): + def __init__(self, widget: QtWidgets.QPushButton | QtWidgets.QToolButton, themedIcon: QtGui.QIcon | ThemedIcon | None): + self.widget = widget + self.themedIcon = themedIcon + super().__init__(parent=self.widget) + ThemeManager.themeUpdated.connect(self._loadIcon) + self._loadIcon() + + def setIcon(self, themedIcon: QtGui.QIcon | ThemedIcon | None) -> None: + self.themedIcon = themedIcon + self._loadIcon() + + def _getIconObject(self) -> QtGui.QIcon: + if isinstance(self.themedIcon, QtGui.QIcon): + return self.themedIcon + elif isinstance(self.themedIcon, ThemedIcon): + return self.themedIcon.icon + else: + return QtGui.QIcon() + + def _loadIcon(self) -> None: + if isinstance(self.widget, QtWidgets.QPushButton): + self.widget.setIcon(self._getIconObject()) + elif isinstance(self.widget, QtWidgets.QToolButton): + self.widget.setIcon(self._getIconObject()) \ No newline at end of file diff --git a/Services/Theme/ThemedSvgWidget.py b/Services/Theme/ThemedSvgWidget.py new file mode 100644 index 0000000..a8be819 --- /dev/null +++ b/Services/Theme/ThemedSvgWidget.py @@ -0,0 +1,22 @@ +from .ThemedIcon import ThemedIcon + +from Core.App import ThemeManager + +from PyQt6 import QtWidgets, QtSvgWidgets + + +class ThemedSvgWidget(QtSvgWidgets.QSvgWidget): + def __init__(self, themedIcon: str | ThemedIcon, parent: QtWidgets.QWidget | None = None): + self.themedIcon = themedIcon + super().__init__(self.themedIcon.path, parent=parent) + ThemeManager.themeUpdated.connect(self._loadIcon) + + def setIcon(self, themedIcon: str | ThemedIcon) -> None: + self.themedIcon = themedIcon + self._loadIcon() + + def _loadIcon(self) -> None: + if isinstance(self.themedIcon, str): + self.load(self.themedIcon) + else: + self.load(self.themedIcon.path) \ No newline at end of file diff --git a/Services/Twitch/Authentication/Integrity/IntegrityGenerator.py b/Services/Twitch/Authentication/Integrity/IntegrityGenerator.py index 7aad1e7..8f042bf 100644 --- a/Services/Twitch/Authentication/Integrity/IntegrityGenerator.py +++ b/Services/Twitch/Authentication/Integrity/IntegrityGenerator.py @@ -21,9 +21,9 @@ class IntegrityRequestInterceptor(QtWebEngineCore.QWebEngineUrlRequestIntercepto intercepted = QtCore.pyqtSignal(dict) def interceptRequest(self, info: QtWebEngineCore.QWebEngineUrlRequestInfo) -> None: - if info.requestUrl().toString() == Config.INTEGRITY_URL and info.requestMethod().data().decode() == "POST": + if info.requestUrl().toString() == Config.INTEGRITY_URL and info.requestMethod().data().decode(errors="ignore") == "POST": info.block(True) - self.intercepted.emit({key.data().decode(): value.data().decode() for key, value in info.httpHeaders().items()}) + self.intercepted.emit({key.data().decode(errors="ignore"): value.data().decode(errors="ignore") for key, value in info.httpHeaders().items()}) class TwitchIntegrityGenerator(QtCore.QObject): @@ -96,7 +96,7 @@ def _interceptedHandler(self, headers: dict) -> None: def _requestDone(self) -> None: if self._reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: try: - data = json.loads(self._reply.readAll().data().decode()) + data = json.loads(self._reply.readAll().data().decode(errors="ignore")) self.integrity = IntegrityToken( headers=self._headers, value=data["token"], diff --git a/Services/Twitch/Gql/TwitchGqlAPI.py b/Services/Twitch/Gql/TwitchGqlAPI.py index 61117ba..ccbd0e6 100644 --- a/Services/Twitch/Gql/TwitchGqlAPI.py +++ b/Services/Twitch/Gql/TwitchGqlAPI.py @@ -67,7 +67,7 @@ def _startRequest(self, headers: dict | None = None) -> None: def _replyFinished(self) -> None: if self._reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: - text = self._reply.readAll().data().decode() + text = self._reply.readAll().data().decode(errors="ignore") try: jsonData = json.loads(text) except: @@ -101,7 +101,7 @@ def _raiseException(self, exception: Exception) -> None: self._error = exception self._setFinished() - def getError(self) -> Exception: + def getError(self) -> Exception | None: return self._error def getData(self) -> TwitchGQLModels.TwitchGQLObject: diff --git a/Services/Twitch/Playback/TwitchPlaybackGenerator.py b/Services/Twitch/Playback/TwitchPlaybackGenerator.py index 6a32528..d700330 100644 --- a/Services/Twitch/Playback/TwitchPlaybackGenerator.py +++ b/Services/Twitch/Playback/TwitchPlaybackGenerator.py @@ -121,7 +121,7 @@ def _getStreamPlayback(self) -> None: def _replyFinished(self) -> None: if self._reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: - resolutions = VariantPlaylistReader.loads(self._reply.readAll().data().decode(), baseUrl=self._reply.url()) + resolutions = VariantPlaylistReader.loads(self._reply.readAll().data().decode(errors="ignore"), baseUrl=self._reply.url()) if len(resolutions) == 0: self._raiseException(Exceptions.ChannelIsOffline(self.login)) else: @@ -185,7 +185,7 @@ def _getVideoPlayback(self) -> None: def _replyFinished(self) -> None: if self._reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError: - resolutions = VariantPlaylistReader.loads(self._reply.readAll().data().decode(), baseUrl=self._reply.url()) + resolutions = VariantPlaylistReader.loads(self._reply.readAll().data().decode(errors="ignore"), baseUrl=self._reply.url()) if len(resolutions) == 0: self._raiseException(Exceptions.VideoNotFound(self.id)) else: diff --git a/Services/Utils/UiUtils.py b/Services/Utils/UiUtils.py index 228e946..9773b3a 100644 --- a/Services/Utils/UiUtils.py +++ b/Services/Utils/UiUtils.py @@ -1,7 +1,10 @@ from Core.App import T from Services.Image.Presets import Icons +from Services.Theme.ThemedIcon import ThemedIcon +from Services.Theme.ThemedIconViewer import ThemedIconViewer +from Services.Theme.ThemedSvgWidget import ThemedSvgWidget -from PyQt6 import QtGui, QtWidgets, QtSvgWidgets +from PyQt6 import QtGui, QtWidgets class UiUtils: @@ -12,9 +15,13 @@ def setPlaceholder(placeholder: QtWidgets.QWidget, widget: QtWidgets.QWidget) -> placeholder.deleteLater() return widget + @staticmethod + def setIconViewer(widget: QtWidgets.QWidget, icon: QtGui.QIcon | ThemedIcon | None) -> ThemedIconViewer: + return ThemedIconViewer(widget, icon) + @classmethod - def setSvgIcon(cls, placeholder: QtWidgets.QWidget, path: str) -> QtWidgets.QWidget: - svgWidget = QtSvgWidgets.QSvgWidget(path, parent=placeholder.parent()) + def setSvgIcon(cls, placeholder: QtWidgets.QWidget, icon: ThemedIcon) -> QtWidgets.QWidget: + svgWidget = ThemedSvgWidget(icon, parent=placeholder.parent()) svgWidget.setSizePolicy(placeholder.sizePolicy()) svgWidget.setMinimumSize(placeholder.minimumSize()) svgWidget.setMaximumSize(placeholder.maximumSize()) @@ -46,7 +53,7 @@ def ask(title: str, content: str, titleTranslate: bool = True, contentTranslate: @staticmethod def askDirectory(directory: str, parent: QtWidgets.QWidget | None = None) -> str | None: fileDialog = QtWidgets.QFileDialog(parent=parent) - fileDialog.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + fileDialog.setWindowIcon(Icons.APP_LOGO.icon) result = fileDialog.getExistingDirectory( parent=parent, caption=T("select-folder"), @@ -58,7 +65,7 @@ def askDirectory(directory: str, parent: QtWidgets.QWidget | None = None) -> str def askSaveAs(directory: str, filters: list[str], initialFilter: str | None = None, parent: QtWidgets.QWidget | None = None) -> str | None: mappedFilters = dict((T("#{fileFormat} file (*.{fileFormat})", fileFormat=key), key) for key in filters) fileDialog = QtWidgets.QFileDialog(parent=parent) - fileDialog.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + fileDialog.setWindowIcon(Icons.APP_LOGO.icon) result = fileDialog.getSaveFileName( parent=parent, caption=T("save-as"), diff --git a/Ui/Account.py b/Ui/Account.py index 48f19d9..aa475a3 100644 --- a/Ui/Account.py +++ b/Ui/Account.py @@ -14,12 +14,13 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self._ui = UiLoader.load("account", self) self._ui.profileImage.setImageSizePolicy(QtCore.QSize(50, 50), QtCore.QSize(300, 300)) self._ui.accountInfo.setText(T("#Log in and link the benefits of your Twitch account with {appName}.\n(Stream Ad-Free benefits, Subscriber-Only Stream access, Subscriber-Only Video access, Twitch Prime or Twitch Turbo benefits, etc.)", appName=CoreConfig.APP_NAME)) - self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED_ICON) + self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED) self._ui.loginButton.clicked.connect(self.login) self._ui.continueButton.clicked.connect(self.startLoginRequested) self._ui.cancelButton.clicked.connect(self.cancelLoginRequested) self._ui.logoutButton.clicked.connect(self.logout) self._ui.refreshAccountButton.clicked.connect(self.refreshAccount) + Utils.setIconViewer(self._ui.refreshAccountButton, Icons.RELOAD) self._ui.profileImage.imageChanged.connect(self.updateAccountImage) self._tempAccountData: AccountData | None = None App.Account.accountUpdated.connect(self.showAccount) diff --git a/Ui/Components/Operators/DocumentViewer.py b/Ui/Components/Operators/DocumentViewer.py index f701f4a..8a07030 100644 --- a/Ui/Components/Operators/DocumentViewer.py +++ b/Ui/Components/Operators/DocumentViewer.py @@ -26,13 +26,13 @@ def setCurrentWidget(self, widget: QtWidgets.QWidget) -> None: if not self.isModal(): super().setCurrentWidget(widget) - def showDocument(self, documentView: Ui.DocumentView, icon: str | QtGui.QIcon | None = None, uniqueValue: typing.Any = None, important: bool = False) -> None: + def showDocument(self, documentView: Ui.DocumentView, icon: QtGui.QIcon | ThemedIcon | None = None, uniqueValue: typing.Any = None, important: bool = False) -> None: if documentView.isModal(): self.setModal(True) - self.setCurrentIndex(self.addTab(documentView, index=0 if important else len(self.modals), icon=icon or Icons.ANNOUNCEMENT_ICON, closable=False, uniqueValue=uniqueValue)) + self.setCurrentIndex(self.addTab(documentView, index=0 if important else len(self.modals), icon=icon or Icons.ANNOUNCEMENT, closable=False, uniqueValue=uniqueValue)) self.modals.append(documentView) else: - self.setCurrentIndex(self.addTab(documentView, index=len(self.modals) if important else -1, icon=icon or Icons.TEXT_FILE_ICON, uniqueValue=uniqueValue)) + self.setCurrentIndex(self.addTab(documentView, index=len(self.modals) if important else -1, icon=icon or Icons.TEXT_FILE, uniqueValue=uniqueValue)) documentView.closeRequested.connect(self.closeDocument) def closeDocument(self, documentView: Ui.DocumentView) -> None: diff --git a/Ui/Components/Operators/NavigationBar.py b/Ui/Components/Operators/NavigationBar.py index 20a0511..80e4616 100644 --- a/Ui/Components/Operators/NavigationBar.py +++ b/Ui/Components/Operators/NavigationBar.py @@ -1,3 +1,7 @@ +from Core import App +from Services.Theme.ThemedIcon import ThemedIcon +from Services.Utils.Utils import Utils + from PyQt6 import QtCore, QtGui, QtWidgets @@ -7,19 +11,26 @@ class PageObject(QtCore.QObject): blockChanged = QtCore.pyqtSignal(object, bool) focusChanged = QtCore.pyqtSignal(object, bool) - def __init__(self, button: QtWidgets.QToolButton, widget: QtWidgets.QWidget, icon: QtGui.QIcon | None = None, parent: QtCore.QObject | None = None): + def __init__(self, button: QtWidgets.QToolButton, widget: QtWidgets.QWidget, icon: QtGui.QIcon | ThemedIcon | None = None, parent: QtCore.QObject | None = None): super().__init__(parent=parent) self.button = button + self.buttonIconViewer = Utils.setIconViewer(self.button, icon) self.widget = widget self.hidden = False self.blocked = False self.focused = False self.button.clicked.connect(self.show) - if icon != None: - self.setPageIcon(icon) + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + + def _setupThemeStyle(self) -> None: + if App.ThemeManager.isDarkModeEnabled(): + self.button.setStyleSheet("QToolButton {background: rgba(210, 179, 255, 255);border: none;} QToolButton:hover {background: rgba(200, 200, 200, 200);} QToolButton:checked {background: rgba(145, 71, 255, 255);}") + else: + self.button.setStyleSheet("QToolButton {background: rgba(255, 255, 255, 150);border: none;} QToolButton:hover {background: rgba(200, 200, 200, 255);} QToolButton:checked {background: rgba(255, 255, 255, 255);}") - def setPageIcon(self, icon: QtGui.QIcon, size: QtCore.QSize | None = None) -> None: - self.button.setIcon(icon) + def setPageIcon(self, icon: QtGui.QIcon | ThemedIcon | None, size: QtCore.QSize | None = None) -> None: + self.buttonIconViewer.setIcon(icon) self.button.setIconSize(size or QtCore.QSize(24, 24)) def setPageName(self, name: str) -> None: @@ -54,11 +65,20 @@ def unfocus(self) -> None: class NavigationBar(QtCore.QObject): focusChanged = QtCore.pyqtSignal(bool) - def __init__(self, stackedWidget: QtWidgets.QStackedWidget, parent: QtCore.QObject | None = None): + def __init__(self, buttonArea: QtWidgets.QWidget, stackedWidget: QtWidgets.QStackedWidget, parent: QtCore.QObject | None = None): super().__init__(parent=parent) + self.buttonArea = buttonArea self.stackedWidget = stackedWidget - self.pages = [] + self.pages: list[PageObject] = [] self.currentPage = None + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + + def _setupThemeStyle(self) -> None: + if App.ThemeManager.isDarkModeEnabled(): + self.buttonArea.setStyleSheet("#navigationBar {background: rgba(175, 154, 206, 230);}") + else: + self.buttonArea.setStyleSheet("#navigationBar {background: rgba(145, 71, 255, 255);}") def setPageButtonVisible(self, pageObject: PageObject, visible: bool) -> None: if visible: @@ -141,7 +161,7 @@ def getAvailablePages(self) -> list[PageObject]: unblockedPages = [pageObject for pageObject in self.pages if not pageObject.blocked] return [pageObject for pageObject in unblockedPages if pageObject.focused] or unblockedPages - def addPage(self, button: QtWidgets.QToolButton, widget: QtWidgets.QWidget, icon: QtGui.QIcon | None = None) -> PageObject: + def addPage(self, button: QtWidgets.QToolButton, widget: QtWidgets.QWidget, icon: QtGui.QIcon | ThemedIcon | None = None) -> PageObject: pageObject = PageObject(button, widget, icon=icon, parent=self) pageObject.showRequested.connect(self.setCurrentPage) pageObject.buttonVisibilityChanged.connect(self.setPageButtonVisible) diff --git a/Ui/Components/Operators/TabManager.py b/Ui/Components/Operators/TabManager.py index 58d5b39..4baf0a6 100644 --- a/Ui/Components/Operators/TabManager.py +++ b/Ui/Components/Operators/TabManager.py @@ -10,24 +10,43 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self.setDocumentMode(True) self.setTabsClosable(True) self.setMovable(True) - self.uniqueTabs = {} + self.uniqueTabs: dict[typing.Any, QtWidgets.QWidget] = {} + self._themedTabIcons: dict[QtWidgets.QWidget, ThemedIcon] = {} self.tabCloseRequested.connect(self.processTabCloseRequest) + App.ThemeManager.themeUpdated.connect(self._updateThemedTabIcons) - def addTab(self, widget: QtWidgets.QWidget, index: int = -1, icon: str | QtGui.QIcon = "", closable: bool = True, uniqueValue: typing.Any = None) -> int: + def addTab(self, widget: QtWidgets.QWidget, index: int = -1, icon: QtGui.QIcon | ThemedIcon | None = None, closable: bool = True, uniqueValue: typing.Any = None) -> int: if uniqueValue != None: tabIndex = self.getUniqueTabIndex(uniqueValue) if tabIndex != None: return tabIndex self.uniqueTabs[uniqueValue] = widget - tabIndex = self.insertTab(index, widget, icon if isinstance(icon, QtGui.QIcon) else QtGui.QIcon(icon), self.getInjectionSafeText(widget.windowTitle())) + if isinstance(icon, ThemedIcon): + self._themedTabIcons[widget] = icon + tabIcon = icon.icon + else: + tabIcon = icon or QtGui.QIcon() + tabIndex = self.insertTab(index, widget, tabIcon, self.getInjectionSafeText(widget.windowTitle())) self.setTabToolTip(tabIndex, widget.windowTitle()) if not closable: self.tabBar().setTabButton(tabIndex, QtWidgets.QTabBar.ButtonPosition.RightSide, None) self.tabCountChanged.emit(self.count()) return tabIndex - def setTabIcon(self, index: int, icon: str | QtGui.QIcon) -> None: - super().setTabIcon(index, icon if isinstance(icon, QtGui.QIcon) else QtGui.QIcon(icon)) + def setTabIcon(self, index: int, icon: QtGui.QIcon | ThemedIcon | None) -> None: + tabWidget = self.widget(index) + if isinstance(icon, ThemedIcon): + self._themedTabIcons[tabWidget] = icon + tabIcon = icon.icon + else: + if tabWidget in self._themedTabIcons: + self._themedTabIcons.pop(tabWidget) + tabIcon = icon or QtGui.QIcon() + super().setTabIcon(index, tabIcon) + + def _updateThemedTabIcons(self) -> None: + for tabWidget, themedIcon in self._themedTabIcons.items(): + super().setTabIcon(self.indexOf(tabWidget), themedIcon.icon) def setTabText(self, index: int, text: str) -> None: super().setTabText(index, self.getInjectionSafeText(text)) @@ -43,13 +62,15 @@ def getUniqueTabIndex(self, uniqueValue: typing.Any) -> int | None: return None def closeTab(self, index: int) -> None: - widget = self.widget(index) + tabWidget = self.widget(index) self.removeTab(index) + if tabWidget in self._themedTabIcons: + self._themedTabIcons.pop(tabWidget) for key, value in self.uniqueTabs.items(): - if value == widget: + if value == tabWidget: del self.uniqueTabs[key] break - widget.close() + tabWidget.close() self.tabCountChanged.emit(self.count()) def processTabCloseRequest(self, index: int) -> None: diff --git a/Ui/Components/Operators/WebViewTabManager.py b/Ui/Components/Operators/WebViewTabManager.py index c74cf8c..010233a 100644 --- a/Ui/Components/Operators/WebViewTabManager.py +++ b/Ui/Components/Operators/WebViewTabManager.py @@ -7,19 +7,23 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self.webViewWidgets = [] - def updateWebTabIcon(self, widget: Ui.WebViewWidget, icon: QtGui.QIcon) -> None: - self.setTabIcon(self.indexOf(widget), self.getDefaultIcon(widget._ui.webView, widget._ui.webView.url()) if icon.isNull() else icon) + def updateWebTabIcon(self, widget: Ui.WebViewWidget, icon: QtGui.QIcon | ThemedIcon) -> None: + if isinstance(icon, QtGui.QIcon) and icon.isNull(): + tabIcon = self.getDefaultIcon(widget._ui.webView, widget._ui.webView.url()) + else: + tabIcon = icon + self.setTabIcon(self.indexOf(widget), tabIcon) def updateWebTabTitle(self, widget: Ui.WebViewWidget, title: str) -> None: self.setTabText(self.indexOf(widget), title) - def getDefaultIcon(self, webView: QtWebEngineWidgets.QWebEngineView, url: QtCore.QUrl) -> str: + def getDefaultIcon(self, webView: QtWebEngineWidgets.QWebEngineView, url: QtCore.QUrl) -> ThemedIcon: if url.toString().startswith("file:///"): - return Icons.FOLDER_ICON + return Icons.FOLDER elif url.toString().startswith("devtools://"): if webView.page().inspectedPage() != None: - return Icons.SETTINGS_ICON - return Icons.WEB_ICON + return Icons.SETTINGS + return Icons.WEB def addWebTab(self, webViewWidget: Ui.WebViewWidget, index: int = -1, closable: bool = True, uniqueValue: typing.Any = None) -> int: webViewWidget.iconChanged.connect(self.updateWebTabIcon) diff --git a/Ui/Components/Pages/AccountPage.py b/Ui/Components/Pages/AccountPage.py index 63d9231..b832641 100644 --- a/Ui/Components/Pages/AccountPage.py +++ b/Ui/Components/Pages/AccountPage.py @@ -14,13 +14,13 @@ def __init__(self, pageObject: PageObject, parent: QtWidgets.QWidget | None = No self.account.startLoginRequested.connect(self._startLoginRequested) self.account.cancelLoginRequested.connect(self._cancelLoginRequested) self.account.profileImageChanged.connect(self._profileImageChanged) - self.addTab(self.account, icon=Icons.ACCOUNT_ICON, closable=False) + self.addTab(self.account, icon=Icons.ACCOUNT, closable=False) self._profile: QtWebEngineCore.QWebEngineProfile | None = None self._closing = False def _profileImageChanged(self, image: QtGui.QPixmap | None) -> None: if image == None: - self.pageObject.setPageIcon(QtGui.QIcon(Icons.ACCOUNT_ICON)) + self.pageObject.setPageIcon(Icons.ACCOUNT) else: self.pageObject.setPageIcon(QtGui.QIcon(image), size=QtCore.QSize(32, 32)) diff --git a/Ui/Components/Pages/DownloadsPage.py b/Ui/Components/Pages/DownloadsPage.py index 3f91978..dc8d9b2 100644 --- a/Ui/Components/Pages/DownloadsPage.py +++ b/Ui/Components/Pages/DownloadsPage.py @@ -25,7 +25,7 @@ def __init__(self, pageObject: PageObject, parent: QtWidgets.QWidget | None = No self.downloads.accountPageShowRequested.connect(self.accountPageShowRequested) self.downloads.progressWindowRequested.connect(self.openDownloadTab) self.downloads.downloadHistoryRequested.connect(self.openDownloadHistory) - self.addTab(self.downloads, icon=Icons.FOLDER_ICON, closable=False) + self.addTab(self.downloads, icon=Icons.FOLDER, closable=False) App.DownloadManager.createdSignal.connect(self.downloaderCreated) App.DownloadManager.destroyedSignal.connect(self.downloaderDestroyed) App.DownloadManager.startedSignal.connect(self.downloadStarted) @@ -40,7 +40,7 @@ def openDownloadTab(self, downloaderId: uuid.UUID) -> None: if tabIndex == None: downloadTab = Ui.Download(downloaderId, parent=self) downloadTab.accountPageShowRequested.connect(self.accountPageShowRequested) - tabIndex = self.addTab(downloadTab, icon=Icons.DOWNLOAD_ICON, uniqueValue=downloaderId) + tabIndex = self.addTab(downloadTab, icon=Icons.DOWNLOAD, uniqueValue=downloaderId) self.setCurrentIndex(tabIndex) self.pageObject.show() @@ -54,7 +54,7 @@ def openDownloadHistory(self) -> None: if tabIndex == None: downloadHistoryTab = Ui.DownloadHistories(parent=self) downloadHistoryTab.accountPageShowRequested.connect(self.accountPageShowRequested) - tabIndex = self.addTab(downloadHistoryTab, icon=Icons.HISTORY_ICON, uniqueValue=Ui.DownloadHistories) + tabIndex = self.addTab(downloadHistoryTab, icon=Icons.HISTORY, uniqueValue=Ui.DownloadHistories) self.setCurrentIndex(tabIndex) self.pageObject.show() diff --git a/Ui/Components/Pages/InformationPage.py b/Ui/Components/Pages/InformationPage.py index a9a73c2..a566ee5 100644 --- a/Ui/Components/Pages/InformationPage.py +++ b/Ui/Components/Pages/InformationPage.py @@ -17,7 +17,7 @@ def __init__(self, pageObject: PageObject, parent: QtWidgets.QWidget | None = No App.Notifications.notificationsUpdated.connect(self.notificationsUpdated) self.notificationsUpdated(App.Notifications.getNotifications()) - def showDocument(self, documentData: DocumentData, icon: str | QtGui.QIcon | None = None, uniqueValue: typing.Any = None, important: bool = False) -> Ui.DocumentView: + def showDocument(self, documentData: DocumentData, icon: QtGui.QIcon | ThemedIcon | None = None, uniqueValue: typing.Any = None, important: bool = False) -> Ui.DocumentView: documentView = Ui.DocumentView(documentData, parent=self) super().showDocument(documentView, icon=icon, uniqueValue=uniqueValue, important=important) self.pageObject.show() @@ -25,7 +25,7 @@ def showDocument(self, documentData: DocumentData, icon: str | QtGui.QIcon | Non def openAbout(self) -> None: tabIndex = self.getUniqueTabIndex(Ui.About) - self.setCurrentIndex(self.addTab(Ui.About(parent=self), icon=Icons.INFO_ICON, uniqueValue=Ui.About) if tabIndex == None else tabIndex) + self.setCurrentIndex(self.addTab(Ui.About(parent=self), icon=Icons.INFO, uniqueValue=Ui.About) if tabIndex == None else tabIndex) self.pageObject.show() def openTermsOfService(self) -> None: @@ -51,7 +51,7 @@ def _updatePageState(self) -> None: self.pageObject.showButton() self.pageObject.unblock() - def showAppInfo(self, documentData: DocumentData, icon: str | QtGui.QIcon | None = None) -> Ui.DocumentView: + def showAppInfo(self, documentData: DocumentData, icon: QtGui.QIcon | ThemedIcon | None = None) -> Ui.DocumentView: return self.showDocument( documentData=documentData, icon=icon, @@ -72,5 +72,5 @@ def notificationsUpdated(self, notifications: list[DocumentData]) -> None: oldVersionIndex = self.getUniqueTabIndex(uniqueValue) if oldVersionIndex != None: self.closeTab(oldVersionIndex) - self.showDocument(notification, icon=None if notification.modal else Icons.NOTICE_ICON, uniqueValue=uniqueValue) + self.showDocument(notification, icon=None if notification.modal else Icons.NOTICE, uniqueValue=uniqueValue) self.setCurrentIndex(newIndex) \ No newline at end of file diff --git a/Ui/Components/Pages/ScheduledDownloadsPage.py b/Ui/Components/Pages/ScheduledDownloadsPage.py index 324dd2f..20d12d6 100644 --- a/Ui/Components/Pages/ScheduledDownloadsPage.py +++ b/Ui/Components/Pages/ScheduledDownloadsPage.py @@ -8,7 +8,7 @@ def __init__(self, pageObject: PageObject, parent: QtWidgets.QWidget | None = No super().__init__(parent=parent) self.pageObject = pageObject self.scheduledDownloads = Ui.ScheduledDownloads(parent=self) - self.addTab(self.scheduledDownloads, icon=Icons.FOLDER_ICON, closable=False) + self.addTab(self.scheduledDownloads, icon=Icons.FOLDER, closable=False) App.ScheduledDownloadManager.downloaderCountChangedSignal.connect(self._changePageText) def _changePageText(self, downloadersCount: int) -> None: diff --git a/Ui/Components/Pages/SearchPage.py b/Ui/Components/Pages/SearchPage.py index c647a49..c4b377e 100644 --- a/Ui/Components/Pages/SearchPage.py +++ b/Ui/Components/Pages/SearchPage.py @@ -12,10 +12,10 @@ def __init__(self, pageObject: PageObject, parent: QtWidgets.QWidget | None = No self.pageObject = pageObject self.home = Ui.Home(parent=self) self.home.searchResultWindowRequested.connect(self.openSearchResultTab) - self.addTab(self.home, icon=Icons.HOME_ICON, closable=False) + self.addTab(self.home, icon=Icons.HOME, closable=False) def openSearchResultTab(self, searchResult: TwitchGQLModels.Channel | TwitchGQLModels.Video | TwitchGQLModels.Clip) -> None: searchResultTab = Ui.SearchResult(searchResult, parent=self) searchResultTab.accountPageShowRequested.connect(self.accountPageShowRequested) - self.setCurrentIndex(self.addTab(searchResultTab, icon=Icons.SEARCH_ICON)) + self.setCurrentIndex(self.addTab(searchResultTab, icon=Icons.SEARCH)) self.pageObject.show() \ No newline at end of file diff --git a/Ui/Components/Utils/FileNameGenerator.py b/Ui/Components/Utils/FileNameGenerator.py index 003bf99..bbbde33 100644 --- a/Ui/Components/Utils/FileNameGenerator.py +++ b/Ui/Components/Utils/FileNameGenerator.py @@ -37,6 +37,7 @@ def getDatetimeVariables(name: str, datetime: QtCore.QDateTime) -> dict[str, str localDatetime = datetime.toTimeZone(App.Preferences.localization.getTimezone()) return { name: localDatetime.toString("yyyy-MM-dd HH:mm:ss"), + "unix_time": f"{localDatetime.toSecsSinceEpoch()}", "date": localDatetime.date().toString("yyyy-MM-dd"), "year": f"{localDatetime.date().year():04}", "month": f"{localDatetime.date().month():02}", @@ -153,6 +154,7 @@ def getNameInfo(nameType: str) -> dict[str, str]: def getTimeInfo(timeType: str) -> dict[str, str]: return { f"{{{timeType}_at}}": f"{T(f'{timeType}-at')} (YYYY-MM-DD HH:MM:SS)", + "{unix_time}": f"{T(f'{timeType}-at')} [{T(f'unix-time')}] (XXXXXXXXXX)", "{date}": f"{T(f'{timeType}-date')} (YYYY-MM-DD)", "{year}": f"{T(f'{timeType}-date')} - {T('year')}", "{month}": f"{T(f'{timeType}-date')} - {T('month')}", diff --git a/Ui/Components/Widgets/DownloadButton.py b/Ui/Components/Widgets/DownloadButton.py index b06a1db..7d59f74 100644 --- a/Ui/Components/Widgets/DownloadButton.py +++ b/Ui/Components/Widgets/DownloadButton.py @@ -11,12 +11,14 @@ class DownloadButton(QtCore.QObject): accountPageShowRequested = QtCore.pyqtSignal() - def __init__(self, content: TwitchGQLModels.Channel | TwitchGQLModels.Stream | TwitchGQLModels.Video | TwitchGQLModels.Clip, button: QtWidgets.QPushButton | QtWidgets.QToolButton, buttonText: str | None = None, parent: QtCore.QObject | None = None): + def __init__(self, content: TwitchGQLModels.Channel | TwitchGQLModels.Stream | TwitchGQLModels.Video | TwitchGQLModels.Clip, button: QtWidgets.QPushButton | QtWidgets.QToolButton, buttonIcon: ThemedIcon | None = None, buttonText: str | None = None, parent: QtCore.QObject | None = None): super().__init__(parent=parent) self.button = button self.buttonText = buttonText self.content = content self.showLoading(False) + if buttonIcon != None: + Utils.setIconViewer(self.button, buttonIcon) if isinstance(content, TwitchGQLModels.Channel): self.button.setEnabled(False) elif isinstance(content, TwitchGQLModels.Stream): diff --git a/Ui/Components/Widgets/InstantDownloadButton.py b/Ui/Components/Widgets/InstantDownloadButton.py index db3ab1d..7908f3b 100644 --- a/Ui/Components/Widgets/InstantDownloadButton.py +++ b/Ui/Components/Widgets/InstantDownloadButton.py @@ -6,8 +6,8 @@ class InstantDownloadButton(DownloadButton): - def __init__(self, content: TwitchGQLModels.Channel | TwitchGQLModels.Stream | TwitchGQLModels.Video | TwitchGQLModels.Clip, button: QtWidgets.QPushButton | QtWidgets.QToolButton, buttonText: str | None = None, parent: QtCore.QObject | None = None): - super().__init__(content, button, buttonText, parent=parent) + def __init__(self, content: TwitchGQLModels.Channel | TwitchGQLModels.Stream | TwitchGQLModels.Video | TwitchGQLModels.Clip, button: QtWidgets.QPushButton | QtWidgets.QToolButton, buttonIcon: ThemedIcon | None = None, buttonText: str | None = None, parent: QtCore.QObject | None = None): + super().__init__(content, button, buttonIcon, buttonText, parent=parent) def showStreamAdWarning(self) -> bool: return True diff --git a/Ui/Components/Widgets/LoginWidget.py b/Ui/Components/Widgets/LoginWidget.py index 8c96186..bbc2b50 100644 --- a/Ui/Components/Widgets/LoginWidget.py +++ b/Ui/Components/Widgets/LoginWidget.py @@ -29,9 +29,9 @@ def showLoginPage(self) -> None: def urlChangeHandler(self, url: QtCore.QUrl) -> None: super().urlChangeHandler(url) if url.toString() != Config.LOGIN_PAGE: - self.showInfo(T("#You left the login page."), icon=Icons.ALERT_RED_ICON, buttonIcon=Icons.LOGIN_ICON, buttonText=T("#Return to login page"), buttonHandler=self.showLoginPage) + self.showInfo(T("#You left the login page."), icon=Icons.ALERT_RED, buttonIcon=Icons.LOGIN, buttonText=T("#Return to login page"), buttonHandler=self.showLoginPage) else: - self.showInfo(T("#Please follow the login procedure."), icon=Icons.INFO_ICON) + self.showInfo(T("#Please follow the login procedure."), icon=Icons.INFO) def hasAccountData(self) -> bool: return self.accountData.username != None and self.accountData.token != None @@ -46,10 +46,10 @@ def setTokenData(self, token: str, expiration: QtCore.QDateTime) -> None: def _getOAuthToken(self, cookie: QtNetwork.QNetworkCookie) -> None: if cookie.domain() == Config.COOKIE_DOMAIN: if cookie.name() == Config.COOKIE_USERNAME: - self.setUsernameData(cookie.value().data().decode()) + self.setUsernameData(cookie.value().data().decode(errors="ignore")) self._checkToken() elif cookie.name() == Config.COOKIE_AUTH_TOKEN: - self.setTokenData(cookie.value().data().decode(), cookie.expirationDate()) + self.setTokenData(cookie.value().data().decode(errors="ignore"), cookie.expirationDate()) self._checkToken() def _checkToken(self) -> None: diff --git a/Ui/Components/Widgets/RetryDownloadButton.py b/Ui/Components/Widgets/RetryDownloadButton.py index 0575978..c571a85 100644 --- a/Ui/Components/Widgets/RetryDownloadButton.py +++ b/Ui/Components/Widgets/RetryDownloadButton.py @@ -8,8 +8,8 @@ class RetryDownloadButton(DownloadButton): - def __init__(self, downloadInfo: DownloadInfo, button: QtWidgets.QPushButton | QtWidgets.QToolButton, downloaderId: uuid.UUID | None = None, buttonText: str | None = None, parent: QtCore.QObject | None = None): - super().__init__(downloadInfo.content, button, buttonText, parent=parent) + def __init__(self, downloadInfo: DownloadInfo, button: QtWidgets.QPushButton | QtWidgets.QToolButton, buttonIcon: ThemedIcon | None = None, buttonText: str | None = None, downloaderId: uuid.UUID | None = None, parent: QtCore.QObject | None = None): + super().__init__(downloadInfo.content, button, buttonIcon, buttonText, parent=parent) self.downloadInfo = downloadInfo self.downloaderId = downloaderId if isinstance(self.downloadInfo.playback, ExternalPlaybackGenerator.ExternalPlayback): diff --git a/Ui/Download.py b/Ui/Download.py index f29f032..1724653 100644 --- a/Ui/Download.py +++ b/Ui/Download.py @@ -27,13 +27,16 @@ def __init__(self, downloaderId: uuid.UUID, parent: QtWidgets.QWidget | None = N self._ui.downloadInfoView = Utils.setPlaceholder(self._ui.downloadInfoView, Ui.DownloadInfoView(parent=self)) self._ui.downloadInfoView.setThumbnailImageSizePolicy(QtCore.QSize(480, 270), QtCore.QSize(1920, 1080)) self._ui.downloadInfoView.setCategoryImageSize(QtCore.QSize(45, 60)) - self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED_ICON) + self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED) self._ui.statusInfoButton.clicked.connect(self.showErrorInfo) + Utils.setIconViewer(self._ui.statusInfoButton, Icons.HELP) self._updateTrackInfoDisplay = UpdateTrackInfoDisplay(target=self._ui.updateTrackInfo, parent=self) self._ui.pauseButton.clicked.connect(self.pauseResume) self._ui.cancelButton.clicked.connect(self.cancel) self._ui.openFolderButton.clicked.connect(self.openFolder) + Utils.setIconViewer(self._ui.openFolderButton, Icons.FOLDER) self._ui.openFileButton.clicked.connect(self.openFile) + Utils.setIconViewer(self._ui.openFileButton, Icons.FILE) self._downloader: StreamDownloader | VideoDownloader | ClipDownloader | None = None self._exception: Exception | None = None self.connectDownloader(App.DownloadManager.get(downloaderId)) @@ -50,8 +53,9 @@ def connectDownloader(self, downloader: StreamDownloader | VideoDownloader | Cli self._retryButtonManager = RetryDownloadButton( downloadInfo=self._downloader.downloadInfo, button=self._ui.retryButton, - downloaderId=self.downloaderId, + buttonIcon=Icons.RETRY, buttonText=self._ui.retryButton.text(), + downloaderId=self.downloaderId, parent=self ) self._retryButtonManager.accountPageShowRequested.connect(self.accountPageShowRequested) diff --git a/Ui/DownloadHistories.py b/Ui/DownloadHistories.py index deceef3..2a5bb72 100644 --- a/Ui/DownloadHistories.py +++ b/Ui/DownloadHistories.py @@ -10,14 +10,18 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self.previewWidgets = {} self._ui = UiLoader.load("downloadHistories", self) - self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.HISTORY_ICON) - self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {self._ui.stackedWidget.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.HISTORY) self._widgetListViewer = PartnerContentInFeedWidgetListViewer(self._ui.previewWidgetView, partnerContentSize=QtCore.QSize(320, 100), parent=self) self._widgetListViewer.widgetClicked.connect(self.openFile) App.DownloadHistory.historyCreated.connect(self.createHistoryView) App.DownloadHistory.historyRemoved.connect(self.removeHistoryView) self.loadHistory() + def _setupThemeStyle(self) -> None: + self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {App.Instance.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") + def loadHistory(self) -> None: self._widgetListViewer.setAutoReloadEnabled(False) for downloadHistory in App.DownloadHistory.getHistoryList(): diff --git a/Ui/DownloadMenu.py b/Ui/DownloadMenu.py index 76d7e46..07faa92 100644 --- a/Ui/DownloadMenu.py +++ b/Ui/DownloadMenu.py @@ -16,8 +16,8 @@ def __init__(self, downloadInfo: DownloadInfo, parent: QtWidgets.QWidget | None self.setWindowGeometryKey(f"{self.getWindowGeometryKey()}/{self.downloadInfo.type.toString()}") self.loadWindowGeometry() self._ui.videoWidget = Utils.setPlaceholder(self._ui.videoWidget, Ui.VideoWidget(self.downloadInfo.content, parent=self)) - self._ui.cropSettingsInfoIcon = Utils.setSvgIcon(self._ui.cropSettingsInfoIcon, Icons.INFO_ICON) - self._ui.cropRangeInfoIcon = Utils.setSvgIcon(self._ui.cropRangeInfoIcon, Icons.ALERT_RED_ICON) + self._ui.cropSettingsInfoIcon = Utils.setSvgIcon(self._ui.cropSettingsInfoIcon, Icons.INFO) + self._ui.cropRangeInfoIcon = Utils.setSvgIcon(self._ui.cropRangeInfoIcon, Icons.ALERT_RED) self.loadOptions() def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: @@ -50,12 +50,15 @@ def loadOptions(self) -> None: self._ui.unmuteVideoCheckBox.setChecked(self.downloadInfo.isUnmuteVideoEnabled()) self._ui.unmuteVideoCheckBox.toggled.connect(self.downloadInfo.setUnmuteVideoEnabled) self._ui.unmuteVideoInfo.clicked.connect(self.showUnmuteVideoInfo) + Utils.setIconViewer(self._ui.unmuteVideoInfo, Icons.HELP) self._ui.updateTrackCheckBox.setChecked(self.downloadInfo.isUpdateTrackEnabled()) self._ui.updateTrackCheckBox.toggled.connect(self.setUpdateTrackEnabled) self._ui.updateTrackInfo.clicked.connect(self.showUpdateTrackInfo) + Utils.setIconViewer(self._ui.updateTrackInfo, Icons.HELP) self._ui.prioritizeCheckBox.setChecked(self.downloadInfo.isPrioritizeEnabled()) self._ui.prioritizeCheckBox.toggled.connect(self.downloadInfo.setPrioritizeEnabled) self._ui.prioritizeInfo.clicked.connect(self.showPrioritizeInfo) + Utils.setIconViewer(self._ui.prioritizeInfo, Icons.HELP) self.reloadCropArea() else: self._ui.cropArea.hide() @@ -66,6 +69,7 @@ def loadOptions(self) -> None: self._ui.prioritizeCheckBox.setChecked(self.downloadInfo.isPrioritizeEnabled()) self._ui.prioritizeCheckBox.toggled.connect(self.downloadInfo.setPrioritizeEnabled) self._ui.prioritizeInfo.clicked.connect(self.showPrioritizeInfo) + Utils.setIconViewer(self._ui.prioritizeInfo, Icons.HELP) def reloadFileDirectory(self) -> None: self._ui.currentDirectory.setText(self.downloadInfo.getAbsoluteFileName()) @@ -108,6 +112,7 @@ def setupCropArea(self) -> None: self._ui.toSpinM.valueChanged.connect(self.endRangeChanged) self._ui.toSpinS.valueChanged.connect(self.endRangeChanged) self._ui.cropSettingsInfoButton.clicked.connect(self.showCropInfo) + Utils.setIconViewer(self._ui.cropSettingsInfoButton, Icons.HELP) startMilliseconds, endMilliseconds = self.downloadInfo.getCropRangeMilliseconds() if startMilliseconds != None: self._ui.cropFromSelectRadioButton.setChecked(True) @@ -121,12 +126,14 @@ def setupAdBlockArea(self) -> None: self._ui.adBlockAlternativeScreenRadioButton.setChecked(not self.downloadInfo.isSkipAdsEnabled()) self._ui.adBlockSkipSegmentsRadioButton.toggled.connect(self.downloadInfo.setSkipAdsEnabled) self._ui.adBlockInfo.clicked.connect(self.showAdBlockInfo) + Utils.setIconViewer(self._ui.adBlockInfo, Icons.HELP) def setupEncoderArea(self) -> None: self._ui.remuxRadioButton.setChecked(self.downloadInfo.isRemuxEnabled()) self._ui.concatRadioButton.setChecked(not self.downloadInfo.isRemuxEnabled()) self._ui.remuxRadioButton.toggled.connect(self.downloadInfo.setRemuxEnabled) self._ui.encoderInfo.clicked.connect(self.showEncoderInfo) + Utils.setIconViewer(self._ui.encoderInfo, Icons.HELP) def startRangeChanged(self) -> None: self.setFromSeconds(self.checkCropRange(self.getFromSeconds(), maximum=int(self.downloadInfo.content.lengthSeconds) - 1)) diff --git a/Ui/DownloadViewControlBar.py b/Ui/DownloadViewControlBar.py index 7e96f70..676c3f7 100644 --- a/Ui/DownloadViewControlBar.py +++ b/Ui/DownloadViewControlBar.py @@ -1,4 +1,5 @@ from Core.Ui import * +from Services.Theme.ThemedIcon import ThemedIcon from Services.Twitch.GQL.TwitchGQLModels import Channel, Stream, Video, Clip from Search import ExternalPlaybackGenerator from Download.DownloadInfo import DownloadInfo @@ -15,9 +16,10 @@ class DownloadViewControlButtonTypes(enum.Enum): FileNotFound = 5 class DownloadViewControlButton: - def __init__(self, button: QtWidgets.QPushButton): + def __init__(self, button: QtWidgets.QPushButton, icon: ThemedIcon | None = None): self.button = button - self._icon = self.button.icon() + self.defaultIcon = icon + self._buttonIconViewer = Utils.setIconViewer(self.button, icon) self._toolTip = self.button.toolTip() self.setHidden() @@ -31,23 +33,23 @@ def set(self, type: DownloadViewControlButtonTypes) -> None: else: if type == DownloadViewControlButtonTypes.Visible: self.button.setEnabled(True) - self.button.setIcon(self._icon) + self._buttonIconViewer.setIcon(self.defaultIcon) self.button.setToolTip(self._toolTip) elif type == DownloadViewControlButtonTypes.Disabled: self.button.setEnabled(False) - self.button.setIcon(self._icon) + self._buttonIconViewer.setIcon(self.defaultIcon) self.button.setToolTip(self._toolTip) elif type == DownloadViewControlButtonTypes.Creating: self.button.setEnabled(False) - self.button.setIcon(QtGui.QIcon(Icons.CREATING_FILE_ICON)) + self._buttonIconViewer.setIcon(Icons.CREATING_FILE) self.button.setToolTip(f"{self._toolTip} ({T('creating', ellipsis=True)})") elif type == DownloadViewControlButtonTypes.Downloading: self.button.setEnabled(False) - self.button.setIcon(QtGui.QIcon(Icons.DOWNLOADING_FILE_ICON)) + self._buttonIconViewer.setIcon(Icons.DOWNLOADING_FILE) self.button.setToolTip(f"{self._toolTip} ({T('downloading', ellipsis=True)})") elif type == DownloadViewControlButtonTypes.FileNotFound: self.button.setEnabled(False) - self.button.setIcon(QtGui.QIcon(Icons.FILE_NOT_FOUND_ICON)) + self._buttonIconViewer.setIcon(Icons.FILE_NOT_FOUND) self.button.setToolTip(f"{self._toolTip} ({T('file-not-found')})") self.button.show() @@ -74,15 +76,15 @@ class DownloadViewControlBar(QtWidgets.QWidget): def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._ui = UiLoader.load("downloadViewControlBar", self) - self._ui.viewIcon = Utils.setSvgIcon(self._ui.viewIcon, Icons.VIEWER_ICON) - self.adsInfoButton = DownloadViewControlButton(self._ui.adsInfoButton) + self._ui.viewIcon = Utils.setSvgIcon(self._ui.viewIcon, Icons.VIEWER) + self.adsInfoButton = DownloadViewControlButton(self._ui.adsInfoButton, icon=Icons.WARNING_RED) self.adsInfoButton.clicked.connect(self._showAdsInfo) - self.retryButton = DownloadViewControlButton(self._ui.retryButton) - self.openFolderButton = DownloadViewControlButton(self._ui.openFolderButton) - self.openFileButton = DownloadViewControlButton(self._ui.openFileButton) - self.openLogsButton = DownloadViewControlButton(self._ui.openLogsButton) - self.deleteButton = DownloadViewControlButton(self._ui.deleteButton) - self.closeButton = DownloadViewControlButton(self._ui.closeButton) + self.retryButton = DownloadViewControlButton(self._ui.retryButton, icon=Icons.RETRY) + self.openFolderButton = DownloadViewControlButton(self._ui.openFolderButton, icon=Icons.FOLDER) + self.openFileButton = DownloadViewControlButton(self._ui.openFileButton, icon=Icons.FILE) + self.openLogsButton = DownloadViewControlButton(self._ui.openLogsButton, icon=Icons.TEXT_FILE) + self.deleteButton = DownloadViewControlButton(self._ui.deleteButton, icon=Icons.TRASH) + self.closeButton = DownloadViewControlButton(self._ui.closeButton, icon=Icons.CLOSE) def showContentInfo(self, content: Channel | Stream | Video | Clip) -> None: if isinstance(content, Channel): diff --git a/Ui/DownloaderView.py b/Ui/DownloaderView.py index 48d7048..c2f03ff 100644 --- a/Ui/DownloaderView.py +++ b/Ui/DownloaderView.py @@ -20,8 +20,9 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._ui = UiLoader.load("downloaderView", self) self._ui.downloadInfoView = Utils.setPlaceholder(self._ui.downloadInfoView, Ui.DownloadInfoView(parent=self)) - self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED_ICON) + self._ui.alertIcon = Utils.setSvgIcon(self._ui.alertIcon, Icons.ALERT_RED) self._ui.statusInfoButton.clicked.connect(self.showErrorInfo) + Utils.setIconViewer(self._ui.statusInfoButton, Icons.HELP) self._updateTrackInfoDisplay = UpdateTrackInfoDisplay(target=self._ui.updateTrackInfo, parent=self) self._downloader: StreamDownloader | VideoDownloader | ClipDownloader | None = None self._exception: Exception | None = None diff --git a/Ui/Downloads.py b/Ui/Downloads.py index 657fc3c..1db6825 100644 --- a/Ui/Downloads.py +++ b/Ui/Downloads.py @@ -14,16 +14,22 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._previewWidgets = {} self._ui = UiLoader.load("downloads", self) + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.STORAGE) self._ui.typeFilter.currentIndexChanged.connect(self.updateFilter) self._ui.statusFilter.currentIndexChanged.connect(self.updateFilter) self.updateFilter() - self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.STORAGE_ICON) - self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {self._ui.stackedWidget.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") self._widgetListViewer = PartnerContentInFeedWidgetListViewer(self._ui.previewWidgetView, partnerContentSize=QtCore.QSize(320, 100), parent=self) self._widgetListViewer.widgetClicked.connect(self.openProgressWindow) self.showStats() self._ui.downloadHistoryButton.clicked.connect(self.downloadHistoryRequested) + Utils.setIconViewer(self._ui.downloadHistoryButton, Icons.HISTORY) self._ui.scheduledShutdownInfo.clicked.connect(self.showScheduledShutdownInfo) + Utils.setIconViewer(self._ui.scheduledShutdownInfo, Icons.HELP) + + def _setupThemeStyle(self) -> None: + self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {App.Instance.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") def downloaderCreated(self, downloaderId: uuid.UUID) -> None: widget = Ui.DownloadPreview(downloaderId, parent=None) diff --git a/Ui/Loading.py b/Ui/Loading.py index 2f7ea79..a79dc2c 100644 --- a/Ui/Loading.py +++ b/Ui/Loading.py @@ -8,7 +8,7 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._ui = UiLoader.load("loading", self) self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint) - self.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setWindowIcon(Icons.APP_LOGO.icon) self._ui.appLogo.setMargin(10) self._ui.appName.setText(Config.APP_NAME) self._ui.version.setText(f"{Config.APP_NAME} {Config.APP_VERSION}") diff --git a/Ui/MainWindow.py b/Ui/MainWindow.py index 6b5ca3c..b2862f1 100644 --- a/Ui/MainWindow.py +++ b/Ui/MainWindow.py @@ -38,19 +38,19 @@ def onLoadingComplete(self) -> None: self.setup() def loadComponents(self) -> None: - self.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setWindowIcon(Icons.APP_LOGO.icon) self._ui.actionGettingStarted.triggered.connect(self.gettingStarted) self._ui.actionAbout.triggered.connect(self.openAbout) self._ui.actionTermsOfService.triggered.connect(self.openTermsOfService) self._ui.actionSponsor.triggered.connect(self.sponsor) - self.navigationBar = NavigationBar(self._ui.navigationArea, parent=self) + self.navigationBar = NavigationBar(self._ui.navigationBar, self._ui.navigationArea, parent=self) self.navigationBar.focusChanged.connect(self.onFocusChange) - self.searchPageObject = self.navigationBar.addPage(self._ui.searchPageButton, self._ui.searchPage, icon=QtGui.QIcon(Icons.SEARCH_ICON)) - self.downloadsPageObject = self.navigationBar.addPage(self._ui.downloadsPageButton, self._ui.downloadsPage, icon=QtGui.QIcon(Icons.DOWNLOAD_ICON)) - self.scheduledDownloadsPageObject = self.navigationBar.addPage(self._ui.scheduledDownloadsPageButton, self._ui.scheduledDownloadsPage, icon=QtGui.QIcon(Icons.SCHEDULED_ICON)) - self.accountPageObject = self.navigationBar.addPage(self._ui.accountPageButton, self._ui.accountPage, icon=QtGui.QIcon(Icons.ACCOUNT_ICON)) - self.settingsPageObject = self.navigationBar.addPage(self._ui.settingsPageButton, self._ui.settingsPage, icon=QtGui.QIcon(Icons.SETTINGS_ICON)) - self.informationPageObject = self.navigationBar.addPage(self._ui.informationPageButton, self._ui.informationPage, icon=QtGui.QIcon(Icons.INFO_ICON)) + self.searchPageObject = self.navigationBar.addPage(self._ui.searchPageButton, self._ui.searchPage, icon=Icons.SEARCH) + self.downloadsPageObject = self.navigationBar.addPage(self._ui.downloadsPageButton, self._ui.downloadsPage, icon=Icons.DOWNLOAD) + self.scheduledDownloadsPageObject = self.navigationBar.addPage(self._ui.scheduledDownloadsPageButton, self._ui.scheduledDownloadsPage, icon=Icons.SCHEDULED) + self.accountPageObject = self.navigationBar.addPage(self._ui.accountPageButton, self._ui.accountPage, icon=Icons.ACCOUNT) + self.settingsPageObject = self.navigationBar.addPage(self._ui.settingsPageButton, self._ui.settingsPage, icon=Icons.SETTINGS) + self.informationPageObject = self.navigationBar.addPage(self._ui.informationPageButton, self._ui.informationPage, icon=Icons.INFO) self.search = SearchPage(self.searchPageObject, parent=self) self.search.accountPageShowRequested.connect(self.accountPageObject.show) self._ui.searchPage.layout().addWidget(self.search) @@ -94,7 +94,7 @@ def setup(self) -> None: if Utils.isFile(Config.TRACEBACK_FILE): file = QtCore.QFile(Config.TRACEBACK_FILE, self) if file.open(QtCore.QIODevice.OpenModeFlag.ReadOnly): - fileName = file.readAll().data().decode() + fileName = file.readAll().data().decode(errors="ignore") url = Utils.joinUrl(Config.HOMEPAGE_URL, "report", params={"lang": App.Translator.getLanguage()}) self.information.showAppInfo( DocumentData( @@ -224,7 +224,7 @@ def statusUpdated(self, isInSetup: bool = False) -> None: DocumentButtonData(text=T("cancel"), action=(self.shutdown if isInSetup else self.confirmShutdown) if status == App.Updater.status.Types.UPDATE_REQUIRED else None, role="reject", default=False) ] ), - icon=Icons.UPDATE_FOUND_ICON + icon=Icons.UPDATE_FOUND ) def onFocusChange(self, focus: bool) -> None: diff --git a/Ui/PropertyView.py b/Ui/PropertyView.py index a35a32a..566571b 100644 --- a/Ui/PropertyView.py +++ b/Ui/PropertyView.py @@ -62,10 +62,13 @@ def setPreviewTab(self) -> None: self._ui.urlArea.setEnabled(False) else: self._ui.saveImageButton.clicked.connect(self.saveImage) + Utils.setIconViewer(self._ui.saveImageButton, Icons.SAVE) self._ui.urlData.setText(self.embedUrl) self._ui.urlData.setToolTip(self.embedUrl) self._ui.copyUrlButton.clicked.connect(self.copyUrl) + Utils.setIconViewer(self._ui.copyUrlButton, Icons.COPY) self._ui.openUrlButton.clicked.connect(self.openUrl) + Utils.setIconViewer(self._ui.openUrlButton, Icons.LAUNCH) def saveImage(self) -> None: VideoWidgetImageSaver.saveImage(self.targetVideoWidget.content, self.targetVideoWidget.thumbnailImage.pixmap(), parent=self) diff --git a/Ui/ScheduledDownloadPreview.py b/Ui/ScheduledDownloadPreview.py index 8ee47f2..2a0d5fb 100644 --- a/Ui/ScheduledDownloadPreview.py +++ b/Ui/ScheduledDownloadPreview.py @@ -26,6 +26,15 @@ def __init__(self, scheduledDownloadId: uuid.UUID, parent: QtWidgets.QWidget | N self._ui.downloadViewControlBar.openFileButton.clicked.connect(self.openFile) self._ui.downloadViewControlBar.openFileButton.setDisabled() self.scheduledDownload = App.ScheduledDownloadManager.get(self.scheduledDownloadId) + self._ui.networkAlertIcon = Utils.setSvgIcon(self._ui.networkAlertIcon, Icons.ALERT_RED) + self._ui.enableButton.clicked.connect(self._enableButtonClicked) + self._enableButtonIconViewer = Utils.setIconViewer(self._ui.enableButton, Icons.TOGGLE_OFF) + self._ui.refreshButton.clicked.connect(self.scheduledDownload.updateChannelData) + Utils.setIconViewer(self._ui.refreshButton, Icons.RELOAD) + self._ui.settingsButton.clicked.connect(self.editScheduledDownload) + Utils.setIconViewer(self._ui.settingsButton, Icons.SETTINGS) + self._ui.deleteButton.clicked.connect(self.tryRemoveScheduledDownload) + Utils.setIconViewer(self._ui.deleteButton, Icons.TRASH) self.scheduledDownload.activeChanged.connect(self._activeChanged) self._activeChanged() self.scheduledDownload.channelDataUpdateStarted.connect(self._channelDataUpdateStarted) @@ -40,11 +49,6 @@ def __init__(self, scheduledDownloadId: uuid.UUID, parent: QtWidgets.QWidget | N self._channelDataUpdateFinished() self._showChannel() self._showPubSubState() - self._ui.networkAlertIcon = Utils.setSvgIcon(self._ui.networkAlertIcon, Icons.ALERT_RED_ICON) - self._ui.enableButton.clicked.connect(self._enableButtonClicked) - self._ui.refreshButton.clicked.connect(self.scheduledDownload.updateChannelData) - self._ui.settingsButton.clicked.connect(self.editScheduledDownload) - self._ui.deleteButton.clicked.connect(self.tryRemoveScheduledDownload) def showEvent(self, event: QtGui.QShowEvent) -> None: self.resizedSignal.emit() @@ -52,7 +56,7 @@ def showEvent(self, event: QtGui.QShowEvent) -> None: def _activeChanged(self) -> None: self._ui.downloaderArea.setEnabled(self.scheduledDownload.isActive()) - self._ui.enableButton.setIcon(QtGui.QIcon(Icons.TOGGLE_ON_ICON if self.scheduledDownload.isEnabled() else Icons.TOGGLE_OFF_ICON)) + self._enableButtonIconViewer.setIcon(Icons.TOGGLE_ON if self.scheduledDownload.isEnabled() else Icons.TOGGLE_OFF) def _enableButtonClicked(self) -> None: if self.scheduledDownload.isActive(): diff --git a/Ui/ScheduledDownloadSettings.py b/Ui/ScheduledDownloadSettings.py index 34cc0cd..397ab5e 100644 --- a/Ui/ScheduledDownloadSettings.py +++ b/Ui/ScheduledDownloadSettings.py @@ -28,9 +28,11 @@ def __init__(self, scheduledDownloadPreset: ScheduledDownloadPreset | None = Non self._ui.filenameTemplate.setText(self.virtualPreset.filenameTemplate) self._ui.filenameTemplate.textChanged.connect(self.filenameTemplateChanged) self._ui.filenameTemplateInfo.clicked.connect(self.filenameTemplateInfoWindow.show) + Utils.setIconViewer(self._ui.filenameTemplateInfo, Icons.HELP) self.reloadFileFormat() self._ui.fileFormat.currentTextChanged.connect(self.fileFormatChanged) self._ui.filenamePreviewInfo.clicked.connect(self.showFilenamePreviewInfo) + Utils.setIconViewer(self._ui.filenamePreviewInfo, Icons.HELP) for quality in self.virtualPreset.getQualityList(): self._ui.preferredQuality.addItem(quality.toString() if quality.isValid() else T(quality.toString())) self._ui.preferredQuality.setCurrentIndex(self.virtualPreset.preferredQualityIndex) @@ -42,14 +44,17 @@ def __init__(self, scheduledDownloadPreset: ScheduledDownloadPreset | None = Non self._ui.preferredResolutionOnlyCheckBox.setChecked(self.virtualPreset.isPreferredResolutionOnlyEnabled()) self._ui.preferredResolutionOnlyCheckBox.toggled.connect(self.virtualPreset.setPreferredResolutionOnlyEnabled) self._ui.preferredResolutionOnlyInfo.clicked.connect(self.showPreferredResolutionOnlyInfo) + Utils.setIconViewer(self._ui.preferredResolutionOnlyInfo, Icons.HELP) self._ui.adBlockSkipSegmentsRadioButton.setChecked(self.virtualPreset.isSkipAdsEnabled()) self._ui.adBlockAlternativeScreenRadioButton.setChecked(not self.virtualPreset.isSkipAdsEnabled()) self._ui.adBlockSkipSegmentsRadioButton.toggled.connect(self.virtualPreset.setSkipAdsEnabled) self._ui.adBlockInfo.clicked.connect(self.showAdBlockInfo) + Utils.setIconViewer(self._ui.adBlockInfo, Icons.HELP) self._ui.remuxRadioButton.setChecked(self.virtualPreset.isRemuxEnabled()) self._ui.concatRadioButton.setChecked(not self.virtualPreset.isRemuxEnabled()) self._ui.remuxRadioButton.toggled.connect(self.virtualPreset.setRemuxEnabled) self._ui.encoderInfo.clicked.connect(self.showEncoderInfo) + Utils.setIconViewer(self._ui.encoderInfo, Icons.HELP) self._ui.nextDownloadLabel.setVisible(self.isEditMode) self.updateFilenamePreview() diff --git a/Ui/ScheduledDownloads.py b/Ui/ScheduledDownloads.py index 09cc82e..fb5f601 100644 --- a/Ui/ScheduledDownloads.py +++ b/Ui/ScheduledDownloads.py @@ -10,13 +10,16 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self.previewWidgets = {} self._ui = UiLoader.load("scheduledDownloads", self) - self.showEnableState() + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.SCHEDULED) self._ui.enableButton.clicked.connect(self.enableButtonClicked) - self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.SCHEDULED_ICON) - self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {self._ui.stackedWidget.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") + self._enableButtonIconViewer = Utils.setIconViewer(self._ui.enableButton, Icons.TOGGLE_OFF) + self.showEnableState() self._widgetListViewer = PartnerContentInFeedWidgetListViewer(self._ui.previewWidgetView, partnerContentSize=QtCore.QSize(320, 100), parent=self) self.showStats() self._ui.addScheduledDownloadButton.clicked.connect(self.addScheduledDownload) + Utils.setIconViewer(self._ui.addScheduledDownloadButton, Icons.PLUS) App.ScheduledDownloadManager.enabledChangedSignal.connect(self.showEnableState) App.ScheduledDownloadManager.createdSignal.connect(self.scheduledDownloadCreated) App.ScheduledDownloadManager.destroyedSignal.connect(self.scheduledDownloadDestroyed) @@ -26,9 +29,12 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self.scheduledDownloadCreated(scheduledDownloadId) self._widgetListViewer.setAutoReloadEnabled(True) + def _setupThemeStyle(self) -> None: + self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {App.Instance.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Base).name()};}}") + def showEnableState(self) -> None: enabled = App.ScheduledDownloadManager.isEnabled() - self._ui.enableButton.setIcon(QtGui.QIcon(Icons.TOGGLE_ON_ICON if App.ScheduledDownloadManager.isEnabled() else Icons.TOGGLE_OFF_ICON)) + self._enableButtonIconViewer.setIcon(Icons.TOGGLE_ON if App.ScheduledDownloadManager.isEnabled() else Icons.TOGGLE_OFF) if not enabled: self._ui.enableButton.setEnabled(not App.ScheduledDownloadManager.isDownloaderRunning()) diff --git a/Ui/SearchResult.py b/Ui/SearchResult.py index d5f4fe1..578f6da 100644 --- a/Ui/SearchResult.py +++ b/Ui/SearchResult.py @@ -37,16 +37,20 @@ def __init__(self, data: TwitchGQLModels.Channel | TwitchGQLModels.Video | Twitc super().__init__(parent=parent) self.data = data self._ui = UiLoader.load("searchResult", self) - self._ui.viewIcon = Utils.setSvgIcon(self._ui.viewIcon, Icons.VIEWER_ICON) - self._ui.verifiedIcon = Utils.setSvgIcon(self._ui.verifiedIcon, Icons.VERIFIED_ICON) - self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.INFO_ICON) - self._ui.loadingIcon = Utils.setSvgIcon(self._ui.loadingIcon, Icons.INFO_ICON) - self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {self._ui.stackedWidget.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Window).name()};}}") + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + self._setupThemeStyle() + self._ui.viewIcon = Utils.setSvgIcon(self._ui.viewIcon, Icons.VIEWER) + self._ui.verifiedIcon = Utils.setSvgIcon(self._ui.verifiedIcon, Icons.VERIFIED) + self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.INFO) + self._ui.loadingIcon = Utils.setSvgIcon(self._ui.loadingIcon, Icons.INFO) self._ui.videoArea.setStyleSheet("#videoArea {background-color: transparent;}") self._ui.videoArea.verticalScrollBar().valueChanged.connect(self.searchMoreVideos) self._widgetListViewer = PartnerContentInFeedWidgetListViewer(self._ui.videoArea, responsive=False, parent=self) self.setup() + def _setupThemeStyle(self) -> None: + self._ui.stackedWidget.setStyleSheet(f"#stackedWidget {{background-color: {App.Instance.palette().color(QtGui.QPalette.ColorGroup.Normal, QtGui.QPalette.ColorRole.Window).name()};}}") + def setLoading(self, loading: bool, showErrorMessage: bool = False) -> None: self._loading = loading if self.isLoading(): @@ -77,8 +81,11 @@ def setup(self) -> None: self._ui.searchType.currentIndexChanged.connect(self.loadSortOrFilter) self._ui.sortOrFilter.currentIndexChanged.connect(self.setSearchOptions) self._ui.refreshChannelButton.clicked.connect(self.refreshChannel) + Utils.setIconViewer(self._ui.refreshChannelButton, Icons.RELOAD) self._ui.refreshVideoListButton.clicked.connect(self.refreshVideoList) + Utils.setIconViewer(self._ui.refreshVideoListButton, Icons.RELOAD) self._ui.openInWebBrowserButton.clicked.connect(self.openInWebBrowser) + Utils.setIconViewer(self._ui.openInWebBrowserButton, Icons.LAUNCH) self.loadSortOrFilter(0) else: self._ui.tabWidget.setCurrentIndex(1) @@ -129,7 +136,7 @@ def showChannel(self, channel: TwitchGQLModels.Channel) -> None: self._ui.viewIcon.setSizePolicy(sizePolicy) self._ui.channelMainWidget = Utils.setPlaceholder(self._ui.channelMainWidget, Ui.VideoDownloadWidget(content, parent=self)) self._ui.channelMainWidget.accountPageShowRequested.connect(self.accountPageShowRequested) - self._ui.channelMainWidget.setThumbnailImageStyleSheet(f"#thumbnailImage {{background-color: #{self.channel.primaryColorHex or self.DEFAULT_CHANNEL_PRIMARY_COLOR};background-image: url('{Icons.CHANNEL_BACKGROUND_WHITE_ICON}');background-position: center center;}}") + self._ui.channelMainWidget.setThumbnailImageStyleSheet(f"#thumbnailImage {{background-color: #{self.channel.primaryColorHex or self.DEFAULT_CHANNEL_PRIMARY_COLOR};background-image: url('{Icons.CHANNEL_BACKGROUND_WHITE.path}');background-position: center center;}}") self._ui.profileImage.loadImage(filePath=Images.PROFILE_IMAGE, url=self.channel.profileImageURL, urlFormatSize=ImageSize.USER_PROFILE, refresh=True) self._ui.displayName.setText(self.channel.displayName) sizePolicy = self._ui.verifiedIcon.sizePolicy() diff --git a/Ui/Settings.py b/Ui/Settings.py index 67cc221..8751915 100644 --- a/Ui/Settings.py +++ b/Ui/Settings.py @@ -19,14 +19,17 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self._ui.streamFilename.setText(App.Preferences.templates.getStreamFilename()) self._ui.streamFilename.editingFinished.connect(self.setStreamFilename) self._ui.streamTemplateInfo.clicked.connect(self.streamTemplateInfoWindow.show) + Utils.setIconViewer(self._ui.streamTemplateInfo, Icons.HELP) self.videoTemplateInfoWindow = FileNameTemplateInfo(FileNameTemplateInfo.TYPE.VIDEO, parent=self) self._ui.videoFilename.setText(App.Preferences.templates.getVideoFilename()) self._ui.videoFilename.editingFinished.connect(self.setVideoFilename) self._ui.videoTemplateInfo.clicked.connect(self.videoTemplateInfoWindow.show) + Utils.setIconViewer(self._ui.videoTemplateInfo, Icons.HELP) self.clipTemplateInfoWindow = FileNameTemplateInfo(FileNameTemplateInfo.TYPE.CLIP, parent=self) self._ui.clipFilename.setText(App.Preferences.templates.getClipFilename()) self._ui.clipFilename.editingFinished.connect(self.setClipFilename) self._ui.clipTemplateInfo.clicked.connect(self.clipTemplateInfoWindow.show) + Utils.setIconViewer(self._ui.clipTemplateInfo, Icons.HELP) for bookmark in App.Preferences.general.getBookmarks(): self.addBookmark(bookmark) self._ui.bookmarkList.model().rowsInserted.connect(self.saveBookmark) @@ -36,16 +39,30 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self._ui.newBookmark.returnPressed.connect(self.tryAddBookmark) self._ui.newBookmark.textChanged.connect(self.reloadBookmarkArea) self._ui.addBookmarkButton.clicked.connect(self.tryAddBookmark) + Utils.setIconViewer(self._ui.addBookmarkButton, Icons.PLUS) self._ui.removeBookmarkButton.clicked.connect(self.removeBookmark) + Utils.setIconViewer(self._ui.removeBookmarkButton, Icons.TRASH) + self._ui.automaticThemeIcon = Utils.setSvgIcon(self._ui.automaticThemeIcon, Icons.THEME_AUTOMATIC) + self._ui.automaticThemeRadioButton.setChecked(App.ThemeManager.getThemeMode().isAuto()) + self._ui.automaticThemeRadioButton.toggled.connect(self._updateThemeMode) + self._ui.lightThemeIcon = Utils.setSvgIcon(self._ui.lightThemeIcon, Icons.THEME_LIGHT) + self._ui.lightThemeRadioButton.setChecked(App.ThemeManager.getThemeMode().isLight()) + self._ui.lightThemeRadioButton.toggled.connect(self._updateThemeMode) + self._ui.darkThemeIcon = Utils.setSvgIcon(self._ui.darkThemeIcon, Icons.THEME_DARK) + self._ui.darkThemeRadioButton.setChecked(App.ThemeManager.getThemeMode().isDark()) + self._ui.darkThemeRadioButton.toggled.connect(self._updateThemeMode) self._ui.searchExternalContent.setChecked(App.Preferences.advanced.isSearchExternalContentEnabled()) self._ui.searchExternalContent.toggled.connect(App.Preferences.advanced.setSearchExternalContentEnabled) self._ui.searchExternalContentInfo.clicked.connect(self.showSearchExternalContentInfo) + Utils.setIconViewer(self._ui.searchExternalContentInfo, Icons.HELP) self._ui.language.addItems(App.Translator.getLanguageList()) self._ui.language.setCurrentIndex(App.Translator.getLanguageKeyList().index(App.Translator.getLanguage())) self._ui.language.currentIndexChanged.connect(self.setLanguage) + self._ui.languageInfoIcon = Utils.setSvgIcon(self._ui.languageInfoIcon, Icons.ALERT_RED) self._ui.timezone.addItems(App.Preferences.localization.getTimezoneNameList()) self._ui.timezone.setCurrentText(App.Preferences.localization.getTimezone().name()) self._ui.timezone.currentTextChanged.connect(self.setTimezone) + self._ui.timezoneInfoIcon = Utils.setSvgIcon(self._ui.timezoneInfoIcon, Icons.ALERT_RED) self._ui.downloadSpeed.setRange(DownloadEngineConfig.FILE_DOWNLOAD_MANAGER_MIN_POOL_SIZE, DownloadEngineConfig.FILE_DOWNLOAD_MANAGER_MAX_POOL_SIZE) self._ui.downloadSpeed.valueChanged.connect(self.setDownloadSpeed) self._ui.speedSpinBox.setRange(DownloadEngineConfig.FILE_DOWNLOAD_MANAGER_MIN_POOL_SIZE, DownloadEngineConfig.FILE_DOWNLOAD_MANAGER_MAX_POOL_SIZE) @@ -55,6 +72,11 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self.reloadBookmarkArea() App.GlobalDownloadManager.runningCountChangedSignal.connect(self.reload) self.reload() + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + + def _setupThemeStyle(self) -> None: + for index in range(self._ui.bookmarkList.count()): + self._ui.bookmarkList.item(index).setIcon(Icons.MOVE.icon) def reload(self) -> None: if App.GlobalDownloadManager.isDownloaderRunning(): @@ -94,7 +116,7 @@ def tryAddBookmark(self) -> None: def addBookmark(self, bookmark: str) -> None: item = QtWidgets.QListWidgetItem(bookmark) - item.setIcon(QtGui.QIcon(Icons.MOVE_ICON)) + item.setIcon(Icons.MOVE.icon) item.setToolTip(T("#Drag to change order.")) self._ui.bookmarkList.addItem(item) self._ui.newBookmark.clear() @@ -106,6 +128,14 @@ def removeBookmark(self) -> None: def saveBookmark(self) -> None: App.Preferences.general.setBookmarks([self._ui.bookmarkList.item(index).text() for index in range(self._ui.bookmarkList.count())]) + def _updateThemeMode(self) -> None: + if self._ui.automaticThemeRadioButton.isChecked(): + App.ThemeManager.setThemeMode(App.ThemeManager.Modes.AUTO) + elif self._ui.lightThemeRadioButton.isChecked(): + App.ThemeManager.setThemeMode(App.ThemeManager.Modes.LIGHT) + elif self._ui.darkThemeRadioButton.isChecked(): + App.ThemeManager.setThemeMode(App.ThemeManager.Modes.DARK) + def showSearchExternalContentInfo(self) -> None: Utils.info("information", "#Allow URL Search to retrieve external content.\nYou can download content outside of Twitch.", parent=self) diff --git a/Ui/Setup.py b/Ui/Setup.py index 42202ce..72673a7 100644 --- a/Ui/Setup.py +++ b/Ui/Setup.py @@ -5,7 +5,7 @@ class Setup(QtWidgets.QDialog): def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._ui = UiLoader.load("setup", self) - self.setWindowIcon(QtGui.QIcon(Icons.APP_LOGO_ICON)) + self.setWindowIcon(Icons.APP_LOGO.icon) self._ui.appLogo.setMargin(10) self._ui.appName.setText(Config.APP_NAME) self._ui.continueButton.clicked.connect(self.proceed) diff --git a/Ui/VideoDownloadWidget.py b/Ui/VideoDownloadWidget.py index 9ce9bc0..24d0c81 100644 --- a/Ui/VideoDownloadWidget.py +++ b/Ui/VideoDownloadWidget.py @@ -13,14 +13,14 @@ def __init__(self, content: Channel | Stream | Video | Clip, resizable: bool = T self.content = content self._ui = UiLoader.load("videoDownloadWidget", self) self._ui.videoWidget = Utils.setPlaceholder(self._ui.videoWidget, Ui.VideoWidget(self.content, resizable=resizable, parent=self)) - self.downloadButtonManager = DownloadButton(self.content, self._ui.downloadButton, buttonText=T("live-download" if isinstance(self.content, Channel) or isinstance(self.content, Stream) else "download"), parent=self) + self.downloadButtonManager = DownloadButton(self.content, self._ui.downloadButton, buttonIcon=Icons.DOWNLOAD, buttonText=T("live-download" if isinstance(self.content, Channel) or isinstance(self.content, Stream) else "download"), parent=self) self.downloadButtonManager.accountPageShowRequested.connect(self.accountPageShowRequested) - self.instantDownloadButtonManager = InstantDownloadButton(self.content, self._ui.instantDownloadButton, parent=self) + self.instantDownloadButtonManager = InstantDownloadButton(self.content, self._ui.instantDownloadButton, buttonIcon=Icons.INSTANT_DOWNLOAD, parent=self) self.instantDownloadButtonManager.accountPageShowRequested.connect(self.accountPageShowRequested) self._contextMenu = QtWidgets.QMenu(parent=self) - self._filePropertyAction = QtGui.QAction(QtGui.QIcon(Icons.FILE_ICON), T("view-file-properties"), parent=self._contextMenu) - self._imagePropertyAction = QtGui.QAction(QtGui.QIcon(Icons.IMAGE_ICON), T("view-image-properties"), parent=self._contextMenu) - self._saveImageAction = QtGui.QAction(QtGui.QIcon(Icons.SAVE_ICON), T("save-image"), parent=self._contextMenu) + self._filePropertyAction = QtGui.QAction(Icons.FILE.icon, T("view-file-properties"), parent=self._contextMenu) + self._imagePropertyAction = QtGui.QAction(Icons.IMAGE.icon, T("view-image-properties"), parent=self._contextMenu) + self._saveImageAction = QtGui.QAction(Icons.SAVE.icon, T("save-image"), parent=self._contextMenu) self._filePropertyAction.triggered.connect(self.showFileProperty) self._imagePropertyAction.triggered.connect(self.showImageProperty) self._saveImageAction.triggered.connect(self.saveImage) @@ -31,6 +31,12 @@ def __init__(self, content: Channel | Stream | Video | Clip, resizable: bool = T self._saveImageAction.setVisible(False) self.customContextMenuRequested.connect(self.contextMenuRequested) self._ui.videoWidget.thumbnailImage.customContextMenuRequested.connect(self.thumbnailImageContextMenuRequested) + App.ThemeManager.themeUpdated.connect(self._setupThemeStyle) + + def _setupThemeStyle(self) -> None: + self._filePropertyAction.setIcon(Icons.FILE.icon) + self._imagePropertyAction.setIcon(Icons.IMAGE.icon) + self._saveImageAction.setIcon(Icons.SAVE.icon) def setThumbnailImageStyleSheet(self, styleSheet: str) -> None: self._ui.videoWidget.thumbnailImage.setStyleSheet(styleSheet) diff --git a/Ui/VideoWidget.py b/Ui/VideoWidget.py index defd0fa..42c58be 100644 --- a/Ui/VideoWidget.py +++ b/Ui/VideoWidget.py @@ -26,6 +26,7 @@ def __init__(self, content: Channel | Stream | Video | Clip, resizable: bool = T self._ui.more.hide() else: self._ui.more.clicked.connect(self.moreClicked) + Utils.setIconViewer(self._ui.more, Icons.LIST) @property def thumbnailImage(self) -> QtWidgets.QLabel: diff --git a/Ui/WebViewWidget.py b/Ui/WebViewWidget.py index 248cd29..8144fcc 100644 --- a/Ui/WebViewWidget.py +++ b/Ui/WebViewWidget.py @@ -6,13 +6,10 @@ class WebViewWidget(QtWidgets.QWidget): - iconChanged = QtCore.pyqtSignal(object, QtGui.QIcon) + iconChanged = QtCore.pyqtSignal(object, object) titleChanged = QtCore.pyqtSignal(object, str) tabCloseRequested = QtCore.pyqtSignal(object) - DEFAULT_LOADING_ICON = QtGui.QIcon(Icons.LOADING_ICON) - DEFAULT_FAILED_ICON = QtGui.QIcon(Icons.WEB_ICON) - def __init__(self, parent: QtWidgets.QWidget | None = None): super().__init__(parent=parent) self._ui = UiLoader.load("webViewWidget", self) @@ -25,13 +22,18 @@ def __init__(self, parent: QtWidgets.QWidget | None = None): self._ui.webView.tabCloseRequested.connect(self.tabCloseRequestHandler) self.newTabRequested = self._ui.webView.newTabRequested self._ui.backButton.clicked.connect(self._ui.webView.back) + Utils.setIconViewer(self._ui.backButton, Icons.BACK) self._ui.forwardButton.clicked.connect(self._ui.webView.forward) + Utils.setIconViewer(self._ui.forwardButton, Icons.FORWARD) self._ui.reloadButton.clicked.connect(self._ui.webView.reload) + Utils.setIconViewer(self._ui.reloadButton, Icons.RELOAD) self._ui.stopLoadingButton.clicked.connect(self._ui.webView.stop) + Utils.setIconViewer(self._ui.stopLoadingButton, Icons.CANCEL) self._ui.urlEdit.returnPressed.connect(self.urlEdited) self.reloadControlArea() self.reloadUrl() - self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.INFO_ICON) + self._ui.infoIcon = Utils.setSvgIcon(self._ui.infoIcon, Icons.INFO) + self._infoButtonIconViewer = Utils.setIconViewer(self._ui.infoButton, None) self.hideInfo() self.setupShortcuts() @@ -53,7 +55,7 @@ def setInspectedMode(self, page: QtWebEngineCore.QWebEnginePage) -> None: self.refreshShortcut.setEnabled(False) self.stopShortcut.setEnabled(False) self._ui.controlArea.hide() - self.showInfo(f"{T('developer-tools')} - {T('#Close the window by pressing [{key}].', key='F12')}", icon=Icons.SETTINGS_ICON, buttonIcon=Icons.CLOSE_ICON, buttonTransparent=True, buttonHandler=self.hideInfo) + self.showInfo(f"{T('developer-tools')} - {T('#Close the window by pressing [{key}].', key='F12')}", icon=Icons.SETTINGS, buttonIcon=Icons.CLOSE, buttonTransparent=True, buttonHandler=self.hideInfo) self._ui.webView.page().setInspectedPage(page) def setProfile(self, profile: QtWebEngineCore.QWebEngineProfile) -> None: @@ -70,12 +72,12 @@ def reloadUrl(self) -> None: self._ui.urlEdit.setText(self._ui.webView.url().toString()) self._ui.urlEdit.clearFocus() - def showInfo(self, text: str, icon: str | None = None, buttonIcon: str | None = None, buttonText: str = "", buttonTransparent: bool = False, buttonHandler: typing.Callable | None = None) -> None: + def showInfo(self, text: str, icon: str | ThemedIcon | None = None, buttonIcon: QtGui.QIcon | ThemedIcon | None = None, buttonText: str = "", buttonTransparent: bool = False, buttonHandler: typing.Callable | None = None) -> None: self._ui.infoLabel.setText(text) if icon == None: self._ui.infoIcon.hide() else: - self._ui.infoIcon.load(icon) + self._ui.infoIcon.setIcon(icon) self._ui.infoIcon.show() try: self._ui.infoButton.clicked.disconnect() @@ -84,7 +86,7 @@ def showInfo(self, text: str, icon: str | None = None, buttonIcon: str | None = if buttonHandler == None: self._ui.infoButton.hide() else: - self._ui.infoButton.setIcon(QtGui.QIcon(buttonIcon)) + self._infoButtonIconViewer.setIcon(buttonIcon) self._ui.infoButton.setText(buttonText) self._ui.infoButton.setStyleSheet("QPushButton:!hover {background-color: transparent;}" if buttonTransparent else "") self._ui.infoButton.clicked.connect(buttonHandler) @@ -112,11 +114,11 @@ def loadStarted(self) -> None: self.reloadControlArea() self.reloadUrl() self._ui.urlEdit.clearFocus() - self.iconChanged.emit(self, self.DEFAULT_LOADING_ICON) + self.iconChanged.emit(self, Icons.LOADING) def loadFinished(self, isSuccessful: bool) -> None: self.reloadControlArea() - self.iconChanged.emit(self, self._ui.webView.icon() if isSuccessful else self.DEFAULT_FAILED_ICON) + self.iconChanged.emit(self, self._ui.webView.icon() if isSuccessful else Icons.WEB) def tabCloseRequestHandler(self) -> None: self.tabCloseRequested.emit(self) diff --git a/requirements.txt b/requirements.txt index 609b663..5b72d93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ -PyQt6==6.5.2 -PyQt6-WebEngine==6.5.0 \ No newline at end of file +PyQt6==6.7.0 +PyQt6-Qt6==6.7.2 +PyQt6-WebEngine==6.7.0 +PyQt6-WebEngine-Qt6==6.7.2 \ No newline at end of file diff --git a/resources/icons/alert_red.svg b/resources/icons/alert_red.svg deleted file mode 100644 index 1c25fba..0000000 --- a/resources/icons/alert_red.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/icons/channel_background_white.svg b/resources/icons/channel_background_white.svg deleted file mode 100644 index 1f71ac7..0000000 --- a/resources/icons/channel_background_white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/resources/icons/creating_file.svg b/resources/icons/creating_file.svg deleted file mode 100644 index 1d9d800..0000000 --- a/resources/icons/creating_file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/icons/dark/account.svg b/resources/icons/dark/account.svg new file mode 100644 index 0000000..5d958ee --- /dev/null +++ b/resources/icons/dark/account.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/alert_red.svg b/resources/icons/dark/alert_red.svg new file mode 100644 index 0000000..08cb2ae --- /dev/null +++ b/resources/icons/dark/alert_red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/announcement.svg b/resources/icons/dark/announcement.svg new file mode 100644 index 0000000..96f672c --- /dev/null +++ b/resources/icons/dark/announcement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/back.svg b/resources/icons/dark/back.svg new file mode 100644 index 0000000..ccfd8d5 --- /dev/null +++ b/resources/icons/dark/back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/cancel.svg b/resources/icons/dark/cancel.svg new file mode 100644 index 0000000..ff5abbc --- /dev/null +++ b/resources/icons/dark/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/channel_background_white.svg b/resources/icons/dark/channel_background_white.svg new file mode 100644 index 0000000..e8def45 --- /dev/null +++ b/resources/icons/dark/channel_background_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/close.svg b/resources/icons/dark/close.svg new file mode 100644 index 0000000..5dbd919 --- /dev/null +++ b/resources/icons/dark/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/copy.svg b/resources/icons/dark/copy.svg new file mode 100644 index 0000000..c83a977 --- /dev/null +++ b/resources/icons/dark/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/creating_file.svg b/resources/icons/dark/creating_file.svg new file mode 100644 index 0000000..b0c9e68 --- /dev/null +++ b/resources/icons/dark/creating_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/download.svg b/resources/icons/dark/download.svg new file mode 100644 index 0000000..cb90959 --- /dev/null +++ b/resources/icons/dark/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/downloading_file.svg b/resources/icons/dark/downloading_file.svg new file mode 100644 index 0000000..73da3d0 --- /dev/null +++ b/resources/icons/dark/downloading_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/file.svg b/resources/icons/dark/file.svg new file mode 100644 index 0000000..4236d51 --- /dev/null +++ b/resources/icons/dark/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/file_not_found.svg b/resources/icons/dark/file_not_found.svg new file mode 100644 index 0000000..4b347a2 --- /dev/null +++ b/resources/icons/dark/file_not_found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/folder.svg b/resources/icons/dark/folder.svg new file mode 100644 index 0000000..fff0026 --- /dev/null +++ b/resources/icons/dark/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/forward.svg b/resources/icons/dark/forward.svg new file mode 100644 index 0000000..256d15b --- /dev/null +++ b/resources/icons/dark/forward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/help.svg b/resources/icons/dark/help.svg new file mode 100644 index 0000000..f690cc8 --- /dev/null +++ b/resources/icons/dark/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/history.svg b/resources/icons/dark/history.svg new file mode 100644 index 0000000..77c3513 --- /dev/null +++ b/resources/icons/dark/history.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/home.svg b/resources/icons/dark/home.svg new file mode 100644 index 0000000..3acacd9 --- /dev/null +++ b/resources/icons/dark/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/image.svg b/resources/icons/dark/image.svg new file mode 100644 index 0000000..76a45d1 --- /dev/null +++ b/resources/icons/dark/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/info.svg b/resources/icons/dark/info.svg new file mode 100644 index 0000000..83c2ba9 --- /dev/null +++ b/resources/icons/dark/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/instant_download.svg b/resources/icons/dark/instant_download.svg new file mode 100644 index 0000000..df0cfde --- /dev/null +++ b/resources/icons/dark/instant_download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/launch.svg b/resources/icons/dark/launch.svg new file mode 100644 index 0000000..5171f37 --- /dev/null +++ b/resources/icons/dark/launch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/list.svg b/resources/icons/dark/list.svg new file mode 100644 index 0000000..acbffdb --- /dev/null +++ b/resources/icons/dark/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/loading.svg b/resources/icons/dark/loading.svg new file mode 100644 index 0000000..7cdba8b --- /dev/null +++ b/resources/icons/dark/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/login.svg b/resources/icons/dark/login.svg new file mode 100644 index 0000000..8b70ef5 --- /dev/null +++ b/resources/icons/dark/login.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/move.svg b/resources/icons/dark/move.svg new file mode 100644 index 0000000..0fddffd --- /dev/null +++ b/resources/icons/dark/move.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/notice.svg b/resources/icons/dark/notice.svg new file mode 100644 index 0000000..9a09967 --- /dev/null +++ b/resources/icons/dark/notice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/plus.svg b/resources/icons/dark/plus.svg new file mode 100644 index 0000000..98c92ce --- /dev/null +++ b/resources/icons/dark/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/reload.svg b/resources/icons/dark/reload.svg new file mode 100644 index 0000000..7fa38c1 --- /dev/null +++ b/resources/icons/dark/reload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/retry.svg b/resources/icons/dark/retry.svg new file mode 100644 index 0000000..ce5df07 --- /dev/null +++ b/resources/icons/dark/retry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/save.svg b/resources/icons/dark/save.svg new file mode 100644 index 0000000..4f463ca --- /dev/null +++ b/resources/icons/dark/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/scheduled.svg b/resources/icons/dark/scheduled.svg new file mode 100644 index 0000000..852bc05 --- /dev/null +++ b/resources/icons/dark/scheduled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/search.svg b/resources/icons/dark/search.svg new file mode 100644 index 0000000..55be0ff --- /dev/null +++ b/resources/icons/dark/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/settings.svg b/resources/icons/dark/settings.svg new file mode 100644 index 0000000..4a5d3a9 --- /dev/null +++ b/resources/icons/dark/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/storage.svg b/resources/icons/dark/storage.svg new file mode 100644 index 0000000..6060116 --- /dev/null +++ b/resources/icons/dark/storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/text_file.svg b/resources/icons/dark/text_file.svg new file mode 100644 index 0000000..5a236e8 --- /dev/null +++ b/resources/icons/dark/text_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/theme_automatic.svg b/resources/icons/dark/theme_automatic.svg new file mode 100644 index 0000000..6398f79 --- /dev/null +++ b/resources/icons/dark/theme_automatic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/theme_dark.svg b/resources/icons/dark/theme_dark.svg new file mode 100644 index 0000000..7fb9607 --- /dev/null +++ b/resources/icons/dark/theme_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/theme_light.svg b/resources/icons/dark/theme_light.svg new file mode 100644 index 0000000..6b971fa --- /dev/null +++ b/resources/icons/dark/theme_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/toggle_off.svg b/resources/icons/dark/toggle_off.svg new file mode 100644 index 0000000..dd6dd17 --- /dev/null +++ b/resources/icons/dark/toggle_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/toggle_on.svg b/resources/icons/dark/toggle_on.svg new file mode 100644 index 0000000..ff2d532 --- /dev/null +++ b/resources/icons/dark/toggle_on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/trash.svg b/resources/icons/dark/trash.svg new file mode 100644 index 0000000..d834b8d --- /dev/null +++ b/resources/icons/dark/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/update_found.svg b/resources/icons/dark/update_found.svg new file mode 100644 index 0000000..8ed9583 --- /dev/null +++ b/resources/icons/dark/update_found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/verified.svg b/resources/icons/dark/verified.svg similarity index 100% rename from resources/icons/verified.svg rename to resources/icons/dark/verified.svg diff --git a/resources/icons/dark/viewer.svg b/resources/icons/dark/viewer.svg new file mode 100644 index 0000000..de30a24 --- /dev/null +++ b/resources/icons/dark/viewer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/warning_red.svg b/resources/icons/dark/warning_red.svg new file mode 100644 index 0000000..a2aeaa6 --- /dev/null +++ b/resources/icons/dark/warning_red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/dark/web.svg b/resources/icons/dark/web.svg new file mode 100644 index 0000000..9718db1 --- /dev/null +++ b/resources/icons/dark/web.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/account.svg b/resources/icons/light/account.svg similarity index 100% rename from resources/icons/account.svg rename to resources/icons/light/account.svg diff --git a/resources/icons/light/alert_red.svg b/resources/icons/light/alert_red.svg new file mode 100644 index 0000000..08cb2ae --- /dev/null +++ b/resources/icons/light/alert_red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/announcement.svg b/resources/icons/light/announcement.svg similarity index 100% rename from resources/icons/announcement.svg rename to resources/icons/light/announcement.svg diff --git a/resources/icons/back.svg b/resources/icons/light/back.svg similarity index 100% rename from resources/icons/back.svg rename to resources/icons/light/back.svg diff --git a/resources/icons/cancel.svg b/resources/icons/light/cancel.svg similarity index 100% rename from resources/icons/cancel.svg rename to resources/icons/light/cancel.svg diff --git a/resources/icons/light/channel_background_white.svg b/resources/icons/light/channel_background_white.svg new file mode 100644 index 0000000..e8def45 --- /dev/null +++ b/resources/icons/light/channel_background_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/close.svg b/resources/icons/light/close.svg similarity index 100% rename from resources/icons/close.svg rename to resources/icons/light/close.svg diff --git a/resources/icons/copy.svg b/resources/icons/light/copy.svg similarity index 100% rename from resources/icons/copy.svg rename to resources/icons/light/copy.svg diff --git a/resources/icons/light/creating_file.svg b/resources/icons/light/creating_file.svg new file mode 100644 index 0000000..4957cbb --- /dev/null +++ b/resources/icons/light/creating_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/download.svg b/resources/icons/light/download.svg similarity index 100% rename from resources/icons/download.svg rename to resources/icons/light/download.svg diff --git a/resources/icons/downloading_file.svg b/resources/icons/light/downloading_file.svg similarity index 100% rename from resources/icons/downloading_file.svg rename to resources/icons/light/downloading_file.svg diff --git a/resources/icons/file.svg b/resources/icons/light/file.svg similarity index 100% rename from resources/icons/file.svg rename to resources/icons/light/file.svg diff --git a/resources/icons/file_not_found.svg b/resources/icons/light/file_not_found.svg similarity index 100% rename from resources/icons/file_not_found.svg rename to resources/icons/light/file_not_found.svg diff --git a/resources/icons/folder.svg b/resources/icons/light/folder.svg similarity index 100% rename from resources/icons/folder.svg rename to resources/icons/light/folder.svg diff --git a/resources/icons/forward.svg b/resources/icons/light/forward.svg similarity index 100% rename from resources/icons/forward.svg rename to resources/icons/light/forward.svg diff --git a/resources/icons/help.svg b/resources/icons/light/help.svg similarity index 100% rename from resources/icons/help.svg rename to resources/icons/light/help.svg diff --git a/resources/icons/history.svg b/resources/icons/light/history.svg similarity index 100% rename from resources/icons/history.svg rename to resources/icons/light/history.svg diff --git a/resources/icons/home.svg b/resources/icons/light/home.svg similarity index 100% rename from resources/icons/home.svg rename to resources/icons/light/home.svg diff --git a/resources/icons/image.svg b/resources/icons/light/image.svg similarity index 100% rename from resources/icons/image.svg rename to resources/icons/light/image.svg diff --git a/resources/icons/info.svg b/resources/icons/light/info.svg similarity index 100% rename from resources/icons/info.svg rename to resources/icons/light/info.svg diff --git a/resources/icons/instant_download.svg b/resources/icons/light/instant_download.svg similarity index 100% rename from resources/icons/instant_download.svg rename to resources/icons/light/instant_download.svg diff --git a/resources/icons/launch.svg b/resources/icons/light/launch.svg similarity index 100% rename from resources/icons/launch.svg rename to resources/icons/light/launch.svg diff --git a/resources/icons/list.svg b/resources/icons/light/list.svg similarity index 100% rename from resources/icons/list.svg rename to resources/icons/light/list.svg diff --git a/resources/icons/loading.svg b/resources/icons/light/loading.svg similarity index 100% rename from resources/icons/loading.svg rename to resources/icons/light/loading.svg diff --git a/resources/icons/login.svg b/resources/icons/light/login.svg similarity index 100% rename from resources/icons/login.svg rename to resources/icons/light/login.svg diff --git a/resources/icons/move.svg b/resources/icons/light/move.svg similarity index 100% rename from resources/icons/move.svg rename to resources/icons/light/move.svg diff --git a/resources/icons/light/notice.svg b/resources/icons/light/notice.svg new file mode 100644 index 0000000..9c0d651 --- /dev/null +++ b/resources/icons/light/notice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/plus.svg b/resources/icons/light/plus.svg similarity index 100% rename from resources/icons/plus.svg rename to resources/icons/light/plus.svg diff --git a/resources/icons/reload.svg b/resources/icons/light/reload.svg similarity index 100% rename from resources/icons/reload.svg rename to resources/icons/light/reload.svg diff --git a/resources/icons/retry.svg b/resources/icons/light/retry.svg similarity index 100% rename from resources/icons/retry.svg rename to resources/icons/light/retry.svg diff --git a/resources/icons/save.svg b/resources/icons/light/save.svg similarity index 100% rename from resources/icons/save.svg rename to resources/icons/light/save.svg diff --git a/resources/icons/scheduled.svg b/resources/icons/light/scheduled.svg similarity index 100% rename from resources/icons/scheduled.svg rename to resources/icons/light/scheduled.svg diff --git a/resources/icons/search.svg b/resources/icons/light/search.svg similarity index 100% rename from resources/icons/search.svg rename to resources/icons/light/search.svg diff --git a/resources/icons/settings.svg b/resources/icons/light/settings.svg similarity index 100% rename from resources/icons/settings.svg rename to resources/icons/light/settings.svg diff --git a/resources/icons/storage.svg b/resources/icons/light/storage.svg similarity index 100% rename from resources/icons/storage.svg rename to resources/icons/light/storage.svg diff --git a/resources/icons/text_file.svg b/resources/icons/light/text_file.svg similarity index 100% rename from resources/icons/text_file.svg rename to resources/icons/light/text_file.svg diff --git a/resources/icons/light/theme_automatic.svg b/resources/icons/light/theme_automatic.svg new file mode 100644 index 0000000..bcdc31a --- /dev/null +++ b/resources/icons/light/theme_automatic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/light/theme_dark.svg b/resources/icons/light/theme_dark.svg new file mode 100644 index 0000000..dbf7c6c --- /dev/null +++ b/resources/icons/light/theme_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/light/theme_light.svg b/resources/icons/light/theme_light.svg new file mode 100644 index 0000000..7f51b94 --- /dev/null +++ b/resources/icons/light/theme_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/light/toggle_off.svg b/resources/icons/light/toggle_off.svg new file mode 100644 index 0000000..82f20a2 --- /dev/null +++ b/resources/icons/light/toggle_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/light/toggle_on.svg b/resources/icons/light/toggle_on.svg new file mode 100644 index 0000000..ff2d532 --- /dev/null +++ b/resources/icons/light/toggle_on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/trash.svg b/resources/icons/light/trash.svg similarity index 100% rename from resources/icons/trash.svg rename to resources/icons/light/trash.svg diff --git a/resources/icons/update_found.svg b/resources/icons/light/update_found.svg similarity index 100% rename from resources/icons/update_found.svg rename to resources/icons/light/update_found.svg diff --git a/resources/icons/light/verified.svg b/resources/icons/light/verified.svg new file mode 100644 index 0000000..4312da4 --- /dev/null +++ b/resources/icons/light/verified.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/viewer.svg b/resources/icons/light/viewer.svg similarity index 100% rename from resources/icons/viewer.svg rename to resources/icons/light/viewer.svg diff --git a/resources/icons/light/warning_red.svg b/resources/icons/light/warning_red.svg new file mode 100644 index 0000000..a2aeaa6 --- /dev/null +++ b/resources/icons/light/warning_red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/web.svg b/resources/icons/light/web.svg similarity index 100% rename from resources/icons/web.svg rename to resources/icons/light/web.svg diff --git a/resources/icons/notice.svg b/resources/icons/notice.svg deleted file mode 100644 index a04ddee..0000000 --- a/resources/icons/notice.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/icons/toggle_off.svg b/resources/icons/toggle_off.svg deleted file mode 100644 index beb43a8..0000000 --- a/resources/icons/toggle_off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/icons/toggle_on.svg b/resources/icons/toggle_on.svg deleted file mode 100644 index 9e5570e..0000000 --- a/resources/icons/toggle_on.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/icons/warning_red.svg b/resources/icons/warning_red.svg deleted file mode 100644 index 2c4b051..0000000 --- a/resources/icons/warning_red.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/translations/KeywordTranslations.json b/resources/translations/KeywordTranslations.json index 300bea8..0863ef1 100644 --- a/resources/translations/KeywordTranslations.json +++ b/resources/translations/KeywordTranslations.json @@ -543,6 +543,10 @@ "en": "Second", "ko": "초" }, + "unix-time": { + "en": "UNIX Time", + "ko": "UNIX 시간" + }, "views": { "en": "Views", "ko": "조회수" diff --git a/resources/ui/account.ui b/resources/ui/account.ui index 5d4a77a..e3060bb 100644 --- a/resources/ui/account.ui +++ b/resources/ui/account.ui @@ -153,7 +153,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -298,7 +298,7 @@ - ../icons/reload.svg../icons/reload.svg + ../icons/light/reload.svg../icons/light/reload.svg diff --git a/resources/ui/download.ui b/resources/ui/download.ui index 1b2e908..ea84d38 100644 --- a/resources/ui/download.ui +++ b/resources/ui/download.ui @@ -151,7 +151,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -182,7 +182,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -380,7 +380,7 @@ - ../icons/retry.svg../icons/retry.svg + ../icons/light/retry.svg../icons/light/retry.svg @@ -403,7 +403,7 @@ - ../icons/folder.svg../icons/folder.svg + ../icons/light/folder.svg../icons/light/folder.svg @@ -426,7 +426,7 @@ - ../icons/file.svg../icons/file.svg + ../icons/light/file.svg../icons/light/file.svg diff --git a/resources/ui/downloadHistories.ui b/resources/ui/downloadHistories.ui index 13c6b78..476ff47 100644 --- a/resources/ui/downloadHistories.ui +++ b/resources/ui/downloadHistories.ui @@ -82,7 +82,7 @@ - ../icons/history.svg + ../icons/light/history.svg true diff --git a/resources/ui/downloadMenu.ui b/resources/ui/downloadMenu.ui index 8263502..978e2e3 100644 --- a/resources/ui/downloadMenu.ui +++ b/resources/ui/downloadMenu.ui @@ -552,7 +552,7 @@ - ../icons/info.svg + ../icons/light/info.svg true @@ -582,7 +582,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -617,7 +617,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -674,7 +674,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -708,7 +708,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -756,7 +756,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -792,7 +792,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -828,7 +828,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg diff --git a/resources/ui/downloadViewControlBar.ui b/resources/ui/downloadViewControlBar.ui index ee8a9de..485d791 100644 --- a/resources/ui/downloadViewControlBar.ui +++ b/resources/ui/downloadViewControlBar.ui @@ -133,7 +133,7 @@ border-radius: 10px; - ../icons/viewer.svg + ../icons/light/viewer.svg true @@ -199,7 +199,7 @@ To block ads, you must log in with a subscribed account. - ../icons/warning_red.svg../icons/warning_red.svg + ../icons/light/warning_red.svg../icons/light/warning_red.svg @@ -222,7 +222,7 @@ To block ads, you must log in with a subscribed account. - ../icons/retry.svg../icons/retry.svg + ../icons/light/retry.svg../icons/light/retry.svg @@ -245,7 +245,7 @@ To block ads, you must log in with a subscribed account. - ../icons/folder.svg../icons/folder.svg + ../icons/light/folder.svg../icons/light/folder.svg @@ -268,7 +268,7 @@ To block ads, you must log in with a subscribed account. - ../icons/file.svg../icons/file.svg + ../icons/light/file.svg../icons/light/file.svg @@ -291,7 +291,7 @@ To block ads, you must log in with a subscribed account. - ../icons/text_file.svg../icons/text_file.svg + ../icons/light/text_file.svg../icons/light/text_file.svg @@ -314,7 +314,7 @@ To block ads, you must log in with a subscribed account. - ../icons/trash.svg../icons/trash.svg + ../icons/light/trash.svg../icons/light/trash.svg @@ -337,7 +337,7 @@ To block ads, you must log in with a subscribed account. - ../icons/close.svg../icons/close.svg + ../icons/light/close.svg../icons/light/close.svg diff --git a/resources/ui/downloaderView.ui b/resources/ui/downloaderView.ui index 9124c57..a16bb24 100644 --- a/resources/ui/downloaderView.ui +++ b/resources/ui/downloaderView.ui @@ -60,7 +60,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -91,7 +91,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg diff --git a/resources/ui/downloads.ui b/resources/ui/downloads.ui index ff4c18b..c39e66a 100644 --- a/resources/ui/downloads.ui +++ b/resources/ui/downloads.ui @@ -277,7 +277,7 @@ - ../icons/storage.svg + ../icons/light/storage.svg true @@ -387,7 +387,7 @@ - ../icons/history.svg../icons/history.svg + ../icons/light/history.svg../icons/light/history.svg @@ -520,7 +520,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg diff --git a/resources/ui/mainWindow.ui b/resources/ui/mainWindow.ui index b969d18..418cae9 100644 --- a/resources/ui/mainWindow.ui +++ b/resources/ui/mainWindow.ui @@ -86,7 +86,7 @@ QToolButton:checked { - ../icons/search.svg../icons/search.svg + ../icons/light/search.svg../icons/light/search.svg @@ -141,7 +141,7 @@ QToolButton:checked { - ../icons/download.svg../icons/download.svg + ../icons/light/download.svg../icons/light/download.svg @@ -193,7 +193,7 @@ QToolButton:checked { - ../icons/scheduled.svg../icons/scheduled.svg + ../icons/light/scheduled.svg../icons/light/scheduled.svg @@ -258,7 +258,7 @@ QToolButton:checked { - ../icons/account.svg../icons/account.svg + ../icons/light/account.svg../icons/light/account.svg @@ -310,7 +310,7 @@ QToolButton:checked { - ../icons/settings.svg../icons/settings.svg + ../icons/light/settings.svg../icons/light/settings.svg @@ -362,7 +362,7 @@ QToolButton:checked { - ../icons/info.svg../icons/info.svg + ../icons/light/info.svg../icons/light/info.svg diff --git a/resources/ui/propertyView.ui b/resources/ui/propertyView.ui index 262fc60..7e794a1 100644 --- a/resources/ui/propertyView.ui +++ b/resources/ui/propertyView.ui @@ -112,7 +112,7 @@ - ../icons/save.svg../icons/save.svg + ../icons/light/save.svg../icons/light/save.svg false @@ -179,7 +179,7 @@ - ../icons/copy.svg../icons/copy.svg + ../icons/light/copy.svg../icons/light/copy.svg @@ -202,7 +202,7 @@ - ../icons/launch.svg../icons/launch.svg + ../icons/light/launch.svg../icons/light/launch.svg diff --git a/resources/ui/scheduledDownloadPreview.ui b/resources/ui/scheduledDownloadPreview.ui index cc996a7..e66a879 100644 --- a/resources/ui/scheduledDownloadPreview.ui +++ b/resources/ui/scheduledDownloadPreview.ui @@ -85,7 +85,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -131,6 +131,12 @@ 0 + + + 60 + 30 + + QPushButton:!hover { background-color: transparent; @@ -138,12 +144,12 @@ - ../icons/toggle_off.svg../icons/toggle_off.svg + ../icons/light/toggle_off.svg../icons/light/toggle_off.svg - 48 - 48 + 36 + 18 @@ -169,7 +175,7 @@ - ../icons/reload.svg../icons/reload.svg + ../icons/light/reload.svg../icons/light/reload.svg @@ -200,7 +206,7 @@ - ../icons/settings.svg../icons/settings.svg + ../icons/light/settings.svg../icons/light/settings.svg @@ -231,7 +237,7 @@ - ../icons/trash.svg../icons/trash.svg + ../icons/light/trash.svg../icons/light/trash.svg diff --git a/resources/ui/scheduledDownloadSettings.ui b/resources/ui/scheduledDownloadSettings.ui index e59bc91..ce2277a 100644 --- a/resources/ui/scheduledDownloadSettings.ui +++ b/resources/ui/scheduledDownloadSettings.ui @@ -165,7 +165,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -210,7 +210,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -343,7 +343,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -383,7 +383,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -417,7 +417,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg diff --git a/resources/ui/scheduledDownloads.ui b/resources/ui/scheduledDownloads.ui index 611e74a..fb7fa49 100644 --- a/resources/ui/scheduledDownloads.ui +++ b/resources/ui/scheduledDownloads.ui @@ -6,7 +6,7 @@ 0 0 - 564 + 600 400 @@ -147,12 +147,12 @@ - ../icons/toggle_off.svg../icons/toggle_off.svg + ../icons/light/toggle_off.svg../icons/light/toggle_off.svg 48 - 48 + 24 @@ -200,7 +200,7 @@ - ../icons/scheduled.svg + ../icons/light/scheduled.svg true @@ -358,7 +358,7 @@ - ../icons/plus.svg../icons/plus.svg + ../icons/light/plus.svg../icons/light/plus.svg diff --git a/resources/ui/searchResult.ui b/resources/ui/searchResult.ui index 1468287..1243ce2 100644 --- a/resources/ui/searchResult.ui +++ b/resources/ui/searchResult.ui @@ -143,7 +143,7 @@ border-radius: 10px; - ../icons/viewer.svg + ../icons/light/viewer.svg true @@ -173,7 +173,7 @@ border-radius: 10px; - ../icons/reload.svg../icons/reload.svg + ../icons/light/reload.svg../icons/light/reload.svg @@ -335,7 +335,7 @@ border-radius: 10px; - ../icons/verified.svg + ../icons/light/verified.svg true @@ -444,7 +444,7 @@ border-radius: 10px; - ../icons/launch.svg../icons/launch.svg + ../icons/light/launch.svg../icons/light/launch.svg @@ -536,7 +536,7 @@ border-radius: 10px; - ../icons/reload.svg../icons/reload.svg + ../icons/light/reload.svg../icons/light/reload.svg @@ -583,7 +583,7 @@ border-radius: 10px; - ../icons/info.svg + ../icons/light/info.svg true @@ -695,7 +695,7 @@ border-radius: 10px; - ../icons/info.svg + ../icons/light/info.svg true diff --git a/resources/ui/settings.ui b/resources/ui/settings.ui index 0826259..c2679bc 100644 --- a/resources/ui/settings.ui +++ b/resources/ui/settings.ui @@ -184,7 +184,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -216,7 +216,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -248,7 +248,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -330,7 +330,7 @@ - ../icons/plus.svg../icons/plus.svg + ../icons/light/plus.svg../icons/light/plus.svg @@ -344,7 +344,7 @@ - ../icons/trash.svg../icons/trash.svg + ../icons/light/trash.svg../icons/light/trash.svg @@ -362,7 +362,7 @@ - + 0 @@ -370,9 +370,150 @@ - Advanced + Appearance - + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + ../icons/light/theme_automatic.svg + + + true + + + Qt::AlignCenter + + + + + + + + 220 + 0 + + + + Automatic (Follow System Settings) + + + + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + ../icons/light/theme_light.svg + + + true + + + Qt::AlignCenter + + + + + + + + 220 + 0 + + + + Light + + + + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + ../icons/light/theme_dark.svg + + + true + + + Qt::AlignCenter + + + + + + + + 220 + 0 + + + + Dark + + + + + + + + + + + + + 0 + 0 + + + + Search + + @@ -402,7 +543,7 @@ - ../icons/help.svg../icons/help.svg + ../icons/light/help.svg../icons/light/help.svg @@ -466,7 +607,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true @@ -543,7 +684,7 @@ - ../icons/alert_red.svg + ../icons/light/alert_red.svg true diff --git a/resources/ui/translators/ko/settings.qm b/resources/ui/translators/ko/settings.qm index 7c9ba67..315d793 100644 Binary files a/resources/ui/translators/ko/settings.qm and b/resources/ui/translators/ko/settings.qm differ diff --git a/resources/ui/videoDownloadWidget.ui b/resources/ui/videoDownloadWidget.ui index 053134e..34b3b6e 100644 --- a/resources/ui/videoDownloadWidget.ui +++ b/resources/ui/videoDownloadWidget.ui @@ -78,7 +78,7 @@ - ../icons/download.svg../icons/download.svg + ../icons/light/download.svg../icons/light/download.svg @@ -101,7 +101,7 @@ - ../icons/instant_download.svg../icons/instant_download.svg + ../icons/light/instant_download.svg../icons/light/instant_download.svg diff --git a/resources/ui/videoWidget.ui b/resources/ui/videoWidget.ui index e608476..3a3aa0b 100644 --- a/resources/ui/videoWidget.ui +++ b/resources/ui/videoWidget.ui @@ -173,7 +173,7 @@ - ../icons/list.svg../icons/list.svg + ../icons/light/list.svg../icons/light/list.svg diff --git a/resources/ui/webViewWidget.ui b/resources/ui/webViewWidget.ui index f4155ec..bb3b45c 100644 --- a/resources/ui/webViewWidget.ui +++ b/resources/ui/webViewWidget.ui @@ -109,7 +109,7 @@ - ../icons/back.svg../icons/back.svg + ../icons/light/back.svg../icons/light/back.svg @@ -132,7 +132,7 @@ - ../icons/forward.svg../icons/forward.svg + ../icons/light/forward.svg../icons/light/forward.svg @@ -155,7 +155,7 @@ - ../icons/reload.svg../icons/reload.svg + ../icons/light/reload.svg../icons/light/reload.svg @@ -178,7 +178,7 @@ - ../icons/cancel.svg../icons/cancel.svg + ../icons/light/cancel.svg../icons/light/cancel.svg @@ -274,7 +274,7 @@ padding-right: 10px; - ../icons/info.svg + ../icons/light/info.svg true