mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 17:09:56 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +115,6 @@ private:
|
||||
KQOAuthManager *oauthManager;
|
||||
KQOAuthRequest *oauthRequest;
|
||||
#endif
|
||||
|
||||
void listUsers();
|
||||
};
|
||||
|
||||
#endif // OAUTHDIALOG_H
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user