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