summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2023-11-17 15:49:14 +0100
committerIvan Solovev <ivan.solovev@qt.io>2023-12-21 14:33:03 +0100
commit375e0e2c74adad9b609f5e48dffb88b07158bbfe (patch)
tree41bbe617ff8115203d2add659be7eaec78b1f26b
parent08b54c9b06b5dbf50c80c2d609625c0af94e2ce6 (diff)
Update BTLE Heartrate Game example UI
Improve the example according to the new design: * Apply new colors * Rework layouts to match the updated design * Use icons in the notification/error box * Apply new font sizes * Update screenshots in the docs, convert them to webp so that they do not take too much space. * Disable the Start button on the Measure tab if the device is not connected, or if the proper service is not discovered. * As a drive-by - fix the wording in the docs. Fixes: QTBUG-118905 Pick-to: 6.7 6.6 Change-Id: I90669ea05c5c6b76eb711862c494f1180dbd8dd7 Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
-rw-r--r--examples/bluetooth/heartrate-game/BluetoothAlarmDialog.qml6
-rw-r--r--examples/bluetooth/heartrate-game/BottomLine.qml3
-rw-r--r--examples/bluetooth/heartrate-game/CMakeLists.txt5
-rw-r--r--examples/bluetooth/heartrate-game/Connect.qml66
-rw-r--r--examples/bluetooth/heartrate-game/GamePage.qml55
-rw-r--r--examples/bluetooth/heartrate-game/GameSettings.qml60
-rw-r--r--examples/bluetooth/heartrate-game/Measure.qml231
-rw-r--r--examples/bluetooth/heartrate-game/SplashScreen.qml2
-rw-r--r--examples/bluetooth/heartrate-game/Stats.qml45
-rw-r--r--examples/bluetooth/heartrate-game/TitleBar.qml57
-rw-r--r--examples/bluetooth/heartrate-game/bluetoothbaseclass.cpp14
-rw-r--r--examples/bluetooth/heartrate-game/bluetoothbaseclass.h19
-rw-r--r--examples/bluetooth/heartrate-game/devicefinder.cpp22
-rw-r--r--examples/bluetooth/heartrate-game/devicefinder.h2
-rw-r--r--examples/bluetooth/heartrate-game/devicehandler.cpp10
-rw-r--r--examples/bluetooth/heartrate-game/deviceinfo.cpp2
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-result.pngbin9468 -> 0 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-result.webpbin0 -> 23682 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-running.pngbin10951 -> 0 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-running.webpbin0 -> 24072 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-search.pngbin14591 -> 0 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-search.webpbin0 -> 29732 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-start.pngbin13214 -> 0 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/images/heartgame-start.webpbin0 -> 33590 bytes
-rw-r--r--examples/bluetooth/heartrate-game/doc/src/heartrate-game.qdoc12
-rw-r--r--examples/bluetooth/heartrate-game/heartrate-game.pro7
-rw-r--r--examples/bluetooth/heartrate-game/images/alert.svg4
-rw-r--r--examples/bluetooth/heartrate-game/images/bluetooth.svg3
-rw-r--r--examples/bluetooth/heartrate-game/images/clock.svg4
-rw-r--r--examples/bluetooth/heartrate-game/images/heart.pngbin2664 -> 2318 bytes
-rw-r--r--examples/bluetooth/heartrate-game/images/progress.svg4
-rw-r--r--examples/bluetooth/heartrate-game/images/search.svg4
32 files changed, 460 insertions, 177 deletions
diff --git a/examples/bluetooth/heartrate-game/BluetoothAlarmDialog.qml b/examples/bluetooth/heartrate-game/BluetoothAlarmDialog.qml
index af0b0545..5cc8f393 100644
--- a/examples/bluetooth/heartrate-game/BluetoothAlarmDialog.qml
+++ b/examples/bluetooth/heartrate-game/BluetoothAlarmDialog.qml
@@ -52,7 +52,7 @@ Item {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
- font.pixelSize: GameSettings.mediumFontSize
+ font.pixelSize: GameSettings.smallFontSize
color: GameSettings.textColor
text: root.permissionError
? qsTr("Bluetooth permissions are not granted. Please grant the permissions in the system settings.")
@@ -70,8 +70,8 @@ Item {
Text {
anchors.centerIn: parent
color: GameSettings.textColor
- font.pixelSize: GameSettings.bigFontSize
- text: qsTr("Quit")
+ font.pixelSize: GameSettings.microFontSize
+ text: qsTr("QUIT")
}
}
}
diff --git a/examples/bluetooth/heartrate-game/BottomLine.qml b/examples/bluetooth/heartrate-game/BottomLine.qml
index caebc307..80fdaa8c 100644
--- a/examples/bluetooth/heartrate-game/BottomLine.qml
+++ b/examples/bluetooth/heartrate-game/BottomLine.qml
@@ -6,7 +6,6 @@ import QtQuick
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- width: parent.width * 0.85
+ width: parent.width
height: parent.height * 0.05
- radius: height*0.5
}
diff --git a/examples/bluetooth/heartrate-game/CMakeLists.txt b/examples/bluetooth/heartrate-game/CMakeLists.txt
index e8507858..36a5a582 100644
--- a/examples/bluetooth/heartrate-game/CMakeLists.txt
+++ b/examples/bluetooth/heartrate-game/CMakeLists.txt
@@ -60,9 +60,14 @@ qt_add_qml_module(heartrate-game
TitleBar.qml
Main.qml
RESOURCES
+ images/alert.svg
+ images/bluetooth.svg
images/bt_off_to_on.png
+ images/clock.svg
images/heart.png
images/logo.png
+ images/progress.svg
+ images/search.svg
)
if (APPLE)
diff --git a/examples/bluetooth/heartrate-game/Connect.qml b/examples/bluetooth/heartrate-game/Connect.qml
index 8582411d..ed5fb63d 100644
--- a/examples/bluetooth/heartrate-game/Connect.qml
+++ b/examples/bluetooth/heartrate-game/Connect.qml
@@ -16,43 +16,39 @@ GamePage {
errorMessage: deviceFinder.error
infoMessage: deviceFinder.info
+ iconType: deviceFinder.icon
+
+ Text {
+ id: viewCaption
+ anchors {
+ top: parent.top
+ topMargin: GameSettings.fieldMargin + connectPage.messageHeight
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width - GameSettings.fieldMargin * 2
+ height: GameSettings.fieldHeight
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: GameSettings.textColor
+ font.pixelSize: GameSettings.smallFontSize
+ text: qsTr("Found Devices")
+ }
Rectangle {
id: viewContainer
- anchors.top: parent.top
+ anchors.top: viewCaption.bottom
// only BlueZ platform has address type selection
anchors.bottom: connectPage.connectionHandler.requiresAddressType ? addressTypeButton.top
: searchButton.top
- anchors.topMargin: GameSettings.fieldMargin + connectPage.messageHeight
anchors.bottomMargin: GameSettings.fieldMargin
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - GameSettings.fieldMargin * 2
color: GameSettings.viewColor
radius: GameSettings.buttonRadius
- Text {
- id: title
- width: parent.width
- height: GameSettings.fieldHeight
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- color: GameSettings.textColor
- font.pixelSize: GameSettings.mediumFontSize
- text: qsTr("FOUND DEVICES")
-
- BottomLine {
- height: 1
- width: parent.width
- color: "#898989"
- }
- }
-
ListView {
id: devices
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.top: title.bottom
+ anchors.fill: parent
model: connectPage.deviceFinder.devices
clip: true
@@ -76,22 +72,22 @@ GamePage {
Text {
id: device
- font.pixelSize: GameSettings.smallFontSize
+ font.pixelSize: GameSettings.microFontSize
text: box.modelData.deviceName
anchors.top: parent.top
- anchors.topMargin: parent.height * 0.1
- anchors.leftMargin: parent.height * 0.1
+ anchors.topMargin: parent.height * 0.15
+ anchors.leftMargin: parent.height * 0.15
anchors.left: parent.left
color: GameSettings.textColor
}
Text {
id: deviceAddress
- font.pixelSize: GameSettings.smallFontSize
+ font.pixelSize: GameSettings.microFontSize
text: box.modelData.deviceAddress
anchors.bottom: parent.bottom
- anchors.bottomMargin: parent.height * 0.1
- anchors.rightMargin: parent.height * 0.1
+ anchors.bottomMargin: parent.height * 0.15
+ anchors.rightMargin: parent.height * 0.15
anchors.right: parent.right
color: Qt.darker(GameSettings.textColor)
}
@@ -114,7 +110,7 @@ GamePage {
State {
name: "public"
PropertyChanges {
- addressTypeText.text: qsTr("Public Address")
+ addressTypeText.text: qsTr("PUBLIC ADDRESS")
}
PropertyChanges {
connectPage.deviceHandler.addressType: DeviceHandler.PublicAddress
@@ -123,7 +119,7 @@ GamePage {
State {
name: "random"
PropertyChanges {
- addressTypeText.text: qsTr("Random Address")
+ addressTypeText.text: qsTr("RANDOM ADDRESS")
}
PropertyChanges {
connectPage.deviceHandler.addressType: DeviceHandler.RandomAddress
@@ -134,8 +130,8 @@ GamePage {
Text {
id: addressTypeText
anchors.centerIn: parent
- font.pixelSize: GameSettings.tinyFontSize
- color: GameSettings.textColor
+ font.pixelSize: GameSettings.microFontSize
+ color: GameSettings.textDarkColor
}
}
@@ -151,9 +147,9 @@ GamePage {
Text {
anchors.centerIn: parent
- font.pixelSize: GameSettings.tinyFontSize
+ font.pixelSize: GameSettings.microFontSize
text: qsTr("START SEARCH")
- color: searchButton.enabled ? GameSettings.textColor : GameSettings.disabledTextColor
+ color: GameSettings.textDarkColor
}
}
}
diff --git a/examples/bluetooth/heartrate-game/GamePage.qml b/examples/bluetooth/heartrate-game/GamePage.qml
index 249f9418..ff81e9cd 100644
--- a/examples/bluetooth/heartrate-game/GamePage.qml
+++ b/examples/bluetooth/heartrate-game/GamePage.qml
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
+import HeartRateGame
Item {
id: page
@@ -11,25 +12,65 @@ Item {
property real messageHeight: msg.height
property bool hasError: errorMessage != ""
property bool hasInfo: infoMessage != ""
+ property int iconType: BluetoothBaseClass.IconNone
+
+ function iconTypeToName(icon: int) : string {
+ switch (icon) {
+ case BluetoothBaseClass.IconNone: return ""
+ case BluetoothBaseClass.IconBluetooth: return "images/bluetooth.svg"
+ case BluetoothBaseClass.IconError: return "images/alert.svg"
+ case BluetoothBaseClass.IconProgress: return "images/progress.svg"
+ case BluetoothBaseClass.IconSearch: return "images/search.svg"
+ }
+ }
Rectangle {
id: msg
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ topMargin: GameSettings.fieldMargin * 0.5
+ leftMargin: GameSettings.fieldMargin
+ rightMargin: GameSettings.fieldMargin
+ }
height: GameSettings.fieldHeight
- color: page.hasError ? GameSettings.errorColor : GameSettings.infoColor
+ radius: GameSettings.buttonRadius
+ color: page.hasError ? GameSettings.errorColor : "transparent"
visible: page.hasError || page.hasInfo
+ border {
+ width: 1
+ color: page.hasError ? GameSettings.errorColor : GameSettings.infoColor
+ }
+
+ Image {
+ id: icon
+ readonly property int imgSize: GameSettings.fieldHeight * 0.5
+ anchors {
+ left: parent.left
+ leftMargin: GameSettings.fieldMargin * 0.5
+ verticalCenter: parent.verticalCenter
+ }
+ visible: source.toString() !== ""
+ source: page.iconTypeToName(page.iconType)
+ sourceSize.width: imgSize
+ sourceSize.height: imgSize
+ fillMode: Image.PreserveAspectFit
+ }
Text {
id: error
- anchors.fill: parent
+ anchors {
+ fill: parent
+ leftMargin: GameSettings.fieldMargin + icon.width
+ rightMargin: GameSettings.fieldMargin + icon.width
+ }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
minimumPixelSize: 5
- font.pixelSize: GameSettings.smallFontSize
+ font.pixelSize: GameSettings.microFontSize
fontSizeMode: Text.Fit
- color: GameSettings.textColor
+ color: page.hasError ? GameSettings.textColor : GameSettings.infoColor
text: page.hasError ? page.errorMessage : page.infoMessage
}
}
diff --git a/examples/bluetooth/heartrate-game/GameSettings.qml b/examples/bluetooth/heartrate-game/GameSettings.qml
index 0fe85460..4032787c 100644
--- a/examples/bluetooth/heartrate-game/GameSettings.qml
+++ b/examples/bluetooth/heartrate-game/GameSettings.qml
@@ -4,35 +4,49 @@
pragma Singleton
import QtQuick
-Item {
+QtObject {
property int wHeight
property int wWidth
// Colors
- readonly property color backgroundColor: "#2d3037"
- readonly property color buttonColor: "#202227"
- readonly property color buttonPressedColor: "#6ccaf2"
- readonly property color disabledButtonColor: "#555555"
- readonly property color viewColor: "#202227"
- readonly property color delegate1Color: Qt.darker(viewColor, 1.2)
- readonly property color delegate2Color: Qt.lighter(viewColor, 1.2)
+ readonly property color lightGreenColor: "#80ebb6"
+ readonly property color backgroundColor: "#2c3038"
+ readonly property color buttonColor: "#2cde85"
+ readonly property color buttonPressedColor: lightGreenColor
+ readonly property color disabledButtonColor: "#808080"
+ readonly property color viewColor: "#262626"
+ readonly property color delegate1Color: "#262626"
+ readonly property color delegate2Color: "#404040"
readonly property color textColor: "#ffffff"
- readonly property color textDarkColor: "#232323"
- readonly property color disabledTextColor: "#777777"
- readonly property color sliderColor: "#6ccaf2"
+ readonly property color textDarkColor: "#0d0d0d"
+ readonly property color textInfoColor: lightGreenColor
+ readonly property color sliderColor: "#00414a"
+ readonly property color sliderBorderColor: lightGreenColor
+ readonly property color sliderTextColor: lightGreenColor
readonly property color errorColor: "#ba3f62"
- readonly property color infoColor: "#3fba62"
+ readonly property color infoColor: lightGreenColor
+ readonly property color titleColor: "#202227"
+ readonly property color selectedTitleColor: "#19545c"
+ readonly property color hoverTitleColor: Qt.rgba(selectedTitleColor.r,
+ selectedTitleColor.g,
+ selectedTitleColor.b,
+ 0.25)
+ readonly property color bottomLineColor: "#e6e6e6"
+ readonly property color heartRateColor: "#f80067"
+
+ // All the fonts are given for the window of certain size.
+ // Resizing the window changes all the fonts accordingly
+ readonly property int defaultSize: 500
+ readonly property real fontScaleFactor: Math.min(wWidth, wHeight) / defaultSize
// Font sizes
- property real microFontSize: hugeFontSize * 0.2
- property real tinyFontSize: hugeFontSize * 0.4
- property real smallTinyFontSize: hugeFontSize * 0.5
- property real smallFontSize: hugeFontSize * 0.6
- property real mediumFontSize: hugeFontSize * 0.7
- property real bigFontSize: hugeFontSize * 0.8
- property real largeFontSize: hugeFontSize * 0.9
- property real hugeFontSize: (wWidth + wHeight) * 0.03
- property real giganticFontSize: (wWidth + wHeight) * 0.04
+ readonly property real microFontSize: 16 * fontScaleFactor
+ readonly property real tinyFontSize: 20 * fontScaleFactor
+ readonly property real smallFontSize: 24 * fontScaleFactor
+ readonly property real mediumFontSize: 32 * fontScaleFactor
+ readonly property real bigFontSize: 36 * fontScaleFactor
+ readonly property real largeFontSize: 54 * fontScaleFactor
+ readonly property real hugeFontSize: 128 * fontScaleFactor
// Some other values
property real fieldHeight: wHeight * 0.08
@@ -41,10 +55,6 @@ Item {
property real buttonRadius: buttonHeight * 0.1
// Some help functions
- function widthForHeight(h, ss) {
- return h / ss.height * ss.width
- }
-
function heightForWidth(w, ss) {
return w / ss.width * ss.height
}
diff --git a/examples/bluetooth/heartrate-game/Measure.qml b/examples/bluetooth/heartrate-game/Measure.qml
index 48e84e76..04ebeb09 100644
--- a/examples/bluetooth/heartrate-game/Measure.qml
+++ b/examples/bluetooth/heartrate-game/Measure.qml
@@ -11,10 +11,15 @@ GamePage {
errorMessage: deviceHandler.error
infoMessage: deviceHandler.info
+ iconType: deviceHandler.icon
property real __timeCounter: 0
property real __maxTimeCount: 60
- property string relaxText: qsTr("Relax!\nWhen you are ready, press Start. You have %1s time to increase heartrate so much as possible.\nGood luck!").arg(__maxTimeCount)
+
+ readonly property string relaxText: qsTr("Relax!")
+ readonly property string startText: qsTr("When you are ready,\npress Start.")
+ readonly property string instructionText: qsTr("You have %1s time to increase heart\nrate as much as possible.").arg(__maxTimeCount)
+ readonly property string goodLuckText: qsTr("Good luck!")
signal showStatsPage
@@ -55,6 +60,10 @@ GamePage {
Rectangle {
id: circle
+
+ readonly property bool hintVisible: !measurePage.deviceHandler.measuring
+ readonly property real innerSpacing: Math.min(width * 0.05, 25)
+
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(measurePage.width, measurePage.height - GameSettings.fieldHeight * 4)
- 2 * GameSettings.fieldMargin
@@ -63,30 +72,127 @@ GamePage {
color: GameSettings.viewColor
Text {
- id: hintText
- anchors.centerIn: parent
- anchors.verticalCenterOffset: -parent.height * 0.1
+ id: relaxTextBox
+ anchors {
+ bottom: startTextBox.top
+ bottomMargin: parent.innerSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width * 0.6
+ height: parent.height * 0.1
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
- width: parent.width * 0.8
- height: parent.height * 0.6
- wrapMode: Text.WordWrap
text: measurePage.relaxText
- visible: !measurePage.deviceHandler.measuring
+ visible: circle.hintVisible
color: GameSettings.textColor
fontSizeMode: Text.Fit
- minimumPixelSize: 10
- font.pixelSize: GameSettings.mediumFontSize
+ font.pixelSize: GameSettings.smallFontSize
+ font.bold: true
}
Text {
- id: text
- anchors.centerIn: parent
- anchors.verticalCenterOffset: -parent.height * 0.15
- font.pixelSize: parent.width * 0.45
+ id: startTextBox
+ anchors {
+ bottom: heart.top
+ bottomMargin: parent.innerSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width * 0.8
+ height: parent.height * 0.15
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: measurePage.startText
+ visible: circle.hintVisible
+ color: GameSettings.textColor
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.tinyFontSize
+ }
+
+ Text {
+ id: measureTextBox
+ anchors {
+ bottom: heart.top
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width * 0.7
+ height: parent.height * 0.35
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
text: measurePage.deviceHandler.hr
visible: measurePage.deviceHandler.measuring
+ color: GameSettings.heartRateColor
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.hugeFontSize
+ font.bold: true
+ }
+
+ Image {
+ id: heart
+ anchors.centerIn: circle
+ width: parent.width * 0.2
+ height: width
+ fillMode: Image.PreserveAspectFit
+ source: "images/heart.png"
+ smooth: true
+ antialiasing: true
+
+ SequentialAnimation {
+ id: heartAnim
+ running: measurePage.deviceHandler.measuring
+ loops: Animation.Infinite
+ alwaysRunToEnd: true
+ PropertyAnimation {
+ target: heart
+ property: "scale"
+ to: 1.4
+ duration: 500
+ easing.type: Easing.InQuad
+ }
+ PropertyAnimation {
+ target: heart
+ property: "scale"
+ to: 1.0
+ duration: 500
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+
+ Text {
+ id: instructionTextBox
+ anchors {
+ top: heart.bottom
+ topMargin: parent.innerSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width * 0.8
+ height: parent.height * 0.15
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: measurePage.instructionText
+ visible: circle.hintVisible
color: GameSettings.textColor
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.tinyFontSize
+ }
+
+ Text {
+ id: goodLuckBox
+ anchors {
+ top: instructionTextBox.bottom
+ topMargin: parent.innerSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width * 0.6
+ height: parent.height * 0.1
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: measurePage.goodLuckText
+ visible: circle.hintVisible
+ color: GameSettings.textColor
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.smallFontSize
+ font.bold: true
}
Item {
@@ -101,14 +207,22 @@ GamePage {
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
+ width: parent.width * 0.35
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
text: measurePage.deviceHandler.minHR
color: GameSettings.textColor
- font.pixelSize: GameSettings.hugeFontSize
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.largeFontSize
Text {
anchors.left: parent.left
anchors.bottom: parent.top
- font.pixelSize: parent.font.pixelSize * 0.8
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ width: parent.width
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.mediumFontSize
color: parent.color
text: "MIN"
}
@@ -117,51 +231,27 @@ GamePage {
Text {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ width: parent.width * 0.35
text: measurePage.deviceHandler.maxHR
color: GameSettings.textColor
- font.pixelSize: GameSettings.hugeFontSize
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.largeFontSize
Text {
anchors.right: parent.right
anchors.bottom: parent.top
- font.pixelSize: parent.font.pixelSize * 0.8
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ width: parent.width
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.mediumFontSize
color: parent.color
text: "MAX"
}
}
}
-
- Image {
- id: heart
- anchors.horizontalCenter: minMaxContainer.horizontalCenter
- anchors.verticalCenter: minMaxContainer.bottom
- width: parent.width * 0.2
- height: width
- source: "images/heart.png"
- smooth: true
- antialiasing: true
-
- SequentialAnimation {
- id: heartAnim
- running: measurePage.deviceHandler.alive
- loops: Animation.Infinite
- alwaysRunToEnd: true
- PropertyAnimation {
- target: heart
- property: "scale"
- to: 1.2
- duration: 500
- easing.type: Easing.InQuad
- }
- PropertyAnimation {
- target: heart
- property: "scale"
- to: 1.0
- duration: 500
- easing.type: Easing.OutQuad
- }
- }
- }
}
Rectangle {
@@ -171,21 +261,43 @@ GamePage {
width: circle.width
height: GameSettings.fieldHeight
radius: GameSettings.buttonRadius
+ border {
+ width: 1
+ color: GameSettings.sliderBorderColor
+ }
Rectangle {
- height: parent.height
+ anchors {
+ top: parent.top
+ topMargin: parent.border.width
+ left: parent.left
+ leftMargin: parent.border.width
+ }
+ height: parent.height - 2 * parent.border.width
+ width: Math.min(1.0, measurePage.__timeCounter / measurePage.__maxTimeCount)
+ * (parent.width - 2 * parent.border.width)
radius: parent.radius
color: GameSettings.sliderColor
- width: Math.min(
- 1.0,
- measurePage.__timeCounter / measurePage.__maxTimeCount) * parent.width
+ }
+
+ Image {
+ readonly property int imgSize: GameSettings.fieldHeight * 0.5
+ anchors {
+ verticalCenter: parent.verticalCenter
+ left: parent.left
+ leftMargin: GameSettings.fieldMargin * 0.5
+ }
+ source: "images/clock.svg"
+ sourceSize.width: imgSize
+ sourceSize.height: imgSize
+ fillMode: Image.PreserveAspectFit
}
Text {
anchors.centerIn: parent
- color: "gray"
+ color: GameSettings.sliderTextColor
text: (measurePage.__maxTimeCount - measurePage.__timeCounter).toFixed(0) + " s"
- font.pixelSize: GameSettings.bigFontSize
+ font.pixelSize: GameSettings.smallFontSize
}
}
}
@@ -197,16 +309,17 @@ GamePage {
anchors.bottomMargin: GameSettings.fieldMargin
width: circle.width
height: GameSettings.fieldHeight
- enabled: !measurePage.deviceHandler.measuring
+ enabled: measurePage.deviceHandler.alive && !measurePage.deviceHandler.measuring
+ && measurePage.errorMessage === ""
radius: GameSettings.buttonRadius
onClicked: measurePage.start()
Text {
anchors.centerIn: parent
- font.pixelSize: GameSettings.tinyFontSize
+ font.pixelSize: GameSettings.microFontSize
text: qsTr("START")
- color: startButton.enabled ? GameSettings.textColor : GameSettings.disabledTextColor
+ color: GameSettings.textDarkColor
}
}
}
diff --git a/examples/bluetooth/heartrate-game/SplashScreen.qml b/examples/bluetooth/heartrate-game/SplashScreen.qml
index 2f9ac1b3..918319d7 100644
--- a/examples/bluetooth/heartrate-game/SplashScreen.qml
+++ b/examples/bluetooth/heartrate-game/SplashScreen.qml
@@ -23,7 +23,7 @@ Item {
Timer {
id: splashTimer
interval: 1000
- onTriggered: splashIsReady = true
+ onTriggered: root.splashIsReady = true
}
Component.onCompleted: splashTimer.start()
diff --git a/examples/bluetooth/heartrate-game/Stats.qml b/examples/bluetooth/heartrate-game/Stats.qml
index 22cdd536..87487c94 100644
--- a/examples/bluetooth/heartrate-game/Stats.qml
+++ b/examples/bluetooth/heartrate-game/Stats.qml
@@ -13,20 +13,45 @@ GamePage {
anchors.centerIn: parent
width: parent.width
- Text {
+ Rectangle {
+ id: resultRect
anchors.horizontalCenter: parent.horizontalCenter
- font.pixelSize: GameSettings.hugeFontSize
- color: GameSettings.textColor
- text: qsTr("RESULT")
- }
+ width: height
+ height: statsPage.height / 2 - GameSettings.fieldHeight
+ radius: height / 2
+ color: GameSettings.viewColor
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- font.pixelSize: GameSettings.giganticFontSize * 3
- color: GameSettings.textColor
- text: (statsPage.deviceHandler.maxHR - statsPage.deviceHandler.minHR).toFixed(0)
+ Column {
+ anchors.centerIn: parent
+
+ Text {
+ id: resultCaption
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: resultRect.width * 0.8
+ height: resultRect.height * 0.15
+ horizontalAlignment: Text.AlignHCenter
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.bigFontSize
+ color: GameSettings.textColor
+ text: qsTr("RESULT")
+ }
+
+ Text {
+ id: resultValue
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: resultRect.width * 0.8
+ height: resultRect.height * 0.4
+ horizontalAlignment: Text.AlignHCenter
+ fontSizeMode: Text.Fit
+ font.pixelSize: GameSettings.hugeFontSize
+ font.bold: true
+ color: GameSettings.heartRateColor
+ text: (statsPage.deviceHandler.maxHR - statsPage.deviceHandler.minHR).toFixed(0)
+ }
+ }
}
+
Item {
height: GameSettings.fieldHeight
width: 1
diff --git a/examples/bluetooth/heartrate-game/TitleBar.qml b/examples/bluetooth/heartrate-game/TitleBar.qml
index 016a4435..ccec7608 100644
--- a/examples/bluetooth/heartrate-game/TitleBar.qml
+++ b/examples/bluetooth/heartrate-game/TitleBar.qml
@@ -13,42 +13,51 @@ Rectangle {
signal titleClicked(int index)
height: GameSettings.fieldHeight
- color: GameSettings.viewColor
+ color: GameSettings.titleColor
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: parent.width / 3
+ height: parent.height
+ x: titleBar.currentIndex * width
+ color: GameSettings.selectedTitleColor
+
+ BottomLine {
+ color: GameSettings.bottomLineColor
+ }
+
+ Behavior on x {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ }
Repeater {
model: 3
- Text {
+ Rectangle {
id: caption
required property int index
+ property bool hoveredOrPressed: mouseArea.pressed || mouseArea.containsMouse
width: titleBar.width / 3
height: titleBar.height
x: index * width
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- text: titleBar.__titles[index]
- font.pixelSize: GameSettings.tinyFontSize
- color: titleBar.currentIndex === index ? GameSettings.textColor
- : GameSettings.disabledTextColor
-
+ color: (titleBar.currentIndex !== index) && hoveredOrPressed
+ ? GameSettings.hoverTitleColor : "transparent"
+ Text {
+ anchors.fill: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: titleBar.__titles[caption.index]
+ font.pixelSize: GameSettings.microFontSize
+ color: GameSettings.textColor
+ }
MouseArea {
+ id: mouseArea
anchors.fill: parent
+ hoverEnabled: true
onClicked: titleBar.titleClicked(caption.index)
}
}
}
-
- Item {
- anchors.bottom: parent.bottom
- width: parent.width / 3
- height: parent.height
- x: titleBar.currentIndex * width
-
- BottomLine {}
-
- Behavior on x {
- NumberAnimation {
- duration: 200
- }
- }
- }
}
diff --git a/examples/bluetooth/heartrate-game/bluetoothbaseclass.cpp b/examples/bluetooth/heartrate-game/bluetoothbaseclass.cpp
index 5db621c4..4c60b180 100644
--- a/examples/bluetooth/heartrate-game/bluetoothbaseclass.cpp
+++ b/examples/bluetooth/heartrate-game/bluetoothbaseclass.cpp
@@ -33,8 +33,22 @@ void BluetoothBaseClass::setInfo(const QString &info)
}
}
+BluetoothBaseClass::IconType BluetoothBaseClass::icon() const
+{
+ return m_icon;
+}
+
+void BluetoothBaseClass::setIcon(IconType icon)
+{
+ if (m_icon != icon) {
+ m_icon = icon;
+ emit iconChanged();
+ }
+}
+
void BluetoothBaseClass::clearMessages()
{
setInfo("");
setError("");
+ setIcon(IconNone);
}
diff --git a/examples/bluetooth/heartrate-game/bluetoothbaseclass.h b/examples/bluetooth/heartrate-game/bluetoothbaseclass.h
index 2d25c546..688a88e5 100644
--- a/examples/bluetooth/heartrate-game/bluetoothbaseclass.h
+++ b/examples/bluetooth/heartrate-game/bluetoothbaseclass.h
@@ -5,6 +5,7 @@
#define BLUETOOTHBASECLASS_H
#include <QtCore/qobject.h>
+#include <QtQmlIntegration/qqmlintegration.h>
class BluetoothBaseClass : public QObject
{
@@ -12,8 +13,21 @@ class BluetoothBaseClass : public QObject
Q_PROPERTY(QString error READ error WRITE setError NOTIFY errorChanged)
Q_PROPERTY(QString info READ info WRITE setInfo NOTIFY infoChanged)
+ Q_PROPERTY(IconType icon READ icon WRITE setIcon NOTIFY iconChanged)
+
+ QML_ELEMENT
+ QML_UNCREATABLE("BluetoothBaseClass is not intended to be created directly")
public:
+ enum IconType : int {
+ IconNone,
+ IconBluetooth,
+ IconError,
+ IconProgress,
+ IconSearch
+ };
+ Q_ENUM(IconType)
+
explicit BluetoothBaseClass(QObject *parent = nullptr);
QString error() const;
@@ -22,15 +36,20 @@ public:
QString info() const;
void setInfo(const QString& info);
+ IconType icon() const;
+ void setIcon(IconType icon);
+
void clearMessages();
signals:
void errorChanged();
void infoChanged();
+ void iconChanged();
private:
QString m_error;
QString m_info;
+ IconType m_icon = IconNone;
};
#endif // BLUETOOTHBASECLASS_H
diff --git a/examples/bluetooth/heartrate-game/devicefinder.cpp b/examples/bluetooth/heartrate-game/devicefinder.cpp
index a73f89c0..29e146c7 100644
--- a/examples/bluetooth/heartrate-game/devicefinder.cpp
+++ b/examples/bluetooth/heartrate-game/devicefinder.cpp
@@ -38,6 +38,8 @@ DeviceFinder::DeviceFinder(DeviceHandler *handler, QObject *parent):
m_demoTimer.setInterval(2000);
connect(&m_demoTimer, &QTimer::timeout, this, &DeviceFinder::scanFinished);
}
+
+ resetMessages();
}
DeviceFinder::~DeviceFinder()
@@ -58,6 +60,7 @@ void DeviceFinder::startSearch()
return;
case Qt::PermissionStatus::Denied:
setError(tr("Bluetooth permissions not granted!"));
+ setIcon(IconError);
return;
case Qt::PermissionStatus::Granted:
break; // proceed to search
@@ -81,6 +84,7 @@ void DeviceFinder::startSearch()
emit scanningChanged();
setInfo(tr("Scanning for devices..."));
+ setIcon(IconProgress);
}
//! [devicediscovery-3]
@@ -101,6 +105,7 @@ void DeviceFinder::addDevice(const QBluetoothDeviceInfo &device)
delete oldDev;
}
setInfo(tr("Low Energy device found. Scanning more..."));
+ setIcon(IconProgress);
//! [devicediscovery-3]
emit devicesChanged();
//! [devicediscovery-4]
@@ -117,6 +122,7 @@ void DeviceFinder::scanError(QBluetoothDeviceDiscoveryAgent::Error error)
setError(tr("Writing or reading from the device resulted in an error."));
else
setError(tr("An unknown error has occurred."));
+ setIcon(IconError);
}
void DeviceFinder::scanFinished()
@@ -127,15 +133,25 @@ void DeviceFinder::scanFinished()
m_devices.append(new DeviceInfo(QBluetoothDeviceInfo()));
}
- if (m_devices.isEmpty())
+ if (m_devices.isEmpty()) {
setError(tr("No Low Energy devices found."));
- else
+ setIcon(IconError);
+ } else {
setInfo(tr("Scanning done."));
+ setIcon(IconBluetooth);
+ }
emit scanningChanged();
emit devicesChanged();
}
+void DeviceFinder::resetMessages()
+{
+ setError("");
+ setInfo(tr("Start search to find devices"));
+ setIcon(IconSearch);
+}
+
void DeviceFinder::connectToService(const QString &address)
{
m_deviceDiscoveryAgent->stop();
@@ -152,7 +168,7 @@ void DeviceFinder::connectToService(const QString &address)
if (currentDevice)
m_deviceHandler->setDevice(currentDevice);
- clearMessages();
+ resetMessages();
}
bool DeviceFinder::scanning() const
diff --git a/examples/bluetooth/heartrate-game/devicefinder.h b/examples/bluetooth/heartrate-game/devicefinder.h
index 29f47fd3..6f1e46b7 100644
--- a/examples/bluetooth/heartrate-game/devicefinder.h
+++ b/examples/bluetooth/heartrate-game/devicefinder.h
@@ -51,6 +51,8 @@ signals:
void devicesChanged();
private:
+ void resetMessages();
+
DeviceHandler *m_deviceHandler;
QBluetoothDeviceDiscoveryAgent *m_deviceDiscoveryAgent;
QList<DeviceInfo *> m_devices;
diff --git a/examples/bluetooth/heartrate-game/devicehandler.cpp b/examples/bluetooth/heartrate-game/devicehandler.cpp
index fa9da637..a90ccfa5 100644
--- a/examples/bluetooth/heartrate-game/devicehandler.cpp
+++ b/examples/bluetooth/heartrate-game/devicehandler.cpp
@@ -47,6 +47,7 @@ void DeviceHandler::setDevice(DeviceInfo *device)
if (simulator) {
setInfo(tr("Demo device connected."));
+ setIcon(IconBluetooth);
return;
}
@@ -75,13 +76,16 @@ void DeviceHandler::setDevice(DeviceInfo *device)
[this](QLowEnergyController::Error error) {
Q_UNUSED(error);
setError("Cannot connect to remote device.");
+ setIcon(IconError);
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
setInfo("Controller connected. Search services...");
+ setIcon(IconProgress);
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
setError("LowEnergy controller disconnected");
+ setIcon(IconError);
});
// Connect
@@ -116,6 +120,7 @@ void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
{
if (gatt == QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate)) {
setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
+ setIcon(IconProgress);
m_foundHeartRateService = true;
}
}
@@ -124,6 +129,7 @@ void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
void DeviceHandler::serviceScanDone()
{
setInfo("Service scan done.");
+ setIcon(IconBluetooth);
// Delete old service if available
if (m_service) {
@@ -143,6 +149,7 @@ void DeviceHandler::serviceScanDone()
m_service->discoverDetails();
} else {
setError("Heart Rate Service not found.");
+ setIcon(IconError);
}
//! [Filter HeartRate service 2]
}
@@ -154,15 +161,18 @@ void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
switch (s) {
case QLowEnergyService::RemoteServiceDiscovering:
setInfo(tr("Discovering services..."));
+ setIcon(IconProgress);
break;
case QLowEnergyService::RemoteServiceDiscovered:
{
setInfo(tr("Service discovered."));
+ setIcon(IconBluetooth);
const QLowEnergyCharacteristic hrChar =
m_service->characteristic(QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement));
if (!hrChar.isValid()) {
setError("HR Data not found.");
+ setIcon(IconError);
break;
}
diff --git a/examples/bluetooth/heartrate-game/deviceinfo.cpp b/examples/bluetooth/heartrate-game/deviceinfo.cpp
index 55dcf0e8..6cbc8ae6 100644
--- a/examples/bluetooth/heartrate-game/deviceinfo.cpp
+++ b/examples/bluetooth/heartrate-game/deviceinfo.cpp
@@ -22,7 +22,7 @@ QBluetoothDeviceInfo DeviceInfo::getDevice() const
QString DeviceInfo::getName() const
{
if (simulator)
- return u"Demo device"_s;
+ return u"Demo BT Device"_s;
return m_device.name();
}
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-result.png b/examples/bluetooth/heartrate-game/doc/images/heartgame-result.png
deleted file mode 100644
index 2dad1b30..00000000
--- a/examples/bluetooth/heartrate-game/doc/images/heartgame-result.png
+++ /dev/null
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-result.webp b/examples/bluetooth/heartrate-game/doc/images/heartgame-result.webp
new file mode 100644
index 00000000..58815ac0
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/doc/images/heartgame-result.webp
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-running.png b/examples/bluetooth/heartrate-game/doc/images/heartgame-running.png
deleted file mode 100644
index 05485f0e..00000000
--- a/examples/bluetooth/heartrate-game/doc/images/heartgame-running.png
+++ /dev/null
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-running.webp b/examples/bluetooth/heartrate-game/doc/images/heartgame-running.webp
new file mode 100644
index 00000000..1de7af6d
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/doc/images/heartgame-running.webp
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-search.png b/examples/bluetooth/heartrate-game/doc/images/heartgame-search.png
deleted file mode 100644
index 4b0b0497..00000000
--- a/examples/bluetooth/heartrate-game/doc/images/heartgame-search.png
+++ /dev/null
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-search.webp b/examples/bluetooth/heartrate-game/doc/images/heartgame-search.webp
new file mode 100644
index 00000000..727486d0
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/doc/images/heartgame-search.webp
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-start.png b/examples/bluetooth/heartrate-game/doc/images/heartgame-start.png
deleted file mode 100644
index 21cc641f..00000000
--- a/examples/bluetooth/heartrate-game/doc/images/heartgame-start.png
+++ /dev/null
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/images/heartgame-start.webp b/examples/bluetooth/heartrate-game/doc/images/heartgame-start.webp
new file mode 100644
index 00000000..9a49e4a1
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/doc/images/heartgame-start.webp
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/doc/src/heartrate-game.qdoc b/examples/bluetooth/heartrate-game/doc/src/heartrate-game.qdoc
index 7bfdb8f6..a986896a 100644
--- a/examples/bluetooth/heartrate-game/doc/src/heartrate-game.qdoc
+++ b/examples/bluetooth/heartrate-game/doc/src/heartrate-game.qdoc
@@ -14,7 +14,7 @@
characteristics and descriptors, and receiving updates from the device once the heart rate
has changed.
- \image heartgame-start.png
+ \image heartgame-start.webp
The example introduces the following Qt classes:
@@ -60,17 +60,17 @@
presented in a list. Note that all found Bluetooth Low Energy devices are listed even
if they do not offer a Heart Rate service.
- \image heartgame-search.png
+ \image heartgame-search.webp
After the user has selected a target device, the example connects to its Heart Rate service
if one is available. It automatically enables notification updates for the Heart Rate value
and presents the current value on the screen.
- \image heartgame-running.png
+ \image heartgame-running.webp
- Once the monitoring process is canceled, a small graph presents a summary of the received
- values.
+ Once the monitoring process is finished, a small summary of the received
+ values is presented.
- \image heartgame-result.png
+ \image heartgame-result.webp
*/
diff --git a/examples/bluetooth/heartrate-game/heartrate-game.pro b/examples/bluetooth/heartrate-game/heartrate-game.pro
index 417f77f3..7cd5dcc5 100644
--- a/examples/bluetooth/heartrate-game/heartrate-game.pro
+++ b/examples/bluetooth/heartrate-game/heartrate-game.pro
@@ -37,9 +37,14 @@ qml_resources.files = \
StatsLabel.qml \
TitleBar.qml \
Main.qml \
+ images/alert.svg \
+ images/bluetooth.svg \
images/bt_off_to_on.png \
+ images/clock.svg \
images/heart.png \
- images/logo.png
+ images/logo.png \
+ images/progress.svg \
+ images/search.svg
qml_resources.prefix = /qt/qml/HeartRateGame
diff --git a/examples/bluetooth/heartrate-game/images/alert.svg b/examples/bluetooth/heartrate-game/images/alert.svg
new file mode 100644
index 00000000..c48c10e6
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/images/alert.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4ZM12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 13C11.4477 13 11 12.5523 11 12V8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V12C13 12.5523 12.5523 13 12 13ZM12 15C12.5523 15 13 15.4477 13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15Z" fill="white"/>
+</svg>
diff --git a/examples/bluetooth/heartrate-game/images/bluetooth.svg b/examples/bluetooth/heartrate-game/images/bluetooth.svg
new file mode 100644
index 00000000..6d01b28f
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/images/bluetooth.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1869 2.24413C11.4926 2.11749 11.8445 2.18749 12.0785 2.42149L16.5785 6.92149C16.8981 7.24101 16.8981 7.75905 16.5785 8.07857L12.6571 12L16.5785 15.9215C16.8981 16.241 16.8981 16.7591 16.5785 17.0786L12.0785 21.5786C11.8445 21.8126 11.4926 21.8826 11.1869 21.7559C10.8812 21.6293 10.6818 21.331 10.6818 21V13.9753L7.57855 17.0786C7.25903 17.3981 6.74098 17.3981 6.42146 17.0786C6.10194 16.7591 6.10194 16.241 6.42146 15.9215L10.3429 12L6.42146 8.07857C6.10194 7.75905 6.10194 7.24101 6.42146 6.92149C6.74098 6.60197 7.25903 6.60197 7.57855 6.92149L10.6818 10.0248V3.00003C10.6818 2.66911 10.8812 2.37077 11.1869 2.24413ZM12.3182 13.9753L14.8429 16.5L12.3182 19.0248V13.9753ZM12.3182 10.0248V4.9753L14.8429 7.50003L12.3182 10.0248Z" fill="#80EBB6"/>
+</svg>
diff --git a/examples/bluetooth/heartrate-game/images/clock.svg b/examples/bluetooth/heartrate-game/images/clock.svg
new file mode 100644
index 00000000..655996ba
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/images/clock.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 20H16V17C16 15.9 15.6083 14.9583 14.825 14.175C14.0417 13.3917 13.1 13 12 13C10.9 13 9.95833 13.3917 9.175 14.175C8.39167 14.9583 8 15.9 8 17V20ZM12 11C13.1 11 14.0417 10.6083 14.825 9.825C15.6083 9.04167 16 8.1 16 7V4H8V7C8 8.1 8.39167 9.04167 9.175 9.825C9.95833 10.6083 10.9 11 12 11ZM5 22C4.44772 22 4 21.5523 4 21C4 20.4477 4.44772 20 5 20H6V17C6 15.9833 6.2375 15.0292 6.7125 14.1375C7.1875 13.2458 7.85 12.5333 8.7 12C7.85 11.4667 7.1875 10.7542 6.7125 9.8625C6.2375 8.97083 6 8.01667 6 7V4H5C4.44772 4 4 3.55228 4 3C4 2.44772 4.44772 2 5 2H19C19.5523 2 20 2.44772 20 3C20 3.55228 19.5523 4 19 4H18V7C18 8.01667 17.7625 8.97083 17.2875 9.8625C16.8125 10.7542 16.15 11.4667 15.3 12C16.15 12.5333 16.8125 13.2458 17.2875 14.1375C17.7625 15.0292 18 15.9833 18 17V20H19C19.5523 20 20 20.4477 20 21C20 21.5523 19.5523 22 19 22H5Z" fill="#80EBB6"/>
+<path d="M17 22H7L7 18C7 15.2386 9.23858 13 12 13C14.7614 13 17 15.2386 17 18V22Z" fill="#80EBB6"/>
+</svg>
diff --git a/examples/bluetooth/heartrate-game/images/heart.png b/examples/bluetooth/heartrate-game/images/heart.png
index f2b3c0a3..4ba0f822 100644
--- a/examples/bluetooth/heartrate-game/images/heart.png
+++ b/examples/bluetooth/heartrate-game/images/heart.png
Binary files differ
diff --git a/examples/bluetooth/heartrate-game/images/progress.svg b/examples/bluetooth/heartrate-game/images/progress.svg
new file mode 100644
index 00000000..449fe5e7
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/images/progress.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19 11C19 11.5523 19.4477 12 20 12C20.5523 12 21 11.5523 21 11V10.8478C21 8.11075 19.7088 5.53404 17.5163 3.89561C17.0739 3.56501 16.4472 3.65565 16.1166 4.09805C15.786 4.54046 15.8767 5.1671 16.3191 5.4977C18.0064 6.75857 19 8.74149 19 10.8478V11ZM4 12C4.55228 12 5 11.5523 5 11V10.8478C5 8.74149 5.99363 6.75857 7.68091 5.4977C8.12331 5.1671 8.21395 4.54046 7.88335 4.09805C7.55275 3.65565 6.92611 3.56501 6.4837 3.89561C4.29117 5.53404 3 8.11075 3 10.8478V11C3 11.5523 3.44772 12 4 12ZM7.10555 19.5528C7.35253 19.0588 7.95321 18.8586 8.44719 19.1055C10.6837 20.2238 13.3162 20.2238 15.5528 19.1056C16.0467 18.8586 16.6474 19.0588 16.8944 19.5528C17.1414 20.0468 16.9412 20.6474 16.4472 20.8944C13.6476 22.2942 10.3523 22.2942 7.55276 20.8944C7.05878 20.6474 6.85856 20.0467 7.10555 19.5528Z" fill="#80EBB6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13 5C13 5.55228 12.5523 6 12 6C11.4477 6 11 5.55228 11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5ZM15 5C15 6.65685 13.6569 8 12 8C10.3431 8 9 6.65685 9 5C9 3.34315 10.3431 2 12 2C13.6569 2 15 3.34315 15 5ZM5 17C5.55228 17 6 16.5523 6 16C6 15.4477 5.55228 15 5 15C4.44772 15 4 15.4477 4 16C4 16.5523 4.44772 17 5 17ZM5 19C6.65685 19 8 17.6569 8 16C8 14.3431 6.65685 13 5 13C3.34315 13 2 14.3431 2 16C2 17.6569 3.34315 19 5 19ZM19 17C19.5523 17 20 16.5523 20 16C20 15.4477 19.5523 15 19 15C18.4477 15 18 15.4477 18 16C18 16.5523 18.4477 17 19 17ZM19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16C16 17.6569 17.3431 19 19 19Z" fill="#80EBB6"/>
+</svg>
diff --git a/examples/bluetooth/heartrate-game/images/search.svg b/examples/bluetooth/heartrate-game/images/search.svg
new file mode 100644
index 00000000..9af5fe4d
--- /dev/null
+++ b/examples/bluetooth/heartrate-game/images/search.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2929 14.2929C14.6834 13.9024 15.3166 13.9024 15.7071 14.2929L21.7071 20.2929C22.0976 20.6834 22.0976 21.3166 21.7071 21.7071C21.3166 22.0976 20.6834 22.0976 20.2929 21.7071L14.2929 15.7071C13.9024 15.3166 13.9024 14.6834 14.2929 14.2929Z" fill="#80EBB6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4ZM2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10Z" fill="#80EBB6"/>
+</svg>