diff --git a/android-sources/AndroidManifest.xml b/android-sources/AndroidManifest.xml index 8edc7fe..58ec7b0 100644 --- a/android-sources/AndroidManifest.xml +++ b/android-sources/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/headers/serverconn.h b/headers/serverconn.h index a87efc1..a12cdb2 100644 --- a/headers/serverconn.h +++ b/headers/serverconn.h @@ -40,6 +40,7 @@ public slots: QVariant getCalendar(QString nation, int year); QVariant getRanking(int competitionId, int categoryId, bool registrationData = false, bool rankingData = false, const int routeNumber = -2); + QVariant getAthlete(int perId); }; diff --git a/resources/qml/Components/FadeAnimation.qml b/resources/qml/Components/FadeAnimation.qml new file mode 100644 index 0000000..40343b7 --- /dev/null +++ b/resources/qml/Components/FadeAnimation.qml @@ -0,0 +1,70 @@ +/* + Speed Climbing Stopwatch - Simple Stopwatch for Climbers + Copyright (C) 2018 Itsblue Development - Dorian Zeder + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, version 3 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import QtQuick 2.0 + +SequentialAnimation { + id: root + property QtObject target + property string fadeProperty: "scale" + property int fadeDuration: 150 + property int fadeDuration_in: fadeDuration + property int fadeDuration_out: fadeDuration + property alias outValue: outAnimation.to + property alias inValue: inAnimation.to + property alias outEasingType: outAnimation.easing.type + property alias inEasingType: inAnimation.easing.type + property string easingType: "Quad" + ParallelAnimation { + NumberAnimation { // in the default case, fade scale to 0 + id: outAnimation + target: root.target + property: "scale" + duration: root.fadeDuration_in + to: 0.9 + easing.type: Easing["In"+root.easingType] + } + NumberAnimation { // in the default case, fade scale to 0 + id: outAnimation2 + target: root.target + property: "opacity" + duration: root.fadeDuration_in + to: 0 + easing.type: Easing["In"+root.easingType] + } + } + PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations + ParallelAnimation { + NumberAnimation { // in the default case, fade scale back to 1 + id: inAnimation + target: root.target + property: root.fadeProperty + duration: root.fadeDuration_out + to: 1 + easing.type: Easing["Out"+root.easingType] + } + NumberAnimation { // in the default case, fade scale to 0 + id: inAnimation2 + target: root.target + property: "opacity" + duration: root.fadeDuration_in + to: 1 + easing.type: Easing["In"+root.easingType] + } + } + +} diff --git a/resources/qml/Components/RankingView.qml b/resources/qml/Components/RankingView.qml index 1843045..a3f8a8b 100644 --- a/resources/qml/Components/RankingView.qml +++ b/resources/qml/Components/RankingView.qml @@ -49,6 +49,10 @@ DataListView { fadeInPa.start() } + onPressAndHold: { + app.openAthlete(thisData["PerId"]) + } + ParallelAnimation { id: fadeInPa NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } @@ -192,8 +196,8 @@ DataListView { } } - Column { - id: detailResultsCol + Item { + id: detailResultsItm anchors { top: partDelCol.bottom @@ -202,7 +206,88 @@ DataListView { margins: 5 } - height: enabled ? ( 20 + spacing ) * detailResultRowRep.model:0 + height: detailResultsLd.status === Loader.Ready && detailResultsLd.enabled ? ( 20 + detailResultsLd.item.spacing ) * detailResultsLd.item.model:0 + + Loader { + id: detailResultsLd + + property alias partDel: partDel + property alias partDelSecondRow: partDelSecondRow + + onEnabledChanged: { + if(enabled){ + sourceComponent = detailResultsComp + } + else { + hideDelayPa.start() + } + } + + anchors.fill: parent + + height: parent.height + + PauseAnimation { + id: hideDelayPa + + duration: 200 + + onRunningChanged: { + if(!running && !detailResultsLd.enabled){ + detailResultsLd.sourceComponent = undefined + } + } + } + } + } + + + states: [ + State { + name: "closed" + PropertyChanges { + target: partDel + height: 70 + } + + PropertyChanges { + target: detailResultsLd + enabled: false + } + }, + State { + name: "opened" + PropertyChanges { + target: partDel + height: 70 + detailResultsLd.height + } + + PropertyChanges { + target: detailResultsLd + enabled: true + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "*" + NumberAnimation { + properties: "height" + duration: 200 + } + } + ] + + } + + Component { + id: detailResultsComp + Column { + id: detailResultsCol + + property alias model: detailResultRowRep.model Repeater { id: detailResultRowRep @@ -228,8 +313,8 @@ DataListView { } delegate: Row { - width: parent.width height: detailResultsCol.height / detailResultRowRep.model - detailResultsCol.spacing + width: parent.width visible: height > 0 @@ -282,43 +367,5 @@ DataListView { } - states: [ - State { - name: "closed" - PropertyChanges { - target: partDel - height: 70 - } - - PropertyChanges { - target: detailResultsCol - enabled: false - } - }, - State { - name: "opened" - PropertyChanges { - target: partDel - height: 70 + detailResultsCol.height - } - - PropertyChanges { - target: detailResultsCol - enabled: true - } - } - ] - - transitions: [ - Transition { - from: "*" - to: "*" - NumberAnimation { - properties: "height" - duration: 200 - } - } - ] - } } diff --git a/resources/qml/Components/RegistrationView.qml b/resources/qml/Components/RegistrationView.qml index 6aac12c..d8ae18f 100644 --- a/resources/qml/Components/RegistrationView.qml +++ b/resources/qml/Components/RegistrationView.qml @@ -42,6 +42,10 @@ DataListView { fadeInPa.start() } + onClicked: { + app.openAthlete(thisData["PerId"]) + } + ParallelAnimation { id: fadeInPa NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } diff --git a/resources/qml/Components/ResultView.qml b/resources/qml/Components/ResultView.qml index 677b1ff..f3621fe 100644 --- a/resources/qml/Components/ResultView.qml +++ b/resources/qml/Components/ResultView.qml @@ -48,6 +48,10 @@ DataListView { fadeInPa.start() } + onClicked: { + app.openAthlete(thisData["PerId"]) + } + ParallelAnimation { id: fadeInPa NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } @@ -177,6 +181,10 @@ DataListView { property var resultData: boulderResRep.getDataForIcon(index) + onResultDataChanged: { + boulderResCv.requestPaint() + } + anchors.centerIn: parent height: parent.height > parent.width ? parent.width * 0.9:parent.height * 0.9 diff --git a/resources/qml/Components/StartlistView.qml b/resources/qml/Components/StartlistView.qml index 6c9840a..ff836db 100644 --- a/resources/qml/Components/StartlistView.qml +++ b/resources/qml/Components/StartlistView.qml @@ -40,6 +40,10 @@ DataListView { fadeInPa.start() } + onClicked: { + app.openAthlete(thisData["PerId"]) + } + ParallelAnimation { id: fadeInPa NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } diff --git a/resources/qml/Pages/AthleteProfilePage.qml b/resources/qml/Pages/AthleteProfilePage.qml new file mode 100644 index 0000000..2b1faf6 --- /dev/null +++ b/resources/qml/Pages/AthleteProfilePage.qml @@ -0,0 +1,222 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.3 + +import "../Components" + +Page { + id: root + + title: perData["firstname"] + " " + perData["lastname"] + property bool titleIsPageTitle: true + property bool ready + property int status: -1 + + property int perId: -1 + + property var perData: ({}) + + Component.onCompleted: { + if(root.loadData(root.perId)){ + root.ready = true + } + } + + function loadData(perId) { + console.log("loading athlete: ", perId) + + root.status = 905 + + var ret = serverConn.getAthlete(perId) + + root.status = ret["status"] + + if(ret["status"] === 200){ + root.perData = ret["data"] + return true + } + else { + return false + } + } + + ScrollView { + id: mainSv + + anchors.fill: parent + anchors.margins: 10 + anchors.rightMargin: 14 + + contentWidth: parent.width - anchors.leftMargin - anchors.rightMargin + + ScrollBar.vertical: ScrollBar { + + anchors { + top: mainSv.top + left: mainSv.right + margins: 10 + leftMargin: 3 + bottom: mainSv.bottom + } + + width: 8 + + active: true + } + + Column { + id: mainCol + + width: parent.width + + Row { + height: root.height * 0.3 + width: parent.width + + Image { + id: photo + + property bool ready: false + + anchors.verticalCenter: parent.verticalCenter + + height: parent.height * 0.9 + width: status === Image.Null || status === Image.Error ? 0:parent.width * 0.5 + + fillMode: Image.PreserveAspectFit + + source: perData["photo"] === undefined ? "":perData["photo"].replace("https", "http").replace("www.digitalrock.de", "egw.ifsc-climbing.org") + asynchronous: true + + FancyBusyIndicator { + height: width + anchors.centerIn: parent + opacity: photo.status === Image.Loading + } + } + + Column { + + anchors.verticalCenter: parent.verticalCenter + + height: parent.height * 0.9 + width: parent.width - photo.width + + Label { + + height: parent.height * 0.2 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: true + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: perData["firstname"] + " " + perData["lastname"] + } + + Label { + height: parent.height * 0.2 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: false + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: perData["nation"] + } + + Label { + height: parent.height * 0.15 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: false + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: "" + perData["federation"] + "" + + onLinkActivated: { + Qt.openUrlExternally(link) + } + } + + Label { + height: parent.height * 0.15 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: false + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: qsTr("age") + ": " + perData["age"] + } + + Label { + height: parent.height * 0.15 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: false + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: qsTr("year of birth") + ": " + perData["birthdate"] + } + + Label { + height: parent.height * 0.15 + width: parent.width + + font.pixelSize: height * 0.6 + font.bold: false + minimumPixelSize: 1 + + fontSizeMode: Text.Fit + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + text: qsTr("city") + ": " + perData["city"] + } + } + + } + + Label { + anchors.horizontalCenter: parent.horizontalCenter + + width: parent.width + + wrapMode: Label.Wrap + + text: perData["freetext"] + + } + + } + } +} diff --git a/resources/qml/Pages/CompetitionCalendarPage.qml b/resources/qml/Pages/CompetitionCalendarPage.qml index 7318666..d78c557 100644 --- a/resources/qml/Pages/CompetitionCalendarPage.qml +++ b/resources/qml/Pages/CompetitionCalendarPage.qml @@ -492,7 +492,9 @@ Page { onClicked: { yearSelectPu.close() root.year = yearSelectPu.yearList[index] + loadingDl.open() root.loadData(root.nation, root.year) + loadingDl.close() } } } diff --git a/resources/qml/main.qml b/resources/qml/main.qml index fa8795d..8f1b4cb 100644 --- a/resources/qml/main.qml +++ b/resources/qml/main.qml @@ -48,6 +48,10 @@ Window { anchors.fill: parent + Component.onCompleted: { + //app.openAthlete(53139) // dorian: 53139 , rustam: 6933 , helen: 53300 + } + Shortcut { sequences: ["Esc", "Back"] enabled: mainStack.depth > 1 @@ -194,6 +198,8 @@ Window { width: parent.width + scale: 1 + elide: "ElideRight" font.bold: true @@ -221,6 +227,12 @@ Window { return(titleString) } + + Behavior on text { + FadeAnimation { + target: toolBarTitleLa + } + } } Label { @@ -246,6 +258,12 @@ Window { return(titleString) } + + Behavior on text { + FadeAnimation { + target: toolBarSubTitleLa + } + } } } @@ -261,7 +279,75 @@ Window { bottom: parent.bottom } - sourceComponent: mainStack.currentItem.headerComponent + //sourceComponent: mainStack.currentItem.headerComponent + + Connections { + target: mainStack + onCurrentItemChanged: { + disappearNa.start() + } + } + + ParallelAnimation { + id: appearNa + + NumberAnimation { + + target: extraComponentLoader + property: "opacity" + + from: 0 + to: 1 + + duration: 150 + } + + NumberAnimation { + + target: extraComponentLoader + property: "scale" + + from: 0.9 + to: 1 + + duration: 150 + } + } + + + ParallelAnimation { + id: disappearNa + + NumberAnimation { + + target: extraComponentLoader + property: "opacity" + + from: 1 + to: 0 + + duration: 150 + } + + NumberAnimation { + + target: extraComponentLoader + property: "scale" + + from: 1 + to: 0.9 + + duration: 150 + } + + onRunningChanged: { + if(!running){ + extraComponentLoader.sourceComponent = mainStack.currentItem.headerComponent + appearNa.start() + } + } + + } } } @@ -359,6 +445,22 @@ Window { loadingDl.close() } + function openAthlete(perId) { + loadingDl.open() + + var athleteComp = Qt.createComponent("qrc:/Pages/AthleteProfilePage.qml").createObject(null, {"perId": perId}) + app.errorCode = athleteComp.status + + if(athleteComp.ready){ + mainStack.push(athleteComp) + } + else { + delete(athleteComp) + } + + loadingDl.close() + } + function getErrorInfo(errorCode) { var infoLevel diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc index e5a1602..a2c5496 100644 --- a/resources/qml/qml.qrc +++ b/resources/qml/qml.qrc @@ -14,5 +14,7 @@ Components/StartlistView.qml Components/RankingView.qml Components/PullRefresher.qml + Pages/AthleteProfilePage.qml + Components/FadeAnimation.qml diff --git a/sources/serverconn.cpp b/sources/serverconn.cpp index 5893c94..fdf3e62 100644 --- a/sources/serverconn.cpp +++ b/sources/serverconn.cpp @@ -57,6 +57,25 @@ QVariant ServerConn::getRanking(int competiotionId, int categoryId, bool registr return data; } +QVariant ServerConn::getAthlete(int perId){ + QString requestUrl = "http://egw.ifsc-climbing.org/egw/ranking/json.php?person=" + QString::number(perId); + + qDebug() << requestUrl; + + QVariantMap ret = this->senddata(QUrl(requestUrl)); + + if(ret["status"] != 200){ + // request was a failure + return QVariantMap({{"status", ret["status"]}, {"data", ""}}); + } + + QJsonDocument jsonReply = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); + + QVariantMap data = {{"status", 200}, {"data", jsonReply.toVariant()}}; + + return data; +} + // ------------------------ // --- Helper functions --- // ------------------------ @@ -73,9 +92,9 @@ QVariantMap ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata) request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QSslConfiguration config = QSslConfiguration::defaultConfiguration(); - config.setProtocol(QSsl::TlsV1_2); - request.setSslConfiguration(config); + //QSslConfiguration config = QSslConfiguration::defaultConfiguration(); + //config.setProtocol(QSsl::TlsV1_2); + //request.setSslConfiguration(config); //send a POST request with the given url and data to the server