Cloud Refactor - CloudService list/select Athlete

.. add list/select athlete to the service semantics
.. implemented exemplar with Today's Plan service.
This commit is contained in:
Mark Liversedge
2017-04-02 18:33:57 +01:00
parent deb5d34415
commit 2d83460db5
7 changed files with 180 additions and 98 deletions

View File

@@ -33,6 +33,7 @@
// 01. Select Service Class (e.g. Activities, Measures)
// 10. Select Cloud Service Type (via CloudServiceFactory)
// 20. Authenticate Account (URL+Key, OAUTH or User/Pass)
// 25. Select Athlete [optional]
// 30. Settings (Folder,sync on startup, sync on import)
// 90. Finalise (Confirm complete and add)
//
@@ -56,6 +57,7 @@ AddCloudWizard::AddCloudWizard(Context *context) : QWizard(context->mainWindow),
setPage(01, new AddClass(this)); // done
setPage(10, new AddService(this)); // done
setPage(20, new AddAuth(this)); // done
setPage(25, new AddAthlete(this)); // done
setPage(30, new AddSettings(this)); // done
setPage(90, new AddFinish(this)); // done
@@ -157,6 +159,7 @@ AddService::initializePage()
QCommandLinkButton *p = new QCommandLinkButton(s->name(), s->description(), this);
p->setStyleSheet(QString("font-size: %1px;").arg(12 * dpiXFactor));
p->setFixedHeight(50 *dpiYFactor);
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(p, s->name());
buttonlayout->addWidget(p);
@@ -256,6 +259,8 @@ AddAuth::initializePage()
{
setSubTitle(tr("Credentials and authorisation"));
hasAthlete = (wizard->cloudService->settings.value(CloudService::AthleteID, "") != "");
// hide all the widgets
combo->hide();
url->hide();
@@ -342,6 +347,63 @@ AddAuth::updateServiceSettings()
}
}
//Select Athlete, if needed
AddAthlete::AddAthlete(AddCloudWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Coached Athletes"));
setSubTitle(tr("Select Athlete for this account"));
QVBoxLayout *layout = new QVBoxLayout(this);
buttons=new QWidget(this);
buttons->setContentsMargins(0,0,0,0);
buttonlayout= new QVBoxLayout(buttons);
buttonlayout->setSpacing(0);
scrollarea=new QScrollArea(this);
scrollarea->setWidgetResizable(true);
scrollarea->setWidget(buttons);
mapper = new QSignalMapper(this);
connect(mapper, SIGNAL(mapped(int)), this, SLOT(clicked(int)));
layout->addWidget(scrollarea);
setFinalPage(false);
}
void
AddAthlete::initializePage()
{
athletes = wizard->cloudService->listAthletes();
// clear whatever we have, if anything
QLayoutItem *item = NULL;
while((item = buttonlayout->takeAt(0)) != NULL) {
if (item->widget()) delete item->widget();
delete item;
}
int i=0;
foreach(CloudServiceAthlete a, athletes) {
// only ones with the capability we need.
QCommandLinkButton *p = new QCommandLinkButton(a.name, a.desc, this);
p->setFixedHeight(50 *dpiYFactor);
p->setStyleSheet(QString("font-size: %1px;").arg(12 * dpiXFactor));
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(p, i++);
buttonlayout->addWidget(p);
}
buttonlayout->addStretch();
}
void
AddAthlete::clicked(int i)
{
// select it
wizard->cloudService->selectAthlete(athletes[i]);
wizard->next();
}
// Scan for Cloud port / usb etc
AddSettings::AddSettings(AddCloudWizard *parent) : QWizardPage(parent), wizard(parent)
@@ -480,6 +542,7 @@ AddFinish::initializePage()
case CloudService::Password: label=tr("Password"); break;
case CloudService::OAuthToken: label=tr("Token"); break;
case CloudService::Folder: label=tr("Folder"); break;
case CloudService::AthleteID: label=tr("Athlete ID"); break;
case CloudService::Combo1: label=want.value().split("::").at(1); sname=want.value().split("::").at(0); break;
case CloudService::Local1:
case CloudService::Local2:

View File

