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

Item {
    id: root
    height: Theme.appHeight
    width: Theme.appWidth

    property bool onScreen: false
    property bool offScreen: false

    property real baseOp: 0.0
    property real stripesSideMargin: 4
    property real stripesBottomMargin: 40
    property real stripesIconsMargin: 30
    property real needleOffset: 18
    property real secondNeedleOffset: 20
    property real arrowAnimationMs: 30
    property real updateInterval: 1000 // One second
    property int introDuration: 100

    SequentialAnimation {
        id: intro

        NumberAnimation { target: watchIndicators; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
        NumberAnimation { target: stripesBackground; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
        ParallelAnimation {
            NumberAnimation { target: batteryIcon; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
            NumberAnimation { target: stepsIcon; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
            NumberAnimation { target: heartBeat; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
            NumberAnimation { target: calendar; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
        }
        ParallelAnimation {
            NumberAnimation { target: secondArrow; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
            NumberAnimation { target: centerDot; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: introDuration }
        }
    }

    Timer {
        running: true
        repeat: true
        interval: updateInterval
        onTriggered: WatchModel.update()
    }

    Image {
        id: watchIndicators
        anchors.centerIn: parent
        source: "images/shield/dots-all.png"
        opacity: baseOp
    }

    /*
    * Health content
    */
    Image {
        id: stripesBackground
        source: "images/shield/small-oval-alt.png"
        anchors.centerIn: parent
        opacity: baseOp
    }

    Image {
        id: batteryIcon
        source: "images/shield/battery-icon.png"
        anchors.verticalCenter: stripesBackground.verticalCenter
        anchors.left: stripesBackground.left
        anchors.leftMargin: root.stripesIconsMargin
        opacity: baseOp
    }

    Image {
        id: stepsIcon
        source: "images/shield/steps-icon.png"
        anchors.verticalCenter: stripesBackground.verticalCenter
        anchors.right: stripesBackground.right
        anchors.rightMargin: root.stripesIconsMargin
        opacity: baseOp
    }

    Row {
        id: heartBeat
        opacity: baseOp
        spacing: 10
        anchors.top: parent.top
        anchors.topMargin: parent.height / 4
        anchors.horizontalCenter: parent.horizontalCenter

        Image {
            anchors.verticalCenter: parent.verticalCenter
            source: "images/shield/heart.png"
        }

        Text {
            anchors.verticalCenter: parent.verticalCenter

            text: HealthModel.HeartRate
            font.pixelSize: 34
            font.family: Theme.fontFamily
            font.weight: Font.Light
            color: Theme.whiteColor
        }
    }

    GradientShape {
        id: batteryBar
        anchors.fill: parent

        SequentialAnimation {
            running: true
            loops: Animation.Infinite
            NumberAnimation {
                target: batteryBar
                property: "extent"
                from: 1.0
                to: 0.0
                duration: 30000
            }
            NumberAnimation {
                target: batteryBar
                property: "extent"
                from: 0.0
                to: 1.0
                duration: 30000
            }
        }
    }

    GradientShape {
        id: healthBar
        anchors.fill: parent
        flip: true

        SequentialAnimation {
            running: true
            loops: Animation.Infinite
            NumberAnimation {
                target: healthBar
                property: "extent"
                from: 0.0
                to: 1.0
                duration: 45000
            }
        }
    }

    /*
    * Watch arrows
    */
    Image {
        id: minuteArrow
        source: "images/shield/hand-minute.png"

        anchors.top: parent.top
        anchors.topMargin: parent.height / 2 - height + needleOffset
        anchors.horizontalCenter: parent.horizontalCenter

        transform: Rotation {
            angle: WatchModel.MinuteAngle
            origin.x: minuteArrow.width / 2
            origin.y: minuteArrow.height - needleOffset
        }
    }

    Image {
        id: hourArrow
        source: "images/shield/hand-hour.png"

        anchors.top: parent.top
        anchors.topMargin: parent.height / 2 - height + needleOffset
        anchors.horizontalCenter: parent.horizontalCenter

        transform: Rotation {
            angle: WatchModel.HourAngle
            origin.x: hourArrow.width / 2
            origin.y: hourArrow.height - needleOffset
        }
    }

    Image {
        id: secondArrow
        source: "images/shield/hand-second.png"

        anchors.top: parent.top
        anchors.topMargin: parent.height / 2 - height + secondNeedleOffset
        anchors.horizontalCenter: parent.horizontalCenter
        opacity: baseOp

        transform: Rotation {
            angle: WatchModel.SecondAngle
            origin.x: secondArrow.width / 2
            origin.y: secondArrow.height - secondNeedleOffset
        }
    }

    Image {
        id: centerDot
        source: "images/shield/hands-middle-dot.png"
        anchors.centerIn: parent
        opacity: baseOp
    }

    /*
    * Calendar text
    */
    Column {
        id: calendar
        opacity: baseOp
        anchors.bottom: parent.bottom
        anchors.bottomMargin: parent.height / 4
        anchors.horizontalCenter: parent.horizontalCenter

        StaticText {
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Friday"
            font.pixelSize: 26
            font.family: Theme.fontFamily
            font.weight: Font.Light
            color: Theme.whiteColor
        }

        StaticText {
            anchors.horizontalCenter: parent.horizontalCenter
            text: "16.08"
            font.pixelSize: 26
            font.family: Theme.fontFamily
            font.weight: Font.Light
            color: Theme.whiteColor
        }
    }

    onOnScreenChanged: {
        console.log("Screen changed")
        if (onScreen) {
            intro.start()
        }
    }

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

    function resetIntro() {
        intro.stop()
        watchIndicators.opacity = baseOp
        secondArrow.opacity = baseOp
        stripesBackground.opacity = baseOp
        batteryIcon.opacity = baseOp
        stepsIcon.opacity = baseOp
        heartBeat.opacity = baseOp
        calendar.opacity = baseOp
    }
}