From 2d83460db585fddc19ec6cc6d48f6add875ff2ca Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sun, 2 Apr 2017 18:33:57 +0100 Subject: [PATCH] Cloud Refactor - CloudService list/select Athlete .. add list/select athlete to the service semantics .. implemented exemplar with Today's Plan service. --- src/Cloud/AddCloudWizard.cpp | 63 +++++++++++++++++++++++ src/Cloud/AddCloudWizard.h | 25 +++++++++- src/Cloud/CloudService.h | 24 ++++++++- src/Cloud/OAuthDialog.cpp | 97 ++---------------------------------- src/Cloud/OAuthDialog.h | 2 - src/Cloud/TodaysPlan.cpp | 63 +++++++++++++++++++++++ src/Cloud/TodaysPlan.h | 4 ++ 7 files changed, 180 insertions(+), 98 deletions(-) diff --git a/src/Cloud/AddCloudWizard.cpp b/src/Cloud/AddCloudWizard.cpp index a35292145..d5ceab2c2 100644 --- a/src/Cloud/AddCloudWizard.cpp +++ b/src/Cloud/AddCloudWizard.cpp @@ -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: diff --git a/src/Cloud/AddCloudWizard.h b/src/Cloud/AddCloudWizard.h index a0d87372a..40f0c3f06 100644 --- a/src/Cloud/AddCloudWizard.h +++ b/src/Cloud/AddCloudWizard.h @@ -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 athletes; +}; + class AddSettings : public QWizardPage { Q_OBJECT diff --git a/src/Cloud/CloudService.h b/src/Cloud/CloudService.h index 795319af9..c0711485e 100644 --- a/src/Cloud/CloudService.h +++ b/src/Cloud/CloudService.h @@ -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 listAthletes() { return QList(); } + 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 "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 settings; diff --git a/src/Cloud/OAuthDialog.cpp b/src/Cloud/OAuthDialog.cpp index d27f36d8c..a5260f672 100644 --- a/src/Cloud/OAuthDialog.cpp +++ b/src/Cloud/OAuthDialog.cpp @@ -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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); - - 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 users; - - for (int i=0;isetFont(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;iisChecked()) { - 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(); - } -} diff --git a/src/Cloud/OAuthDialog.h b/src/Cloud/OAuthDialog.h index d2a2de6bd..2a3880a9e 100644 --- a/src/Cloud/OAuthDialog.h +++ b/src/Cloud/OAuthDialog.h @@ -115,8 +115,6 @@ private: KQOAuthManager *oauthManager; KQOAuthRequest *oauthRequest; #endif - - void listUsers(); }; #endif // OAUTHDIALOG_H diff --git a/src/Cloud/TodaysPlan.cpp b/src/Cloud/TodaysPlan.cpp index cdfa498e0..26c78657f 100644 --- a/src/Cloud/TodaysPlan.cpp +++ b/src/Cloud/TodaysPlan.cpp @@ -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 +TodaysPlan::listAthletes() +{ + QList 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 listAthletes(); + bool selectAthlete(CloudServiceAthlete); + // dirent style api CloudServiceEntry *root() { return root_; } QList readdir(QString path, QStringList &errors, QDateTime from, QDateTime to);