diff --git a/headers/serverconn.h b/headers/serverconn.h index 2c69943..8008a9f 100644 --- a/headers/serverconn.h +++ b/headers/serverconn.h @@ -10,27 +10,19 @@ class ServerConn : public QObject { Q_OBJECT - Q_PROPERTY(QVariant foodplan READ getFoodplan NOTIFY foodplanChanged) public: explicit ServerConn(QObject *parent = nullptr); private: QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery()); - // variables - QVariant foodplan; - signals: - void foodplanChanged(); public slots: - void refreshFoodplan(); QVariant getCalendar(QString nation, int year); QVariant getRanking(int competitionId, int categoryId, bool registrationData = false, bool rankingData = false, const int routeNumber = -2); - // functions for qml - QVariant getFoodplan(); }; #endif // SERVERCONN_H diff --git a/resources/qml/Components/DataListView.qml b/resources/qml/Components/DataListView.qml index 8fff3a3..bc512f3 100644 --- a/resources/qml/Components/DataListView.qml +++ b/resources/qml/Components/DataListView.qml @@ -5,16 +5,17 @@ ListView { id: control property int status: -1 + property bool loading: false signal refresh() anchors.margins: 10 anchors.rightMargin: 14 + clip: true - enabled: status === 200 || status === 902 - opacity: enabled ? 1:0 - + //enabled: status === 200 || status === 902 + //opacity: enabled ? 1:0 ScrollBar.vertical: ScrollBar { parent: control.parent @@ -34,12 +35,7 @@ ListView { active: true } - onContentYChanged: { - if(contentY < -control.height * 0.3 && control.status !== 905){ - contentY = 0 - control.refresh() - } - } + Behavior on opacity { NumberAnimation { @@ -47,12 +43,6 @@ ListView { } } - Behavior on contentY { - NumberAnimation { - duration: 200 - } - } - InfoArea { id: infoArea @@ -67,4 +57,16 @@ ListView { excludedCodes: [200, 902, 905] errorCode: control.status } + + PullRefresher { + target: control + + postRefreshDelay: 0 + + busyIndicator: FancyBusyIndicator {} + + onRefreshRequested: { + control.refresh() + } + } } diff --git a/resources/qml/Components/FancyBusyIndicator.qml b/resources/qml/Components/FancyBusyIndicator.qml index 32d8421..ba6631f 100644 --- a/resources/qml/Components/FancyBusyIndicator.qml +++ b/resources/qml/Components/FancyBusyIndicator.qml @@ -5,7 +5,10 @@ import QtQuick.Controls.Styles 1.2 BusyIndicator { id: control - property double animationSpeed: 0.5 + property double animationSpeed: 1000 + property double formFactor: 4.5 + + property color lineColor: "#21be2b" contentItem: Item { implicitWidth: 64 @@ -14,54 +17,20 @@ BusyIndicator { Item { id: item - x: parent.width / 2 - 32 - y: parent.height / 2 - 32 - - width: 64 - height: 64 - - opacity: control.running ? 1 : 0 + anchors.fill: parent property int currentHeight: 0 - onCurrentHeightChanged: { - } - - Behavior on opacity { - OpacityAnimator { - duration: 250 - } - } - SequentialAnimation { + running: control.running loops: Animation.Infinite - running: true - NumberAnimation { target: item - - duration: 2000 * 1/control.animationSpeed - - to: 1000 - - properties: "currentHeight" - - easing.type: Easing.InOutQuad - - } - - NumberAnimation { - target: item - - duration: 2000 * 1/control.animationSpeed - - to: 0 - - properties: "currentHeight" - - easing.type: Easing.InOutQuad - + property: "currentHeight" + from: 0 + to: 800 + duration: control.animationSpeed } } @@ -77,7 +46,7 @@ BusyIndicator { Rectangle { - property double heightMultiplier: Math.abs( Math.sin(( (item.currentHeight + (index*20))*0.01) * (Math.PI/2) ) ) + property double heightMultiplier: Math.abs( Math.sin( ( ((item.currentHeight/100) + (index*(control.formFactor/repeater.model)))) * (Math.PI/8) ) ) anchors.verticalCenter: parent.verticalCenter @@ -86,7 +55,8 @@ BusyIndicator { radius: width * 0.5 - color: "#21be2b" + color: control.lineColor + } } } diff --git a/resources/qml/Components/PullRefresher.qml b/resources/qml/Components/PullRefresher.qml new file mode 100644 index 0000000..9a70dfb --- /dev/null +++ b/resources/qml/Components/PullRefresher.qml @@ -0,0 +1,313 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import QtGraphicalEffects 1.0 + +Item { + id: control + + state: "idle" + + property var target // targeted ListView + + property bool autoConfigureTarget: true // should the target be automaticaly be configured? + + property int postRefreshDelay: 1000 // delay after reload funcion has finished + property int preRefreshDelay: 1000 // delay before reload funcion is called + + property int refreshPosition: height * 1.2 // position of the item when refreshing + property int dragOutPosition: height * 2 // position of the item when starting to refresh + + property color backgroundColor: "white" // color for the pre-defined background + property color pullIndicatorColor: "black" // color for the pre-defined pull indicator + //property color busyIndicatorColor: "pink" // color for the pre-defined busy indicator + + readonly property double dragProgress: Math.min( userPosition / dragOutPosition, 1) + + property Component background: Item { + RectangularGlow { + anchors.fill: backgroundRe + + scale: 0.8 * backgroundRe.scale + cornerRadius: backgroundRe.radius + color: "black" + + glowRadius: 0.001 + spread: 0.2 + } + + Rectangle { + id: backgroundRe + + anchors.fill: parent + + radius: width * 0.5 + color: control.backgroundColor + } + } + property Component busyIndicator: BusyIndicator { running: true } + property Component pullIndicator: Canvas { + + property double drawProgress: control.dragProgress + + onDrawProgressChanged: { + requestPaint() + } + + onPaint: { + var ctx = getContext("2d"); + + var topMargin = height * 0.1 + var bottomMargin = topMargin + var rightMargin = 0 + var leftMargin = 0 + + var arrowHeight = height - topMargin - bottomMargin + + var peakHeight = arrowHeight * 0.35 + var peakWidth = peakHeight + + var lineWidth = 2 + + var progress = drawProgress + + // modify all values to math the progress + + arrowHeight = arrowHeight * progress + if(progress > 0.3){ + peakHeight = peakHeight * (progress - 0.3) * 1/0.7 + peakWidth = peakWidth * (progress - 0.3) * 1/0.7 + } + else { + peakHeight = 0 + peakWidth = 0 + } + + // clear canvas + ctx.reset() + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = control.pullIndicatorColor; + + // middle line + ctx.moveTo(width/2, topMargin); + ctx.lineTo(width/2, arrowHeight + topMargin); + + // right line + ctx.moveTo(width/2 - lineWidth * 0.3, arrowHeight + topMargin); + ctx.lineTo(width/2 + peakWidth,arrowHeight + topMargin - peakHeight); + // left line + ctx.moveTo(width/2 + lineWidth * 0.3, arrowHeight + topMargin); + ctx.lineTo(width/2 - peakWidth,arrowHeight + topMargin - peakHeight); + + ctx.stroke(); + } + } + + signal refreshRequested + + // internal properties + property int minimumPosition: 0 + property int maximumPosition: 0 + property int userPosition: 0 + property int position: Math.max( minimumPosition, Math.min(maximumPosition, userPosition)) + + height: 50 + width: height + + Component.onCompleted: { + if(control.autoConfigureTarget){ + target.boundsBehavior = Flickable.DragOverBounds + target.boundsMovement = Flickable.StopAtBounds + } + } + + function refresh() { + control.refreshRequested() + postRefreshTimer.start() + } + + anchors { + top: control.target.top + horizontalCenter: control.target.horizontalCenter + topMargin: control.position - height * 1.1 + } + + onUserPositionChanged: { + if(control.state === "idle" && userPosition >= control.dragOutPosition){ + control.state = "ready" + } + else if(control.state === "ready" && userPosition < control.dragOutPosition){ + control.state = "refreshing" + preRefreshTimer.start() + } + } + + Loader { + id: backgroundLd + + anchors.fill: parent + + sourceComponent: control.background + } + + Loader { + id: pullIndicatorLd + + anchors.centerIn: parent + + height: parent.height * 0.6 + width: height + + rotation: 180 + + sourceComponent: control.pullIndicator + } + + Loader { + id: busyIndicatorLd + + anchors.centerIn: parent + + height: parent.height * 0.7 + width: height + + opacity: 0 + + sourceComponent: control.busyIndicator + } + + Behavior on minimumPosition { + enabled: control.state !== "hidden" && control.state !== "idle" + NumberAnimation { + duration: 200 + } + } + + Timer { + id: preRefreshTimer + interval: control.preRefreshDelay <= 0 ? 1:control.preRefreshDelay + running: false + repeat: false + onTriggered: { + control.refresh() + } + } + + Timer { + id: postRefreshTimer + interval: control.postRefreshDelay <= 0 ? 1:control.postRefreshDelay + running: false + repeat: false + onTriggered: { + control.state = "hidden" + } + } + + states: [ + State { + name: "idle" + + PropertyChanges { + target: control + minimumPosition: userPosition > maximumPosition ? maximumPosition:userPosition + userPosition: Math.abs( target.verticalOvershoot ) + maximumPosition: control.dragOutPosition + } + + PropertyChanges { + target: pullIndicatorLd + rotation: 0 + } + }, + State { + name: "ready" + PropertyChanges { + target: control + maximumPosition: control.dragOutPosition + userPosition: Math.abs( target.verticalOvershoot ) + minimumPosition: control.maximumPosition - 1 + } + + PropertyChanges { + target: pullIndicatorLd + rotation: 180 + } + }, + State { + name: "refreshing" + PropertyChanges { + target: control + minimumPosition: control.refreshPosition + userPosition: 0 + maximumPosition: control.refreshPosition + + } + + PropertyChanges { + target: pullIndicatorLd + opacity: 0 + } + + PropertyChanges { + target: busyIndicatorLd + opacity: 1 + } + }, + State { + name: "hidden" + PropertyChanges { + target: control + minimumPosition: control.refreshPosition + userPosition: 0 + maximumPosition: control.refreshPosition + scale: 0 + } + + PropertyChanges { + target: pullIndicatorLd + opacity: 0 + } + + PropertyChanges { + target: busyIndicatorLd + opacity: 1 + } + } + ] + + transitions: [ + Transition { + NumberAnimation { + duration: 200 + properties: "rotation, opacity" + } + }, + + Transition { + from: "refreshing" + to: "hidden" + + PauseAnimation { + duration: 200 + } + + NumberAnimation { + duration: 200 + properties: "scale" + } + + onRunningChanged: { + if(control.state === "hidden" && !running){ + control.state = "idle" + } + } + }, + + Transition { + from: "hidden" + to: "idle" + } + + ] + +} diff --git a/resources/qml/Components/RankingView.qml b/resources/qml/Components/RankingView.qml index bed8068..033ed88 100644 --- a/resources/qml/Components/RankingView.qml +++ b/resources/qml/Components/RankingView.qml @@ -17,12 +17,26 @@ DataListView { id: partDel property int ind: index + property var thisData: listData[ "participants" ][index] state: "closed" width: parent.width height: 70 + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + text: "" onClicked: { diff --git a/resources/qml/Components/RegistrationView.qml b/resources/qml/Components/RegistrationView.qml index 649f7ef..8638a29 100644 --- a/resources/qml/Components/RegistrationView.qml +++ b/resources/qml/Components/RegistrationView.qml @@ -1,4 +1,4 @@ -import QtQuick 2.0 +import QtQuick 2.9 import QtQuick.Controls 2.4 DataListView { @@ -8,16 +8,33 @@ DataListView { model: listData[ root.listKey ] === undefined ? 0:listData[ root.listKey ].length + + delegate: ItemDelegate { id: partDel + property var thisData: listData[ "athletes" ][index] + width: parent.width + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + text: "" Label { anchors.fill: parent - anchors.leftMargin: width * 0.05 + anchors.leftMargin: parent.width * 0.05 verticalAlignment: Text.AlignVCenter diff --git a/resources/qml/Components/ResultView.qml b/resources/qml/Components/ResultView.qml index 8ed2bc3..84c4772 100644 --- a/resources/qml/Components/ResultView.qml +++ b/resources/qml/Components/ResultView.qml @@ -16,12 +16,27 @@ DataListView { id: partDel property int ind: index + property var thisData: listData[ "participants" ][partDel.ind] width: parent.width height: 70 text: "" + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + + Rectangle { anchors.fill: parent @@ -46,7 +61,7 @@ DataListView { Label { height: parent.height - width: parent.width * 0.1 + width: text === "" ? parent.width * 0.08:parent.width * 0.1 fontSizeMode: Text.Fit font.bold: true @@ -54,7 +69,7 @@ DataListView { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: listData[ "participants" ][index]["result_rank"] + text: listData[ "participants" ][index]["result_rank"] === undefined ? "":listData[ "participants" ][index]["result_rank"] } Label { @@ -158,7 +173,7 @@ DataListView { var offsetX = width * 0.05 var offsetY = height * 0.05 - console.log("drawing result rect with width: " + width + " and height: " + height) + //console.log("drawing result rect with width: " + width + " and height: " + height) var context = getContext("2d"); diff --git a/resources/qml/Components/StartlistView.qml b/resources/qml/Components/StartlistView.qml index c080e09..9f28b3d 100644 --- a/resources/qml/Components/StartlistView.qml +++ b/resources/qml/Components/StartlistView.qml @@ -11,8 +11,23 @@ DataListView { delegate: ItemDelegate { id: partDel + property var thisData: listData[ "participants" ][index] + width: parent.width + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + text: "" Row { diff --git a/resources/qml/Pages/CompetitionCalendarPage.qml b/resources/qml/Pages/CompetitionCalendarPage.qml index d5189da..a87578e 100644 --- a/resources/qml/Pages/CompetitionCalendarPage.qml +++ b/resources/qml/Pages/CompetitionCalendarPage.qml @@ -123,7 +123,7 @@ Page { } function loadData(nation, year) { - loadingDl.open() + //loadingDl.open() //console.log("loading calendar data and old data is: " + (root.calendarData !== undefined) + " because of: " + root.calendarData + " and: ") @@ -155,7 +155,7 @@ Page { return false } - loadingDl.close() + //loadingDl.close() app.errorCode = root.status return true @@ -198,7 +198,7 @@ Page { //console.log(calendarList.listData[i]["date"] + ": " + startDate + " to " + endDate) if(endDate.getTime() < new Date().getTime()){ - // end date is already over -> move the list view up! + // end date is already over -> move the list view down! calendarList.positionViewAtIndex(i, ListView.Top) //console.log("moving down!") } @@ -214,16 +214,30 @@ Page { property var cats: calendarList.listData[index]["cats"] property int catId: calendarList.listData[index]["cat_id"] property bool over + property var thisData: calendarList.listData[index] width: parent.width height: compDelCol.height + 10 enabled: calendarList.listData[index]["cats"] !== undefined && calendarList.listData[index]["cats"].length > 0 + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + onClicked: { catSelectPu.appear(index, false) } + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: competitionDel; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: competitionDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + Rectangle { id: delBackroundRect @@ -300,7 +314,7 @@ Page { y: root.height - height //root.height / 2 - height / 2 width: root.width - height: catsLv.height //root.height * 0.6 + height: catsLv.implicitHeight modal: true focus: true @@ -353,14 +367,10 @@ Page { property int delegateHeight: 50 - anchors { - top: parent.top - left: parent.left - right: parent.right - } + anchors.fill: parent - width: parent.width - height: root.height * 0.6 < ( (delegateHeight + spacing) * model ) ? root.height * 0.6 : (delegateHeight + spacing) * model + 75 + implicitWidth: parent.width + implicitHeight: root.height * 0.6 < ( (delegateHeight + spacing) * model ) ? root.height * 0.6 : (delegateHeight + spacing) * model + 75 model: catSelectPu.catObj !== undefined ? catSelectPu.catObj.length:0 @@ -423,7 +433,7 @@ Page { y: root.height - height //root.height / 2 - height / 2 width: root.width - height: root.height * 0.6 + height: yearsLv.implicitHeight modal: true focus: true @@ -433,8 +443,8 @@ Page { contentItem: ListView { id: yearsLv - width: parent.width - height: root.height * 0.6 + implicitWidth: parent.width + implicitHeight: root.height * 0.6 model: yearSelectPu.yearList !== undefined ? yearSelectPu.yearList.length:0 @@ -504,7 +514,7 @@ Page { y: root.height - height //root.height / 2 - height / 2 width: root.width - height: cupsLv.height //root.height * 0.6 + height: cupsLv.implicitHeight modal: true focus: true @@ -522,8 +532,8 @@ Page { right: parent.right } - width: parent.width - height: root.height * 0.6 < ( (delegateHeight + spacing) * model ) ? root.height * 0.6 : (delegateHeight + spacing) * model + 75 + implicitWidth: parent.width + implicitHeight: root.height * 0.6 < ( (delegateHeight + spacing) * model ) ? root.height * 0.6 : (delegateHeight + spacing) * model + 75 model: cupSelectPu.cupList !== undefined ? cupSelectPu.cupList.length:0 diff --git a/resources/qml/Pages/RankingPage.qml b/resources/qml/Pages/RankingPage.qml index e20f800..79affde 100644 --- a/resources/qml/Pages/RankingPage.qml +++ b/resources/qml/Pages/RankingPage.qml @@ -32,13 +32,18 @@ Page { property string compNameKey: root.reg ? "name":"comp_name" Component.onCompleted: { + loadingDl.open() root.loadData(root.comId, root.catId, root.reg, root.rak, root.routeNumber) + loadingDl.close() } function loadData(comp, cat, reg, rak, route){ - loadingDl.open() root.status = 905 console.log("[info][QML] getting ranking data of comp: " + comp + " , cat: " + cat + " and status: " + root.catStatus) + if(route === -1){ + route = -2 + } + var ret = serverConn.getRanking(comp, cat, reg, rak, route) root.status = ret["status"] @@ -77,20 +82,17 @@ Page { else if(root.status !== 200) { root.ready = false root.rankingData = {} - loadingDl.close() return false } if((ret["data"][ root.listKey ] === undefined || ret["data"][ root.listKey ].length < 1) && ( root.status === 200 || root.status === 404 ) ){ root.status = 901 root.ready = false - loadingDl.close() return false } console.log("done! status: " + root.status) root.ready = true - loadingDl.close() return true } @@ -265,9 +267,17 @@ Page { onClicked: { console.log("changing to index: " + index + " (" + routeSelectTb.tabs[index][0] + ", " + routeSelectTb.tabs[index][1] + ")") - if(root.routeNumber !== routeSelectTb.tabs[index][0]){ - root.routeNumber = routeSelectTb.tabs[index][0] - root.loadData(root.comId, root.catId, root.reg, root.rak, root.routeNumber) + if(root.routeNumber !== parseInt(routeSelectTb.tabs[index][0])){ + loadingDl.open() + if(root.loadData(root.comId, root.catId, root.reg, root.rak, routeSelectTb.tabs[index][0]) && root.status === 200){ + root.routeNumber = routeSelectTb.tabs[index][0] + } + else { + routeSelectTb.setCurrentIndex(routeSelectTb.getIndex(root.routeNumber)) + } + + loadingDl.close() + } } } diff --git a/resources/qml/main.qml b/resources/qml/main.qml index a72fe3f..36edb3c 100644 --- a/resources/qml/main.qml +++ b/resources/qml/main.qml @@ -51,7 +51,7 @@ Window { StackView { id: mainStack - enabled: !loadingDl.opened + //enabled: !loadingDl.opened anchors { top: toolBar.bottom @@ -211,6 +211,8 @@ Window { width: parent.width height: text !== "" ? undefined:0 + elide: "ElideRight" + font.bold: false color: "black" @@ -304,7 +306,7 @@ Window { } function openCalendar(nation){ - //loadingDl.open() + loadingDl.open() var calComp = Qt.createComponent("qrc:/Pages/CompetitionCalendarPage.qml").createObject(null, {"nation": nation}) app.errorCode = calComp.status @@ -315,7 +317,7 @@ Window { delete(calComp) } - //loadingDl.close() + loadingDl.close() } function openResults(comp, cat, status){ diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc index eeaaff2..e5a1602 100644 --- a/resources/qml/qml.qrc +++ b/resources/qml/qml.qrc @@ -13,5 +13,6 @@ Components/RegistrationView.qml Components/StartlistView.qml Components/RankingView.qml + Components/PullRefresher.qml diff --git a/resources/shared/Banner.png b/resources/shared/Banner.png new file mode 100644 index 0000000..1d717b4 Binary files /dev/null and b/resources/shared/Banner.png differ diff --git a/resources/shared/Banner.xcf b/resources/shared/Banner.xcf new file mode 100644 index 0000000..d8f227a Binary files /dev/null and b/resources/shared/Banner.xcf differ diff --git a/sources/serverconn.cpp b/sources/serverconn.cpp index e08d83f..6dd4f62 100644 --- a/sources/serverconn.cpp +++ b/sources/serverconn.cpp @@ -4,25 +4,6 @@ ServerConn::ServerConn(QObject *parent) : QObject(parent) { } -void ServerConn::refreshFoodplan() { - QVariant tmpFoodplan; - - QVariantMap ret = this->senddata(QUrl("http://egw.ifsc-climbing.org/egw/ranking/json.php")); - - if(ret["status"] != 200){ - // request was a failure - return; - } - - QJsonDocument jsonReply = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); - - this->foodplan = jsonReply.toVariant(); - - //qDebug() << this->foodplan; - - emit this->foodplanChanged(); -} - QVariant ServerConn::getCalendar(QString nation, int year){ QVariantMap ret = this->senddata(QUrl("http://egw.ifsc-climbing.org/egw/ranking/json.php?year=" + QString::number(year) + "&nation=" + nation)); @@ -128,7 +109,5 @@ QVariantMap ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata) // --- Functions for QML --- // ------------------------- -QVariant ServerConn::getFoodplan() { - return this->foodplan; -} +