diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-03-02 12:00:54 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-03-02 12:03:04 +0100 |
commit | 03e8aabf1754f5061f207d2c9c082ba6199db0e5 (patch) | |
tree | 66124163b4da541dfb17c30299b9aeb82f9fad79 | |
parent | 795d551639340d81641aa8c6eabff1776e0b2ee1 (diff) | |
download | kube-03e8aabf1754f5061f207d2c9c082ba6199db0e5.tar.gz kube-03e8aabf1754f5061f207d2c9c082ba6199db0e5.zip |
Visualize that a folder has new mails.
We listen for new mail notifications, and highlight the folder
accordingly.
-rw-r--r-- | components/kube/qml/Kube.qml | 9 | ||||
-rw-r--r-- | framework/qml/FolderListView.qml | 34 | ||||
-rw-r--r-- | framework/qml/TreeView.qml | 33 | ||||
-rw-r--r-- | framework/src/domain/folderlistmodel.cpp | 41 | ||||
-rw-r--r-- | framework/src/domain/folderlistmodel.h | 8 | ||||
-rw-r--r-- | framework/src/sinkfabric.cpp | 5 |
6 files changed, 94 insertions, 36 deletions
diff --git a/components/kube/qml/Kube.qml b/components/kube/qml/Kube.qml index 3a970733..eba70c70 100644 --- a/components/kube/qml/Kube.qml +++ b/components/kube/qml/Kube.qml | |||
@@ -100,7 +100,14 @@ Controls2.ApplicationWindow { | |||
100 | Shortcut { | 100 | Shortcut { |
101 | id: syncShortcut | 101 | id: syncShortcut |
102 | sequence: StandardKey.Refresh | 102 | sequence: StandardKey.Refresh |
103 | onActivated: !!app.currentFolder ? Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": app.currentFolder}) : Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": app.currentAccount}) | 103 | onActivated: { |
104 | if (!!app.currentFolder) { | ||
105 | Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": app.currentFolder}); | ||
106 | Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": app.currentAccount, "type": "folder"}) | ||
107 | } else { | ||
108 | Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": app.currentAccount}) | ||
109 | } | ||
110 | } | ||
104 | } | 111 | } |
105 | //END Shortcuts | 112 | //END Shortcuts |
106 | 113 | ||
diff --git a/framework/qml/FolderListView.qml b/framework/qml/FolderListView.qml index 25c6cbe5..a24a9ff6 100644 --- a/framework/qml/FolderListView.qml +++ b/framework/qml/FolderListView.qml | |||
@@ -30,6 +30,33 @@ Kube.TreeView { | |||
30 | Controls1.TableViewColumn { | 30 | Controls1.TableViewColumn { |
31 | title: "Name" | 31 | title: "Name" |
32 | role: "name" | 32 | role: "name" |
33 | delegate: Item { | ||
34 | DropArea { | ||
35 | anchors.fill: parent | ||
36 | Rectangle { | ||
37 | anchors.fill: parent | ||
38 | color: Kube.Colors.viewBackgroundColor | ||
39 | opacity: 0.3 | ||
40 | visible: parent.containsDrag | ||
41 | } | ||
42 | onDropped: { | ||
43 | Kube.Fabric.postMessage(Kube.Messages.moveToFolder, {"mail": drop.source.mail, "folder": model.domainObject}) | ||
44 | drop.accept(Qt.MoveAction) | ||
45 | drop.source.visible = false | ||
46 | } | ||
47 | } | ||
48 | |||
49 | Kube.Label { | ||
50 | anchors { | ||
51 | verticalCenter: parent.verticalCenter | ||
52 | left: parent.left | ||
53 | right: parent.right | ||
54 | } | ||
55 | text: styleData.value | ||
56 | elide: Qt.ElideRight | ||
57 | color: model.hasNewData ? Kube.Colors.highlightColor : Kube.Colors.viewBackgroundColor | ||
58 | } | ||
59 | } | ||
33 | } | 60 | } |
34 | 61 | ||
35 | model: Kube.FolderListModel { | 62 | model: Kube.FolderListModel { |
@@ -43,11 +70,4 @@ Kube.TreeView { | |||
43 | Kube.Fabric.postMessage(Kube.Messages.folderSelection, {"folder": model.data(index, Kube.FolderListModel.DomainObject), | 70 | Kube.Fabric.postMessage(Kube.Messages.folderSelection, {"folder": model.data(index, Kube.FolderListModel.DomainObject), |
44 | "trash": model.data(index, Kube.FolderListModel.Trash)}); | 71 | "trash": model.data(index, Kube.FolderListModel.Trash)}); |
45 | } | 72 | } |
46 | |||
47 | |||
48 | onDropped: { | ||
49 | Kube.Fabric.postMessage(Kube.Messages.moveToFolder, {"mail": drop.source.mail, "folder": model.domainObject}) | ||
50 | drop.accept(Qt.MoveAction) | ||
51 | drop.source.visible = false | ||
52 | } | ||
53 | } | 73 | } |
diff --git a/framework/qml/TreeView.qml b/framework/qml/TreeView.qml index eb140514..7b9a1103 100644 --- a/framework/qml/TreeView.qml +++ b/framework/qml/TreeView.qml | |||
@@ -36,12 +36,16 @@ FocusScope { | |||
36 | * FIXME: This is what QItemSelectionModel selection vs current selection are for. Try to use that instead. | 36 | * FIXME: This is what QItemSelectionModel selection vs current selection are for. Try to use that instead. |
37 | */ | 37 | */ |
38 | property var activeIndex: null | 38 | property var activeIndex: null |
39 | signal dropped(var drop, var model) | ||
40 | signal activated(var index) | 39 | signal activated(var index) |
41 | onActivated: { | 40 | onActivated: { |
42 | activeIndex = index | 41 | activeIndex = index |
43 | } | 42 | } |
44 | 43 | ||
44 | function indexFromRow(row) { | ||
45 | //FIXME Uses internal API to get to the model index | ||
46 | return treeView.__model.mapRowToModelIndex(row) | ||
47 | } | ||
48 | |||
45 | Flickable { | 49 | Flickable { |
46 | id: flickableItem | 50 | id: flickableItem |
47 | 51 | ||
@@ -115,8 +119,7 @@ FocusScope { | |||
115 | style: TreeViewStyle { | 119 | style: TreeViewStyle { |
116 | rowDelegate: Controls2.Control { | 120 | rowDelegate: Controls2.Control { |
117 | id: delegateRoot | 121 | id: delegateRoot |
118 | //FIXME Uses internal API to get to the model index | 122 | property bool isActive: root.activeIndex === indexFromRow(styleData.row) |
119 | property bool isActive: root.activeIndex === treeView.__model.mapRowToModelIndex(styleData.row) | ||
120 | height: Kube.Units.gridUnit * 1.5 | 123 | height: Kube.Units.gridUnit * 1.5 |
121 | //FIXME This is the only way I could find to get the correct width. parent.width is way to wide | 124 | //FIXME This is the only way I could find to get the correct width. parent.width is way to wide |
122 | width: parent.parent.parent ? parent.parent.parent.width : 0 | 125 | width: parent.parent.parent ? parent.parent.parent.width : 0 |
@@ -145,30 +148,6 @@ FocusScope { | |||
145 | text: styleData.isExpanded ? "-" : "+" | 148 | text: styleData.isExpanded ? "-" : "+" |
146 | } | 149 | } |
147 | 150 | ||
148 | itemDelegate: Item { | ||
149 | DropArea { | ||
150 | anchors.fill: parent | ||
151 | Rectangle { | ||
152 | anchors.fill: parent | ||
153 | color: Kube.Colors.viewBackgroundColor | ||
154 | opacity: 0.3 | ||
155 | visible: parent.containsDrag | ||
156 | } | ||
157 | onDropped: root.dropped(drop, model) | ||
158 | } | ||
159 | |||
160 | Kube.Label { | ||
161 | anchors { | ||
162 | verticalCenter: parent.verticalCenter | ||
163 | left: parent.left | ||
164 | right: parent.right | ||
165 | } | ||
166 | text: styleData.value | ||
167 | elide: Qt.ElideRight | ||
168 | color: Kube.Colors.viewBackgroundColor | ||
169 | } | ||
170 | } | ||
171 | |||
172 | backgroundColor: Kube.Colors.textColor | 151 | backgroundColor: Kube.Colors.textColor |
173 | highlightedTextColor: Kube.Colors.highlightedTextColor | 152 | highlightedTextColor: Kube.Colors.highlightedTextColor |
174 | } | 153 | } |
diff --git a/framework/src/domain/folderlistmodel.cpp b/framework/src/domain/folderlistmodel.cpp index f929b01e..6555abbf 100644 --- a/framework/src/domain/folderlistmodel.cpp +++ b/framework/src/domain/folderlistmodel.cpp | |||
@@ -21,6 +21,8 @@ | |||
21 | #include "folderlistmodel.h" | 21 | #include "folderlistmodel.h" |
22 | #include <sink/store.h> | 22 | #include <sink/store.h> |
23 | #include <sink/log.h> | 23 | #include <sink/log.h> |
24 | #include <sink/notifier.h> | ||
25 | #include <sink/notification.h> | ||
24 | #include <settings/settings.h> | 26 | #include <settings/settings.h> |
25 | 27 | ||
26 | using namespace Sink; | 28 | using namespace Sink; |
@@ -59,6 +61,7 @@ QHash< int, QByteArray > FolderListModel::roleNames() const | |||
59 | roles[DomainObject] = "domainObject"; | 61 | roles[DomainObject] = "domainObject"; |
60 | roles[Status] = "status"; | 62 | roles[Status] = "status"; |
61 | roles[Trash] = "trash"; | 63 | roles[Trash] = "trash"; |
64 | roles[HasNewData] = "hasNewData"; | ||
62 | 65 | ||
63 | return roles; | 66 | return roles; |
64 | } | 67 | } |
@@ -92,14 +95,46 @@ QVariant FolderListModel::data(const QModelIndex &idx, int role) const | |||
92 | return folder->getSpecialPurpose().contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash); | 95 | return folder->getSpecialPurpose().contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash); |
93 | } | 96 | } |
94 | return false; | 97 | return false; |
98 | case HasNewData: | ||
99 | return mHasNewData.contains(folder->identifier()); | ||
95 | } | 100 | } |
96 | return QSortFilterProxyModel::data(idx, role); | 101 | return QSortFilterProxyModel::data(idx, role); |
97 | } | 102 | } |
98 | 103 | ||
104 | static QModelIndex findRecursive(QAbstractItemModel *model, const QModelIndex &parent, int role, const QVariant &value) | ||
105 | { | ||
106 | for (auto row = 0; row < model->rowCount(parent); row++) { | ||
107 | const auto idx = model->index(row, 0, parent); | ||
108 | if (model->data(idx, role) == value) { | ||
109 | return idx; | ||
110 | } | ||
111 | auto result = findRecursive(model, idx, role, value); | ||
112 | if (result.isValid()) { | ||
113 | return result; | ||
114 | } | ||
115 | } | ||
116 | return {}; | ||
117 | } | ||
118 | |||
99 | void FolderListModel::runQuery(const Query &query) | 119 | void FolderListModel::runQuery(const Query &query) |
100 | { | 120 | { |
101 | mModel = Store::loadModel<Folder>(query); | 121 | mModel = Store::loadModel<Folder>(query); |
102 | setSourceModel(mModel.data()); | 122 | setSourceModel(mModel.data()); |
123 | |||
124 | Sink::Query resourceQuery; | ||
125 | resourceQuery.setFilter(query.getResourceFilter()); | ||
126 | mNotifier.reset(new Sink::Notifier{resourceQuery}); | ||
127 | mNotifier->registerHandler([&](const Sink::Notification ¬ification) { | ||
128 | if (notification.type == Sink::Notification::Info && notification.code == ApplicationDomain::NewContentAvailable) { | ||
129 | if (!notification.entities.isEmpty()) { | ||
130 | mHasNewData.insert(notification.entities.first()); | ||
131 | auto idx = findRecursive(this, {}, Id, QVariant::fromValue(notification.entities.first())); | ||
132 | if (idx.isValid()) { | ||
133 | emit dataChanged(idx, idx); | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | }); | ||
103 | } | 138 | } |
104 | 139 | ||
105 | void FolderListModel::setAccountId(const QVariant &accountId) | 140 | void FolderListModel::setAccountId(const QVariant &accountId) |
@@ -164,6 +199,12 @@ bool FolderListModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) | |||
164 | return enabled; | 199 | return enabled; |
165 | } | 200 | } |
166 | 201 | ||
202 | void FolderListModel::fetchMore(const QModelIndex &parent) | ||
203 | { | ||
204 | mHasNewData.remove(parent.data(Id).toByteArray()); | ||
205 | QAbstractItemModel::fetchMore(parent); | ||
206 | } | ||
207 | |||
167 | void FolderListModel::setFolderId(const QVariant &folderId) | 208 | void FolderListModel::setFolderId(const QVariant &folderId) |
168 | { | 209 | { |
169 | const auto folder = folderId.toString().toUtf8(); | 210 | const auto folder = folderId.toString().toUtf8(); |
diff --git a/framework/src/domain/folderlistmodel.h b/framework/src/domain/folderlistmodel.h index 738cf4a0..d3bef397 100644 --- a/framework/src/domain/folderlistmodel.h +++ b/framework/src/domain/folderlistmodel.h | |||
@@ -22,6 +22,8 @@ | |||
22 | 22 | ||
23 | #include <krecursivefilterproxymodel.h> | 23 | #include <krecursivefilterproxymodel.h> |
24 | #include <QSharedPointer> | 24 | #include <QSharedPointer> |
25 | #include <QSet> | ||
26 | #include <sink/notifier.h> | ||
25 | 27 | ||
26 | namespace Sink { | 28 | namespace Sink { |
27 | class Query; | 29 | class Query; |
@@ -54,7 +56,8 @@ public: | |||
54 | Id, | 56 | Id, |
55 | DomainObject, | 57 | DomainObject, |
56 | Status, | 58 | Status, |
57 | Trash | 59 | Trash, |
60 | HasNewData | ||
58 | }; | 61 | }; |
59 | Q_ENUMS(Roles) | 62 | Q_ENUMS(Roles) |
60 | 63 | ||
@@ -68,8 +71,11 @@ public: | |||
68 | protected: | 71 | protected: |
69 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; | 72 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; |
70 | bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; | 73 | bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; |
74 | void fetchMore(const QModelIndex &left) Q_DECL_OVERRIDE; | ||
71 | 75 | ||
72 | private: | 76 | private: |
73 | void runQuery(const Sink::Query &query); | 77 | void runQuery(const Sink::Query &query); |
74 | QSharedPointer<QAbstractItemModel> mModel; | 78 | QSharedPointer<QAbstractItemModel> mModel; |
79 | QSet<QByteArray> mHasNewData; | ||
80 | QScopedPointer<Sink::Notifier> mNotifier; | ||
75 | }; | 81 | }; |
diff --git a/framework/src/sinkfabric.cpp b/framework/src/sinkfabric.cpp index 8492f272..916cc8fa 100644 --- a/framework/src/sinkfabric.cpp +++ b/framework/src/sinkfabric.cpp | |||
@@ -203,6 +203,11 @@ public: | |||
203 | if (notification.code == Sink::ApplicationDomain::TransmissionSuccess) { | 203 | if (notification.code == Sink::ApplicationDomain::TransmissionSuccess) { |
204 | message["type"] = "info"; | 204 | message["type"] = "info"; |
205 | message["message"] = QObject::tr("A message has been sent."); | 205 | message["message"] = QObject::tr("A message has been sent."); |
206 | } else if (notification.code == Sink::ApplicationDomain::NewContentAvailable) { | ||
207 | message["type"] = "info"; | ||
208 | if (!notification.entities.isEmpty()) { | ||
209 | message["folderId"] = notification.entities.first(); | ||
210 | } | ||
206 | } else { | 211 | } else { |
207 | return; | 212 | return; |
208 | } | 213 | } |