Do not display notifications when user status is do not disturb.

- This information is retrieved from the notifications endpoint.
- Add icons for the different pre defined status.
- Make functions available to QML to decide which status icon to display.
- Display the user status icon on the avatar and
move the online/offline connection status to the folder icon.

Signed-off-by: Camila <hello@camila.codes>
This commit is contained in:
Camila 2021-03-16 20:24:11 +01:00
parent f4d8699db8
commit 974e2fb718
No known key found for this signature in database
GPG key ID: 7A4A6121E88E2AD4
20 changed files with 287 additions and 82 deletions

View file

@ -45,7 +45,8 @@ AccountState::AccountState(AccountPtr account)
, _waitingForNewCredentials(false)
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
, _remoteWipe(new RemoteWipe(_account))
, _userStatus(new UserStatus(this, this))
, _userStatus(new UserStatus(this))
, _notificationStatus("online")
{
qRegisterMetaType<AccountState *>("AccountState*");
@ -127,9 +128,19 @@ void AccountState::setState(State state)
emit stateChanged(_state);
}
QString AccountState::currentUserStatus() const
QString AccountState::status() const
{
return _userStatus->currentUserStatus();
return _userStatus->status();
}
QString AccountState::statusMessage() const
{
return _userStatus->message();
}
QUrl AccountState::statusIcon() const
{
return _userStatus->icon();
}
QString AccountState::stateString(State state)
@ -212,6 +223,16 @@ void AccountState::setNavigationAppsEtagResponseHeader(const QByteArray &value)
_navigationAppsEtagResponseHeader = value;
}
QString AccountState::notificationStatus() const
{
return _notificationStatus;
}
void AccountState::setNotificationStatus(const QString &status)
{
_notificationStatus = status;
}
void AccountState::checkConnectivity()
{
if (isSignedOut() || _waitingForNewCredentials) {
@ -429,8 +450,10 @@ void AccountState::fetchNavigationApps(){
job->getNavigationApps();
}
void AccountState::fetchCurrentUserStatus() {
_userStatus->fetchCurrentUserStatus();
void AccountState::fetchUserStatus()
{
connect(_userStatus, &UserStatus::fetchUserStatusFinished, this, &AccountState::userStatusChanged);
_userStatus->fetchUserStatus(_account);
}
void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){

View file

@ -162,9 +162,31 @@ public:
///Asks for user credentials
void handleInvalidCredentials();
QString currentUserStatus() const;
/** Returns the user status (online, dnd, away, offline, invisible)
* https://gist.github.com/georgehrke/55a0412007f13be1551d1f9436a39675
*/
QString status() const;
void fetchCurrentUserStatus();
/** Returns the user status Message (emoji + text)
*/
QString statusMessage() const;
/** Returns the user status icon url
*/
QUrl statusIcon() const;
/** Returns the user status retrieved by the notificatons endpoint: dnd or online
* https://github.com/nextcloud/desktop/issues/2318#issuecomment-680698429
*/
QString notificationStatus() const;
/** Set new user status retrieved by the notificatons endpoint: dnd or online
*/
void setNotificationStatus(const QString &status);
/** Fetch the user status (status, icon, message)
*/
void fetchUserStatus();
public slots:
/// Triggers a ping to the server to update state and
@ -230,6 +252,7 @@ private:
AccountAppList _apps;
UserStatus *_userStatus;
QString _notificationStatus;
};
class AccountApp : public QObject

View file

@ -48,6 +48,8 @@ void ServerNotificationHandler::slotFetchNotifications()
this, &ServerNotificationHandler::slotNotificationsReceived);
QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived,
this, &ServerNotificationHandler::slotEtagResponseHeaderReceived);
QObject::connect(_notificationJob.data(), &JsonApiJob::desktopNotificationStatusReceived,
this, &ServerNotificationHandler::slotDesktopNotificationStatusReceived);
_notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue<AccountState *>(_accountState));
_notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader());
_notificationJob->start();
@ -62,6 +64,14 @@ void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray
}
}
void ServerNotificationHandler::slotDesktopNotificationStatusReceived(const bool status)
{
auto *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
if (account != nullptr) {
account->setDesktopNotificationsStatus(status);
}
}
void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData)
{
iconCache.insert(sender()->property("activityId").toInt(),iconData);

View file

@ -26,6 +26,7 @@ private slots:
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotIconDownloaded(QByteArray iconData);
void slotDesktopNotificationStatusReceived(const bool status);
private:
QPointer<JsonApiJob> _notificationJob;

View file

@ -35,7 +35,7 @@ MenuItem {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
}
onClicked: {
if (!isCurrentUser) {
@ -71,8 +71,8 @@ MenuItem {
Layout.preferredHeight: (userLineLayout.height -16)
Layout.preferredWidth: (userLineLayout.height -16)
Rectangle {
id: accountStateIndicatorBackground
width: accountStateIndicator.sourceSize.width + 2
id: accountStatusIndicatorBackground
width: accountStatusIndicator.sourceSize.width + 2
height: width
anchors.bottom: accountAvatar.bottom
anchors.right: accountAvatar.right
@ -80,18 +80,16 @@ MenuItem {
radius: width*0.5
}
Image {
id: accountStateIndicator
source: model.isConnected
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
id: accountStatusIndicator
source: model.statusIcon
cache: false
x: accountStateIndicatorBackground.x + 1
y: accountStateIndicatorBackground.y + 1
x: accountStatusIndicatorBackground.x + 1
y: accountStatusIndicatorBackground.y + 1
sourceSize.width: Style.accountAvatarStateIndicatorSize
sourceSize.height: Style.accountAvatarStateIndicatorSize
Accessible.role: Accessible.Indicator
Accessible.name: model.isConnected ? qsTr("Account connected") : qsTr("Account not connected")
Accessible.name: model.isStatusOnline ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")
}
}
@ -110,9 +108,9 @@ MenuItem {
font.bold: true
}
Label {
id: userStatus
id: userStatusMessage
width: 128
text: status
text: statusMessage
elide: Text.ElideRight
color: "black"
font.pixelSize: 10
@ -231,13 +229,4 @@ MenuItem {
}
}
}
Connections {
target: UserModel
onRefreshCurrentUserGui: {
accountStateIndicator.source = model.isConnected
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
}
}
} // MenuItem userLine

View file

@ -88,7 +88,7 @@ void User::slotBuildNotificationDisplay(const ActivityList &list)
// Assemble a tray notification for the NEW notification
ConfigFile cfg;
if (cfg.optionalServerNotifications() /*and header is not X-Nextcloud-User-Status*/) {
if (cfg.optionalServerNotifications() && isDesktopNotificationsAllowed()) {
if (AccountManager::instance()->accounts().count() == 1) {
emit guiLog(activity._subject, "");
} else {
@ -208,7 +208,7 @@ void User::slotRefresh()
slotRefreshActivities();
}
slotRefreshNotifications();
_account.data()->fetchCurrentUserStatus();
_account.data()->fetchUserStatus();
timer.start();
}
}
@ -559,9 +559,19 @@ QString User::server(bool shortened) const
return serverUrl;
}
QString User::currentUserStatus() const
QString User::status() const
{
return _account->currentUserStatus();
return _account->status();
}
QString User::statusMessage() const
{
return _account->statusMessage();
}
QUrl User::statusIcon() const
{
return _account->statusIcon();
}
QImage User::avatar() const
@ -613,6 +623,12 @@ bool User::isConnected() const
return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected);
}
bool User::isDesktopNotificationsAllowed() const
{
return _account.data()->notificationStatus() == "online";
}
void User::removeAccount() const
{
AccountManager::instance()->deleteAccount(_account.data());
@ -674,6 +690,16 @@ Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
return _users[id]->isConnected();
}
Q_INVOKABLE QUrl UserModel::statusIcon(const int &id)
{
if (id < 0 || id >= _users.size()) {
return {};
}
return _users[id]->statusIcon();
}
QImage UserModel::avatarById(const int &id)
{
if (id < 0 || id >= _users.size())
@ -711,7 +737,8 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
});
connect(u, &User::userStatusChanged, this, [this, row] {
emit dataChanged(index(row, 0), index(row, 0), {UserModel::StatusRole});
emit dataChanged(index(row, 0), index(row, 0), {UserModel::StatusIconRole,
UserModel::StatusMessageRole});
});
_users << u;
@ -852,8 +879,10 @@ QVariant UserModel::data(const QModelIndex &index, int role) const
return _users[index.row()]->name();
} else if (role == ServerRole) {
return _users[index.row()]->server();
} else if (role == StatusRole) {
return _users[index.row()]->currentUserStatus();
} else if (role == StatusIconRole) {
return _users[index.row()]->statusIcon();
} else if (role == StatusMessageRole) {
return _users[index.row()]->statusMessage();
} else if (role == AvatarRole) {
return _users[index.row()]->avatarUrl();
} else if (role == IsCurrentUserRole) {
@ -871,7 +900,8 @@ QHash<int, QByteArray> UserModel::roleNames() const
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ServerRole] = "server";
roles[StatusRole] = "status";
roles[StatusIconRole] = "statusIcon";
roles[StatusMessageRole] = "statusMessage";
roles[AvatarRole] = "avatar";
roles[IsCurrentUserRole] = "isCurrentUser";
roles[IsConnectedRole] = "isConnected";

