/*
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.")
}
}
}