- finished up some minor details

- fixed bigs
- improved pull to refresh functionality
This commit is contained in:
Dorian Zedler 2019-05-11 15:16:23 +02:00
parent 9af048eaad
commit 0f62b52f6e
15 changed files with 460 additions and 120 deletions

View File

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

View File

@ -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()
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
}
}
}

View File

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

View File

@ -13,5 +13,6 @@
<file>Components/RegistrationView.qml</file>
<file>Components/StartlistView.qml</file>
<file>Components/RankingView.qml</file>
<file>Components/PullRefresher.qml</file>
</qresource>
</RCC>

BIN
resources/shared/Banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
resources/shared/Banner.xcf Normal file

Binary file not shown.

View File

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