Compare commits

...

8 Commits

Author SHA1 Message Date
Roberto Viola
a1957eb772 splits works too 2024-08-18 15:28:41 +02:00
Roberto Viola
2dd2f65182 it seems to work fine 2024-08-18 15:14:32 +02:00
Roberto Viola
1d84c17aa1 fixing build and adding concept2 mail requests 2024-08-18 14:42:54 +02:00
Roberto Viola
3ab751c455 Update qzsettings.cpp 2024-08-18 14:18:59 +02:00
Roberto Viola
75f9a49d07 Merge branch 'master' into concept2_log 2024-08-18 14:00:17 +02:00
Roberto Viola
e23db9dc02 Merge branch 'master' into concept2_log 2024-08-15 10:35:51 +02:00
Roberto Viola
4600245d70 it works! 2023-04-02 19:11:03 +02:00
Roberto Viola
810af0881a auth works! 2023-04-02 11:36:10 +02:00
7 changed files with 619 additions and 5 deletions

View 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!")
}
}
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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"

View 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>

View 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},

View File

@@ -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;