diff --git a/android-sources/AndroidManifest.xml b/android-sources/AndroidManifest.xml index 58ec7b0..e2a3c80 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 a12cdb2..e097a58 100644 --- a/headers/serverconn.h +++ b/headers/serverconn.h @@ -41,6 +41,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); + QVariant getWidgetData(QVariantMap params); }; diff --git a/resources/qml/Components/RegistrationView.qml b/resources/qml/Components/RegistrationView.qml index d8ae18f..56ebad4 100644 --- a/resources/qml/Components/RegistrationView.qml +++ b/resources/qml/Components/RegistrationView.qml @@ -26,14 +26,13 @@ DataListView { model: listData[ root.listKey ] === undefined ? 0:listData[ root.listKey ].length - - delegate: ItemDelegate { id: partDel property var thisData: listData[ "athletes" ][index] width: parent.width + height: parseInt(thisData.cat) === root.catId ? undefined:0 opacity: 0 scale: 0.9 diff --git a/resources/qml/Pages/AthleteProfilePage.qml b/resources/qml/Pages/AthleteProfilePage.qml index 9d35531..6acde5b 100644 --- a/resources/qml/Pages/AthleteProfilePage.qml +++ b/resources/qml/Pages/AthleteProfilePage.qml @@ -209,6 +209,8 @@ Page { Label { anchors.horizontalCenter: parent.horizontalCenter + visible: bestResultsRep.model > 0 + width: parent.width wrapMode: Label.Wrap @@ -229,12 +231,34 @@ Page { } + Button { + id: toggleAllResultsBt + anchors.horizontalCenter: parent.horizontalCenter + visible: bestResultsRep.model > 0 + + flat: true + + text: bestResultsRep.showAllResults ? qsTr("show best results"):qsTr("show all results") + + onClicked: { + bestResultsRep.showAllResults = !bestResultsRep.showAllResults + } + + Behavior on text { + FadeAnimation { + target: toggleAllResultsBt + fadeDuration: 150 + } + } + + } Repeater { id: bestResultsRep - property var bestResults: getBestResults() + property var bestResults: showAllResults ? getAllResults() : getBestResults() + property bool showAllResults: false function getBestResults(){ @@ -260,16 +284,44 @@ Page { return bestResults } + function getAllResults() { + var allResults = root.perData["results"] + + allResults.sort(function(a, b) { + // sort results by date + var aTs = Date.fromLocaleString(Qt.locale(), a["date"], "yyyy-MM-dd").getTime() + var bTs = Date.fromLocaleString(Qt.locale(), b["date"], "yyyy-MM-dd").getTime() + return bTs - aTs; + }); + + return allResults + } + width: parent.width - model: bestResults.length > 12 ? 12:bestResults.length + model: bestResults.length delegate: Row { id: bestResultRow + property var thisData: bestResultsRep.bestResults[index] + width: parent.width height: 50 + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: bestResultRow; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: bestResultRow; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + Label { id: bestResultRankLa diff --git a/resources/qml/Pages/CompetitionCalendarPage.qml b/resources/qml/Pages/CompetitionCalendarPage.qml index 6bd282f..2bb6e11 100644 --- a/resources/qml/Pages/CompetitionCalendarPage.qml +++ b/resources/qml/Pages/CompetitionCalendarPage.qml @@ -298,6 +298,14 @@ Page { text: name } +/* + Label { + id: infola + + width: parent.width + + text: calendarList.listData[index].info + }*/ Label { id: dateLa @@ -323,6 +331,7 @@ Page { } } + } Dialog { @@ -347,13 +356,21 @@ Page { catSelectPu.cupCat = cupCat catSelectPu.index = index + var tmpCatObj + if(cupCat){ - catSelectPu.catObj = root.calendarData["cups"][index]["cats"] + tmpCatObj = root.calendarData["cups"][index]["cats"] } else { - catSelectPu.catObj = calendarList.listData[index]["cats"] + tmpCatObj = calendarList.listData[index]["cats"] } + tmpCatObj.sort(function(a, b) { + return parseInt(a["GrpId"]) - parseInt(b["GrpId"]); + }); + + catSelectPu.catObj = tmpCatObj + catSelectPu.open() } diff --git a/resources/qml/Pages/RankingPage.qml b/resources/qml/Pages/RankingPage.qml index 609d060..207e0bf 100644 --- a/resources/qml/Pages/RankingPage.qml +++ b/resources/qml/Pages/RankingPage.qml @@ -29,6 +29,52 @@ Page { property string subTitle: getSubtitle() property bool titleIsPageTitle: true + property Component headerComponent: Item { + anchors.fill: parent + + + Button { + id: cupToolBt + + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: parent.width * 0.1 + } + + height: parent.height * 0.5 + width: height + + onClicked: { + catSelectPu.appear() + } + + onPressed: cupToolBt.scale = 0.9 + onReleased: cupToolBt.scale = 1.0 + + background: Image { + + anchors.centerIn: parent + + source: "qrc:/icons/more_black.png" + + height: parent.height > parent.width ? parent.width : parent.height + width: height + + mipmap: true + + fillMode: Image.PreserveAspectFit + Behavior on scale { + PropertyAnimation { + duration: 100 + } + } + } + } + + + } + property bool ready property int status: -1 @@ -44,6 +90,14 @@ Page { property bool res: catStatus === 0 || catStatus === 1 // result data property bool stl: catStatus === 2 || catStatus === 3 // startlist data + /* + enum DataType { + RankingData, + RegistrationData, + ResultData, + StartlistData + }*/ + property var rankingData property string listKey: root.reg ? "athletes":"participants" @@ -57,7 +111,9 @@ Page { function loadData(comp, cat, reg, rak, route){ root.status = 905 + console.log("[info][QML] getting ranking data of comp: " + comp + " , cat: " + cat + " and status: " + root.catStatus) + if(route === -1){ route = -2 } @@ -65,12 +121,13 @@ Page { var ret = serverConn.getRanking(comp, cat, reg, rak, route) root.status = ret["status"] + console.log(root.status) if(parseInt(ret["status"]) === 200){ // request was successfull -> prepare data if(root.reg){ // if the data is registration data, athletes of other cats need to be removed - +/* var validAthletes = [] for(var i = 0; i < ret["data"]["athletes"].length; i++){ @@ -79,7 +136,7 @@ Page { validAthletes.push(ret["data"]["athletes"][i]) } } - ret["data"]["athletes"] = validAthletes + ret["data"]["athletes"] = validAthletes*/ root.rankingData = ret["data"] } @@ -349,4 +406,109 @@ Page { } } + Dialog { + id: catSelectPu + + property var catObj: undefined + + x: 0 //root.width / 2 - width / 2 + y: root.height - height //root.height / 2 - height / 2 + + width: root.width + height: catsLv.implicitHeight + + modal: true + focus: true + + title: qsTr("select category") + + function appear() { + + var tmpCatObj + + + tmpCatObj = root.rankingData["categorys"] + + + tmpCatObj.sort(function(a, b) { + return parseInt(a["GrpId"]) - parseInt(b["GrpId"]); + }); + + catSelectPu.catObj = tmpCatObj + + catSelectPu.open() + + } + + function getText(index){ + // ---------------------------- + // get the text + // returns list with [catId, catName] + return [catSelectPu.catObj[index]["GrpId"], catSelectPu.catObj[index]["name"]] + } + + ListView { + id: catsLv + + property int delegateHeight: 50 + + anchors.fill: parent + + 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 + + ScrollIndicator.vertical: ScrollIndicator { + parent: catsLv.parent + anchors { + top: catsLv.top + left: catsLv.right + margins: 10 + leftMargin: 3 + bottom: catsLv.bottom + } + + } + + delegate: Button { + id: catBt + + property var catData: catSelectPu.getText(index) + + width: parent.width + height: text !== "" ? catsLv.delegateHeight:0 + + flat: true + + text: catData[1] + + onClicked: { + catSelectPu.close() + root.catId = catData[0] + root.loadData(root.comId, root.catId, root.reg, root.rak, root.routeNumber) + } + } + } + + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } + NumberAnimation { + property: "y" + from: root.height - catSelectPu.height * 0.7 + to: root.height - catSelectPu.height + } + } + + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 } + NumberAnimation { + property: "y" + from: root.height - catSelectPu.height + to: root.height - catSelectPu.height * 0.7 + } + } + } + + } diff --git a/resources/qml/Pages/StartPage.qml b/resources/qml/Pages/StartPage.qml index 2e58d4a..5939dd9 100644 --- a/resources/qml/Pages/StartPage.qml +++ b/resources/qml/Pages/StartPage.qml @@ -25,6 +25,20 @@ Page { id: root title: "start" + property Component headerComponent: null + + Label { + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: root.height * 0.03 + } + + font.pixelSize: anchors.topMargin + font.bold: true + + text: "blueROCK" + } Grid { id: menuGr @@ -34,7 +48,7 @@ Page { rows: app.landscape() ? 1:3 columns: app.landscape() ? 3:1 - spacing: app.landscape() ? parent.height * 0.1:parent.width * 0.1 + spacing: !app.landscape() ? parent.height * 0.08:parent.width * 0.1 property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.2 @@ -82,4 +96,24 @@ Page { } + Label { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: root.height * 0.03 + } + + width: parent.width * 0.9 + height: anchors.bottomMargin + + wrapMode: "Wrap" + horizontalAlignment: Text.AlignHCenter + + text: "Resultservice and rankings provided by digital ROCK, (c) 1990-2019 by Ralf Becker." + + onLinkActivated: { + Qt.openUrlExternally(link) + } + } + } diff --git a/resources/qml/Pages/WidgetPage.qml b/resources/qml/Pages/WidgetPage.qml new file mode 100644 index 0000000..00e97c5 --- /dev/null +++ b/resources/qml/Pages/WidgetPage.qml @@ -0,0 +1,266 @@ +/* + blueROCK - for digital rock + Copyright (C) 2019 Dorian Zedler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import QtGraphicalEffects 1.0 + +import "../Components" + +Page { + id: root + + enum WidgetType { + Competitions, + + Profile, + + Registration, + Startlist, + Result, + + Ranking, + Aggregated // not yet implemented + } + + title: widgetLd.item === null ? "":widgetLd.item.title + property Component headerComponent: widgetLd.item === null ? null:widgetLd.item.headerComponent + + property var params + + property int status: -1 + property bool ready: false + + property var widgetData + + property int widgetType + + Component.onCompleted: { + loadData(params) + } + + function loadData(params) { + // params is an object and can contain: { + // comp: competitionId, + // person: personId, + // cat: categoryId, + // nation: nationString ('', 'GER', 'SUI') + // type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'), + //} + + var ret = serverConn.getWidgetData(params) + + if(ret["status"] === 200){ + root.widgetData = ret["data"] + root.widgetType = checkWidgetType(params, root.widgetData) + console.log(widgetType) + widgetLd.load() + root.ready = true + } + + root.status = ret["status"] + + } + + function updateData(params, openLoadingDl) { + if(openLoadingDl) + loadingDl.open() + + + for(var prop in params){ + if(params.hasOwnProperty(prop)){ + root.params[prop] = params[prop] + } + } + + loadData(root.params) + + if(openLoadingDl) + loadingDl.close() + + } + + function checkWidgetType(params, widgetData){ + + var widgetType + + function hasParam(object, key, value){ + if(object[key] !== undefined){ + if(value !== undefined){ + return object[key] === value + } + return true + } + return false + } + + // check the type of the requested widget + + if(hasParam(params, 'perId')){ + // person profile + widgetType = WidgetPage.WidgetType.Profile + } + + else if(hasParam(params, 'nation')){ + // competition calendar + widgetType = WidgetPage.WidgetType.Competitions + } + + else if(hasParam(params, 'comp') && hasParam(params, 'type', 'starters')){ + // registration + widgetType = WidgetPage.WidgetType.Registration + } + else if(hasParam(params, 'type', 'startlist') || (widgetData.participants !== undefined && widgetData.participants[0] && !widgetData.participants[0].result_rank && widgetData.discipline !== 'ranking')){ + // startlist + widgetType = WidgetPage.WidgetType.Startlist + } + else if(hasParam(params, 'comp') && hasParam(params, 'cat')){ + // results + widgetType = WidgetPage.WidgetType.Result + } + + else if( hasParam(params, 'cat') && hasParam(params, 'cup') && !hasParam(params, 'comp')){ + // ranking data + widgetType = WidgetPage.WidgetType.Ranking + } + else if(hasParam(params, 'type', 'nat_team_ranking') || hasParam(params, 'type', 'sektionenwertung') || hasParam(params, 'type', 'regionalzentren')){ + // aggregated + widgetType = WidgetPage.WidgetType.Aggregated + } + + return widgetType + } + + Loader { + id: widgetLd + + property alias selector: selectorPu + property var updateData: root.updateData + property alias params: root.params + + anchors.fill: parent + + source: "" + + function load() { + widgetLd.source = getFile(root.widgetType) + widgetLd.item.widgetData = root.widgetData + } + + function getFile(widgetType) { + var path = "qrc:/Widgets/" + switch(widgetType){ + case WidgetPage.WidgetType.Competitions: + path += "CalendarWidget" + break + case WidgetPage.WidgetType.Profile: + path += "ProfileWidget" + break + case WidgetPage.WidgetType.Registration: + path += "RegistrationWidget" + break + } + + path += ".qml" + return path + } + } + + Dialog { + id: selectorPu + + property var dataObj + + signal selectionFinished(int index, var data) + + x: 0 //root.width / 2 - width / 2 + y: root.height - height //root.height / 2 - height / 2 + + width: root.width + height: selectorLv.implicitHeight + + modal: true + focus: true + + title: qsTr("select category") + + function appear(dataObj) { + selectorPu.dataObj = dataObj + selectorPu.open() + } + + ListView { + id: selectorLv + + property int delegateHeight: 50 + + anchors.fill: parent + + implicitWidth: parent.width + implicitHeight: root.height * 0.6 < ( (delegateHeight + spacing) * model ) ? root.height * 0.6 : (delegateHeight + spacing) * model + 75 + + model: selectorPu.dataObj !== undefined ? selectorPu.dataObj.length:0 + + ScrollIndicator.vertical: ScrollIndicator { + parent: selectorLv.parent + anchors { + top: selectorLv.top + left: selectorLv.right + margins: 10 + leftMargin: 3 + bottom: selectorLv.bottom + } + + } + + delegate: Button { + id: catBt + + width: parent.width + height: text !== "" ? selectorLv.delegateHeight:0 + + flat: true + + text: selectorPu.dataObj[index].text + + onClicked: { + selectorPu.close() + selectorPu.selectionFinished(index, selectorPu.dataObj[index].data) + } + } + } + + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } + NumberAnimation { + property: "y" + from: root.height - selectorPu.height * 0.7 + to: root.height - selectorPu.height + } + } + + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 } + NumberAnimation { + property: "y" + from: root.height - selectorPu.height + to: root.height - selectorPu.height * 0.7 + } + } + } +} diff --git a/resources/qml/Widgets/CalendarWidget.qml b/resources/qml/Widgets/CalendarWidget.qml new file mode 100644 index 0000000..d435950 --- /dev/null +++ b/resources/qml/Widgets/CalendarWidget.qml @@ -0,0 +1,340 @@ +/* + blueROCK - for digital rock + Copyright (C) 2019 Dorian Zedler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import QtQuick 2.9 +import QtQuick.Controls 2.4 + +import "../Components" + +DataListView { + id: control + + property string title: (params.nation === "" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTr("competition calendar") + property Component headerComponent: Item { + anchors.fill: parent + + Row { + anchors.fill: parent + anchors.rightMargin: 5 + + spacing: width * 0.05 + + Label { + id: yearLa + anchors.verticalCenter: parent.verticalCenter + + width: parent.width * 0.4 + height: parent.height * 0.6 + + fontSizeMode: Text.Fit + font.pixelSize: height + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + minimumPixelSize: 1 + + text: control.year + + Behavior on text { + FadeAnimation { + target: yearLa + } + } + + } + + Button { + id:yearToolBt + + anchors { + verticalCenter: parent.verticalCenter + } + + height: parent.height * 0.5 + width: parent.width * 0.25 + + onClicked: { + control.changeYear() + } + + onPressed: yearToolBt.scale = 0.9 + onReleased: yearToolBt.scale = 1.0 + + background: Image { + + anchors.centerIn: parent + + source: "qrc:/icons/calendar.png" + + height: parent.height > parent.width ? parent.width : parent.height + width: height + + mipmap: true + + fillMode: Image.PreserveAspectFit + Behavior on scale { + PropertyAnimation { + duration: 100 + } + } + } + } + + Button { + id: cupToolBt + + anchors { + verticalCenter: parent.verticalCenter + } + + height: parent.height * 0.5 + width: parent.width * 0.25 + + onClicked: { + cupSelectPu.open() + } + + onPressed: cupToolBt.scale = 0.9 + onReleased: cupToolBt.scale = 1.0 + + background: Image { + + anchors.centerIn: parent + + source: "qrc:/icons/cup.png" + + height: parent.height > parent.width ? parent.width : parent.height + width: height + + mipmap: true + + fillMode: Image.PreserveAspectFit + Behavior on scale { + PropertyAnimation { + duration: 100 + } + } + } + } + + } + } + + property var widgetData + + property int year: new Date().getFullYear() + + anchors.fill: parent + + //boundsBehavior: Flickable.StopAtBounds + + model: widgetData["competitions"].length + + status: root.status + + onRefresh: { + updateData({}, false) + } + + onModelChanged: { + autoScroll() + } + + function autoScroll() { + + var compList = control.widgetData["competitions"] + + if(parseInt(control.year) === new Date().getFullYear()){ + for(var i = 0; i < compList.length; i ++){ + // get the start date pf the competition + var startDate = Date.fromLocaleString(Qt.locale(), compList[i]["date"], "yyyy-MM-dd") + + // get the duration of the competition + var durationString = compList[i]["duration"] === undefined ? "1":compList[i]["duration"] + var days = parseInt(durationString.replace(/\D/g,'')) + // calculate the end date of the competition + var endDate = new Date(startDate.valueOf()) + endDate.setDate(endDate.getDate() + days); + + //console.log(compList[i]["date"] + ": " + startDate + " to " + endDate) + + if(endDate.getTime() < new Date().getTime()){ + // end date is already over -> move the list view down! + control.positionViewAtIndex(i, ListView.Top) + //console.log("moving down!") + } + } + } + } + + function getCompCatData(compCatId) { + var obj = app.compCats + + for(var prop in obj) { + // go through the whole array and search for data keys + if (obj.hasOwnProperty(prop) && obj[prop]["cat_id"].indexOf(compCatId) >= 0) { + //console.log("found cat: " + obj[prop]['label']) + return obj[prop] + } + } + } + + function openComp(compIndex){ + var cats = control.widgetData["competitions"][compIndex]["cats"] + + cats.sort(function(a, b) { + return parseInt(a["GrpId"]) - parseInt(b["GrpId"]); + }); + + var selectOptions = [] + + for(var prop in cats){ + if (cats.hasOwnProperty(prop)) { + selectOptions.push({text: cats[prop]["name"], data:{cat: cats[prop]["GrpId"], comp: control.widgetData["competitions"][compIndex]["WetId"], status:cats[prop]["status"]}}) + } + } + + selector.appear(selectOptions) + } + + function changeYear(){ + var years = control.widgetData["years"] + + var selectOptions = [] + + for(var prop in years){ + if (years.hasOwnProperty(prop)) { + selectOptions.push({text: years[prop], data:{year: years[prop]}}) + } + } + + selector.appear(selectOptions) + } + + + + Connections { + target: selector + onSelectionFinished: { + if(data.comp !== undefined){ + console.log(data.status) + app.openWidget({comp: data.comp, cat: data.cat, type:data.status === 4 ? 'starters':''}) + } + else if(data.year !== undefined){ + updateData({year: data.year}, true) + control.year = data.year + } + } + } + + delegate: ItemDelegate { + id: competitionDel + + property bool over + property var thisData: control.widgetData["competitions"][index] + + property string name: thisData["name"] + property string date: thisData["date_span"] + property var cats: thisData["cats"] + property int catId: thisData["cat_id"] === undefined ? 0:thisData["cat_id"] + + width: parent.width + height: compDelCol.height + 10 + + enabled: thisData["cats"] !== undefined && thisData["cats"].length > 0 + + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + onClicked: { + control.openComp(index) + } + + 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 + + anchors.fill: parent + + opacity: 0.5 + + color: control.getCompCatData(catId) === undefined ? "white":control.getCompCatData(catId)["bgcolor"] + } + + Column { + id: compDelCol + + anchors.centerIn: parent + + width: parent.width * 0.97 + + spacing: 10 + + Label { + id: nameLa + + width: parent.width + + font.bold: true + + wrapMode: Text.WordWrap + + text: name + } +/* + Label { + id: infola + + width: parent.width + + text: thisData.info + }*/ + + Label { + id: dateLa + + color: "grey" + + text: date + } + } + + Rectangle { + id: bottomLineRa + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + + height: 1 + + color: "lightgrey" + + } + } +} diff --git a/resources/qml/Widgets/ProfileWidget.qml b/resources/qml/Widgets/ProfileWidget.qml new file mode 100644 index 0000000..fd8c71c --- /dev/null +++ b/resources/qml/Widgets/ProfileWidget.qml @@ -0,0 +1,388 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.3 + +import "../Components" + +Page { + id: control + + title: widgetData["firstname"] + " " + widgetData["lastname"] + property bool titleIsPageTitle: true + property bool ready + property int status: -1 + + property int perId: -1 + + property var widgetData: ({}) + + Component.onCompleted: { + if(control.loadData(control.perId)){ + control.ready = true + } + } + + function loadData(perId) { + console.log("loading athlete: ", perId) + + control.status = 905 + + var ret = serverConn.getAthlete(perId) + + control.status = ret["status"] + + if(ret["status"] === 200){ + control.widgetData = 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: control.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: widgetData["photo"] === undefined ? "":widgetData["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: widgetData["firstname"] + " " + widgetData["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: widgetData["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: "" + widgetData["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") + ": " + widgetData["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") + ": " + widgetData["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") + ": " + widgetData["city"] + } + } + + } + + Label { + anchors.horizontalCenter: parent.horizontalCenter + + visible: bestResultsRep.model > 0 + + width: parent.width + + wrapMode: Label.Wrap + + text: widgetData["freetext"] + + } + + Rectangle { + id: separatorLine + + anchors.horizontalCenter: parent.horizontalCenter + + height: 1 + width: parent.width * 0.9 + + color: "black" + + } + + Button { + id: toggleAllResultsBt + anchors.horizontalCenter: parent.horizontalCenter + + visible: bestResultsRep.model > 0 + + flat: true + + text: bestResultsRep.showAllResults ? qsTr("show best results"):qsTr("show all results") + + onClicked: { + bestResultsRep.showAllResults = !bestResultsRep.showAllResults + } + + Behavior on text { + FadeAnimation { + target: toggleAllResultsBt + fadeDuration: 150 + } + } + + } + + Repeater { + id: bestResultsRep + + property var bestResults: showAllResults ? getAllResults() : getBestResults() + property bool showAllResults: false + + function getBestResults(){ + + var allResults = control.widgetData["results"] + + allResults.sort(function(a, b) { + // sort results by weight + var year = new Date().getFullYear(); + var weightA = a.rank/2 + (year-parseInt(a.date)) + 4*!a.nation; + var weightB = b.rank/2 + (year-parseInt(b.date)) + 4*!b.nation; + return weightA - weightB; + }); + + var bestResults = allResults.slice(0,12) + + bestResults.sort(function(a, b) { + // sort results by date + var aTs = Date.fromLocaleString(Qt.locale(), a["date"], "yyyy-MM-dd").getTime() + var bTs = Date.fromLocaleString(Qt.locale(), b["date"], "yyyy-MM-dd").getTime() + return bTs - aTs; + }); + + return bestResults + } + + function getAllResults() { + var allResults = control.widgetData["results"] + + allResults.sort(function(a, b) { + // sort results by date + var aTs = Date.fromLocaleString(Qt.locale(), a["date"], "yyyy-MM-dd").getTime() + var bTs = Date.fromLocaleString(Qt.locale(), b["date"], "yyyy-MM-dd").getTime() + return bTs - aTs; + }); + + return allResults + } + + width: parent.width + + model: bestResults.length + + delegate: Row { + id: bestResultRow + + property var thisData: bestResultsRep.bestResults[index] + + width: parent.width + height: 50 + + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + ParallelAnimation { + id: fadeInPa + NumberAnimation { target: bestResultRow; property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { target: bestResultRow; property: "scale"; from: 0.8; to: 1.0; duration: 400 } + } + + Label { + id: bestResultRankLa + + width: parent.width * 0.2 + height: parent.height + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + fontSizeMode: Text.Fit + minimumPixelSize: 1 + + font.pixelSize: height * 0.6 + + text: bestResultsRep.bestResults[index]["rank"] + } + + Label { + id: bestResultCompLa + + width: parent.width * 0.6 + height: parent.height + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + fontSizeMode: Text.Fit + minimumPixelSize: height * 0.3 + + font.pixelSize: height * 0.3 + + elide: "ElideRight" + + text: "" + bestResultsRep.bestResults[index]["name"] + "" + + onLinkActivated: { + app.openResults( bestResultsRep.bestResults[index]["WetId"], bestResultsRep.bestResults[index]["GrpId"], 1 ) + } + } + + Label { + id: bestResultDateLa + + width: parent.width * 0.2 + height: parent.height + + scale: 0.8 + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + fontSizeMode: Text.Fit + minimumPixelSize: 1 + + font.pixelSize: height * 0.6 + + text: bestResultsRep.bestResults[index]["date"] + } + } + } + + } + } +} diff --git a/resources/qml/Widgets/RegistrationWidget.qml b/resources/qml/Widgets/RegistrationWidget.qml new file mode 100644 index 0000000..70b66a1 --- /dev/null +++ b/resources/qml/Widgets/RegistrationWidget.qml @@ -0,0 +1,104 @@ +/* + blueROCK - for digital rock + Copyright (C) 2019 Dorian Zedler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import QtQuick 2.9 +import QtQuick.Controls 2.4 + +import "../Components" + +DataListView { + id: control + + property var widgetData: ({}) + + model: widgetData[ 'athletes' ] === undefined ? 0:widgetData[ 'athletes' ].length + + onRefresh: { + updateData({}, false) + } + + delegate: ItemDelegate { + id: partDel + + property var thisData: widgetData[ "athletes" ][index] + + width: parent.width + height: parseInt(thisData.cat) === parseInt(params.cat) ? undefined:0 + + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + onClicked: { + app.openAthlete(thisData["PerId"]) + } + + 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: parent.width * 0.05 + + verticalAlignment: Text.AlignVCenter + + fontSizeMode: Text.Fit + font.bold: true + font.pixelSize: Math.abs( height * 0.4 ) + minimumPixelSize: height * 0.3 + + elide: "ElideRight" + + text: control.getText(index) + } + } + + function getText(index){ + + // ---------------------------- + // if we have registration data + + var fedName // federation name + + if(widgetData["federations"] !== undefined){ + // not an international competition -> get name of federation + + for(var i = 0; i < widgetData["federations"].length; i ++ ){ + //console.log("checking " + i + ": cat: " + parseInt(widgetData["categorys"][i]["GrpId"]) + " searched cat: " + root.catId) + if(widgetData["federations"][i]["fed_id"] === widgetData[ 'athletes' ][index]["reg_fed_id"]){ + fedName = widgetData["federations"][i]["shortcut"] + } + } + } + else { + // an international competition -> get nation + + fedName = widgetData[ 'athletes' ][index]["nation"] + } + + return widgetData[ "athletes" ][index]["firstname"] + " " + widgetData[ "athletes" ][index]["lastname"] + " (" + fedName + ")" + } +} diff --git a/resources/qml/Widgets/ResultWidget.qml b/resources/qml/Widgets/ResultWidget.qml new file mode 100644 index 0000000..d354ba9 --- /dev/null +++ b/resources/qml/Widgets/ResultWidget.qml @@ -0,0 +1,100 @@ +/* + blueROCK - for digital rock + Copyright (C) 2019 Dorian Zedler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import QtQuick 2.9 +import QtQuick.Controls 2.4 + +import "../Components" + +DataListView { + id: control + + property var widgetData: ({}) + + model: widgetData[ root.listKey ] === undefined ? 0:widgetData[ root.listKey ].length + + delegate: ItemDelegate { + id: partDel + + property var thisData: widgetData[ "athletes" ][index] + + width: parent.width + height: parseInt(thisData.cat) === root.catId ? undefined:0 + + opacity: 0 + scale: 0.9 + + onThisDataChanged: { + fadeInPa.start() + } + + onClicked: { + app.openAthlete(thisData["PerId"]) + } + + 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: parent.width * 0.05 + + verticalAlignment: Text.AlignVCenter + + fontSizeMode: Text.Fit + font.bold: true + font.pixelSize: Math.abs( height * 0.4 ) + minimumPixelSize: height * 0.3 + + elide: "ElideRight" + + text: control.getText(index) + } + } + + function getText(index){ + + // ---------------------------- + // if we have registration data + + var fedName // federation name + + if(widgetData["federations"] !== undefined){ + // not an international competition -> get name of federation + + for(var i = 0; i < widgetData["federations"].length; i ++ ){ + //console.log("checking " + i + ": cat: " + parseInt(widgetData["categorys"][i]["GrpId"]) + " searched cat: " + root.catId) + if(widgetData["federations"][i]["fed_id"] === widgetData[ root.listKey ][index]["reg_fed_id"]){ + fedName = widgetData["federations"][i]["shortcut"] + } + } + } + else { + // an international competition -> get nation + + fedName = widgetData[ root.listKey ][index]["nation"] + } + + return widgetData[ "athletes" ][index]["firstname"] + " " + widgetData[ "athletes" ][index]["lastname"] + " (" + fedName + ")" + } +} diff --git a/resources/qml/main.qml b/resources/qml/main.qml index e19609d..1cb0a31 100644 --- a/resources/qml/main.qml +++ b/resources/qml/main.qml @@ -36,15 +36,10 @@ Window { property int errorCode: -1 - property var competitionCategoryColors: { - "61": "lightgrey", - "58": "lightgreen", - - "69": "#B8C8FF", - "70": "#F0F0F0", - "71": "#D8E8FF", - "256": "#D8E8FF" - } + // comp cats source: + // - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/icc_calendar.php + // - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/dav_calendar.php + // - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/sac_calendar.php property var compCats: { 'int' : { @@ -165,8 +160,10 @@ Window { Component.onCompleted: { //app.openAthlete(53139) // dorian: 53139 , rustam: 6933 , helen: 53300 + openWidget({nation:'GER'}) } + Shortcut { sequences: ["Esc", "Back"] enabled: mainStack.depth > 1 @@ -179,10 +176,6 @@ Window { ServerConn { id: serverConn - - Component.onCompleted: { - //serverConn.refreshFoodplan() - } } StackView { @@ -524,6 +517,21 @@ Window { return app.height < app.width } + function openWidget(params){ + loadingDl.open() + + var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params}) + app.errorCode = calComp.status + if(calComp.ready){ + mainStack.push(calComp) + } + else { + delete(calComp) + } + + loadingDl.close() + } + function openCalendar(nation){ loadingDl.open() @@ -542,7 +550,7 @@ Window { function openResults(comp, cat, status){ // comp: (int) competiotion ID // cat: (int) category ID - // reg: (int) 0: result imported into ranking; 1: result-service result available; 2: result-service startlist available; 3: old non result-server startlist (no longer used); 4: starters / registration data + // status: (int) 0: result imported into ranking; 1: result-service result available; 2: result-service startlist available; 3: old non result-server startlist (no longer used); 4: starters / registration data loadingDl.open() @@ -560,6 +568,11 @@ Window { loadingDl.close() } + function hasParam(url, param, value){ + if (typeof value == 'undefined') value = ''; + return url.indexOf(param+'='+value) !== -1; + } + function openAthlete(perId) { loadingDl.open() @@ -605,7 +618,7 @@ Window { case 500: infoLevel = 2 errorString = "Internal server error" - errorDescription = "The server was unable to process this request, this is probaply the servers vault. Please try again later." + errorDescription = "The server was unable to process this request, this is probaply the servers fault. Please try again later." break case 900: infoLevel = 2 diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc index a2c5496..eb0a96d 100644 --- a/resources/qml/qml.qrc +++ b/resources/qml/qml.qrc @@ -16,5 +16,10 @@ Components/PullRefresher.qml Pages/AthleteProfilePage.qml Components/FadeAnimation.qml + Pages/WidgetPage.qml + Widgets/CalendarWidget.qml + Widgets/ProfileWidget.qml + Widgets/ResultWidget.qml + Widgets/RegistrationWidget.qml diff --git a/resources/shared/icons/favicon.xcf b/resources/shared/favicon.xcf similarity index 100% rename from resources/shared/icons/favicon.xcf rename to resources/shared/favicon.xcf diff --git a/resources/shared/faviconNoBackground.png b/resources/shared/faviconNoBackground.png new file mode 100644 index 0000000..80cc102 Binary files /dev/null and b/resources/shared/faviconNoBackground.png differ diff --git a/resources/shared/icons/favicon.old.png b/resources/shared/icons/favicon.old.png deleted file mode 100644 index 15f210f..0000000 Binary files a/resources/shared/icons/favicon.old.png and /dev/null differ diff --git a/resources/shared/icons/more_black.png b/resources/shared/icons/more_black.png new file mode 100644 index 0000000..fcd8543 Binary files /dev/null and b/resources/shared/icons/more_black.png differ diff --git a/resources/shared/icons/sac.png b/resources/shared/icons/sac.png index c16a111..d2ec1dd 100644 Binary files a/resources/shared/icons/sac.png and b/resources/shared/icons/sac.png differ diff --git a/resources/shared/shared.qrc b/resources/shared/shared.qrc index 59effc4..7d1082e 100644 --- a/resources/shared/shared.qrc +++ b/resources/shared/shared.qrc @@ -7,5 +7,7 @@ icons/backDark.png icons/calendar.png icons/cup.png + Banner.png + icons/more_black.png diff --git a/sources/serverconn.cpp b/sources/serverconn.cpp index fdf3e62..26595de 100644 --- a/sources/serverconn.cpp +++ b/sources/serverconn.cpp @@ -23,7 +23,10 @@ ServerConn::ServerConn(QObject *parent) : QObject(parent) } 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)); + QString requestUrl = "http://egw.ifsc-climbing.org/egw/ranking/json.php?year=" + (year == 0 ? "":QString::number(year)) + "&nation=" + nation; + qDebug() << requestUrl; + + QVariantMap ret = this->senddata(QUrl(requestUrl)); if(ret["status"] != 200){ // request was a failure @@ -76,6 +79,29 @@ QVariant ServerConn::getAthlete(int perId){ return data; } +QVariant ServerConn::getWidgetData(QVariantMap params){ + QString requestUrl = "http://egw.ifsc-climbing.org/egw/ranking/json.php?"; + + for(QVariantMap::const_iterator iter = params.begin(); iter != params.end(); ++iter){ + requestUrl += iter.key() + "=" + iter.value().toString() + "&"; + } + + 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 --- // ------------------------