Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 8760c06a authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Improve UX by ordering synced collections

Sync Inbox collection first, then all Favorite collections, then all
regular collections and finally sync the Trash collection as the last
one.

Inbox and Favorite collections are usually those that users are most
interested in so sync them first so that users don't have to wait for
their Trash and other less-interesting collections to sync.
parent 3ae32f6d
......@@ -50,6 +50,8 @@
#include "servermanager_p.h"
#include "recursivemover_p.h"
#include "tagmodifyjob.h"
#include "specialcollectionattribute.h"
#include "favoritecollectionattribute.h"
#include "akonadiagentbase_debug.h"
#include <KLocalizedString>
......@@ -882,6 +884,8 @@ void ResourceBasePrivate::slotCollectionSyncDone(KJob *job)
if (scheduler->currentTask().type == ResourceScheduler::SyncAll) {
CollectionFetchJob *list = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive);
list->setFetchScope(q->changeRecorder()->collectionFetchScope());
list->fetchScope().fetchAttribute<SpecialCollectionAttribute>();
list->fetchScope().fetchAttribute<FavoriteCollectionAttribute>();
list->fetchScope().setResource(mId);
list->fetchScope().setListFilter(CollectionFetchScope::Sync);
q->connect(list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*)));
......@@ -893,14 +897,59 @@ void ResourceBasePrivate::slotCollectionSyncDone(KJob *job)
scheduler->taskDone();
}
namespace {
bool sortCollectionsForSync(const Collection &l, const Collection &r)
{
const auto lType = l.hasAttribute<SpecialCollectionAttribute>()
? l.attribute<SpecialCollectionAttribute>()->collectionType()
: QByteArray();
const bool lInbox = (lType == "inbox") || (l.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
const bool lFav = l.hasAttribute<FavoriteCollectionAttribute>();
const auto rType = r.hasAttribute<SpecialCollectionAttribute>()
? r.attribute<SpecialCollectionAttribute>()->collectionType()
: QByteArray();
const bool rInbox = (rType == "inbox") || (r.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
const bool rFav = r.hasAttribute<FavoriteCollectionAttribute>();
// inbox is always first
if (lInbox) {
return true;
} else if (rInbox) {
return false;
}
// favorites right after inbox
if (lFav) {
return !rInbox;
} else if (rFav) {
return lInbox;
}
// trash is always last (unless it's favorite)
if (lType == "trash") {
return false;
} else if (rType == "trash") {
return true;
}
// Fallback to sorting by id
return l.id() < r.id();
}
}
void ResourceBasePrivate::slotLocalListDone(KJob *job)
{
Q_Q(ResourceBase);
if (job->error()) {
emit q->error(job->errorString());
} else {
const Collection::List cols = static_cast<CollectionFetchJob *>(job)->collections();
for (const Collection &col : cols) {
Collection::List cols = static_cast<CollectionFetchJob *>(job)->collections();
std::sort(cols.begin(), cols.end(), sortCollectionsForSync);
for (const Collection &col : qAsConst(cols)) {
scheduler->scheduleSync(col);
}
scheduler->scheduleFullSyncCompletion();
......
......@@ -33,6 +33,7 @@ set(akonadicore_base_SRCS
entitydisplayattribute.cpp
entityhiddenattribute.cpp
exception.cpp
favoritecollectionattribute.cpp
firstrun.cpp
gidextractor.cpp
indexpolicyattribute.cpp
......@@ -96,6 +97,7 @@ ecm_generate_headers(AkonadiCore_base_HEADERS
EntityDisplayAttribute
EntityHiddenAttribute
ExceptionBase
FavoriteCollectionAttribute
GidExtractorInterface
IndexPolicyAttribute
Item
......
......@@ -28,6 +28,7 @@
#include "entitydeletedattribute.h"
#include "tagattribute.h"
#include "entityannotationsattribute.h"
#include "favoritecollectionattribute.h"
#include <QHash>
......@@ -99,6 +100,7 @@ public:
AttributeFactory::registerAttribute<EntityDeletedAttribute>();
AttributeFactory::registerAttribute<EntityAnnotationsAttribute>();
AttributeFactory::registerAttribute<TagAttribute>();
AttributeFactory::registerAttribute<FavoriteCollectionAttribute>();
}
bool initialized;
};
......
/*
Copyright 2017 Daniel Vrátil <dvratil@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library 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 Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "favoritecollectionattribute.h"
using namespace Akonadi;
FavoriteCollectionAttribute::FavoriteCollectionAttribute()
: Attribute()
{
}
Attribute *FavoriteCollectionAttribute::clone() const
{
return new FavoriteCollectionAttribute();
}
QByteArray FavoriteCollectionAttribute::type() const
{
return "favorite";
}
void FavoriteCollectionAttribute::deserialize(const QByteArray &)
{
// unused
}
QByteArray FavoriteCollectionAttribute::serialized() const
{
return {};
}
/*
Copyright 2017 Daniel Vrátil <dvratil@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library 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 Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#ifndef AKONADI_CORE_FAVORITECOLLECTIONATTRIBUTE_H_
#define AKONADI_CORE_FAVORITECOLLECTIONATTRIBUTE_H_
#include "attribute.h"
#include "akonadicore_export.h"
namespace Akonadi {
class AKONADICORE_EXPORT FavoriteCollectionAttribute : public Attribute
{
public:
explicit FavoriteCollectionAttribute();
Attribute *clone() const override;
QByteArray type() const override;
void deserialize(const QByteArray &data) override;
QByteArray serialized() const override;
};
}
#endif
......@@ -32,6 +32,8 @@
#include "entitytreemodel.h"
#include "mimetypechecker.h"
#include "pastehelper_p.h"
#include "favoritecollectionattribute.h"
#include "collectionmodifyjob.h"
using namespace Akonadi;
......@@ -78,6 +80,14 @@ public:
if (!referencedCollections.contains(col)) {
reference(col);
}
auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ col });
if (idx.isValid()) {
auto c = q->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
if (c.isValid() && !c.hasAttribute<FavoriteCollectionAttribute>()) {
c.addAttribute(new FavoriteCollectionAttribute());
new CollectionModifyJob(c, q);
}
}
}
}
......@@ -189,6 +199,14 @@ public:
collectionIds << collectionId;
reference(collectionId);
select(collectionId);
const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId });
if (idx.isValid()) {
auto col = q->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
if (col.isValid() && !col.hasAttribute<FavoriteCollectionAttribute>()) {
col.addAttribute(new FavoriteCollectionAttribute());
new CollectionModifyJob(col, q);
}
}
}
void remove(const Collection::Id &collectionId)
......@@ -197,6 +215,14 @@ public:
labelMap.remove(collectionId);
dereference(collectionId);
deselect(collectionId);
const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId });
if (idx.isValid()) {
auto col = q->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
if (col.isValid() && col.hasAttribute<FavoriteCollectionAttribute>()) {
col.removeAttribute<FavoriteCollectionAttribute>();
new CollectionModifyJob(col, q);
}
}
}
void set(const QList<Collection::Id> &collections)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment