mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
8 Commits
treadmill-
...
concept2_l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1957eb772 | ||
|
|
2dd2f65182 | ||
|
|
1d84c17aa1 | ||
|
|
3ab751c455 | ||
|
|
75f9a49d07 | ||
|
|
e23db9dc02 | ||
|
|
4600245d70 | ||
|
|
810af0881a |
58
src/WebConcept2LogAuth.qml
Normal file
58
src/WebConcept2LogAuth.qml
Normal file
@@ -0,0 +1,58 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Dialogs 1.0
|
||||
import QtGraphicalEffects 1.12
|
||||
import Qt.labs.settings 1.0
|
||||
import QtMultimedia 5.15
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtWebView 1.1
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
visible: true
|
||||
|
||||
WebView {
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
visible: !rootItem.generalPopupVisible
|
||||
url: rootItem.getConcept2logAuthUrl
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popupConcept2LogConnectedWeb
|
||||
parent: Overlay.overlay
|
||||
enabled: rootItem.generalPopupVisible
|
||||
onEnabledChanged: { if(rootItem.generalPopupVisible) popupConcept2LogConnectedWeb.open() }
|
||||
onClosed: { rootItem.generalPopupVisible = false; }
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
width: 380
|
||||
height: 120
|
||||
modal: true
|
||||
focus: true
|
||||
palette.text: "white"
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
enter: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
|
||||
}
|
||||
exit: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
|
||||
}
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Label {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 370
|
||||
height: 120
|
||||
text: qsTr("Your Concept2 account is now connected!<br><br>When you will press STOP on QZ a file<br>will be automatically uploaded to Concept2 Log!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
466
src/homeform.cpp
466
src/homeform.cpp
@@ -42,6 +42,29 @@
|
||||
homeform *homeform::m_singleton = 0;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
#include <QAndroidJniEnvironment>
|
||||
#include <QtAndroid>
|
||||
#endif
|
||||
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#define STRAVA_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
#define CONCEPT2LOG_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE CONCEPT2LOG_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE CONCEPT2LOG_SECRET_KEY!!!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef STRAVA_CLIENT_ID
|
||||
#define STRAVA_CLIENT_ID 7976
|
||||
#if defined(WIN32)
|
||||
@@ -54,6 +77,16 @@ using namespace std::chrono_literals;
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
#define STRAVA_CLIENT_ID_S STRINGIFY(STRAVA_CLIENT_ID)
|
||||
|
||||
#ifndef CONCEPT2LOG_CLIENT_ID
|
||||
#define CONCEPT2LOG_CLIENT_ID lz8lm0PbaphYM10GqgZjf5oEhp8dj9klr2yw6o52
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE CONCEPT2LOG_CLIENT_ID!!!")
|
||||
#else
|
||||
#warning "DEFINE CONCEPT2LOG_CLIENT_ID!!!"
|
||||
#endif
|
||||
#endif
|
||||
#define CONCEPT2LOG_CLIENT_ID_S STRINGIFY(CONCEPT2LOG_CLIENT_ID)
|
||||
|
||||
DataObject::DataObject(const QString &name, const QString &icon, const QString &value, bool writable, const QString &id,
|
||||
int valueFontSize, int labelFontSize, const QString &valueFontColor, const QString &secondLine,
|
||||
const int gridId, bool largeButton, QString largeButtonLabel, QString largeButtonColor) {
|
||||
@@ -498,6 +531,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
QObject::connect(stack, SIGNAL(gpx_save_clicked()), this, SLOT(gpx_save_clicked()));
|
||||
QObject::connect(stack, SIGNAL(fit_save_clicked()), this, SLOT(fit_save_clicked()));
|
||||
QObject::connect(stack, SIGNAL(strava_connect_clicked()), this, SLOT(strava_connect_clicked()));
|
||||
QObject::connect(stack, SIGNAL(concept2log_connect_clicked()), this, SLOT(concept2log_connect_clicked()));
|
||||
QObject::connect(stack, SIGNAL(refresh_bluetooth_devices_clicked()), this,
|
||||
SLOT(refresh_bluetooth_devices_clicked()));
|
||||
QObject::connect(home, SIGNAL(lap_clicked()), this, SLOT(Lap()));
|
||||
@@ -3608,7 +3642,7 @@ void homeform::Start() { Start_inner(true); }
|
||||
void homeform::Start_inner(bool send_event_to_device) {
|
||||
QSettings settings;
|
||||
qDebug() << QStringLiteral("Start pressed - paused") << paused << QStringLiteral("stopped") << stopped;
|
||||
|
||||
|
||||
m_overridePower = false;
|
||||
|
||||
if (settings.value(QZSettings::tts_enabled, QZSettings::default_tts_enabled).toBool())
|
||||
@@ -3781,6 +3815,7 @@ void homeform::Stop() {
|
||||
|
||||
void homeform::Lap() {
|
||||
qDebug() << QStringLiteral("lap pressed");
|
||||
log_requested_workouts();
|
||||
if (bluetoothManager) {
|
||||
if (bluetoothManager->device()) {
|
||||
|
||||
@@ -5958,6 +5993,12 @@ void homeform::fit_save_clicked() {
|
||||
emit stravaUploadRequestedChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.value(QZSettings::concept2log_accesstoken, QZSettings::default_concept2log_accesstoken)
|
||||
.toString()
|
||||
.isEmpty()) {
|
||||
concept2log_upload_file("time", bluetoothManager->device()->odometer() == 0 ? 1 : bluetoothManager->device()->odometer(), abs(bluetoothManager->device()->elapsedTime().secsTo(QTime(0,0,0,0))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6081,6 +6122,8 @@ QStringList homeform::bluetoothDevices() {
|
||||
|
||||
QStringList homeform::metrics() { return bluetoothdevice::metrics(); }
|
||||
|
||||
// ******************************************************** STRAVA ***************************************************
|
||||
|
||||
struct OAuth2Parameter {
|
||||
QString responseType = QStringLiteral("code");
|
||||
QString approval_prompt = QStringLiteral("force");
|
||||
@@ -6496,11 +6539,430 @@ void homeform::strava_connect_clicked() {
|
||||
connect(strava, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &homeform::onStravaAuthorizeWithBrowser);
|
||||
connect(strava, &QOAuth2AuthorizationCodeFlow::granted, this, &homeform::onStravaGranted);
|
||||
|
||||
strava->grant();
|
||||
if(strava)
|
||||
strava->grant();
|
||||
// qDebug() <<
|
||||
// QAbstractOAuth2::post("https://www.strava.com/oauth/authorize?client_id=7976&scope=activity:read_all,activity:write&redirect_uri=http://127.0.0.1&response_type=code&approval_prompt=force");
|
||||
}
|
||||
|
||||
// ******************************************************** END STRAVA ***************************************************
|
||||
|
||||
// ******************************************************* CONCEPT2 LOG **************************************************
|
||||
|
||||
void homeform::errorOccurredUploadConcept2log(QNetworkReply::NetworkError code) {
|
||||
qDebug() << QStringLiteral("concept2 log upload error!") << code;
|
||||
setToastRequested("Concept2 Log Upload Failed!");
|
||||
emit toastRequestedChanged(toastRequested());
|
||||
}
|
||||
|
||||
void homeform::writeFileCompletedConcept2log() {
|
||||
qDebug() << QStringLiteral("concept2 log upload completed!");
|
||||
|
||||
QNetworkReply *reply = static_cast<QNetworkReply *>(QObject::sender());
|
||||
|
||||
QString response = reply->readAll();
|
||||
// QString uploadError = QStringLiteral("invalid response or parser error");
|
||||
// NOTE: clazy-unused-non-trivial-variable
|
||||
|
||||
qDebug() << "reply:" << response;
|
||||
|
||||
setToastRequested("Concept2 Log Upload Completed!");
|
||||
emit toastRequestedChanged(toastRequested());
|
||||
|
||||
}
|
||||
|
||||
bool homeform::concept2log_upload_file(const QString& type, double distance, int time, int intervals) {
|
||||
if (!bluetoothManager || !bluetoothManager->device() ||
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::ROWING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
concept2log_refreshtoken();
|
||||
QSettings settings;
|
||||
QString token = settings.value(QZSettings::concept2log_accesstoken).toString();
|
||||
// The V3 API doc said "https://api.strava.com" but it is not working yet
|
||||
QUrl url = QUrl(QStringLiteral("https://log-dev.concept2.com/api/users/me/results"));
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
|
||||
// QString boundary = QString::number(qrand() * (90000000000) / (RAND_MAX + 1) + 10000000000, 16);
|
||||
QString boundary = QVariant(QRandomGenerator::global()->generate()).toString() +
|
||||
QVariant(QRandomGenerator::global()->generate()).toString() +
|
||||
QVariant(QRandomGenerator::global()->generate()).toString(); // NOTE: qrand is deprecated
|
||||
|
||||
|
||||
// this must be performed asynchronously and call made
|
||||
// to notifyWriteCompleted(QString remotename, QString message) when done
|
||||
if (managerConcept2log) {
|
||||
delete managerConcept2log;
|
||||
managerConcept2log = 0;
|
||||
}
|
||||
managerConcept2log = new QNetworkAccessManager(this);
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader(QByteArray("Authorization"), "Bearer " + token.toLatin1());
|
||||
|
||||
QJsonObject obj;
|
||||
obj["type"] = "rower";
|
||||
obj["date"] = QDateTime::currentDateTime().toString("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
obj["distance"] = distance * 1000.0;
|
||||
obj["time"] = time * 10;
|
||||
obj["weight_class"] = "H";
|
||||
obj["stroke_rate"] = 100;
|
||||
|
||||
QJsonObject workout;
|
||||
QJsonArray splits;
|
||||
|
||||
if (type == "time") {
|
||||
workout["type"] = "FixedTimeSplits";
|
||||
QJsonObject split;
|
||||
split["type"] = "time";
|
||||
split["time"] = time * 10;
|
||||
split["distance"] = distance * 1000.0;
|
||||
split["stroke_rate"] = 33;
|
||||
splits.append(split);
|
||||
} else if (type == "distance") {
|
||||
workout["type"] = "FixedDistanceSplits";
|
||||
QJsonObject split;
|
||||
split["type"] = "distance";
|
||||
split["distance"] = distance * 1000.0;
|
||||
split["time"] = time * 10;
|
||||
split["stroke_rate"] = 33;
|
||||
splits.append(split);
|
||||
} else if (type == "intervals") {
|
||||
workout["type"] = "FixedTimeSplits";
|
||||
workout["intervalType"] = "time";
|
||||
workout["intervalCount"] = intervals;
|
||||
int intervalTime = time / intervals;
|
||||
for (int i = 0; i < intervals; ++i) {
|
||||
QJsonObject split;
|
||||
split["type"] = "time";
|
||||
split["time"] = intervalTime * 10;
|
||||
split["stroke_rate"] = 33;
|
||||
split["distance"] = (distance * 1000.0) / intervals; // Assuming equal distance per interval
|
||||
splits.append(split);
|
||||
}
|
||||
}
|
||||
|
||||
workout["splits"] = splits;
|
||||
obj["workout"] = workout;
|
||||
|
||||
QJsonDocument doc(obj);
|
||||
QByteArray data = doc.toJson(QJsonDocument::Compact);
|
||||
qDebug() << "Uploading workout:" << data;
|
||||
|
||||
replyConcept2log = managerConcept2log->post(request, data);
|
||||
connect(replyConcept2log, &QNetworkReply::finished, this, &homeform::writeFileCompletedConcept2log);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
||||
connect(replyConcept2log, &QNetworkReply::errorOccurred, this, &homeform::errorOccurredUploadConcept2log);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// used to validate concept2 log
|
||||
static int concept2_log_example = 0;
|
||||
void homeform::log_requested_workouts() {
|
||||
switch (concept2_log_example) {
|
||||
case 0:
|
||||
// 1 minute fixed time
|
||||
concept2log_upload_file("time", 0.1, 60);
|
||||
break;
|
||||
case 1:
|
||||
// 200m fixed distance
|
||||
concept2log_upload_file("distance", 0.2, 60);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Short interval workout with at least 2 intervals (assuming 30 seconds each)
|
||||
concept2log_upload_file("intervals", 0.1, 120, 3);
|
||||
break;
|
||||
}
|
||||
concept2_log_example++;
|
||||
if(concept2_log_example > 2)
|
||||
concept2_log_example = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
QAbstractOAuth::ModifyParametersFunction
|
||||
homeform::buildModifyParametersFunctionConcept2log(const QUrl &clientIdentifier, const QUrl &clientIdentifierSharedKey) {
|
||||
return [clientIdentifier, clientIdentifierSharedKey](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
|
||||
if (stage == QAbstractOAuth::Stage::RequestingAuthorization) {
|
||||
parameters->insert(QStringLiteral("responseType"), QStringLiteral("code")); /* Request refresh token*/
|
||||
QByteArray code = parameters->value(QStringLiteral("code")).toByteArray();
|
||||
// DON'T TOUCH THIS LINE, THANKS Roberto Viola
|
||||
(*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code); // NOTE: Old code replaced by
|
||||
}
|
||||
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
||||
parameters->insert(QStringLiteral("client_id"), clientIdentifier);
|
||||
parameters->insert(QStringLiteral("client_secret"), clientIdentifierSharedKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void homeform::concept2log_refreshtoken() {
|
||||
|
||||
QSettings settings;
|
||||
// QUrlQuery params; //NOTE: clazy-unuse-non-tirial-variable
|
||||
|
||||
if (settings.value(QZSettings::concept2log_refreshtoken).toString().isEmpty()) {
|
||||
|
||||
concept2log_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request(QUrl(QStringLiteral("https://log-dev.concept2.com/oauth/access_token?")));
|
||||
request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
// set params
|
||||
QString data;
|
||||
data += QStringLiteral("client_id=" CONCEPT2LOG_CLIENT_ID_S);
|
||||
#ifdef CONCEPT2LOG_SECRET_KEY
|
||||
data += "&client_secret=";
|
||||
data += STRINGIFY(CONCEPT2LOG_SECRET_KEY);
|
||||
#endif
|
||||
data += QStringLiteral("&refresh_token=") + settings.value(QZSettings::concept2log_refreshtoken).toString();
|
||||
data += QStringLiteral("&grant_type=refresh_token");
|
||||
data += QStringLiteral("&scope=results:write");
|
||||
|
||||
// make request
|
||||
if (managerConcept2log) {
|
||||
|
||||
delete managerConcept2log;
|
||||
managerConcept2log = nullptr;
|
||||
}
|
||||
managerConcept2log = new QNetworkAccessManager(this);
|
||||
qDebug() << data;
|
||||
QNetworkReply *reply = managerConcept2log->post(request, data.toLatin1());
|
||||
|
||||
// blocking request
|
||||
QEventLoop loop;
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << QStringLiteral("HTTP response code: ") << statusCode;
|
||||
|
||||
// oops, no dice
|
||||
if (reply->error() != 0) {
|
||||
qDebug() << QStringLiteral("Got error") << reply->errorString().toStdString().c_str();
|
||||
setToastRequested("Concept2 Log Auth Failed!");
|
||||
emit toastRequestedChanged(toastRequested());
|
||||
return;
|
||||
}
|
||||
|
||||
// lets extract the access token, and possibly a new refresh token
|
||||
QByteArray r = reply->readAll();
|
||||
qDebug() << QStringLiteral("Got response:") << r.data();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
|
||||
|
||||
// failed to parse result !?
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << tr("JSON parser error") << parseError.errorString();
|
||||
}
|
||||
|
||||
QString access_token = document[QStringLiteral("access_token")].toString();
|
||||
QString refresh_token = document[QStringLiteral("refresh_token")].toString();
|
||||
|
||||
settings.setValue(QZSettings::concept2log_accesstoken, access_token);
|
||||
settings.setValue(QZSettings::concept2log_refreshtoken, refresh_token);
|
||||
settings.setValue(QZSettings::concept2log_lastrefresh, QDateTime::currentDateTime());
|
||||
|
||||
setToastRequested("Concept2 Log Login OK!");
|
||||
emit toastRequestedChanged(toastRequested());
|
||||
}
|
||||
|
||||
|
||||
void homeform::onConcept2logGranted() {
|
||||
|
||||
concept2logAuthWebVisible = false;
|
||||
concept2logWebVisibleChanged(concept2logAuthWebVisible);
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::concept2log_accesstoken, concept2log->token());
|
||||
settings.setValue(QZSettings::concept2log_refreshtoken, concept2log->refreshToken());
|
||||
settings.setValue(QZSettings::concept2log_lastrefresh, QDateTime::currentDateTime());
|
||||
qDebug() << QStringLiteral("concept2log authenticathed") << concept2log->token() << concept2log->refreshToken();
|
||||
concept2log_refreshtoken();
|
||||
setGeneralPopupVisible(true);
|
||||
}
|
||||
|
||||
void homeform::onConcept2logAuthorizeWithBrowser(const QUrl &url) {
|
||||
|
||||
// ui->textBrowser->append(tr("Open with browser:") + url.toString());
|
||||
QSettings settings;
|
||||
bool strava_auth_external_webbrowser =
|
||||
settings.value(QZSettings::strava_auth_external_webbrowser, QZSettings::default_strava_auth_external_webbrowser)
|
||||
.toBool();
|
||||
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS))
|
||||
strava_auth_external_webbrowser = true;
|
||||
#endif
|
||||
concept2logAuthUrl = url.toString();
|
||||
qDebug() << getConcept2logAuthUrl();
|
||||
emit concept2logAuthUrlChanged(getConcept2logAuthUrl());
|
||||
|
||||
if (strava_auth_external_webbrowser)
|
||||
QDesktopServices::openUrl(url);
|
||||
else {
|
||||
concept2logAuthWebVisible = true;
|
||||
concept2logWebVisibleChanged(concept2logAuthWebVisible);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void homeform::onSslErrorsConcept2log(QNetworkReply *reply, const QList<QSslError> &error) {
|
||||
|
||||
reply->ignoreSslErrors();
|
||||
qDebug() << QStringLiteral("homeform::onSslErrors") << error;
|
||||
}
|
||||
|
||||
void homeform::networkRequestFinishedConcept2log(QNetworkReply *reply) {
|
||||
|
||||
QSettings settings;
|
||||
|
||||
// we can handle SSL handshake errors, if we got here then some kind of protocol was agreed
|
||||
if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
||||
|
||||
QByteArray payload = reply->readAll(); // JSON
|
||||
QString refresh_token;
|
||||
QString access_token;
|
||||
|
||||
// parse the response and extract the tokens, pretty much the same for all services
|
||||
// although polar choose to also pass a user id, which is needed for future calls
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
refresh_token = document[QStringLiteral("refresh_token")].toString();
|
||||
access_token = document[QStringLiteral("access_token")].toString();
|
||||
}
|
||||
|
||||
settings.setValue(QZSettings::concept2log_accesstoken, access_token);
|
||||
settings.setValue(QZSettings::concept2log_refreshtoken, refresh_token);
|
||||
settings.setValue(QZSettings::concept2log_lastrefresh, QDateTime::currentDateTime());
|
||||
|
||||
qDebug() << access_token << refresh_token;
|
||||
|
||||
} else {
|
||||
|
||||
// general error getting response
|
||||
QString error =
|
||||
QString(tr("Error retrieving access token, %1 (%2)")).arg(reply->errorString()).arg(reply->error());
|
||||
qDebug() << error << reply->url() << reply->readAll();
|
||||
}
|
||||
}
|
||||
|
||||
void homeform::replyDataReceivedConcept2log(const QByteArray &v) {
|
||||
|
||||
qDebug() << v;
|
||||
|
||||
QByteArray data;
|
||||
QSettings settings;
|
||||
QString s(v);
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(s.toUtf8());
|
||||
settings.setValue(QZSettings::concept2log_accesstoken, jsonResponse[QStringLiteral("access_token")]);
|
||||
settings.setValue(QZSettings::concept2log_refreshtoken, jsonResponse[QStringLiteral("refresh_token")]);
|
||||
settings.setValue(QZSettings::concept2log_expires, jsonResponse[QStringLiteral("expires_in")]);
|
||||
|
||||
qDebug() << jsonResponse[QStringLiteral("access_token")] << jsonResponse[QStringLiteral("refresh_token")]
|
||||
<< jsonResponse[QStringLiteral("expires_in")];
|
||||
|
||||
QString urlstr = QStringLiteral("https://log-dev.concept2.com/oauth/access_token?");
|
||||
QUrlQuery params;
|
||||
params.addQueryItem(QStringLiteral("client_id"), QStringLiteral(CONCEPT2LOG_CLIENT_ID_S));
|
||||
#ifdef CONCEPT2LOG_SECRET_KEY
|
||||
#define _STR(x) #x
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
params.addQueryItem("client_secret", STRINGIFY(CONCEPT2LOG_SECRET_KEY));
|
||||
#endif
|
||||
|
||||
params.addQueryItem(QStringLiteral("code"), concept2log_code);
|
||||
data.append(params.query(QUrl::FullyEncoded).toUtf8());
|
||||
|
||||
// trade-in the temporary access code retrieved by the Call-Back URL for the finale token
|
||||
QUrl url = QUrl(urlstr);
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
|
||||
|
||||
// now get the final token - but ignore errors
|
||||
if (managerConcept2log) {
|
||||
|
||||
delete managerConcept2log;
|
||||
managerConcept2log = 0;
|
||||
}
|
||||
managerConcept2log = new QNetworkAccessManager(this);
|
||||
// connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
|
||||
// SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & ))); connect(manager,
|
||||
// SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
|
||||
managerConcept2log->post(request, data);
|
||||
}
|
||||
|
||||
void homeform::callbackReceivedConcept2log(const QVariantMap &values) {
|
||||
qDebug() << QStringLiteral("homeform::callbackReceivedConcept2Log") << values;
|
||||
if (!values.value(QZSettings::code).toString().isEmpty()) {
|
||||
concept2log_code = values.value(QZSettings::code).toString();
|
||||
|
||||
qDebug() << concept2log_code;
|
||||
}
|
||||
}
|
||||
|
||||
QOAuth2AuthorizationCodeFlow *homeform::concept2log_connect() {
|
||||
if (managerConcept2log) {
|
||||
|
||||
delete managerConcept2log;
|
||||
managerConcept2log = nullptr;
|
||||
}
|
||||
if (concept2log) {
|
||||
|
||||
delete concept2log;
|
||||
concept2log = nullptr;
|
||||
}
|
||||
if (concept2logReplyHandler) {
|
||||
|
||||
delete concept2logReplyHandler;
|
||||
concept2logReplyHandler = nullptr;
|
||||
}
|
||||
managerConcept2log = new QNetworkAccessManager(this);
|
||||
OAuth2Parameter parameter;
|
||||
concept2log = new QOAuth2AuthorizationCodeFlow(managerConcept2log, this);
|
||||
concept2log->setScope(QStringLiteral("results:write"));
|
||||
concept2log->setClientIdentifier(QStringLiteral(CONCEPT2LOG_CLIENT_ID_S));
|
||||
concept2log->setAuthorizationUrl(QUrl(QStringLiteral("https://log-dev.concept2.com/oauth/authorize")));
|
||||
concept2log->setAccessTokenUrl(QUrl(QStringLiteral("https://log-dev.concept2.com/oauth/access_token")));
|
||||
#ifdef CONCEPT2LOG_SECRET_KEY
|
||||
#define _STR(x) #x
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
concept2log->setClientIdentifierSharedKey(STRINGIFY(CONCEPT2LOG_SECRET_KEY));
|
||||
#elif defined(WIN32)
|
||||
#pragma message("DEFINE CONCEPT2LOG_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE CONCEPT2LOG_SECRET_KEY!!!"
|
||||
#endif
|
||||
concept2log->setModifyParametersFunction(
|
||||
buildModifyParametersFunctionConcept2log(QUrl(QLatin1String("")), QUrl(QLatin1String(""))));
|
||||
concept2logReplyHandler = new QOAuthHttpServerReplyHandler(QHostAddress(QStringLiteral("127.0.0.1")), 8091, this);
|
||||
connect(concept2logReplyHandler, &QOAuthHttpServerReplyHandler::replyDataReceived, this, &homeform::replyDataReceivedConcept2log);
|
||||
connect(concept2logReplyHandler, &QOAuthHttpServerReplyHandler::callbackReceived, this, &homeform::callbackReceivedConcept2log);
|
||||
|
||||
concept2log->setReplyHandler(concept2logReplyHandler);
|
||||
|
||||
return concept2log;
|
||||
}
|
||||
|
||||
void homeform::concept2log_connect_clicked() {
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qt.networkauth.*=true"));
|
||||
|
||||
concept2log_connect();
|
||||
connect(concept2log, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &homeform::onConcept2logAuthorizeWithBrowser);
|
||||
connect(concept2log, &QOAuth2AuthorizationCodeFlow::granted, this, &homeform::onConcept2logGranted);
|
||||
|
||||
if(concept2log)
|
||||
concept2log->grant();
|
||||
}
|
||||
|
||||
// ************************************************** END CONCEPT2 LOG ***************************************************
|
||||
|
||||
bool homeform::generalPopupVisible() { return m_generalPopupVisible; }
|
||||
|
||||
void homeform::setGeneralPopupVisible(bool value) {
|
||||
|
||||
@@ -190,9 +190,13 @@ class homeform : public QObject {
|
||||
Q_PROPERTY(bool currentCoordinateValid READ currentCoordinateValid)
|
||||
Q_PROPERTY(bool trainProgramLoadedWithVideo READ trainProgramLoadedWithVideo)
|
||||
|
||||
// strava
|
||||
Q_PROPERTY(QString getStravaAuthUrl READ getStravaAuthUrl NOTIFY stravaAuthUrlChanged)
|
||||
Q_PROPERTY(bool stravaWebVisible READ stravaWebVisible NOTIFY stravaWebVisibleChanged)
|
||||
|
||||
// concept2log
|
||||
Q_PROPERTY(QString getConcept2logAuthUrl READ getConcept2logAuthUrl NOTIFY concept2logAuthUrlChanged)
|
||||
Q_PROPERTY(bool concept2logWebVisible READ concept2logWebVisible NOTIFY concept2logWebVisibleChanged)
|
||||
|
||||
public:
|
||||
static homeform *singleton() { return m_singleton; }
|
||||
@@ -597,10 +601,16 @@ class homeform : public QObject {
|
||||
|
||||
bool trainProgramLoadedWithVideo() { return (trainProgram && trainProgram->videoAvailable); }
|
||||
|
||||
// strava
|
||||
QString getStravaAuthUrl() { return stravaAuthUrl; }
|
||||
bool stravaWebVisible() { return stravaAuthWebVisible; }
|
||||
|
||||
// concept2log
|
||||
QString getConcept2logAuthUrl() { return concept2logAuthUrl; }
|
||||
bool concept2logWebVisible() { return concept2logAuthWebVisible; }
|
||||
|
||||
trainprogram *trainingProgram() { return trainProgram; }
|
||||
|
||||
|
||||
DataObject *speed;
|
||||
DataObject *inclination;
|
||||
DataObject *cadence;
|
||||
@@ -733,6 +743,7 @@ class homeform : public QObject {
|
||||
QTimer *timer;
|
||||
QTimer *backupTimer;
|
||||
|
||||
// strava
|
||||
QString strava_code;
|
||||
QOAuth2AuthorizationCodeFlow *strava_connect();
|
||||
void strava_refreshtoken();
|
||||
@@ -743,6 +754,22 @@ class homeform : public QObject {
|
||||
QString stravaAuthUrl;
|
||||
bool stravaAuthWebVisible;
|
||||
|
||||
// Concept2 Log
|
||||
QOAuth2AuthorizationCodeFlow *concept2log = nullptr;
|
||||
QNetworkAccessManager *managerConcept2log = nullptr;
|
||||
QOAuthHttpServerReplyHandler *concept2logReplyHandler = nullptr;
|
||||
void log_requested_workouts();
|
||||
|
||||
QString concept2log_code;
|
||||
QOAuth2AuthorizationCodeFlow *concept2log_connect();
|
||||
void concept2log_refreshtoken();
|
||||
QNetworkReply *replyConcept2log;
|
||||
QAbstractOAuth::ModifyParametersFunction buildModifyParametersFunctionConcept2log(const QUrl &clientIdentifier,
|
||||
const QUrl &clientIdentifierSharedKey);
|
||||
bool concept2log_upload_file(const QString& type, double distance, int time, int intervals = 0);
|
||||
QString concept2logAuthUrl;
|
||||
bool concept2logAuthWebVisible;
|
||||
|
||||
static quint64 cryptoKeySettingsProfiles();
|
||||
|
||||
static QString copyAndroidContentsURI(QUrl file, QString subfolder);
|
||||
@@ -824,9 +851,11 @@ class homeform : public QObject {
|
||||
void gpx_open_clicked(const QUrl &fileName);
|
||||
void gpx_save_clicked();
|
||||
void fit_save_clicked();
|
||||
void strava_connect_clicked();
|
||||
void trainProgramSignals();
|
||||
void refresh_bluetooth_devices_clicked();
|
||||
|
||||
// strava
|
||||
void strava_connect_clicked();
|
||||
void onStravaGranted();
|
||||
void onStravaAuthorizeWithBrowser(const QUrl &url);
|
||||
void replyDataReceived(const QByteArray &v);
|
||||
@@ -835,6 +864,19 @@ class homeform : public QObject {
|
||||
void callbackReceived(const QVariantMap &values);
|
||||
void writeFileCompleted();
|
||||
void errorOccurredUploadStrava(QNetworkReply::NetworkError code);
|
||||
|
||||
// Concept2 Log
|
||||
void concept2log_connect_clicked();
|
||||
void onConcept2logGranted();
|
||||
void onConcept2logAuthorizeWithBrowser(const QUrl &url);
|
||||
void replyDataReceivedConcept2log(const QByteArray &v);
|
||||
void onSslErrorsConcept2log(QNetworkReply *reply, const QList<QSslError> &error);
|
||||
void networkRequestFinishedConcept2log(QNetworkReply *reply);
|
||||
void callbackReceivedConcept2log(const QVariantMap &values);
|
||||
void errorOccurredUploadConcept2log(QNetworkReply::NetworkError code);
|
||||
void writeFileCompletedConcept2log();
|
||||
|
||||
|
||||
void pelotonWorkoutStarted(const QString &name, const QString &instructor);
|
||||
void pelotonWorkoutChanged(const QString &name, const QString &instructor);
|
||||
void pelotonLoginState(bool ok);
|
||||
@@ -908,9 +950,15 @@ class homeform : public QObject {
|
||||
void previewWorkoutPointsChanged(int value);
|
||||
void previewWorkoutDescriptionChanged(QString value);
|
||||
void previewWorkoutTagsChanged(QString value);
|
||||
|
||||
// strava
|
||||
void stravaAuthUrlChanged(QString value);
|
||||
void stravaWebVisibleChanged(bool value);
|
||||
|
||||
// concept2 log
|
||||
void concept2logAuthUrlChanged(QString value);
|
||||
void concept2logWebVisibleChanged(bool value);
|
||||
|
||||
void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);
|
||||
|
||||
void heartRate(uint8_t heart);
|
||||
|
||||
15
src/main.qml
15
src/main.qml
@@ -30,6 +30,7 @@ ApplicationWindow {
|
||||
signal fit_save_clicked()
|
||||
signal refresh_bluetooth_devices_clicked()
|
||||
signal strava_connect_clicked()
|
||||
signal concept2log_connect_clicked()
|
||||
signal loadSettings(url name)
|
||||
signal saveSettings(url name)
|
||||
signal deleteSettings(url name)
|
||||
@@ -721,6 +722,7 @@ ApplicationWindow {
|
||||
popupSaveFile.open()
|
||||
}
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
id: fit_save
|
||||
text: qsTr("Save FIT")
|
||||
@@ -739,6 +741,7 @@ ApplicationWindow {
|
||||
stackView.push("Wizard.qml")
|
||||
drawer.close()
|
||||
}
|
||||
|
||||
}
|
||||
ItemDelegate {
|
||||
id: help
|
||||
@@ -749,6 +752,7 @@ ApplicationWindow {
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
id: community
|
||||
text: qsTr("Community")
|
||||
@@ -757,6 +761,7 @@ ApplicationWindow {
|
||||
Qt.openUrlExternally("https://www.facebook.com/groups/149984563348738");
|
||||
drawer.close()
|
||||
}
|
||||
|
||||
}
|
||||
ItemDelegate {
|
||||
text: qsTr("Credits")
|
||||
@@ -799,6 +804,16 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
text: qsTr("Connect to Concept2 Log (Testing)")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("WebConcept2LogAuth.qml")
|
||||
concept2log_connect_clicked()
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogGPX
|
||||
title: "Please choose a file"
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
<file>WebStravaAuth.qml</file>
|
||||
<file>Toast.qml</file>
|
||||
<file>ToastManager.qml</file>
|
||||
<file>WebConcept2LogAuth.qml</file>
|
||||
<file>inner_templates/chartjs/elliptical.png</file>
|
||||
<file>inner_templates/chartjs/row.png</file>
|
||||
<file>inner_templates/chartjs/run.png</file>
|
||||
|
||||
@@ -644,6 +644,16 @@ const QString QZSettings::fit_file_saved_on_quit = QStringLiteral("fit_file_save
|
||||
const QString QZSettings::gem_module_inclination = QStringLiteral("gem_module_inclination");
|
||||
const QString QZSettings::treadmill_simulate_inclination_with_speed =
|
||||
QStringLiteral("treadmill_simulate_inclination_with_speed");
|
||||
const QString QZSettings::concept2log_accesstoken = QStringLiteral("concept2log_accesstoken");
|
||||
const QString QZSettings::default_concept2log_accesstoken = QStringLiteral("");
|
||||
const QString QZSettings::concept2log_refreshtoken = QStringLiteral("concept2log_refreshtoken");
|
||||
const QString QZSettings::default_concept2log_refreshtoken = QStringLiteral("");
|
||||
const QString QZSettings::concept2log_lastrefresh = QStringLiteral("concept2log_lastrefresh");
|
||||
const QString QZSettings::default_concept2log_lastrefresh = QStringLiteral("");
|
||||
const QString QZSettings::concept2log_expires = QStringLiteral("concept2log_expires");
|
||||
const QString QZSettings::default_concept2log_expires = QStringLiteral("");
|
||||
const QString QZSettings::concept2log_code = QStringLiteral("concept2log_code");
|
||||
const QString QZSettings::default_concept2log_code = QStringLiteral("");
|
||||
const QString QZSettings::garmin_companion = QStringLiteral("garmin_companion");
|
||||
const QString QZSettings::iconcept_elliptical = QStringLiteral("iconcept_elliptical");
|
||||
const QString QZSettings::gears_gain = QStringLiteral("gears_gain");
|
||||
@@ -753,7 +763,7 @@ const QString QZSettings::dircon_id = QStringLiteral("dircon_id");
|
||||
const QString QZSettings::proform_elliptical_ip = QStringLiteral("proform_elliptical_ip");
|
||||
const QString QZSettings::default_proform_elliptical_ip = QStringLiteral("");
|
||||
|
||||
const uint32_t allSettingsCount = 635;
|
||||
const uint32_t allSettingsCount = 640;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1297,6 +1307,11 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::gem_module_inclination, QZSettings::default_gem_module_inclination},
|
||||
{QZSettings::treadmill_simulate_inclination_with_speed,
|
||||
QZSettings::default_treadmill_simulate_inclination_with_speed},
|
||||
{QZSettings::concept2log_accesstoken, QZSettings::default_concept2log_accesstoken},
|
||||
{QZSettings::concept2log_refreshtoken, QZSettings::default_concept2log_refreshtoken},
|
||||
{QZSettings::concept2log_lastrefresh, QZSettings::default_concept2log_lastrefresh},
|
||||
{QZSettings::concept2log_expires, QZSettings::default_concept2log_expires},
|
||||
{QZSettings::concept2log_code, QZSettings::default_concept2log_code},
|
||||
{QZSettings::garmin_companion, QZSettings::default_garmin_companion},
|
||||
{QZSettings::peloton_companion_workout_ocr, QZSettings::default_companion_peloton_workout_ocr},
|
||||
{QZSettings::iconcept_elliptical, QZSettings::default_iconcept_elliptical},
|
||||
|
||||
@@ -1806,6 +1806,21 @@ class QZSettings {
|
||||
static const QString treadmill_simulate_inclination_with_speed;
|
||||
static constexpr bool default_treadmill_simulate_inclination_with_speed = false;
|
||||
|
||||
static const QString concept2log_accesstoken;
|
||||
static const QString default_concept2log_accesstoken;
|
||||
|
||||
static const QString concept2log_refreshtoken;
|
||||
static const QString default_concept2log_refreshtoken;
|
||||
|
||||
static const QString concept2log_lastrefresh;
|
||||
static const QString default_concept2log_lastrefresh;
|
||||
|
||||
static const QString concept2log_expires;
|
||||
static const QString default_concept2log_expires;
|
||||
|
||||
static const QString concept2log_code;
|
||||
static const QString default_concept2log_code;
|
||||
|
||||
static const QString garmin_companion;
|
||||
static constexpr bool default_garmin_companion = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user