mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
feat(BRIDGE-37): Remote notification support
This commit is contained in:
@ -57,6 +57,7 @@ UsersTab::UsersTab(QWidget *parent)
|
||||
connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState);
|
||||
connect(ui_.checkSync, &QCheckBox::toggled, this, &UsersTab::onCheckSyncToggled);
|
||||
connect(ui_.sliderSync, &QSlider::valueChanged, this, &UsersTab::onSliderSyncValueChanged);
|
||||
connect(ui_.sendNotificationButton, &QPushButton::clicked, this, &UsersTab::onSendUserNotification);
|
||||
|
||||
users_.append(defaultUser());
|
||||
|
||||
@ -216,6 +217,7 @@ void UsersTab::updateGUIState() {
|
||||
ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked());
|
||||
ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0);
|
||||
ui_.groupboxSync->setEnabled(user.get());
|
||||
ui_.groupBoxNotification->setEnabled(hasSelectedUser && (UserState::Connected == state));
|
||||
|
||||
if (user)
|
||||
ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername());
|
||||
@ -489,3 +491,41 @@ void UsersTab::onSliderSyncValueChanged(int value) {
|
||||
app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining.
|
||||
this->updateGUIState();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return the title for the notification.
|
||||
//****************************************************************************************************************************************************
|
||||
QString UsersTab::notificationTitle() const {
|
||||
return ui_.notificationTitle->text();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return the subtitle for the notification.
|
||||
//****************************************************************************************************************************************************
|
||||
QString UsersTab::notificationSubtitle() const {
|
||||
return ui_.notificationSubtitleText->text();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return the body for the notification.
|
||||
//****************************************************************************************************************************************************
|
||||
QString UsersTab::notificationBody() const {
|
||||
return ui_.notticationBodyText->text();
|
||||
}
|
||||
|
||||
|
||||
void UsersTab::onSendUserNotification() {
|
||||
SPUser const user = selectedUser();
|
||||
if (!user) {
|
||||
app().log().error(QString("%1 failed. Unkown user.").arg(__FUNCTION__));
|
||||
return;
|
||||
}
|
||||
|
||||
GRPCService &grpc = app().grpc();
|
||||
|
||||
if (grpc.isStreaming()) {
|
||||
QString const userID = user->id();
|
||||
grpc.sendEvent(newUserNotificationEvent(userID, notificationTitle(), notificationSubtitle(), notificationBody()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
#include "Tabs/ui_UsersTab.h"
|
||||
#include "UserTable.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief The 'Users' tab of the main window.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -50,12 +49,15 @@ public: // member functions.
|
||||
bool nextUserTwoPasswordsError() const; ///< Check if next user login should trigger 2nd password error.
|
||||
bool nextUserTwoPasswordsAbort() const; ///< Check if next user login should trigger 2nd password abort.
|
||||
QString usernamePasswordErrorMessage() const; ///< Return the username password error message.
|
||||
QString notificationTitle() const; ///< Return the user notification title.
|
||||
QString notificationSubtitle() const; ///< Return the user notification subtitle.
|
||||
QString notificationBody() const; ///< Return the user notification body.
|
||||
|
||||
public slots:
|
||||
void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode.
|
||||
void logoutUser(QString const &userID); ///< slot for the logging out of a user.
|
||||
void removeUser(QString const &userID); ///< Slot for the removal of a user.
|
||||
static void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
|
||||
static void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
|
||||
void processBadEventUserFeedback(QString const& userID, bool doResync); ///< Slot for the reception of a bad event user feedback.
|
||||
|
||||
private slots:
|
||||
@ -69,6 +71,7 @@ private slots:
|
||||
void onCheckSyncToggled(bool checked); ///< Slot for the 'Synchronizing' check box.
|
||||
void onSliderSyncValueChanged(int value); ///< Slot for the sync 'Progress' slider.
|
||||
void updateGUIState(); ///< Update the GUI state.
|
||||
void onSendUserNotification(); ///< Send a user notification event to the GUI.
|
||||
|
||||
private: // member functions.
|
||||
qint32 selectedIndex() const; ///< Get the index of the selected row.
|
||||
|
||||
@ -7,13 +7,19 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1221</width>
|
||||
<height>894</height>
|
||||
<height>408</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableUserList">
|
||||
<property name="selectionMode">
|
||||
@ -31,332 +37,419 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonNewUser">
|
||||
<property name="text">
|
||||
<string>New User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonEditUser">
|
||||
<property name="text">
|
||||
<string>Edit User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRemoveUser">
|
||||
<property name="text">
|
||||
<string>Remove User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupboxSync">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Sync</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkSync">
|
||||
<property name="text">
|
||||
<string>Synchronizing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSync">
|
||||
<property name="text">
|
||||
<string>0%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sliderSync">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxBadEvent">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Bad Event</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editUserBadEvent">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>error message</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonUserBadEvent">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxUsedSpace">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Used Bytes Changed</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hBoxUsedBytes" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="spinUsedBytes">
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000000000000000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonUsedBytesChanged">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxIMAPLoginFailed">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>IMAP Login Failure</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editIMAPLoginFailedUsername">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>username or primary email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonImapLoginFailed">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxNextLogin">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Next Login Attempt</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkUsernamePasswordError">
|
||||
<property name="text">
|
||||
<string>Username/password error:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editUsernamePasswordError">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Username/password error.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkHV3Required">
|
||||
<property name="text">
|
||||
<string>HV3 required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkHV3Error">
|
||||
<property name="text">
|
||||
<string>HV3 error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkFreeUserError">
|
||||
<property name="text">
|
||||
<string>Free user error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFARequired">
|
||||
<property name="text">
|
||||
<string>2FA required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFAError">
|
||||
<property name="text">
|
||||
<string>2FA error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFAAbort">
|
||||
<property name="text">
|
||||
<string>2FA abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsRequired">
|
||||
<property name="text">
|
||||
<string>2nd password required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsError">
|
||||
<property name="text">
|
||||
<string>2nd password error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsAbort">
|
||||
<property name="text">
|
||||
<string>2nd password abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<height>905</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonNewUser">
|
||||
<property name="text">
|
||||
<string>New User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonEditUser">
|
||||
<property name="text">
|
||||
<string>Edit User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRemoveUser">
|
||||
<property name="text">
|
||||
<string>Remove User</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxNotification">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Notification</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6" stretch="0,0,0,0">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="notificationTitle">
|
||||
<property name="placeholderText">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="notificationSubtitleText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Subtitle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="notticationBodyText">
|
||||
<property name="placeholderText">
|
||||
<string>Body</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sendNotificationButton">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupboxSync">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Sync</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkSync">
|
||||
<property name="text">
|
||||
<string>Synchronizing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSync">
|
||||
<property name="text">
|
||||
<string>0%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sliderSync">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxBadEvent">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Bad Event</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editUserBadEvent">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>error message</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonUserBadEvent">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxUsedSpace">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Used Bytes Changed</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hBoxUsedBytes" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="spinUsedBytes">
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000000000000000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonUsedBytesChanged">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxIMAPLoginFailed">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>IMAP Login Failure</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editIMAPLoginFailedUsername">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>username or primary email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonImapLoginFailed">
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxNextLogin">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Next Login Attempt</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkUsernamePasswordError">
|
||||
<property name="text">
|
||||
<string>Username/password error:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="editUsernamePasswordError">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Username/password error.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkHV3Required">
|
||||
<property name="text">
|
||||
<string>HV3 required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkHV3Error">
|
||||
<property name="text">
|
||||
<string>HV3 error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkFreeUserError">
|
||||
<property name="text">
|
||||
<string>Free user error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFARequired">
|
||||
<property name="text">
|
||||
<string>2FA required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFAError">
|
||||
<property name="text">
|
||||
<string>2FA error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTFAAbort">
|
||||
<property name="text">
|
||||
<string>2FA abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsRequired">
|
||||
<property name="text">
|
||||
<string>2nd password required</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsError">
|
||||
<property name="text">
|
||||
<string>2nd password error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTwoPasswordsAbort">
|
||||
<property name="text">
|
||||
<string>2nd password abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@ -1330,6 +1330,7 @@ void QMLBackend::connectGrpcEvents() {
|
||||
connect(client, &GRPCClient::knowledgeBasSuggestionsReceived, this, &QMLBackend::receivedKnowledgeBaseSuggestions);
|
||||
connect(client, &GRPCClient::repairStarted, this, &QMLBackend::repairStarted);
|
||||
connect(client, &GRPCClient::allUsersLoaded, this, &QMLBackend::allUsersLoaded);
|
||||
connect(client, &GRPCClient::userNotificationReceived, this, &QMLBackend::processUserNotification);
|
||||
|
||||
// cache events
|
||||
connect(client, &GRPCClient::cantMoveDiskCache, this, &QMLBackend::cantMoveDiskCache);
|
||||
@ -1418,3 +1419,25 @@ void QMLBackend::triggerRepair() const {
|
||||
app().grpc().triggerRepair();
|
||||
)
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] notification The user notification received from the event loop.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::processUserNotification(bridgepp::UserNotification const& notification) {
|
||||
this->userNotificationStack_.push(notification);
|
||||
trayIcon_->showUserNotification(notification.title, notification.subtitle);
|
||||
emit receivedUserNotification(notification);
|
||||
}
|
||||
|
||||
void QMLBackend::userNotificationDismissed() {
|
||||
if (!this->userNotificationStack_.size()) return;
|
||||
|
||||
// Remove the user notification from the top of the queue as it has been dismissed.
|
||||
this->userNotificationStack_.pop();
|
||||
if (!this->userNotificationStack_.size()) return;
|
||||
|
||||
// Display the user notification that is on top of the queue, if there is one.
|
||||
auto notification = this->userNotificationStack_.top();
|
||||
emit receivedUserNotification(notification);
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/GRPC/GRPCUtils.h>
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
#include <stack>
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
@ -174,6 +175,8 @@ signals: // Signal used by the Qt property system. Many of them are unused but r
|
||||
void isAutostartOnChanged(bool value); ///<Signal for the change of the 'isAutostartOn' property.
|
||||
void usersChanged(UserList *users); ///<Signal for the change of the 'users' property.
|
||||
void dockIconVisibleChanged(bool value); ///<Signal for the change of the 'dockIconVisible' property.
|
||||
void receivedUserNotification(bridgepp::UserNotification const& notification); ///< Signal to display the userNotification modal
|
||||
|
||||
|
||||
public slots: // slot for signals received from QML -> To be forwarded to Bridge via RPC Client calls.
|
||||
void toggleAutostart(bool active); ///< Slot for the autostart toggle.
|
||||
@ -209,6 +212,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
||||
void notifyAutoconfigClicked(QString const &client) const; ///< Slot for gAutoconfigClicked gRPC event.
|
||||
void notifyExternalLinkClicked(QString const &article) const; ///< Slot for KBArticleClicked gRPC event.
|
||||
void triggerRepair() const; ///< Slot for the triggering of the bridge repair function i.e. 'resync'.
|
||||
void userNotificationDismissed(); ///< Slot to pop the notification from the stack and display the rest.
|
||||
|
||||
public slots: // slots for functions that need to be processed locally.
|
||||
void setNormalTrayIcon(); ///< Set the tray icon to normal.
|
||||
@ -224,6 +228,7 @@ public slots: // slot for signals received from gRPC that need transformation in
|
||||
void onLoginAlreadyLoggedIn(QString const &userID); ///< Slot for the LoginAlreadyLoggedIn gRPC event.
|
||||
void onUserBadEvent(QString const& userID, QString const& errorMessage); ///< Slot for the userBadEvent gRPC event.
|
||||
void onIMAPLoginFailed(QString const& username); ///< Slot the the imapLoginFailed event.
|
||||
void processUserNotification(bridgepp::UserNotification const& notification); ///< Slot for the userNotificationReceived gRCP event.
|
||||
|
||||
signals: // Signals received from the Go backend, to be forwarded to QML
|
||||
void toggleAutostartFinished(); ///< Signal for the 'toggleAutostartFinished' gRPC stream event.
|
||||
@ -310,6 +315,7 @@ private: // data members
|
||||
QList<QString> badEventDisplayQueue_; ///< THe queue for displaying 'bad event feedback request dialog'.
|
||||
std::unique_ptr<TrayIcon> trayIcon_; ///< The tray icon for the application.
|
||||
bridgepp::BugReportFlow reportFlow_; ///< The bug report flow.
|
||||
std::stack<bridgepp::UserNotification> userNotificationStack_; ///< The stack which holds all of the active notifications that the user needs to acknowledge.
|
||||
friend class AppController;
|
||||
};
|
||||
|
||||
|
||||
@ -71,6 +71,7 @@
|
||||
<file>qml/icons/systray-mono-update.png</file>
|
||||
<file>qml/icons/systray-mono-warn.png</file>
|
||||
<file>qml/icons/systray.svg</file>
|
||||
<file>qml/icons/ic-notification-bell.svg</file>
|
||||
<file alias="bridge.svg">../../../../dist/bridge.svg</file>
|
||||
<file alias="bridgeMacOS.svg">../../../../dist/bridgeMacOS.svg</file>
|
||||
<file>qml/KeychainSettings.qml</file>
|
||||
@ -78,6 +79,7 @@
|
||||
<file>qml/MainWindow.qml</file>
|
||||
<file>qml/NoAccountView.qml</file>
|
||||
<file>qml/NotificationDialog.qml</file>
|
||||
<file>qml/UserNotificationDialog.qml</file>
|
||||
<file>qml/NotificationPopups.qml</file>
|
||||
<file>qml/Notifications/Notification.qml</file>
|
||||
<file>qml/Notifications/NotificationFilter.qml</file>
|
||||
|
||||
@ -331,6 +331,15 @@ void TrayIcon::showErrorPopupNotification(QString const &title, QString const &m
|
||||
this->showMessage(title, message, notificationErrorIcon_);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// Used only by user notifications received from the event loop
|
||||
/// \param[in] title The title.
|
||||
/// \param[in] subtitle The subtitle.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::showUserNotification(QString const &title, QString const &subtitle) {
|
||||
this->showMessage(title, subtitle, QSystemTrayIcon::NoIcon);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] svgPath The path of the SVG file for the icon.
|
||||
|
||||
@ -42,6 +42,8 @@ public: // data members
|
||||
TrayIcon& operator=(TrayIcon&&) = delete; ///< Disabled move assignment operator.
|
||||
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
||||
void showErrorPopupNotification(QString const& title, QString const &message); ///< Display a pop up notification.
|
||||
void showUserNotification(QString const& title, QString const &subtitle); ///< Display an OS pop up notification (without icon).
|
||||
|
||||
|
||||
signals:
|
||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal for selecting a user with a given userID
|
||||
|
||||
@ -22,6 +22,7 @@ Dialog {
|
||||
|
||||
default property alias data: additionalChildrenContainer.children
|
||||
property var notification
|
||||
property bool isUserNotification: false
|
||||
|
||||
modal: true
|
||||
shouldShow: notification && notification.active && !notification.dismissed
|
||||
@ -39,13 +40,13 @@ Dialog {
|
||||
return "";
|
||||
}
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return "/qml/icons/ic-info.svg";
|
||||
case Notification.NotificationType.Success:
|
||||
return "/qml/icons/ic-success.svg";
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "/qml/icons/ic-alert.svg";
|
||||
case Notification.NotificationType.Info:
|
||||
return "/qml/icons/ic-info.svg";
|
||||
case Notification.NotificationType.Success:
|
||||
return "/qml/icons/ic-success.svg";
|
||||
case Notification.NotificationType.Warning:
|
||||
case Notification.NotificationType.Danger:
|
||||
return "/qml/icons/ic-alert.svg";
|
||||
}
|
||||
}
|
||||
sourceSize.height: 64
|
||||
|
||||
@ -109,4 +109,8 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.repairBridge
|
||||
}
|
||||
UserNotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.userNotification
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,8 @@ QtObject {
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Danger
|
||||
Danger,
|
||||
UserNotification
|
||||
}
|
||||
|
||||
property list<Action> action
|
||||
@ -36,6 +37,9 @@ QtObject {
|
||||
readonly property var occurred: active ? new Date() : undefined
|
||||
property string title // title is used in dialogs only
|
||||
property int type
|
||||
property string subtitle
|
||||
property string username
|
||||
|
||||
|
||||
onActiveChanged: {
|
||||
dismissed = false;
|
||||
|
||||
@ -62,7 +62,7 @@ QtObject {
|
||||
target: Backend
|
||||
}
|
||||
}
|
||||
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent, root.repairBridge]
|
||||
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent, root.repairBridge, root.userNotification]
|
||||
property Notification alreadyLoggedIn: Notification {
|
||||
brief: qsTr("Already signed in")
|
||||
description: qsTr("This account is already signed in.")
|
||||
@ -1187,7 +1187,7 @@ QtObject {
|
||||
}
|
||||
target: root
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
function onRepairStarted() {
|
||||
root.repairBridge.active = false;
|
||||
@ -1200,6 +1200,35 @@ QtObject {
|
||||
|
||||
}
|
||||
|
||||
property Notification userNotification: Notification {
|
||||
brief: title
|
||||
group: Notifications.Group.Dialogs
|
||||
type: Notification.NotificationType.UserNotification
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg" // If it's not included QML complains
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("Okay")
|
||||
onTriggered: {
|
||||
root.userNotification.active = false;
|
||||
Backend.userNotificationDismissed();
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Connections {
|
||||
function onReceivedUserNotification(notification) {
|
||||
const userPrimaryEmailOrUsername = Backend.users.primaryEmailOrUsername(notification.userID)
|
||||
root.userNotification.title = notification.title
|
||||
root.userNotification.subtitle = notification.subtitle
|
||||
root.userNotification.description = notification.body
|
||||
root.userNotification.username = userPrimaryEmailOrUsername
|
||||
root.userNotification.active = true
|
||||
}
|
||||
target: Backend
|
||||
}
|
||||
}
|
||||
|
||||
signal askChangeAllMailVisibility(var isVisibleNow)
|
||||
signal askDeleteAccount(var user)
|
||||
signal askEnableBeta
|
||||
|
||||
@ -73,6 +73,11 @@ T.ApplicationWindow {
|
||||
if (obj.shouldShow === false) {
|
||||
continue;
|
||||
}
|
||||
// User notifications should have display priority
|
||||
if (obj.shouldShow && obj.isUserNotification) {
|
||||
topmost = obj;
|
||||
break;
|
||||
}
|
||||
if (topmost && (topmost.popupType > obj.popupType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2024 Proton AG
|
||||
// This file is part of Proton Mail Bridge.
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Proton
|
||||
import Notifications
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property var notification
|
||||
property bool isUserNotification: true
|
||||
padding: 40
|
||||
|
||||
modal: true
|
||||
shouldShow: notification && notification.active && !notification.dismissed
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 16
|
||||
Layout.preferredHeight: 64
|
||||
Layout.preferredWidth: 64
|
||||
source: {
|
||||
if (!root.notification) {
|
||||
return "";
|
||||
}
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.UserNotification:
|
||||
return "/qml/icons/ic-notification-bell.svg"
|
||||
}
|
||||
}
|
||||
sourceSize.height: 64
|
||||
sourceSize.width: 64
|
||||
visible: source != ""
|
||||
}
|
||||
// Title Label
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 4
|
||||
Layout.preferredWidth: 320
|
||||
colorScheme: root.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.notification.title
|
||||
wrapMode: Text.WordWrap
|
||||
type: Label.LabelType.Title
|
||||
}
|
||||
// Username or primary email
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 24
|
||||
Layout.preferredWidth: 320
|
||||
colorScheme: root.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.notification.username
|
||||
wrapMode: Text.WordWrap
|
||||
visible: root.notification.username.length > 0
|
||||
type: Label.LabelType.Caption
|
||||
}
|
||||
// Subtitle
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 24
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 320
|
||||
colorScheme: root.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.notification.subtitle
|
||||
wrapMode: Text.WordWrap
|
||||
visible: root.notification.subtitle.length > 0
|
||||
type: Label.LabelType.Lead
|
||||
color: root.colorScheme.text_weak
|
||||
}
|
||||
Label {
|
||||
Layout.bottomMargin: 24
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 320
|
||||
colorScheme: root.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.notification.description
|
||||
type: Label.LabelType.Body
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: function (link) {
|
||||
Backend.openExternalLink(link);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 40
|
||||
|
||||
Repeater {
|
||||
model: root.notification.action
|
||||
|
||||
delegate: Button {
|
||||
Layout.fillWidth: true
|
||||
action: modelData
|
||||
colorScheme: root.colorScheme
|
||||
loading: modelData.loading
|
||||
secondary: index > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64">
|
||||
<g clip-path="url(#a)">
|
||||
<circle cx="13.031" cy="5.288" r="3.166" fill="#2C83DC" transform="rotate(-30 13.031 5.288)"/>
|
||||
<path fill="url(#b)" d="M3.599 27.28A19.757 19.757 0 0 1 36.793 8.115L53.92 25.808a12.42 12.42 0 0 0 4.581 2.998c3.454 1.288 5.556 4.214 3.201 7.05-2.76 3.325-8.795 8.475-21.796 15.981S19.428 61.994 15.169 62.723c-3.634.621-5.117-2.662-4.506-6.298a12.422 12.422 0 0 0-.306-5.466L3.6 27.28Z"/>
|
||||
<path fill="url(#c)" d="M3.599 27.28A19.757 19.757 0 0 1 36.793 8.115L53.92 25.808a12.42 12.42 0 0 0 4.581 2.998c3.454 1.288 5.556 4.214 3.201 7.05-2.76 3.325-8.795 8.475-21.796 15.981S19.428 61.994 15.169 62.723c-3.634.621-5.117-2.662-4.506-6.298a12.422 12.422 0 0 0-.306-5.466L3.6 27.28Z"/>
|
||||
<ellipse cx="37.094" cy="46.965" fill="url(#d)" rx="26.875" ry="3.75" transform="rotate(-30 37.094 46.965)"/>
|
||||
<ellipse cx="37.094" cy="46.965" fill="url(#e)" rx="26.875" ry="3.75" transform="rotate(-30 37.094 46.965)"/>
|
||||
<path fill="#fff" fill-opacity=".2" d="m48.156 19.855-2.032-2.1c-6.302 1.79-12.908 4.57-19.41 8.324-7.591 4.383-14.103 9.553-19.19 14.959l.914 3.201c4.88-5.52 11.835-11.152 20.16-15.958 6.703-3.87 13.425-6.704 19.558-8.426Z"/>
|
||||
<circle cx="36.469" cy="45.883" r="2.5" fill="url(#f)" transform="rotate(-30 36.469 45.883)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="d" x1="49.16" x2="37.094" y1="30.878" y2="49.439" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#256097"/>
|
||||
<stop offset="1" stop-color="#2C83DC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="e" x1="49.68" x2="49.037" y1="46.478" y2="51.592" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#27ABF4" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#27ABF4" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="35.719" x2="36.469" y1="43.133" y2="48.383" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".345" stop-color="#B2EAFE" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#B2EAFE"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="rotate(131.347 12.11 6.294) scale(52.6374 54.7116)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B2EAFE"/>
|
||||
<stop offset="1" stop-color="#27ABF4"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="rotate(60 -23.22 57.501) scale(21.25 76.0937)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" stop-opacity="0"/>
|
||||
<stop offset=".46" stop-color="#fff" stop-opacity=".4"/>
|
||||
<stop offset=".58" stop-color="#B2EAFE" stop-opacity=".5"/>
|
||||
</radialGradient>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h64v64H0z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@ -729,4 +729,23 @@ SPStreamEvent newGenericErrorEvent(grpc::ErrorCode errorCode) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The user ID that received the notification.
|
||||
/// \param[in] title The title of the notification.
|
||||
/// \param[in] subtitle The subtitle of the notification.
|
||||
/// \param[in] body The body of the notification.
|
||||
/// \return The event.
|
||||
//****************************************************************************************************************************************************
|
||||
SPStreamEvent newUserNotificationEvent(QString const &userID, QString const title, QString const subtitle, QString const body) {
|
||||
auto event = new grpc::UserNotificationEvent;
|
||||
event->set_userid(userID.toStdString());
|
||||
event->set_body(body.toStdString());
|
||||
event->set_subtitle(subtitle.toStdString());
|
||||
event->set_title(title.toStdString());
|
||||
auto appEvent = new grpc::AppEvent;
|
||||
appEvent->set_allocated_usernotification(event);
|
||||
return wrapAppEvent(appEvent);
|
||||
}
|
||||
|
||||
|
||||
} // namespace bridgepp
|
||||
|
||||
@ -94,6 +94,9 @@ SPStreamEvent newSyncProgressEvent(QString const &userID, double progress, qint6
|
||||
// Generic error event
|
||||
SPStreamEvent newGenericErrorEvent(grpc::ErrorCode errorCode); ///< Create a new GenericErrrorEvent event.
|
||||
|
||||
// User notification event
|
||||
SPStreamEvent newUserNotificationEvent(QString const &userID, QString const title, QString const subtitle, QString const body);
|
||||
|
||||
} // namespace bridgepp
|
||||
|
||||
|
||||
|
||||
@ -1206,6 +1206,17 @@ void GRPCClient::processAppEvent(AppEvent const &event) {
|
||||
this->logTrace("App event received: AllUsersLoaded");
|
||||
emit allUsersLoaded();
|
||||
break;
|
||||
case AppEvent::kUserNotification: {
|
||||
this->logTrace("App event received: UserNotification");
|
||||
UserNotification notification{
|
||||
.title = QString::fromStdString(event.usernotification().title()),
|
||||
.subtitle = QString::fromStdString(event.usernotification().subtitle()),
|
||||
.body = QString::fromStdString(event.usernotification().body()),
|
||||
.userID = QString::fromStdString(event.usernotification().userid()),
|
||||
};
|
||||
emit userNotificationReceived(notification);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this->logError("Unknown App event received.");
|
||||
}
|
||||
|
||||
@ -56,6 +56,24 @@ public:
|
||||
};
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief A struct for user notitifications.
|
||||
//****************************************************************************************************************************************************
|
||||
struct UserNotification {
|
||||
// The following lines make the type transmissible to QML (but not instanciable there)
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString title MEMBER title)
|
||||
Q_PROPERTY(QString subtitle MEMBER subtitle)
|
||||
Q_PROPERTY(QString body MEMBER body)
|
||||
Q_PROPERTY(QString userID MEMBER userID)
|
||||
public:
|
||||
QString title; ///< The title of the notification.
|
||||
QString subtitle; ///< The subtitle of the notification.
|
||||
QString body; ///< The body of the notification.
|
||||
QString userID; ///< The userID that received the notification.
|
||||
};
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief gRPC client class. This class encapsulate the gRPC service, abstracting all data type conversions.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -125,6 +143,7 @@ signals: // app related signals
|
||||
void knowledgeBasSuggestionsReceived(QList<KnowledgeBaseSuggestion> const& suggestions);
|
||||
void repairStarted();
|
||||
void allUsersLoaded();
|
||||
void userNotificationReceived(UserNotification const& notification);
|
||||
|
||||
|
||||
public: // cache related calls
|
||||
|
||||
@ -20,6 +20,7 @@ package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@ -500,6 +501,18 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:gocyc
|
||||
|
||||
case events.Raise:
|
||||
f.Printf("Hello!")
|
||||
|
||||
case events.UserNotification:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n--- NOTIFICATION ---\n\n")
|
||||
fmt.Printf("Sent to: %s\n", user.Username)
|
||||
fmt.Printf("Title: %s\n", event.Title)
|
||||
fmt.Printf("Subtitle: %s\n", event.Subtitle)
|
||||
fmt.Printf("Message: %s\n\n", event.Body)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -277,6 +277,7 @@ message AppEvent {
|
||||
KnowledgeBaseSuggestionsEvent knowledgeBaseSuggestions = 12;
|
||||
RepairStartedEvent repairStarted = 13;
|
||||
AllUsersLoadedEvent allUsersLoaded = 14;
|
||||
UserNotificationEvent userNotification = 15;
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,6 +546,14 @@ message SyncProgressEvent {
|
||||
int64 remainingMs = 4;
|
||||
}
|
||||
|
||||
message UserNotificationEvent {
|
||||
string title = 1;
|
||||
string subtitle = 2;
|
||||
string body = 3;
|
||||
string userID = 4;
|
||||
}
|
||||
|
||||
|
||||
//**********************************************************
|
||||
// Generic errors
|
||||
//**********************************************************
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
)
|
||||
@ -249,6 +250,16 @@ func NewAllUsersLoadedEvent() *StreamEvent {
|
||||
return appEvent(&AppEvent{Event: &AppEvent_AllUsersLoaded{AllUsersLoaded: &AllUsersLoadedEvent{}}})
|
||||
}
|
||||
|
||||
func NewUserNotificationEvent(event events.UserNotification) *StreamEvent {
|
||||
return appEvent(&AppEvent{Event: &AppEvent_UserNotification{
|
||||
UserNotification: &UserNotificationEvent{
|
||||
UserID: event.UserID,
|
||||
Title: event.Title,
|
||||
Subtitle: event.Subtitle,
|
||||
Body: event.Body,
|
||||
}}})
|
||||
}
|
||||
|
||||
// Event category factory functions.
|
||||
|
||||
func appEvent(appEvent *AppEvent) *StreamEvent {
|
||||
|
||||
@ -404,6 +404,9 @@ func (s *Service) watchEvents() {
|
||||
|
||||
case events.AllUsersLoaded:
|
||||
_ = s.SendEvent(NewAllUsersLoadedEvent())
|
||||
|
||||
case events.UserNotification:
|
||||
_ = s.SendEvent(NewUserNotificationEvent(event))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user