@@ -114,12 +114,13 @@ class AddAuth : public QWizardPage
public slots:
void initializePage();
bool validatePage();
int nextId() const { return wizard->cloudService->type() & CloudService::Measures ? 90 : 30; }
int nextId() const { return wizard->cloudService->type() & CloudService::Measures ? 90 : (hasAthlete ? 25 : 30); }
void updateServiceSettings();
void doAuth();
private:
AddCloudWizard *wizard;
bool hasAthlete;
// all laid out in a formlayout of rows
QLabel *comboLabel;
@@ -138,6 +139,28 @@ class AddAuth : public QWizardPage
QLabel *token;
};
class AddAthlete : public QWizardPage
{
Q_OBJECT
public:
AddAthlete(AddCloudWizard *);
void initializePage();
bool validate() const { return false; }
int nextId() const { return 30; }
public slots:
void clicked(int);
private:
AddCloudWizard *wizard;
QSignalMapper *mapper;
QWidget *buttons;
QVBoxLayout *buttonlayout;
QScrollArea *scrollarea;
QList<CloudServiceAthlete> athletes;
};
class AddSettings : public QWizardPage
{
Q_OBJECT

View File

@@ -49,6 +49,21 @@
class RideItem;
class CloudServiceEntry;
// Representing an Athlete when the service allows for
// a coach or manager relationship -- i.e. it lists athletes
// so you can choose which one you want to sync with
class CloudServiceAthlete
{
public:
CloudServiceAthlete() : local(NULL) {}
QString id;
QString name;
QString desc;
void *local; // available for the service to use
};
class CloudService : public QObject {
Q_OBJECT
@@ -109,6 +124,10 @@ class CloudService : public QObject {
}
void notifyReadComplete(QByteArray *data, QString name, QString message) { emit readComplete(data,name,message); }
// list and select an athlete - list will need to block rather than notify asynchronously
virtual QList<CloudServiceAthlete> listAthletes() { return QList<CloudServiceAthlete>(); }
virtual bool selectAthlete(CloudServiceAthlete) { return false; }
// The service must define what settings it needs in the "settings" map.
// Each entry maps a setting type to the appsetting symbol name
// Local - setting maintained internally by the cloud service which require no interation
@@ -119,7 +138,10 @@ class CloudService : public QObject {
// drive.file that are selected in order to set a combo to allow the user to
// select the setting for "<athlete-private>google_drive/drive_scope"
// Only 1 is supported at present, we can add more if needed later
enum CloudServiceSetting { Username, Password, OAuthToken, Key, URL, DefaultURL, Folder,
//
// AthleteID can only be provided if the service implements the listAthletes and selectAthlete
// entry points -- to list and accept the choice of athlete by the user
enum CloudServiceSetting { Username, Password, OAuthToken, Key, URL, DefaultURL, Folder, AthleteID,
Local1, Local2, Local3, Local4, Local5, Local6,
Combo1 } setting_;
QHash<CloudServiceSetting, QString> settings;

View File

@@ -564,7 +564,9 @@ void OAuthDialog::networkRequestFinished(QNetworkReply *reply) {
} else if (site == TODAYSPLAN) {
appsettings->setCValue(context->athlete->cyclist, GC_TODAYSPLAN_TOKEN, access_token);
service->setSetting(GC_TODAYSPLAN_TOKEN, access_token);
listUsers();
QString info = QString(tr("Today's Plan authorization was successful."));
QMessageBox information(QMessageBox::Information, tr("Information"), info);
information.exec();
}
} else {
@@ -576,96 +578,3 @@ void OAuthDialog::networkRequestFinished(QNetworkReply *reply) {
// job done, dialog can be closed
accept();
}
void
OAuthDialog::listUsers() //XXX NEEDS FIXUP FOR ADDCLOUDWIZARD
{
if (site == TODAYSPLAN) {
// use the configed URL
QString url = QString("%1/rest/users/delegates/users")
.arg(appsettings->cvalue(context->athlete->cyclist, GC_TODAYSPLAN_URL, "https://whats.todaysplan.com.au").toString());
// request using the bearer token
QNetworkRequest request(url);
QString token = appsettings->cvalue(context->athlete->cyclist, GC_TODAYSPLAN_TOKEN, "").toString();
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
QNetworkAccessManager *man = new QNetworkAccessManager(this);
connect(man, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
QNetworkReply *reply = man->get(request);
// blocking request
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
// did we get a good response ?
QByteArray r = reply->readAll();
#if QT_VERSION > 0x050000
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
if (parseError.error == QJsonParseError::NoError) {
//qDebug() << "response: " << r;
if (document.array().count()>1) {
layout->removeWidget(view);
delete view;
layout->setMargin(10);
QLabel *label = new QLabel(tr("Select an athlete"));
label->setFont(QFont("Helvetica", 15, QFont::Bold));
layout->addWidget(label);
layout->addSpacing(5);
QList<QCheckBox*> users;
for (int i=0;i<document.array().count();i++) {
//qDebug() << document.array()[i].toObject()["_name"].toString() << document.array()[i].toObject()["relationship"].toString();
if (document.array()[i].toObject()["relationship"].toString() == "" ||
document.array()[i].toObject()["relationship"].toString() == "coach" ||
document.array()[i].toObject()["relationship"].toString() == "manager") {
QCheckBox *ck = new QCheckBox(document.array()[i].toObject()["_name"].toString());
ck->setFont(QFont("Helvetica", 15));
ck->setProperty("id", document.array()[i].toObject()["id"].toInt());
ck->setProperty("name", document.array()[i].toObject()["_name"].toString());
users.append(ck);
layout->addWidget(ck);
//qDebug()<< document.array()[i].toObject()["id"].toInt() << document.array()[i].toObject()["_name"].toString();
}
}
layout->addSpacing(5);
QPushButton *btnSelect = new QPushButton(tr("Select"));
btnSelect->setMaximumWidth(200);
layout->addWidget(btnSelect);
layout->addStretch();
QEventLoop loop;
connect(btnSelect, SIGNAL(clicked()), &loop, SLOT(quit()));
loop.exec();
for (int i=0;i<users.count();i++) {
QCheckBox *ck = users.at(i);
if (ck->isChecked()) {
appsettings->setCValue(context->athlete->cyclist, GC_TODAYSPLAN_ATHLETE_ID, ck->property("id"));
appsettings->setCValue(context->athlete->cyclist, GC_TODAYSPLAN_ATHLETE_NAME, ck->property("name"));
return;
}
}
}
} else {
//qDebug() << "response: " << r << "error:" << reply->errorString();
}
#endif
// Use default athlete
QString info = QString(tr("Today's Plan authorization was successful."));
QMessageBox information(QMessageBox::Information, tr("Information"), info);
information.exec();
}
}

View File

@@ -115,8 +115,6 @@ private:
KQOAuthManager *oauthManager;
KQOAuthRequest *oauthRequest;
#endif
void listUsers();
};
#endif // OAUTHDIALOG_H

View File

@@ -65,6 +65,8 @@ TodaysPlan::TodaysPlan(Context *context) : CloudService(context), context(contex
settings.insert(URL, GC_TODAYSPLAN_URL);
settings.insert(DefaultURL, "https://whats.todaysplan.com.au");
settings.insert(Key, GC_TODAYSPLAN_USERKEY);
settings.insert(AthleteID, GC_TODAYSPLAN_ATHLETE_ID);
settings.insert(Local1, GC_TODAYSPLAN_ATHLETE_NAME);
}
TodaysPlan::~TodaysPlan() {
@@ -458,6 +460,67 @@ TodaysPlan::readFileCompleted()
notifyReadComplete(buffers.value(reply), replyName(reply), tr("Completed."));
}
QList<CloudServiceAthlete>
TodaysPlan::listAthletes()
{
QList<CloudServiceAthlete> returning;
// use the configed URL
QString url = QString("%1/rest/users/delegates/users").arg(getSetting(GC_TODAYSPLAN_URL, "https://whats.todaysplan.com.au").toString());
// request using the bearer token
QNetworkRequest request(url);
QString token = getSetting(GC_TODAYSPLAN_TOKEN, "").toString();
request.setRawHeader("Authorization", (QString("Bearer %1").arg(token)).toLatin1());
QNetworkReply *reply = nam->get(request);
// blocking request
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
// did we get a good response ?
QByteArray r = reply->readAll();
#if QT_VERSION > 0x050000
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
if (parseError.error == QJsonParseError::NoError) {
if (document.array().count()>0) {
for (int i=0;i<document.array().count();i++) {
//qDebug() << document.array()[i].toObject()["_name"].toString() << document.array()[i].toObject()["relationship"].toString();
if (document.array()[i].toObject()["relationship"].toString() == "" ||
document.array()[i].toObject()["relationship"].toString() == "coach" ||
document.array()[i].toObject()["relationship"].toString() == "manager") {
CloudServiceAthlete add;
add.id = QString("%1").arg(document.array()[i].toObject()["id"].toInt());
add.name = document.array()[i].toObject()["_name"].toString();
QString rel = document.array()[i].toObject()["relationship"].toString();
if (rel=="") rel="athlete";
add.desc = QString("relationship: %1").arg(rel);
returning << add;
}
}
}
}
#endif
return returning;
}
bool
TodaysPlan::selectAthlete(CloudServiceAthlete athlete)
{
// extract athlete name and identifier from the selected athlete
// TODO
setSetting(GC_TODAYSPLAN_ATHLETE_ID, athlete.id.toInt());
setSetting(GC_TODAYSPLAN_ATHLETE_NAME, athlete.name);
return true;
}
static bool addTodaysPlan() {
CloudServiceFactory::instance().addService(new TodaysPlan(NULL));
return true;

View File

@@ -54,6 +54,10 @@ class TodaysPlan : public CloudService {
// create a folder
bool createFolder(QString);
// athlete selection
QList<CloudServiceAthlete> listAthletes();
bool selectAthlete(CloudServiceAthlete);
// dirent style api
CloudServiceEntry *root() { return root_; }
QList<CloudServiceEntry*> readdir(QString path, QStringList &errors, QDateTime from, QDateTime to);