diff --git a/cura/API/Account.py b/cura/API/Account.py index e190fe9b42..991a2ef967 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from datetime import datetime -from typing import Optional, Dict, TYPE_CHECKING, Union +from typing import Optional, Dict, TYPE_CHECKING, Callable from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS @@ -56,6 +56,7 @@ class Account(QObject): lastSyncDateTimeChanged = pyqtSignal() syncStateChanged = pyqtSignal(int) # because SyncState is an int Enum manualSyncEnabledChanged = pyqtSignal(bool) + updatePackagesEnabledChanged = pyqtSignal(bool) def __init__(self, application: "CuraApplication", parent = None) -> None: super().__init__(parent) @@ -66,6 +67,8 @@ class Account(QObject): self._logged_in = False self._sync_state = SyncState.IDLE self._manual_sync_enabled = False + self._update_packages_enabled = False + self._update_packages_action = None # type: Callable self._last_sync_str = "-" self._callback_port = 32118 @@ -143,6 +146,18 @@ class Account(QObject): if not self._update_timer.isActive(): self._update_timer.start() + def setUpdatePackagesAction(self, action: Callable): + """ Set the callback which will be invoked when the user clicks the update packages button + + Should be invoked after your service sets the sync state to SYNCING and before setting the + sync state to SUCCESS. + + Action will be reset to None when the next sync starts + """ + self._update_packages_action = action + self._update_packages_enabled = True + self.updatePackagesEnabledChanged.emit(self._update_packages_enabled) + def _onAccessTokenChanged(self): self.accessTokenChanged.emit() @@ -185,6 +200,9 @@ class Account(QObject): sync is currently running, a sync will be requested. """ + self._update_packages_action = None + self._update_packages_enabled = False + self.updatePackagesEnabledChanged.emit(self._update_packages_enabled) if self._update_timer.isActive(): self._update_timer.stop() elif self._sync_state == SyncState.SYNCING: @@ -251,6 +269,10 @@ class Account(QObject): def manualSyncEnabled(self) -> bool: return self._manual_sync_enabled + @pyqtProperty(bool, notify=updatePackagesEnabledChanged) + def updatePackagesEnabled(self) -> bool: + return self._update_packages_enabled + @pyqtSlot() @pyqtSlot(bool) def sync(self, user_initiated: bool = False) -> None: @@ -259,11 +281,14 @@ class Account(QObject): self._sync() + @pyqtSlot() + def update_packages(self): + if self._update_packages_action is not None: + self._update_packages_action() + @pyqtSlot() def popupOpened(self) -> None: self._setManualSyncEnabled(True) - self._sync_state = SyncState.IDLE - self.syncStateChanged.emit(self._sync_state) @pyqtSlot() def logout(self) -> None: diff --git a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py index 7c39354317..ac1f4ba6ab 100644 --- a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py +++ b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py @@ -95,10 +95,6 @@ class CloudPackageChecker(QObject): user_subscribed_packages = {plugin["package_id"] for plugin in subscribed_packages_payload} user_installed_packages = self._package_manager.getAllInstalledPackageIDs() - if user_subscribed_packages == self._last_notified_packages: - # already notified user about these - return - # We need to re-evaluate the dismissed packages # (i.e. some package might got updated to the correct SDK version in the meantime, # hence remove them from the Dismissed Incompatible list) @@ -109,7 +105,15 @@ class CloudPackageChecker(QObject): # We check if there are packages installed in Web Marketplace but not in Cura marketplace package_discrepancy = list(user_subscribed_packages.difference(user_installed_packages)) + if package_discrepancy: + account = self._application.getCuraAPI().account + account.setUpdatePackagesAction(lambda: self._onSyncButtonClicked(None, None)) + + if user_subscribed_packages == self._last_notified_packages: + # already notified user about these + return + Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages") self._model.addDiscrepancies(package_discrepancy) self._model.initialize(self._package_manager, subscribed_packages_payload) @@ -144,7 +148,8 @@ class CloudPackageChecker(QObject): self._message.hide() self._message = None - def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None: - sync_message.hide() + def _onSyncButtonClicked(self, sync_message: Optional[Message], sync_message_action: Optional[str]) -> None: + if sync_message is not None: + sync_message.hide() self._hideSyncMessage() # Should be the same message, but also sets _message to None self.discrepancies.emit(self._model) diff --git a/resources/qml/Account/SyncState.qml b/resources/qml/Account/SyncState.qml index 4e5543f751..77547e48f8 100644 --- a/resources/qml/Account/SyncState.qml +++ b/resources/qml/Account/SyncState.qml @@ -49,7 +49,7 @@ Row // Sync state icon + message width: 20 * screenScaleFactor height: width - source: Cura.API.account.manualSyncEnabled ? UM.Theme.getIcon("update") : UM.Theme.getIcon("checked") + // source is determined by State color: UM.Theme.getColor("account_sync_state_icon") RotationAnimator @@ -80,14 +80,36 @@ Row // Sync state icon + message Label { id: stateLabel - text: catalog.i18nc("@state", catalog.i18nc("@label", "Account synced")) + // text is determined by State color: UM.Theme.getColor("text") font: UM.Theme.getFont("medium") renderType: Text.NativeRendering width: contentWidth + UM.Theme.getSize("default_margin").height height: contentHeight verticalAlignment: Text.AlignVCenter - visible: !Cura.API.account.manualSyncEnabled + visible: !Cura.API.account.manualSyncEnabled && !Cura.API.account.updatePackagesEnabled + } + + Label + { + id: updatePackagesButton + text: catalog.i18nc("@button", "Install pending updates") + color: UM.Theme.getColor("secondary_button_text") + font: UM.Theme.getFont("medium") + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + height: contentHeight + width: contentWidth + UM.Theme.getSize("default_margin").height + visible: Cura.API.account.updatePackagesEnabled + + MouseArea + { + anchors.fill: parent + onClicked: Cura.API.account.update_packages() + hoverEnabled: true + onEntered: updatePackagesButton.font.underline = true + onExited: updatePackagesButton.font.underline = false + } } Label