C

Qt Quick Ultralite Watch Demo

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

import QtQuick 2.15
import QtQuickUltralite.Extras 2.0
import Watch 1.0

Rectangle {
    id: root
    height: Theme.appHeight
    width: Theme.appWidth
    color: Theme.backgroundColor

    property bool onScreen: false
    property bool offScreen: false

    readonly property int heartbeatDuration: 130
    readonly property int heartImgWidth: 39
    readonly property int heartImgHeight: 34
    readonly property int introDuration: 200
    readonly property int linesIntroDuration: 50
    readonly property int introDurationLong: 250
    readonly property real startOp: 0.0
    readonly property int startWidth: 0
    readonly property real startTopMargin: height / 2 - heartRate.height / 2

    SequentialAnimation {
        id: intro

        NumberAnimation { target: heartRate; property: "margin"; to: 25; easing.type: Easing.InOutCubic; duration: introDurationLong }
        NumberAnimation { target: horizontalLines; property: "stOpacity"; to: 0.3; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: horizontalLines; property: "ndOpacity"; to: 0.5; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: horizontalLines; property: "rdOpacity"; to: 0.6; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: horizontalLines; property: "foOpacity"; to: 0.6; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: horizontalLines; property: "fvOpacity"; to: 0.6; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: verticalLines; property: "stOpacity"; to: 1.0; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: verticalLines; property: "ndOpacity"; to: 1.0; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: verticalLines; property: "rdOpacity"; to: 1.0; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: verticalLines; property: "foOpacity"; to: 1.0; easing.type: Easing.OutCubic; duration: linesIntroDuration }
        NumberAnimation { target: graphMask; property: "width"; to: graph.width; duration: 2*introDuration }
        ParallelAnimation {
            NumberAnimation { target: graphPoint; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
            NumberAnimation { target: minHeader; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
        }
        NumberAnimation { target: maxHeader; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
    }

    SequentialAnimation {
        id: heartbeat
        loops: 2

        ParallelAnimation {
            NumberAnimation { target: heartImg; property: "height"; to: heartImgHeight * 1.2; easing.type: Easing.InCubic; duration: heartbeatDuration }
            NumberAnimation { target: heartImg; property: "width"; to: heartImgWidth * 1.2; easing.type: Easing.InCubic; duration: heartbeatDuration }
        }
        ParallelAnimation {
            NumberAnimation { target: heartImg; property: "height"; to: heartImgHeight; easing.type: Easing.InCubic; duration: heartbeatDuration }
            NumberAnimation { target: heartImg; property: "width"; to: heartImgWidth; easing.type: Easing.InCubic; duration: heartbeatDuration }
        }
    }

    Image {
        id: heartImg
        anchors.right: heartRate.left
        anchors.rightMargin: 15
        anchors.verticalCenter: heartRate.verticalCenter
        source: "images/health/heart.png"

        Behavior on width {
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutCubic;
            }
        }

        Behavior on height {
            NumberAnimation {
                duration: 300
                easing.type: Easing.OutCubic;
            }
        }
    }

    Text {
        anchors.top: parent.top
        anchors.topMargin: margin
        anchors.horizontalCenter: parent.horizontalCenter

        property int margin: startTopMargin

        id: heartRate
        text: HealthModel.HeartRate
        font.pixelSize: 61
        font.family: Theme.fontFamily
        font.weight: Font.Medium
        color: Theme.whiteColor
    }

    Column {
        id: horizontalLines
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        spacing: 38

        property real stOpacity: startOp
        property real ndOpacity: startOp
        property real rdOpacity: startOp
        property real foOpacity: startOp
        property real fvOpacity: startOp

        Repeater {
            model: [
                { value: "200" },
                { value: "150" },
                { value: "100" },
                { value: "50" },
                { value: "" }
            ]

            delegate: Item {
                opacity: getHorizontalStripeOp(index)

                Image {
                    id: horizontalImg
                    source: "images/health/horizontal-line.png"
                }

                Text {
                    anchors.left: horizontalImg.right
                    anchors.leftMargin: 10
                    anchors.verticalCenter: horizontalImg.verticalCenter

                    text: modelData.value
                    font.pixelSize: 17
                    font.family: Theme.fontFamily
                    font.weight: Font.Medium
                    color: Theme.grayColor
                }
            }
        }
    }

    Row {
        id: verticalLines
        anchors.left: parent.left
        anchors.leftMargin: 60
        anchors.bottom: horizontalLines.bottom
        spacing: 70

        property real stOpacity: startOp
        property real ndOpacity: startOp
        property real rdOpacity: startOp
        property real foOpacity: startOp

        Repeater {
            model: [
                { hour: "10:00" },
                { hour: "12:00" },
                { hour: "14:00" },
                { hour: "16:00" }
            ]

            delegate: Item {
                width: verticalImg.width
                height: verticalImg.height
                opacity: getVerticalStripeOp(index)

                Image {
                    anchors.centerIn: parent
                    id: verticalImg
                    source: "images/health/vertical-line.png"
                }

                Text {
                    anchors.top: verticalImg.bottom
                    anchors.topMargin: 10
                    anchors.horizontalCenter: verticalImg.horizontalCenter

                    text: modelData.hour
                    font.pixelSize: 14
                    font.family: Theme.fontFamily
                    font.weight: Font.Medium
                    color: Theme.grayColor
                }
            }
        }
    }

    /*
    * Graph images
    */
    Item {
        id: graphMask
        width:startWidth
        height:graph.height
        clip: true
        anchors.bottom: horizontalLines.bottom
        anchors.left: horizontalLines.left
        Image {
            id: graph
            source: "images/health/graph.png"
        }
    }

    Image {
        id: graphPoint
        source: "images/health/graph-point.png"
        anchors.bottom: graphMask.bottom
        anchors.bottomMargin: 50
        anchors.right: graphMask.right
        anchors.rightMargin: 7
        opacity: startOp
    }

    /*
    * Minimal heart rate container
    */
    StaticText {
        anchors.top: horizontalLines.bottom
        anchors.topMargin: 40
        anchors.left: parent.left
        anchors.leftMargin: 120
        opacity: startOp

        id: minHeader
        text: "MIN"
        font.pixelSize: 14
        font.family: Theme.fontFamily
        font.weight: Font.Medium
        color: Theme.grayColor
    }

    Text {
        anchors.top: minHeader.bottom
        anchors.horizontalCenter: minHeader.horizontalCenter
        opacity: minHeader.opacity

        text: HealthModel.MinHeartRate
        font.pixelSize: 30
        font.family: Theme.fontFamily
        font.weight: Font.Medium
        color: Theme.whiteColor
    }

    /*
    * Maximal heart rate container
    */
    StaticText {
        anchors.top: horizontalLines.bottom
        anchors.topMargin: 40
        anchors.right: parent.right
        anchors.rightMargin: 120
        opacity: startOp

        id: maxHeader
        text: "MAX"
        font.pixelSize: 14
        font.family: Theme.fontFamily
        font.weight: Font.Medium
        color: Theme.grayColor
    }

    Text {
        anchors.top: maxHeader.bottom
        anchors.horizontalCenter: maxHeader.horizontalCenter
        opacity: maxHeader.opacity

        text: HealthModel.MaxHeartRate
        font.pixelSize: 30
        font.family: Theme.fontFamily
        font.weight: Font.Medium
        color: Theme.whiteColor
    }

    Timer {
        id: degreeTimer
        interval: 1000
        running: true
        repeat: true
        onTriggered: {
            HealthModel.update()
            heartbeat.start()
        }
    }

    onOnScreenChanged: {
        if (onScreen) {
            intro.start()
        }
    }

    onOffScreenChanged: {
        if (offScreen) {
            resetIntro()
        }
    }

    function resetIntro() {
        intro.stop()
        heartRate.margin = startTopMargin
        horizontalLines.stOpacity = startOp
        horizontalLines.ndOpacity = startOp
        horizontalLines.rdOpacity = startOp
        horizontalLines.foOpacity = startOp
        horizontalLines.fvOpacity = startOp
        verticalLines.stOpacity = startOp
        verticalLines.ndOpacity = startOp
        verticalLines.rdOpacity = startOp
        verticalLines.foOpacity = startOp
        graphMask.width = startWidth
        graphPoint.opacity = startOp
        minHeader.opacity = startOp
        maxHeader.opacity = startOp
    }

    function getHorizontalStripeOp(index : int) : real {
        switch(index) {
            case 0:
                return horizontalLines.stOpacity
            case 1:
                return horizontalLines.ndOpacity
            case 2:
                return horizontalLines.rdOpacity
            case 3:
                return horizontalLines.foOpacity
            case 4:
                return horizontalLines.fvOpacity
        }
    }

    function getVerticalStripeOp(index : int) : real {
        switch(index) {
            case 0:
                return verticalLines.stOpacity
            case 1:
                return verticalLines.ndOpacity
            case 2:
                return verticalLines.rdOpacity
            case 3:
                return verticalLines.foOpacity
        }
    }
}