diff --git a/android-sources/AndroidManifest.xml b/android-sources/AndroidManifest.xml index bec38e0..b7ae0fc 100644 --- a/android-sources/AndroidManifest.xml +++ b/android-sources/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/resources/qml/Components/DataListView.qml b/resources/qml/Components/DataListView.qml index 6f5c02b..07d80a4 100644 --- a/resources/qml/Components/DataListView.qml +++ b/resources/qml/Components/DataListView.qml @@ -24,6 +24,8 @@ ListView { property int status: -1 property bool loading: false + property var listData + property var emptyProperties: [] signal refresh() diff --git a/resources/qml/Components/SpeedFlowChart.qml b/resources/qml/Components/SpeedFlowChart.qml new file mode 100644 index 0000000..0e2b6a4 --- /dev/null +++ b/resources/qml/Components/SpeedFlowChart.qml @@ -0,0 +1,314 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.1 + +ListView { + id: control + + property var flowchartData + property var allFlowchartData + property int rounds + property int tileSize: app.height / 8 * 0.8 + + anchors.fill: parent + anchors.margins: 10 + model: rounds + 2 + + spacing: app.width * 0.1 + + orientation: ListView.LeftToRight + boundsBehavior: ListView.StopAtBounds + + onFlowchartDataChanged: { + prepareData() + } + + function prepareData() { + + // array to store the restructured data + var allData = [] + + for(var round in flowchartData['route_names']){ + if(flowchartData['route_names'].hasOwnProperty(round) && parseInt(round) >= 0){ + console.log(round) + + if(allData.length > 0){ + allData[allData.length-1].push(round) + allData[allData.length-1].push(flowchartData['route_names'][round]) + } + + if(parseInt(round) === 0){ + // this is the first round + + // find pairs (always wors vs. best (1-16; 1-15; ...)) (they are sorted by the rank of the better athlete (1-2-3-4-5-6-7-8) + + var qualificationResults = [] + + for(var x = 0; x < flowchartData['participants'].length; x++){ + qualificationResults.push(flowchartData['participants'][x]) + } + + qualificationResults.sort(function(a, b) { + return parseInt(a["result_rank0"]) - parseInt(b["result_rank0"]); + }); + + var nextRoundPairs = [] + var nextRoundMatches = Math.pow(2, control.model-1) + + for(var i = 0; i < nextRoundMatches; i++){ + nextRoundPairs.push([qualificationResults[i], qualificationResults[nextRoundMatches*2-i-1]]) + } + + // build second round pairs (sorted by the rank of the better athlete and worst vs. best (1-8; 2-7; ... )) + + var sortedFirstRoundPairs = [] + + for(i = 0; i < nextRoundMatches; i += 1){ + sortedFirstRoundPairs.push([nextRoundPairs.shift(), nextRoundPairs.pop()]) + } + + // sort these pairs (containing two pairs of athletes) by the rank of the better athlete (1-4;2-3) + + var finalSortedFirstRoundPairs = [] + + for(i=0; i < nextRoundMatches/4; i++){ + finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[i]) + finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[nextRoundMatches/2-i-1]) + } + + // convert the list of pairs of pairs of athletes back to a single list of pairs of athletes + + var finalFirstRoundPairs = [] + + for(i=0; i < finalSortedFirstRoundPairs.length; i++){ + finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][0]) + finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][1]) + } + + // push the first round to all data + + allData.push(finalFirstRoundPairs) + + } + else if(parseInt(round) > 0 ){ + // this is not the first round + + var nextRound = [] + + // only used when the current round is the 1/2 final + var smallFinal = [] + var Final = [] + + for(var i = 0; i < allData[allData.length-1].length-2; i+=1){ + + var thisPair = allData[allData.length-1][i] + var thisWinner + var thisLooser + var thisWinnerIsFirstOfNewPair = i%2 === 0 + + if(thisWinnerIsFirstOfNewPair){ + nextRound.push([]) + } + + //thisPair[0]["result_rank"] = thisPair[0]["result_rank"+round] + //thisPair[1]["result_rank"] = thisPair[1]["result_rank"+round] + + if(parseInt(thisPair[0]["result_rank"+round]) < parseInt(thisPair[1]["result_rank"+round])){ + thisWinner = thisPair[0] + thisLooser = thisPair[1] + } + else { + thisWinner = thisPair[1] + thisLooser = thisPair[0] + } + + console.log(thisWinner['firstname']+" has won in round " + round) + //thisWinner.win = false + //thisLooser.win = true + + if(round - control.rounds === 2){ + // if we are in the 1/2 final + + Final.push(thisWinner) + smallFinal.push(thisLooser) + } + else { + nextRound[nextRound.length-1].push(thisWinner) + } + } + + if(smallFinal.length > 0 && Final.length > 0){ + + // Final + allData.push([Final, parseInt(round)+2, flowchartData['route_names'][String(parseInt(round)+2)] + " / " + flowchartData['route_names'][String(parseInt(round)+1)] ]) + // small Final + allData.push([smallFinal, parseInt(round)+1]) + + break + } + else { + //nextRound.push(round) + //nextRound.push(flowchartData['route_names'][round]) + allData.push(nextRound) + } + } + } + } + control.allFlowchartData = allData + } + + delegate: Column { + id: roundCol + + property int thisIndex: index + property int thisRound: control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-2] + property bool thisIsLastRound: thisIndex === control.model - 1 + + width: app.width * 0.5 + height: control.height + + //spacing: app.width * 0.1 + + Label { + id: roundNameLa + width: parent.width + horizontalAlignment: Text.AlignHCenter + font.bold: true + text: control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-1] + } + + Repeater { + id: rectRep + model: Math.max( Math.pow(2, control.model-1) * Math.pow(0.5, (index)), 2) + delegate: Item { + id: matchItm + + property bool lowerPart: (index%2 > 0) + + property int thisIndex: index + property var thisMatchData: control.allFlowchartData[ thisIsSemifinal ? roundCol.thisIndex+1 : roundCol.thisIndex][matchItm.thisIndex] + property bool thisIsFinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 2 + property bool thisIsSemifinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 1 + + Component.onCompleted: { + if(thisIsSemifinal){ + console.log("this is semi final") + console.log(thisMatchData) + } + console.log(thisMatchData) + } + + height: !roundCol.thisIsLastRound ? (roundCol.height - roundNameLa.height) / rectRep.model - roundCol.spacing : (thisIsFinal ? (roundCol.height - roundNameLa.height) * 0.5 + control.tileSize * 0.5 : (roundCol.height - roundNameLa.height) - (roundCol.height - roundNameLa.height) * 0.5 + control.tileSize * 0.5 ) + width: parent.width + + Canvas { + id: lineCanvas + width: app.width; height: app.height + contextType: "2d" + + visible: !roundCol.thisIsLastRound + + property int targetX: matchItm.width + control.spacing + property int targetY: matchItm.lowerPart ? 0:matchItm.height + + Path { + id: myPath + startX: matchRect.x + matchRect.width * matchRect.scale ; startY: matchRect.y + matchRect.height * 0.5 + + PathCurve { x: lineCanvas.targetX ; y: lineCanvas.targetY } + } + + onPaint: { + context.strokeStyle = Qt.rgba(.4,.6,.8); + context.path = myPath; + context.stroke(); + } + } + + Rectangle { + id: matchRect + + anchors { + centerIn: !matchItm.thisIsFinal ? parent:undefined + bottom: matchItm.thisIsFinal ? parent.bottom:undefined + } + + //anchors.verticalCenterOffset: matchItm.lowerPart ? 10:10 + width: parent.width + height: control.tileSize + //scale: 0.9 + color: "white" + border.color: "lightgrey" + border.width: 1 + radius: height * 0.2 + Material.elevation: 10 + + Grid { + columns: app.landscape() ? 2:0 + rows: app.landscape() ? 0:2 + + anchors.fill: parent + anchors.margins: matchRect.radius * 0.5 + + Repeater { + // for the two athletes + model: 2 + + delegate: Row { + height: app.landscape() ? parent.height:parent.height / 2 + width: app.landscape() ? parent.width / 2 : parent.width + + spacing: app.landscape() ? width * 0.1:height * 0.05 + + Label { + height: parent.height + width: parent.width * 0.2 + + verticalAlignment: Text.AlignVCenter + font.pixelSize: height * 0.6 + fontSizeMode: Text.Fit + minimumPixelSize: 1 + + text: matchItm.thisMatchData[index]['result_rank0'] + } + + Label { + height: parent.height + width: parent.width * 0.5 + + verticalAlignment: Text.AlignVCenter + font.pixelSize: height * 0.6 + fontSizeMode: Text.Fit + minimumPixelSize: 1 + + color: (parseInt(matchItm.thisMatchData[0]["result_rank" + roundCol.thisRound ]) < parseInt(matchItm.thisMatchData[1]["result_rank" + roundCol.thisRound])) === (index === 0 ? true:false) ? "green":"black" + + text: matchItm.thisMatchData[index]['firstname'] + " " + matchItm.thisMatchData[index]['lastname'] + } + + /*Rectangle { + id: hasWonRect + + anchors.verticalCenter: parent.verticalCenter + + height: width + width: parent.width * 0.1 + + scale: 0.7 + + radius: width * 0.5 + + color: "green" + + visible: (parseInt(matchItm.thisMatchData[0]["result_rank" + roundCol.thisRound ]) < parseInt(matchItm.thisMatchData[1]["result_rank" + roundCol.thisRound])) === (index === 0 ? true:false) + }*/ + + } + } + } + } + } + } + } +} + diff --git a/resources/qml/Widgets/CalendarWidget.qml b/resources/qml/Widgets/CalendarWidget.qml index 3fa274e..c82d4c8 100644 --- a/resources/qml/Widgets/CalendarWidget.qml +++ b/resources/qml/Widgets/CalendarWidget.qml @@ -93,6 +93,7 @@ DataListView { //boundsBehavior: Flickable.StopAtBounds model: widgetData["competitions"].length + //listData: widgetData['competitions'] onRefresh: { updateData({}, false) @@ -101,13 +102,14 @@ DataListView { Component.onCompleted: { initFilters() - if(model > 0){ - control.ready = true + if(model){ control.status = 200 + control.ready = true } else { control.ready = false control.status = 901 + return } autoScroll() @@ -128,8 +130,11 @@ DataListView { if(parseInt(control.year) === new Date().getFullYear()){ for(var i = 0; i < compList.length; i ++){ - // get the start date pf the competition + // get the start date of the competition var startDate = Date.fromLocaleString(Qt.locale(), compList[i]["date"], "yyyy-MM-dd") + //control.widgetData["competitions"][i]["month"] = startDate.getMonth() + + //console.log("got date: " + startDate + " from string: " + compList[i]["date"] + " -> month is: " + compList[i]["month"]) // get the duration of the competition var durationString = compList[i]["duration"] === undefined ? "1":compList[i]["duration"] @@ -146,6 +151,8 @@ DataListView { //console.log("moving down!") } } + + //control.widgetData = control.widgetData } else { //console.log("not current year") @@ -367,7 +374,7 @@ DataListView { width: parent.width height: includedByFilter ? compDelCol.height + 10 : 0 - enabled: (thisData["cats"] !== undefined && thisData["cats"].length > 0) || control.widgetData["competitions"][index]["homepage"] !== undefined || getCompInfoUrl(index) !== undefined + enabled: (thisData["cats"] !== undefined && thisData["cats"].length > 0) || competitionDel.thisData["homepage"] !== undefined || getCompInfoUrl(index) !== undefined //visible: includedByFilter opacity: 0 @@ -448,6 +455,10 @@ DataListView { text: name } + Label { + //text: model.month + } + Label { id: dateLa @@ -473,6 +484,17 @@ DataListView { } } + section.property: "month" + section.delegate: ItemDelegate { + id: name + background: Rectangle { + color: "red" + } + + width: parent.width + text: section + } + Dialog { id: filterSelectPu diff --git a/resources/qml/Widgets/ResultWidget.qml b/resources/qml/Widgets/ResultWidget.qml index 6fc1a59..e6caba5 100644 --- a/resources/qml/Widgets/ResultWidget.qml +++ b/resources/qml/Widgets/ResultWidget.qml @@ -18,7 +18,9 @@ import QtQuick 2.9 import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.3 import "../Components" @@ -31,7 +33,31 @@ DataListView { property string subTitle: getSubtitle() property bool titleIsPageTitle: true - property Component headerComponent: ToolButton { + property Component headerComponent: RowLayout { + + height: parent.height + width: 100//childrenRect.width + + spacing: 0 + + ToolButton { + id: flowToolBt + + visible: control.widgetData['discipline'] === 'speed' + + onClicked: { + if(speedFlowChartBackgroundRect.state === "hidden"){ + speedFlowChartBackgroundRect.state ="visible" + } + else { + speedFlowChartBackgroundRect.state = "hidden" + } + } + + icon.name: "flowchart" + } + + ToolButton { id: moreToolBt onClicked: { @@ -39,6 +65,7 @@ DataListView { } icon.name: "menu" + } } property var widgetData: currentWidgetData @@ -642,4 +669,54 @@ DataListView { } + Rectangle { + id: speedFlowChartBackgroundRect + + state: "hidden" + + anchors.fill: parent + color: Material.background + + SpeedFlowChart { + anchors.fill: parent + + flowchartData: control.widgetData; + + //participants: control.widgetData['participants'].slice() + //route_names: control.widgetData['route_names'] + + rounds: control.widgetData['route_names'][2].includes("8") ? 2:1 + } + + states: [ + State { + name: "hidden" + PropertyChanges { + target: speedFlowChartBackgroundRect + opacity: 0 + scale: 0.9 + height: 0 + } + }, + + State { + name: "visible" + PropertyChanges { + target: speedFlowChartBackgroundRect + opacity: 1 + scale: 1 + height: parent.height + } + } + ] + + transitions: [ + Transition { + NumberAnimation { + properties: "opacity,scale" + duration: 200 + } + } + ] + } } diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc index fe58ac3..9a4cbe3 100644 --- a/resources/qml/qml.qrc +++ b/resources/qml/qml.qrc @@ -17,5 +17,6 @@ Widgets/StartlistWidget.qml Widgets/RankingWidget.qml Pages/AthleteSearchPage.qml + Components/SpeedFlowChart.qml diff --git a/resources/shared/icons/bluerock/20x20/flowchart.png b/resources/shared/icons/bluerock/20x20/flowchart.png new file mode 100644 index 0000000..6469b0a Binary files /dev/null and b/resources/shared/icons/bluerock/20x20/flowchart.png differ diff --git a/resources/shared/icons/bluerock/20x20@2/flowchart.png b/resources/shared/icons/bluerock/20x20@2/flowchart.png new file mode 100644 index 0000000..c87cc2e Binary files /dev/null and b/resources/shared/icons/bluerock/20x20@2/flowchart.png differ diff --git a/resources/shared/icons/bluerock/20x20@3/flowchart.png b/resources/shared/icons/bluerock/20x20@3/flowchart.png new file mode 100644 index 0000000..98bd45f Binary files /dev/null and b/resources/shared/icons/bluerock/20x20@3/flowchart.png differ diff --git a/resources/shared/icons/bluerock/20x20@4/flowchart.png b/resources/shared/icons/bluerock/20x20@4/flowchart.png new file mode 100644 index 0000000..dc859de Binary files /dev/null and b/resources/shared/icons/bluerock/20x20@4/flowchart.png differ diff --git a/resources/shared/shared.qrc b/resources/shared/shared.qrc index fc0916f..babeb75 100644 --- a/resources/shared/shared.qrc +++ b/resources/shared/shared.qrc @@ -42,5 +42,9 @@ icons/bluerock/20x20@3/year.png icons/bluerock/20x20@4/filter.png icons/bluerock/20x20@4/year.png + icons/bluerock/20x20/flowchart.png + icons/bluerock/20x20@2/flowchart.png + icons/bluerock/20x20@3/flowchart.png + icons/bluerock/20x20@4/flowchart.png