diff --git a/app/icons/Close.svg b/app/icons/Close.svg new file mode 100644 index 000000000..e33397dfc --- /dev/null +++ b/app/icons/Close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/icons/icons.qrc b/app/icons/icons.qrc index 7f82ade42..56ba3d5fa 100644 --- a/app/icons/icons.qrc +++ b/app/icons/icons.qrc @@ -14,5 +14,6 @@ CloseButton.svg UploadImage.svg ReachedDataLimitImage.svg + Close.svg diff --git a/app/qmlV2/component/MMInput.qml b/app/qmlV2/component/MMInput.qml index 85a810435..3b40627d0 100644 --- a/app/qmlV2/component/MMInput.qml +++ b/app/qmlV2/component/MMInput.qml @@ -15,7 +15,7 @@ import "../Style.js" as Style Item { id: control - enum Type { Normal, Password, Search, Calendar, Scan, CopyButton } + enum Type { Normal, Password, Search, Calendar, Scan, CopyButton, SendButton } property int type: MMInput.Type.Normal property alias text: textField.text @@ -23,6 +23,8 @@ Item { property string warningMsg property string errorMsg + signal sendButtonClicked + width: 280 * __dp height: rect.height + messageItem.height @@ -120,7 +122,7 @@ Item { height: rect.height visible: control.type === MMInput.Type.Password || control.type === MMInput.Type.Scan || - ((control.type !== MMInput.Type.CopyButton) && textField.activeFocus && textField.text.length>0) + ((control.type !== MMInput.Type.CopyButton || control.type !== MMInput.Type.SendButton) && textField.activeFocus && textField.text.length>0) MouseArea { anchors.fill: parent @@ -140,13 +142,13 @@ Item { Button { id: button - visible: control.type === MMInput.Type.CopyButton + visible: control.type === MMInput.Type.CopyButton || control.type === MMInput.Type.SendButton anchors.verticalCenter: parent.verticalCenter contentItem: Text { anchors.centerIn: button font: Qt.font(Style.t5) - text: qsTr("Copy") + text: control.type === MMInput.Type.CopyButton ? qsTr("Copy") : qsTr("Send") leftPadding: 2 * __dp rightPadding: 2 * __dp topPadding: 2 * __dp @@ -163,9 +165,14 @@ Item { } onClicked: { - textField.selectAll() - textField.copy() - textField.deselect() + if(control.type === MMInput.Type.CopyButton) { + textField.selectAll() + textField.copy() + textField.deselect() + } + else if(control.type === MMInput.Type.SendButton) { + sendButtonClicked() + } } } diff --git a/app/qmlV2/component/MMNotification.qml b/app/qmlV2/component/MMNotification.qml new file mode 100644 index 000000000..986775369 --- /dev/null +++ b/app/qmlV2/component/MMNotification.qml @@ -0,0 +1,91 @@ +/*************************************************************************** + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick +import "../Style.js" as Style +import notificationType 1.0 + +Row { + id: notification + + width: listView.width - 2 * Style.commonSpacing + height: Style.notificationHeight + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined + + readonly property int innerSpacing: 5 * __dp + + Rectangle { + width: parent.width + height: parent.height + color: Style.forest + radius: Style.notificationRadius + + Rectangle { + id: borderRect + + anchors.centerIn: parent + width: parent.width - notification.innerSpacing + height: parent.height - notification.innerSpacing + radius: Style.notificationRadius + color: Style.transparent + border.width: __dp + border.color: { + switch( type ) { + case NotificationType.Information: return Style.sky + case NotificationType.Warning: return Style.warning + case NotificationType.Error: return Style.negative + default: return Style.positive + } + } + } + + Rectangle { + id: icon + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Style.commonSpacing + width: 18 * __dp + height: 18 * __dp + color: borderRect.border.color + radius: width/2 + } + + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: icon.right + width: parent.width - 3 * Style.commonSpacing - closeButton.width - icon.width + text: message + color: Style.white + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + leftPadding: Style.commonSpacing - notification.innerSpacing + font: Qt.font(Style.t3) + clip: true + } + + Image { + id: closeButton + + anchors.right: parent.right + anchors.rightMargin: Style.commonSpacing + anchors.verticalCenter: parent.verticalCenter + scale: maRemove.containsMouse ? 1.2 : 1 + source: Style.closeIcon + + MouseArea { + id: maRemove + + anchors.fill: parent + hoverEnabled: true + onClicked: notificationModel.remove(id) + } + } + } +} diff --git a/app/qmlV2/component/MMNotificationView.qml b/app/qmlV2/component/MMNotificationView.qml new file mode 100644 index 000000000..8211d8498 --- /dev/null +++ b/app/qmlV2/component/MMNotificationView.qml @@ -0,0 +1,54 @@ +/*************************************************************************** + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick +import "../Style.js" as Style + +Item { + id: control + + anchors.top: parent.top + anchors.topMargin: Style.commonSpacing + width: parent.width + height: parent.height + + // just for information - will be removed in release version + Rectangle { + anchors.bottom: parent.bottom + width: control.width + height: 20 + color: Style.white + + Text { + text: listView.count + anchors.centerIn: parent + color: Style.forest + } + } + + ListView { + id: listView + + anchors.top: parent.top + width: parent.width + height: Style.notificationHeight * listView.count + spacing * (listView.count - 1) + spacing: Style.notificationSpace + clip: true + model: notificationModel + delegate: MMNotification { + + } + + add: Transition { + NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 200 } + NumberAnimation { property: "scale"; easing.type: Easing.OutCubic; from: 0; to: 1.0; duration: 200 } + } + + } +} diff --git a/gallery/CMakeLists.txt b/gallery/CMakeLists.txt index d0f19d73b..634927f10 100644 --- a/gallery/CMakeLists.txt +++ b/gallery/CMakeLists.txt @@ -29,9 +29,9 @@ find_package( qt_standard_project_setup() qt_policy(SET QTP0001 NEW) -set(GALLERY_HDRS helper.h) +set(GALLERY_HDRS helper.h notificationmodel.h) -set(GALLERY_SRCS helper.cpp) +set(GALLERY_SRCS helper.cpp notificationmodel.cpp) if (IOS OR ANDROID) add_compile_definitions(MOBILE_OS) diff --git a/gallery/main.cpp b/gallery/main.cpp index 0055d1efa..1d33f5b93 100644 --- a/gallery/main.cpp +++ b/gallery/main.cpp @@ -16,6 +16,7 @@ #include "helper.h" #include #include +#include "notificationmodel.h" int main( int argc, char *argv[] ) { @@ -30,6 +31,9 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "_hotReload", &hotReload ); #endif + NotificationModel notificationModel; + engine.rootContext()->setContextProperty("notificationModel", ¬ificationModel); + // path to local wrapper pages engine.rootContext()->setContextProperty( "_qmlWrapperPath", QGuiApplication::applicationDirPath() + "/HotReload/qml/pages/" ); engine.rootContext()->setContextProperty( "__dp", Helper::calculateDpRatio() ); diff --git a/gallery/notificationmodel.cpp b/gallery/notificationmodel.cpp new file mode 100644 index 000000000..cf53a5228 --- /dev/null +++ b/gallery/notificationmodel.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "notificationmodel.h" +#include + +Notification::Notification(uint id, const QString &message, uint interval, NotificationType::MessageType type = NotificationType::Information) +{ + mId = id; + mMessage = message; + mInterval = interval; + mType = type; +} + +NotificationModel::NotificationModel(QObject *parent) : QAbstractListModel{parent} +{ + qmlRegisterUncreatableType("notificationType", 1, 0, "NotificationType", "Not creatable as it is an enum type"); + + mTimer = new QTimer(this); + connect(mTimer, &QTimer::timeout, this, &NotificationModel::timerFired); + mTimer->start(1000); + + // Initial data + mNotifications << Notification{ nextId(), "Ahoj", 10, NotificationType::Information }; + mNotifications << Notification{ nextId(), "Hello all", 5, NotificationType::Information }; +} + +NotificationModel::~NotificationModel() +{ + mTimer->stop(); + delete mTimer; +} + +QHash NotificationModel::roleNames() const +{ + return { + { IdRole, "id" }, + { MessageRole, "message" }, + { TypeRole, "type" } + }; +} + +int NotificationModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return mNotifications.size(); +} + +QVariant NotificationModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return {}; + + Notification notification = mNotifications.at(index.row()); + if (role == IdRole) return notification.id(); + if (role == MessageRole) return notification.message(); + if (role == TypeRole) return notification.type(); + + return {}; +} + +// remove item by message +void NotificationModel::remove(uint id) +{ + for(int i=0; i +#include + +class NotificationType +{ + Q_GADGET + public: + + enum MessageType { + Information, + Warning, + Error + }; + Q_ENUM(MessageType) + + private: + explicit NotificationType(); +}; + +class Notification +{ + Q_GADGET + + public: + Notification(uint id, const QString &message, uint interval, NotificationType::MessageType type); + uint id() { return mId; } + QString message() { return mMessage; } + NotificationType::MessageType type() { return mType; } + bool canBeRemoved() { return (mInterval-- == 0); } + + private: + uint mId; + QString mMessage; + uint mInterval; // [seconds] + NotificationType::MessageType mType; +}; + +class NotificationModel : public QAbstractListModel +{ + Q_OBJECT + + public: + enum MyRoles { + IdRole = Qt::UserRole + 1, MessageRole, TypeRole + }; + Q_ENUM(MyRoles) + + NotificationModel(QObject *parent = nullptr); + ~NotificationModel(); + + QHash roleNames() const override; + int rowCount(const QModelIndex & parent = QModelIndex()) const override; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + + Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged); + Q_INVOKABLE void remove(uint id); + Q_INVOKABLE void add(const QString &message, uint interval, NotificationType::MessageType type); + + private: + uint nextId() { static uint id = 0; return id++; } + void timerFired(); + + signals: + void rowCountChanged(); + + private: + QList mNotifications; + QTimer *mTimer; +}; + +#endif // NOTIFICATIONMODEL_H diff --git a/gallery/qml.qrc b/gallery/qml.qrc index 3e165441a..0a15b2f06 100644 --- a/gallery/qml.qrc +++ b/gallery/qml.qrc @@ -19,6 +19,9 @@ ../app/qmlV2/component/MMCheckBox.qml ../app/qmlV2/component/MMRadioButton.qml ../app/qmlV2/component/MMSwitch.qml + qml/pages/NotificationPage.qml + ../app/qmlV2/component/MMNotification.qml + ../app/qmlV2/component/MMNotificationView.qml ../app/qmlV2/component/MMDrawer.qml qml/pages/DrawerPage.qml qml/pages/ChecksPage.qml diff --git a/gallery/qml/Main.qml b/gallery/qml/Main.qml index 1859be814..11866cd69 100644 --- a/gallery/qml/Main.qml +++ b/gallery/qml/Main.qml @@ -124,6 +124,10 @@ ApplicationWindow { title: "Checks" source: "ChecksPage.qml" } + ListElement { + title: "Notifications" + source: "NotificationPage.qml" + } ListElement { title: "Drawers" source: "DrawerPage.qml" diff --git a/gallery/qml/pages/NotificationPage.qml b/gallery/qml/pages/NotificationPage.qml new file mode 100644 index 000000000..11417ddb2 --- /dev/null +++ b/gallery/qml/pages/NotificationPage.qml @@ -0,0 +1,56 @@ +/*************************************************************************** + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic + +import "../../app/qmlV2/component" +import notificationType 1.0 + +Page { + id: pane + + Rectangle { + anchors.fill: parent + color: "white" + } + + Column { + width: parent.width + spacing: 20 + anchors.centerIn: parent + + MMInput { + type: MMInput.Type.SendButton + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Write an informative message" + onSendButtonClicked: { notificationModel.add(text, 100, NotificationType.Information); text = "" } + } + MMInput { + type: MMInput.Type.SendButton + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Write a warning message" + onSendButtonClicked: { notificationModel.add(text, 100, NotificationType.Warning); text = "" } + } + MMInput { + type: MMInput.Type.SendButton + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Write an error message" + onSendButtonClicked: { notificationModel.add(text, 100, NotificationType.Error); text = "" } + } + Text { + text: "Note: Notification will be removed in 10s" + anchors.horizontalCenter: parent.horizontalCenter + color: "green" + } + } + + MMNotificationView {} +}