/* Speed climbing reaction timer Copyright (C) 2019 Itsblue Development | Dorian Zedler This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.0 import QtMultimedia 5.15 import QtGraphicalEffects 1.0 import QtQuick.Controls.Material 2.15 import com.itsblue.SpeedClimbingStartTrainer 2.0 import "./Components" Window { visible: true width: 640 height: 480 title: qsTr("Speed climbing reaction trainer") Page { id: app state: "IDLE" anchors.fill: parent property double startTime: -1 property double stopTime: -1 property double reactionTime: 0 property int highscore: settings.read("highscore") AppSettings { id: settings } AppTheme { id: theme } Item { id: soundsItm SoundEffect { id: readySe source: "qrc:/sounds/ready_1.wav" onPlayingChanged: { if(playing) return if(app.state === "RUNNING"){ startSe.play() } } } SoundEffect { id: startSe source: "qrc:/sounds/IFSC_STARTSIGNAL_SINE.wav" onPlayingChanged: { if(!playing) return app.startTime = new Date().getTime() } } SoundEffect { id: failedSe source: "qrc:/sounds/IFSC frequenzy conform false start sound.wav" } } Rectangle { anchors.fill: parent color: theme.style.backgroundColor Behavior on color { ColorAnimation { duration: 200 } } } Item { id: topContainerItm anchors { top: parent.top left: parent.left right: app.landscape() ? startBt.left:parent.right bottom: app.landscape() ? parent.bottom:startBt.top bottomMargin: app.landscape() ? undefined:parent.height * 0.1 rightMargin: app.landscape() ? parent.width * 0.05:0 } Text { id: topLa anchors.centerIn: parent width: parent.width * 0.8 text: "" color: theme.style.textColor fontSizeMode: Text.Fit verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: app.landscape() ? parent.width * 0.1 : parent.height * 0.4 minimumPixelSize: 0 Behavior on text { FadeAnimation{ target: topLa } } } Text { id: highscoreLa anchors { top: topLa.bottom horizontalCenter: parent.horizontalCenter } width: topLa.width * 0.5 height: topLa.height visible: text !== "highscore: -1" text: "" color: theme.style.textColor fontSizeMode: Text.Fit verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: app.landscape() ? parent.width * 0.1 : parent.height * 0.4 minimumPixelSize: 0 Behavior on text { FadeAnimation{ target: highscoreLa } } } Row { anchors { top: topLa.bottom horizontalCenter: parent.horizontalCenter horizontalCenterOffset: width * 0.1 } opacity: app.state === "IDLE" ? 1:0 Switch { anchors.verticalCenter: parent.verticalCenter checked: settings.read("theme") === "1" scale: 0.7 onCheckedChanged: { if(checked){ settings.write("theme", 1) } else { settings.write("theme", 0) } theme.refreshTheme() } indicator: Rectangle { property bool checked: parent.checked property bool down: parent.down height: parent.height width: height * 2 radius: height * 0.5 color: parent.checked ? "#17a81a" : "transparent" border.color: parent.checked ? "#17a81a" : "#cccccc" Behavior on color{ ColorAnimation{ duration: 200 } } Rectangle { x: parent.checked ? parent.width - width : 0 width: parent.height height: parent.height radius: height * 0.5 color: parent.down ? theme.style.buttonPressedColor:theme.style.backgroundColor //"#cccccc" : "#ffffff" border.color: parent.checked ? (parent.down ? "#17a81a" : "#21be2b") : "#999999" Behavior on x{ NumberAnimation { property: "x" duration: 200 easing.type: Easing.InOutQuad } } } } } ToolButton { anchors.verticalCenter: parent.verticalCenter bottomPadding: 0 topPadding: 0 text: "\uf05a" scale: 1.2 font.family: fontAwesome.name font.pixelSize: height * 0.4 Material.theme: theme.style.name === "dark" ? Material.Dark:Material.Light onClicked: infoDisclaimerDialog.open() } Behavior on opacity { NumberAnimation { duration: 200 } } } } FancyButton { id: startBt text: "start" property int size: app.landscape() ? parent.width * 0.5:parent.height * 0.5 backgroundColor: theme.style.buttonColor textColor: theme.style.textColor anchors { bottom: parent.bottom right: parent.right bottomMargin: app.landscape() ? parent.height * 0.5 - startBt.height * 0.5:parent.height * 0.1 rightMargin: app.landscape() ? parent.width * 0.05:parent.width * 0.5 - startBt.width * 0.5 } font.pixelSize: height * 0.15 height: app.landscape() ? (size > parent.height * 0.9 ? parent.height * 0.9:size) : (size > parent.width * 0.9 ? parent.width * 0.9:size) width: height onPressed: { app.start() } onReleased: { app.stop() } } function landscape() { return app.height < app.width } function start() { if(app.state !== "IDLE"){ app.reset() return } app.state = "RUNNING" readySe.play() } function stop() { app.stopTime = new Date().getTime() if(app.state !== "RUNNING"){ return } if(readySe.playing){ app.state = "IDLE" return } app.state = "STOPPED" startSe.stop() app.reactionTime = app.stopTime - app.startTime - 3000 if(app.reactionTime < 100) { failedSe.play() } app.refreshHighscore() } function refreshHighscore(){ const currentHighscore = parseInt(settings.read("highscore")) // migrate old Highscores if(currentHighscore < 100) { settings.write("highscore", currentHighscore+100) app.highscore = currentHighscore + 100 } if(app.reactionTime > 100 && ( app.reactionTime < currentHighscore || settings.read("highscore") === "-1" )){ settings.write("highscore", app.reactionTime) app.highscore = app.reactionTime } } function reset() { app.state = "IDLE" app.startTime = 0 } states: [ State { name: "IDLE" PropertyChanges { target: topLa text: "Hold down start to start" } }, State { name: "RUNNING" PropertyChanges { target: topLa text: "release to stop" } }, State { name: "STOPPED" PropertyChanges { target: topLa text: "Your reaction time: " + app.reactionTime } PropertyChanges { target: startBt text: "reset" } PropertyChanges { target: highscoreLa text: "highscore: " + app.highscore } } ] FontLoader { id: fontAwesome source: "qrc:/fonts/fa5solid.woff" Component.onCompleted: { console.log("Font name: " + fontAwesome.name) } } DisclaimerDialog { id: infoDisclaimerDialog Material.theme: theme.style.name === "dark" ? Material.Dark:Material.Light title: "Speedclimbing reaction trainer v" + APP_VERSION + "
By Itsblue Development, " + qsTr("privacy policy") + "" content: qsTr("This app was built using the Qt Framework licensed under the GNU lgplV3 license.

This app is open source and licensed under the GNU agplV3 license, the source code can be found here.") } } }