View file

@ -19,7 +19,8 @@ class User : public QObject
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString server READ server CONSTANT)
Q_PROPERTY(QString status READ currentUserStatus NOTIFY userStatusChanged)
Q_PROPERTY(QUrl statusIcon READ statusIcon NOTIFY userStatusChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY userStatusChanged)
Q_PROPERTY(bool hasLocalFolder READ hasLocalFolder NOTIFY hasLocalFolderChanged)
Q_PROPERTY(bool serverHasTalk READ serverHasTalk NOTIFY serverHasTalkChanged)
Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
@ -36,7 +37,6 @@ public:
void openLocalFolder();
QString name() const;
QString server(bool shortened = true) const;
QString currentUserStatus() const;
bool hasLocalFolder() const;
bool serverHasTalk() const;
AccountApp *talkApp() const;
@ -47,6 +47,10 @@ public:
void logout() const;
void removeAccount() const;
QString avatarUrl() const;
bool isDesktopNotificationsAllowed() const;
QString status() const;
QString statusMessage() const;
QUrl statusIcon() const;
signals:
void guiLog(const QString &, const QString &);
@ -135,6 +139,7 @@ public:
Q_INVOKABLE bool currentUserHasLocalFolder();
int currentUserId() const;
Q_INVOKABLE bool isUserConnected(const int &id);
Q_INVOKABLE QUrl statusIcon(const int &id);
Q_INVOKABLE void switchCurrentUser(const int &id);
Q_INVOKABLE void login(const int &id);
Q_INVOKABLE void logout(const int &id);
@ -145,7 +150,8 @@ public:
enum UserRoles {
NameRole = Qt::UserRole + 1,
ServerRole,
StatusRole,
StatusIconRole,
StatusMessageRole,
AvatarRole,
IsCurrentUserRole,
IsConnectedRole,

View file

@ -35,8 +35,8 @@ Window {
}
onVisibleChanged: {
currentAccountStateIndicator.source = ""
currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)
folderStateIndicator.source = ""
folderStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
@ -49,8 +49,8 @@ Window {
Connections {
target: UserModel
onRefreshCurrentUserGui: {
currentAccountStateIndicator.source = ""
currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)
folderStateIndicator.source = ""
folderStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
}
@ -328,7 +328,7 @@ Window {
Accessible.name: qsTr("Current user avatar")
Rectangle {
id: currentAccountStateIndicatorBackground
id: currentAccountStatusIndicatorBackground
width: Style.accountAvatarStateIndicatorSize + 2
height: width
anchors.bottom: currentAccountAvatar.bottom
@ -348,18 +348,16 @@ Window {
}
Image {
id: currentAccountStateIndicator
source: UserModel.isUserConnected(UserModel.currentUserId)
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
id: currentAccountStatusIndicator
source: UserModel.currentUser.statusIcon
cache: false
x: currentAccountStateIndicatorBackground.x + 1
y: currentAccountStateIndicatorBackground.y + 1
x: currentAccountStatusIndicatorBackground.x + 1
y: currentAccountStatusIndicatorBackground.y + 1
sourceSize.width: Style.accountAvatarStateIndicatorSize
sourceSize.height: Style.accountAvatarStateIndicatorSize
Accessible.role: Accessible.Indicator
Accessible.name: UserModel.isUserConnected(UserModel.currentUserId()) ? qsTr("Connected") : qsTr("Disconnected")
Accessible.name: UserModel.isUserStatusOnline(UserModel.currentUserId()) ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")
}
}
@ -381,7 +379,7 @@ Window {
Label {
id: currentUserStatus
width: Style.currentAccountLabelWidth
text: UserModel.currentUser.status
text: UserModel.currentUser.statusMessage
elide: Text.ElideRight
color: Style.ncTextColor
font.pixelSize: Style.subLinePixelSize
@ -423,6 +421,42 @@ Window {
Accessible.onPressAction: openLocalFolderButton.clicked()
}
Rectangle {
id: folderStateIndicatorBackground
width: Style.folderStateIndicatorSize
height: width
anchors.top: openLocalFolderButton.verticalCenter
anchors.left: openLocalFolderButton.horizontalCenter
color: Style.ncBlue
radius: width*0.5
}
Rectangle {
id: folderStateRectangle
width: Style.folderStateIndicatorSize
height: width
anchors.bottom: openLocalFolderButton.bottom
anchors.right: openLocalFolderButton.right
color: openLocalFolderButton.containsMouse ? "white" : "transparent"
opacity: 0.2
radius: width*0.5
}
Image {
id: folderStateIndicator
source: UserModel.isUserConnected(UserModel.currentUserId)
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
cache: false
x: folderStateIndicatorBackground.x
y: folderStateIndicatorBackground.y
sourceSize.width: Style.folderStateIndicatorSize
sourceSize.height: Style.folderStateIndicatorSize
Accessible.role: Accessible.Indicator
Accessible.name: UserModel.isUserConnected(UserModel.currentUserId()) ? qsTr("Connected") : qsTr("Disconnected")
}
HeaderButton {
id: trayWindowTalkButton

View file

@ -18,7 +18,7 @@
#include "networkjobs.h"
#include "folderman.h"
#include "creds/abstractcredentials.h"
#include <theme.h>
#include "theme.h"
#include <QTimer>
#include <QJsonDocument>
@ -26,47 +26,66 @@
namespace OCC {
UserStatus::UserStatus(AccountState *accountState, QObject *parent)
UserStatus::UserStatus(QObject *parent)
: QObject(parent)
, _accountState(accountState)
, _status("online")
, _message("")
{
connect(this, &UserStatus::fetchedCurrentUserStatus, _accountState, &AccountState::userStatusChanged);
}
void UserStatus::fetchCurrentUserStatus()
void UserStatus::fetchUserStatus(AccountPtr account)
{
if (_job) {
_job->deleteLater();
}
AccountPtr account = _accountState->account();
_job = new JsonApiJob(account, QStringLiteral("/ocs/v2.php/apps/user_status/api/v1/user_status"), this);
connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchedCurrentStatus);
connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchUserStatusFinished);
_job->start();
}
void UserStatus::slotFetchedCurrentStatus(const QJsonDocument &json)
void UserStatus::slotFetchUserStatusFinished(const QJsonDocument &json)
{
const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject();
const auto icon = retrievedData.value("icon").toString();
const auto emoji = retrievedData.value("icon").toString();
const auto message = retrievedData.value("message").toString();
auto status = retrievedData.value("status").toString();
_status = retrievedData.value("status").toString();
if(message.isEmpty()) {
if(status == "dnd") {
status = tr("Do not disturb");
}
} else {
status = message;
}
const auto visibleStatusText = message.isEmpty()
? _status == "dnd"? tr("Do not disturb") : _status
: message;
_currentUserStatus = QString("%1 %2").arg(icon, status);
emit fetchedCurrentUserStatus();
_message = QString("%1 %2").arg(emoji, visibleStatusText);
emit fetchUserStatusFinished();
}
QString UserStatus::currentUserStatus() const
QString UserStatus::status() const
{
return _currentUserStatus;
return _status;
}
QString UserStatus::message() const
{
return _message;
}
QUrl UserStatus::icon() const
{
// online, away, dnd, invisible, offline
if(_status == "online") {
return Theme::instance()->statusOnlineImageSource();
} else if (_status == "away") {
return Theme::instance()->statusAwayImageSource();
} else if (_status == "dnd") {
return Theme::instance()->statusDoNotDisturbImageSource();
} else if (_status == "invisible") {
return Theme::instance()->statusInvisibleImageSource();
} else if (_status == "offline") {
return Theme::instance()->statusInvisibleImageSource();
}
return Theme::instance()->statusOnlineImageSource();
}
} // namespace OCC

View file

@ -17,10 +17,9 @@
#include <QObject>
#include <QPointer>
#include <QVariant>
#include "accountfwd.h"
namespace OCC {
class AccountState;
class JsonApiJob;
class UserStatus : public QObject
@ -28,20 +27,22 @@ class UserStatus : public QObject
Q_OBJECT
public:
explicit UserStatus(AccountState *accountState, QObject *parent = nullptr);
void fetchCurrentUserStatus();
QString currentUserStatus() const;
explicit UserStatus(QObject *parent = nullptr);
void fetchUserStatus(AccountPtr account);
QString status() const;
QString message() const;
QUrl icon() const;
private slots:
void slotFetchedCurrentStatus(const QJsonDocument &json);
void slotFetchUserStatusFinished(const QJsonDocument &json);
signals:
void fetchedCurrentUserStatus();
void fetchUserStatusFinished();
private:
QPointer<AccountState> _accountState;
QPointer<JsonApiJob> _job; // the currently running job
QString _currentUserStatus;
QString _status;
QString _message;
};

View file

@ -871,6 +871,11 @@ bool JsonApiJob::finished()
if(reply()->rawHeaderList().contains("ETag"))
emit etagResponseHeaderReceived(reply()->rawHeader("ETag"), statusCode);
const auto desktopNotificationStatus = reply()->rawHeader(QByteArray("X-Nextcloud-User-Status"));
if(!desktopNotificationStatus.isEmpty()) {
emit desktopNotificationStatusReceived(desktopNotificationStatus == "online");
}
QJsonParseError error;
auto json = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
// empty or invalid response and status code is != 304 because jsonStr is expected to be empty

View file

@ -420,6 +420,12 @@ signals:
* @param statusCode - the OCS status code: 100 (!) for success
*/
void etagResponseHeaderReceived(const QByteArray &value, int statusCode);
/**
* @brief desktopNotificationStatusReceived - signal to report the if user is online or dnd
* @param status - set desktop notifications allowed status
*/
void desktopNotificationStatusReceived(const bool status);
private:
QUrlQuery _additionalParams;

View file

@ -139,6 +139,26 @@ QUrl Theme::stateOfflineImageSource() const
return imagePathToUrl(themeImagePath("state-offline", 16));
}
QUrl Theme::statusOnlineImageSource() const
{
return imagePathToUrl(themeImagePath("user-status-online", 16));
}
QUrl Theme::statusDoNotDisturbImageSource() const
{
return imagePathToUrl(themeImagePath("user-status-dnd", 16));
}
QUrl Theme::statusAwayImageSource() const
{
return imagePathToUrl(themeImagePath("user-status-away", 16));
}
QUrl Theme::statusInvisibleImageSource() const
{
return imagePathToUrl(themeImagePath("user-status-invisible", 16));
}
QString Theme::version() const
{
return MIRALL_VERSION_STRING;

View file

@ -42,6 +42,11 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject
Q_PROPERTY(QString appName READ appName CONSTANT)
Q_PROPERTY(QUrl stateOnlineImageSource READ stateOnlineImageSource CONSTANT)
Q_PROPERTY(QUrl stateOfflineImageSource READ stateOfflineImageSource CONSTANT)
Q_PROPERTY(QUrl stateOnlineImageSource READ stateOnlineImageSource CONSTANT)
Q_PROPERTY(QUrl statusOnlineImageSource READ statusOnlineImageSource CONSTANT)
Q_PROPERTY(QUrl statusDoNotDisturbImageSource READ statusDoNotDisturbImageSource CONSTANT)
Q_PROPERTY(QUrl statusAwayImageSource READ statusAwayImageSource CONSTANT)
Q_PROPERTY(QUrl statusInvisibleImageSource READ statusInvisibleImageSource CONSTANT)
#ifndef TOKEN_AUTH_ONLY
Q_PROPERTY(QIcon folderDisabledIcon READ folderDisabledIcon CONSTANT)
Q_PROPERTY(QIcon folderOfflineIcon READ folderOfflineIcon CONSTANT)
@ -122,6 +127,30 @@ public:
* @return QUrl full path to an icon
*/
QUrl stateOfflineImageSource() const;
/**
* @brief Returns full path to an online user status icon
* @return QUrl full path to an icon
*/
QUrl statusOnlineImageSource() const;
/**
* @brief Returns full path to an do not disturb user status icon
* @return QUrl full path to an icon
*/
QUrl statusDoNotDisturbImageSource() const;
/**
* @brief Returns full path to an away user status icon
* @return QUrl full path to an icon
*/
QUrl statusAwayImageSource() const;
/**
* @brief Returns full path to an invisible user status icon
* @return QUrl full path to an icon
*/
QUrl statusInvisibleImageSource() const;
/**
* @brief configFileName

View file

@ -195,5 +195,9 @@
<file>theme/share.svg</file>
<file>theme/reply.svg</file>
<file>theme/magnifying-glass.svg</file>
<file>theme/colored/user-status-online.svg</file>
<file>theme/colored/user-status-invisible.svg</file>
<file>theme/colored/user-status-away.svg</file>
<file>theme/colored/user-status-dnd.svg</file>
</qresource>
</RCC>

View file

@ -33,6 +33,7 @@ QtObject {
property int accountAvatarSize: (trayWindowHeaderHeight - 16)
property int accountAvatarStateIndicatorSize: 16
property int folderStateIndicatorSize: 16
property int accountLabelWidth: 128
property int accountDropDownCaretSize: 20

View file

@ -0,0 +1 @@
<svg width="24" height="24" enable-background="new 0 0 24 24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" fill="none"/><path d="m10.615 2.1094c-4.8491 0.68106-8.6152 4.8615-8.6152 9.8906 0 5.5 4.5 10 10 10 5.0292 0 9.2096-3.7661 9.8906-8.6152-1.4654 1.601-3.5625 2.6152-5.8906 2.6152-4.4 0-8-3.6-8-8 0-2.3281 1.0143-4.4252 2.6152-5.8906z" fill="#f4a331"/></svg>

After

Width:  |  Height:  |  Size: 415 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10z" fill="#ed484c"/><path d="m8 10h8c1.108 0 2 0.892 2 2s-0.892 2-2 2h-8c-1.108 0-2-0.892-2-2s0.892-2 2-2z" fill="#fdffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" style="paint-order:stroke markers fill"/></svg>

After

Width:  |  Height:  |  Size: 445 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10zm0 4a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 295 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m8 16h8v-8h-8v8zm4-14c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10z" fill="#49B382"/></svg>

After

Width:  |  Height:  |  Size: 236 B