From 93995959d01d0de275a517f9c89e687ce81cef69 Mon Sep 17 00:00:00 2001 From: unrealdragon Date: Wed, 23 Sep 2026 17:36:00 +0530 Subject: [PATCH] QoL Fixes and Changes --- CMakeLists.txt | 3 + ColorInput.qml | 21 ++ CustomCheckBox.qml | 17 + Main.qml | 155 ++++++++- RequestResponsePopup.qml | 30 ++ SettingsBarIcon.qml | 5 +- SettingsPopup.qml | 660 +++++++++++++++++++++++++++++---------- WaterTank.qml | 147 +++++---- configinterface.cpp | 7 + configinterface.h | 2 + requestworker.cpp | 99 +++++- requestworker.h | 9 +- wifihelper.cpp | 7 + 13 files changed, 919 insertions(+), 243 deletions(-) create mode 100644 ColorInput.qml create mode 100644 CustomCheckBox.qml create mode 100644 RequestResponsePopup.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 55fd9f6..957c9c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,9 @@ qt_add_qml_module(appWaterLevelRewrite wifihelper.cpp RESOURCES media.qrc + QML_FILES CustomCheckBox.qml + QML_FILES RequestResponsePopup.qml + QML_FILES ColorInput.qml ) if(APPLE) diff --git a/ColorInput.qml b/ColorInput.qml new file mode 100644 index 0000000..a4cebe7 --- /dev/null +++ b/ColorInput.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Controls +import QtQuick.Controls.Material + +TextField { + id: color_text + text: "F05FAF5F" + property alias color_text: base.color + Rectangle { + id: base + anchors { + right: parent.right + rightMargin: 10 + verticalCenter: parent.verticalCenter + } + width: 10 + height: parent.height / 2 + color: "#" + color_text.text + } +} diff --git a/CustomCheckBox.qml b/CustomCheckBox.qml new file mode 100644 index 0000000..0cdbb38 --- /dev/null +++ b/CustomCheckBox.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls.Basic + +CheckBox { + id: control + property color font_color: "#0000FF00" + property int font_point_size: 22 + font.pointSize: font_point_size + checked: true + contentItem: Text { + text: control.text + font: control.font + color: control.font_color + verticalAlignment: Text.AlignVCenter + leftPadding: control.indicator.width + control.spacing + } +} diff --git a/Main.qml b/Main.qml index b3be173..2cfde8a 100644 --- a/Main.qml +++ b/Main.qml @@ -5,8 +5,8 @@ import QtQuick.Controls import QtQuick.Controls.Material Window { - width: 640 - height: 1080 + width: 960 + height: 1400 visible: true title: qsTr("Water Level Rewrite") id: root @@ -24,12 +24,36 @@ Window { property string status: "Initializing" property string connection_text_string: "Reading WiFi Status..." property string alarm_status_text: "" + property double water_tank_height_factor: 1.3 + property double water_tank_width_factor: 2 property var root_config: ({}) + property var base_config: { + "max": 0, + "min": 0, + "alert": 0, + "interval": 0, + "url": "none", + "water_tank_width": 2, + "water_tank_height": 1.3, + "rate_of_fill_enable": true, + "current_reading_enable": true, + "percentage_enable": true, + "filling_status_enable": true, + "expected_to_fill_enable": true, + "accent_color": "F05FAF5F", + "dark_accent_color": "FF5F7F5F", + "danger_color": "F0AF2F1F", + "water_color": "F00F7FAF", + "water_color_above_alert": "F00FAF0F", + "background_color": "1F1F1F", + "font_normal_text_size": 16, + "font_enlarged_text_size": 20 + } Rectangle { anchors.fill: parent color: root.background_color - + id: root_rect Rectangle { id: top_bar height: Math.min(root.height / 12, 100) @@ -41,6 +65,7 @@ Window { } color: "#0A0F0F0F" SettingsBarIcon { + id: settings_icon height: top_bar.height - 20 width: height anchors { @@ -57,12 +82,40 @@ Window { settings_popup.open() } } + + SettingsBarIcon { + height: top_bar.height - 20 + width: height + anchors { + top: parent.top + left: parent.left + bottomMargin: 5 + topMargin: 10 + leftMargin: 5 + } + text: "🖹" + color: "#0A0F0F0F" + radius: 100 + accent_color: root.accent_color + onOpen: { + request_response_popup.open() + } + } + } + + RequestResponsePopup { + id: request_response_popup + accent_color: root.accent_color + font_enlarged_text_size: 20 + } SettingsPopup { id: settings_popup accent_color: root.accent_color - font_enlarged_text_size: 20 + background_color: root.background_color + font_enlarged_text_size: root.font_enlarged_text_size + font_normal_text_size: root.font_normal_text_size function setConfig(new_config: var) { settings_popup.config.max = new_config.max settings_popup.config.min = new_config.min @@ -73,6 +126,9 @@ Window { console.log("Saving config: ", config) config_interface.saveConfig(config); } + onDeleteConfig: () => { + config_interface.deleteConfig(); + } } Text { @@ -80,7 +136,6 @@ Window { anchors { top: top_bar.bottom topMargin: 20 - bottom: tank.top horizontalCenter: parent.horizontalCenter } text: root.connection_text_string @@ -91,20 +146,24 @@ Window { WaterTank { id: tank anchors { - top: top_bar.bottom + top: connection_text.bottom horizontalCenter: parent.horizontalCenter - topMargin: 100 + topMargin: 40 } - height: (parent.height - top_bar.height) / 2 - width: parent.width / 2.2 + height: (root_rect.height) / root.water_tank_height_factor + width: root_rect.width / root.water_tank_width_factor base_color: root.background_color distance: 0 tank_lid_color: "#F0121212" tank_base_color: "#0FFFFFFF" - water_level_color: "#F00F7FAF" + water_level_color: root.water_color tank_base_border_color: "#F0121212" + water_color_above_alert: root.water_color_above_alert min_thres_input: 0 max_thres_input: 0 + text_color: root.accent_color + font_normal_text_size: root.font_normal_text_size + font_enlarged_text_size: root.font_enlarged_text_size } @@ -194,7 +253,7 @@ Window { if(start_alarm.mode === 0) { requester.startAlarm(); - root.alarm_status_text = "Started Alarm; Waiting for Trigger: " + root.root_config.alert + "%" + root.alarm_status_text = "Started Alarm; Waiting for Trigger: " + root.root_config.alert } else { requester.stopAlarm(); @@ -213,14 +272,16 @@ Window { onStatusChanged: () => { root.status = requester.status; } - onNetworkReply: (water_level, fill_rate, expected) => { + onNetworkReply: (water_level, fill_rate, expected, data) => { tank.setDistance(water_level); tank.rate_of_fill = fill_rate; tank.expected_to_fill = expected; status.color = root.accent_color; + request_response_popup.request_response_string = data; } - onRequestFailed: () => { + onRequestFailed: (data) => { status.color = root.danger_color; + request_response_popup.request_response_string = data; } onConnectionText: (text, error) => { @@ -238,17 +299,83 @@ Window { id: config_interface fileName: "config.json" onConfig: (config) => { - settings_popup.config = config + settings_popup.config = config; console.log("Loading initial config"); setConfig(config); if(root.status === "Initializing") { root.status = "Press Connect."; } } + onConfigDeleted: () => { + let config = base_config; + settings_popup.config = config; + console.log("Loading initial config"); + setConfig(config); + if(root.status === "Initializing") { + root.status = "Press Connect."; + } + } + function setConfig(config) { tank.setConfig(config); requester.setConfig(config); root.root_config = config; + if(config.water_tank_height === undefined) { + config.water_tank_height = 2 + } + if(config.water_tank_width === undefined) { + config.water_tank_width = 2.2 + } + if(config.rate_of_fill_enable === undefined) { + config.rate_of_fill_enable = true + } + if(config.current_reading_enable === undefined) { + config.current_reading_enable = true + } + if(config.percentage_enable === undefined) { + config.percentage_enable = true + } + if(config.filling_status_enable === undefined) { + config.filling_status_enable = true + } + if(config.expected_to_fill_enable === undefined) { + config.expected_to_fill_enable = true + } + if(config.font_enlarged_text_size === undefined) { + config.font_enlarged_text_size = 20 + } + if(config.font_normal_text_size === undefined) { + config.font_normal_text_size = 16 + } + if(config.accent_color === undefined) { + config.accent_color = "F05FAF5F" + } + if(config.danger_color === undefined) { + config.danger_color = "F0AF2F1F" + } + if(config.dark_accent_color === undefined) { + config.dark_accent_color = "FF5F7F5F" + } + if(config.water_color === undefined) { + config.water_color = "F00F7FAF" + } + if(config.water_color_above_alert === undefined) { + config.water_color_above_alert = "F00FAF0F" + } + if(config.background_color === undefined) { + config.background_color = "1F1F1F" + } + root.water_tank_height_factor = config.water_tank_height + root.water_tank_width_factor = config.water_tank_width + root.accent_color = "#" + config.accent_color + root.danger_color = "#" + config.danger_color + root.dark_accent_color = "#" + config.dark_accent_color + root.water_color = "#" + config.water_color + root.water_color_above_alert = "#" + config.water_color_above_alert + root.background_color = "#" + config.background_color + root.font_enlarged_text_size = config.font_enlarged_text_size + root.font_normal_text_size = config.font_normal_text_size + } } onClosing: { diff --git a/RequestResponsePopup.qml b/RequestResponsePopup.qml new file mode 100644 index 0000000..468bcda --- /dev/null +++ b/RequestResponsePopup.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Controls +import QtQuick.Controls.Material + +Popup { + id: request_response_popup + property color accent_color: "#00000000" + property int font_enlarged_text_size: 20 + property int font_normal_text_size: 16 + property string request_response_string: "" + background: Rectangle { + color: "#F0121212" + radius: 5 + border.color: request_response_popup.accent_color + border.width: 1 + } + height: Math.max(parent.height / 1.6, contentHeight) + width: parent.width / 1.2 + anchors.centerIn: Overlay.overlay + ScrollView { + id: view + anchors.fill: parent + TextArea { + text: request_response_string + font.pointSize: font_normal_text_size + color: request_response_popup.accent_color + } + } +} diff --git a/SettingsBarIcon.qml b/SettingsBarIcon.qml index d360331..35072b7 100644 --- a/SettingsBarIcon.qml +++ b/SettingsBarIcon.qml @@ -8,6 +8,7 @@ Rectangle { signal open; property color accent_color: "#00000000" property int font_enlarged_text_size: 20 + property string text: "⚙" Behavior on color { ColorAnimation { duration: 200 @@ -15,8 +16,10 @@ Rectangle { } Text { id: settings_text - text: "⚙" + text: settings_rect.text anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter color: settings_rect.accent_color font.pointSize: settings_rect.font_enlarged_text_size } diff --git a/SettingsPopup.qml b/SettingsPopup.qml index b62624f..47d555b 100644 --- a/SettingsPopup.qml +++ b/SettingsPopup.qml @@ -6,15 +6,32 @@ import QtQuick.Controls.Material Popup { id: settings_popup signal saveConfig(config: var) + signal deleteConfig() property color accent_color: "#00000000" property int font_enlarged_text_size: 20 property int font_normal_text_size: 16 + property color background_color: "#00000000" property var config: { "max": 0, "min": 0, "alert": 0, "interval": 0, - "url": "none" + "url": "none", + "water_tank_width": 2, + "water_tank_height": 1.3, + "rate_of_fill_enable": true, + "current_reading_enable": true, + "percentage_enable": true, + "filling_status_enable": true, + "expected_to_fill_enable": true, + "accent_color": "F05FAF5F", + "dark_accent_color": "FF5F7F5F", + "danger_color": "F0AF2F1F", + "water_color": "F00F7FAF", + "water_color_above_alert": "F00FAF0F", + "background_color": "1F1F1F", + "font_normal_text_size": 16, + "font_enlarged_text_size": 20 } background: Rectangle { @@ -31,170 +48,461 @@ Popup { anchors.fill: parent color: "#F0121212" - Text { - id: settings_label - text: "Settings" - width: parent.width + ScrollView { + id: rect_base_scroll_view anchors { top: parent.top horizontalCenter: parent.horizontalCenter + bottom: save_config.top + bottomMargin: 10 } - horizontalAlignment: Text.AlignHCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_enlarged_text_size - } + width: parent.width + clip: true + // Use contentWidth and contentHeight to define the scrollable area + contentWidth: availableWidth + contentHeight: settings_column.height - Text { - id: max_thres_label - text: "Max Threshold: " - anchors { - topMargin: 20 - top: settings_label.bottom - left: parent.left - verticalCenter: max_thres_input.verticalCenter + Column { + id: settings_column + width: rect_base_scroll_view.width + spacing: 20 + + Text { + id: settings_label + text: "Settings" + width: parent.width + horizontalAlignment: Text.AlignHCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_enlarged_text_size + } + + // Max Threshold + Row { + width: parent.width + spacing: 10 + Text { + text: "Max Threshold: " + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: max_thres_input.verticalCenter + } + + } + TextField { + id: max_thres_input + text: config.max + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Min Threshold + Row { + width: parent.width + spacing: 10 + Text { + text: "Min Threshold: " + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: min_thres_input.verticalCenter + } + + } + TextField { + id: min_thres_input + text: config.min + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Alert Threshold + Row { + width: parent.width + spacing: 10 + Text { + text: "Alert Threshold: " + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: alert_thres_input.verticalCenter + } + + } + TextField { + id: alert_thres_input + text: config.alert + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Update Interval + Row { + width: parent.width + spacing: 10 + Text { + text: "Update Interval: " + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: interval_input.verticalCenter + } + } + TextField { + id: interval_input + text: config.interval + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // URL + Row { + width: parent.width + spacing: 10 + Text { + text: "Url: " + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: http_url_input.verticalCenter + } + + } + TextField { + id: http_url_input + text: config.url + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + cursorPosition: 0 + } + } + + // Tank Image Width + Row { + width: parent.width + spacing: 10 + Text { + text: "Tank Image Width" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: water_tank_width_input.verticalCenter + } + + } + TextField { + id: water_tank_width_input + text: String(config.water_tank_width) + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + cursorPosition: 0 + } + } + + // Tank Image Height + Row { + width: parent.width + spacing: 10 + Text { + text: "Tank Image Height" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: water_tank_height_input.verticalCenter + } + } + TextField { + id: water_tank_height_input + text: String(config.water_tank_height) + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + cursorPosition: 0 + } + } + + // Accent Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Accent Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: accent_color_input.verticalCenter + } + } + ColorInput { + id: accent_color_input + text: config.accent_color + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Dark Accent Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Dark Accent Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: dark_accent_color_input.verticalCenter + } + } + ColorInput { + id: dark_accent_color_input + text: config.dark_accent_color + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Danger Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Danger Text Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: danger_color_input.verticalCenter + } + } + ColorInput { + id: danger_color_input + text: config.danger_color + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Water Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Water Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: water_color_input.verticalCenter + } + } + ColorInput { + id: water_color_input + text: config.water_color + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Alert Water Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Alert Water Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: water_color_above_alert_input.verticalCenter + } + } + ColorInput { + id: water_color_above_alert_input + text: config.water_color_above_alert + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Background Color + Row { + width: parent.width + spacing: 10 + Text { + text: "Backgound Color" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: background_color_input.verticalCenter + } + } + ColorInput { + id: background_color_input + text: config.background_color + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + } + } + + // Normal Font Size + Row { + width: parent.width + spacing: 10 + Text { + text: "Font Size" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: font_normal_text_size_input.verticalCenter + } + } + TextField { + id: font_normal_text_size_input + text: String(config.font_normal_text_size) + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + cursorPosition: 0 + } + } + + // Header Font Size + Row { + width: parent.width + spacing: 10 + Text { + text: "Header Font Size" + width: parent.width * 0.4 + verticalAlignment: Text.AlignVCenter + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + anchors { + verticalCenter: font_enlarged_text_size_input.verticalCenter + } + } + TextField { + id: font_enlarged_text_size_input + text: String(config.font_enlarged_text_size) + width: parent.width * 0.55 + color: settings_popup.accent_color + font.pointSize: settings_popup.font_normal_text_size + cursorPosition: 0 + } + } + + // CheckBox + CustomCheckBox { + id: current_reading_enable + checked: config.current_reading_enable + font_color: settings_popup.accent_color + font_point_size: settings_popup.font_normal_text_size + text: "Enable Current Reading" + leftPadding: 0 + } + + // CheckBox + CustomCheckBox { + id: percentage_enable + checked: config.percentage_enable + font_color: settings_popup.accent_color + font_point_size: settings_popup.font_normal_text_size + text: "Enable Percentage Reading" + leftPadding: 0 + } + + // CheckBox + CustomCheckBox { + id: filling_status_enable + checked: config.filling_status_enable + font_color: settings_popup.accent_color + font_point_size: settings_popup.font_normal_text_size + text: "Enable Filling Status" + leftPadding: 0 + } + + // CheckBox + CustomCheckBox { + id: rate_of_fill_enable + checked: config.rate_of_fill_enable + font_color: settings_popup.accent_color + font_point_size: settings_popup.font_normal_text_size + text: "Enable Text Rate of Fill" + leftPadding: 0 + } + + // CheckBox + CustomCheckBox { + id: expected_to_fill_enable + checked: config.expected_to_fill_enable + font_color: settings_popup.accent_color + font_point_size: settings_popup.font_normal_text_size + text: "Enable Expected to fill" + leftPadding: 0 + } + + ConnectButton { + id: delete_config_button + width: parent.width / 1.5 + height: 80 + anchors { + horizontalCenter: parent.horizontalCenter + } + + font_color: settings_popup.accent_color + font_enlarged_text_size: settings_popup.font_enlarged_text_size + font_normal_text_size: settings_popup.font_normal_text_size + color: settings_popup.background_color + accent_color: settings_popup.accent_color + text: "Delete/Reset Config" + text2: text + onConnect: { + settings_popup.deleteConfig(); + } + } } - height: max_thres_input.height - verticalAlignment: Text.AlignVCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size } - - TextField { - id: max_thres_input - text: config.max - anchors { - right: parent.right - rightMargin: 5 - top: settings_label.bottom - topMargin: 20 - } - width: parent.width / 2 - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - - Text { - id: min_thres_label - text: "Min Threshold: " - anchors { - topMargin: 20 - top: max_thres_label.bottom - left: parent.left - verticalCenter: min_thres_input.verticalCenter - } - height: min_thres_input.height - verticalAlignment: Text.AlignVCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - TextField { - id: min_thres_input - text: config.min - anchors { - right: parent.right - rightMargin: 5 - top: max_thres_label.bottom - topMargin: 20 - } - width: parent.width / 2 - color: root.accent_color - font.pointSize: root.font_normal_text_size - } - - - Text { - id: alert_thres_label - text: "Alert Threshold: " - anchors { - topMargin: 20 - top: min_thres_label.bottom - left: parent.left - verticalCenter: alert_thres_input.verticalCenter - } - height: alert_thres_input.height - verticalAlignment: Text.AlignVCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - TextField { - id: alert_thres_input - text: config.alert - anchors { - right: parent.right - rightMargin: 5 - top: min_thres_label.bottom - topMargin: 20 - } - width: parent.width / 2 - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - Text { - id: interval_label - text: "Update Interval: " - anchors { - topMargin: 20 - top: alert_thres_label.bottom - left: parent.left - verticalCenter: interval_input.verticalCenter - } - height: interval_input.height - verticalAlignment: Text.AlignVCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - - TextField { - id: interval_input - text: config.interval - anchors { - right: parent.right - rightMargin: 5 - top: alert_thres_input.bottom - topMargin: 20 - } - width: parent.width / 2 - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - Text { - id: http_url_label - text: "Url: " - anchors { - topMargin: 20 - top: interval_label.bottom - left: parent.left - verticalCenter: http_url_input.verticalCenter - } - height: http_url_input.height - verticalAlignment: Text.AlignVCenter - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - } - - TextField { - id: http_url_input - text: config.url - anchors { - right: parent.right - rightMargin: 20 - leftMargin: 20 - top: interval_input.bottom - topMargin: 20 - } - width: Math.max(parent.width / 2, parent.width - http_url_label.width - anchors.leftMargin) - color: settings_popup.accent_color - font.pointSize: settings_popup.font_normal_text_size - cursorPosition: 0 - } - Rectangle { id: save_config anchors { @@ -237,7 +545,42 @@ Popup { let alert = parseFloat(alert_thres_input.text); let url = http_url_input.text; let interval = parseInt(interval_input.text); - var new_config = {"max": max, "min": min, "alert": alert, "url": url, "interval": interval} + let image_width = parseFloat(water_tank_width_input.text); + let image_height = parseFloat(water_tank_height_input.text); + let current_reading = current_reading_enable.checked; + let percentage_reading = percentage_enable.checked + let filling_status = filling_status_enable.checked; + let rate_of_fill = rate_of_fill_enable.checked; + let expected_to_fill = expected_to_fill_enable.checked; + + let normal_font_size = parseInt(font_normal_text_size_input.text); + let enlarged_font_size = parseInt(font_enlarged_text_size_input.text); + + let accent_color = accent_color_input.text; + let dark_accent_color = dark_accent_color_input.text; + let danger_color = danger_color_input.text; + let water_color = water_color_input.text; + let water_color_above_alert = water_color_above_alert_input.text; + let background_color = background_color_input.text; + + var new_config = {"max": max, "min": min, "alert": alert, "url": url, + "interval": interval, + "water_tank_height": image_height, + "water_tank_width": image_width, + "current_reading_enable": current_reading, + "percentage_enable": percentage_reading, + "filling_status_enable": filling_status, + "rate_of_fill_enable": rate_of_fill, + "expected_to_fill_enable": expected_to_fill, + "font_normal_text_size": normal_font_size, + "font_enlarged_text_size": enlarged_font_size, + "accent_color": accent_color, + "dark_accent_color": dark_accent_color, + "danger_color": danger_color, + "water_color": water_color, + "water_color_above_alert": water_color_above_alert, + "background_color": background_color + } settings_popup.saveConfig(new_config) settings_popup.close(); } @@ -245,7 +588,6 @@ Popup { save_config.color = settings_rect_pressed_prev } } - } } } \ No newline at end of file diff --git a/WaterTank.qml b/WaterTank.qml index 6e20cb1..9814428 100644 --- a/WaterTank.qml +++ b/WaterTank.qml @@ -13,6 +13,8 @@ Item { property color tank_base_border_color: "#F0121212" property color text_color: "#F05FAF5F" property double height_max: tank_base.height - 10 + property int font_normal_text_size: 0 + property int font_enlarged_text_size: 0 property double current_level: 0 property double percentage_filled: 0 property int distance: 0 @@ -21,6 +23,11 @@ Item { property double rate_of_fill: 0 property double expected_to_fill: 0 property double alert_threshold: 0 + property bool enable_current_reading: true + property bool enable_percentage_reading: true + property bool enable_filling_status: true + property bool enable_rate_of_fill: true + property bool enable_expected_to_fill: true Rectangle { id: base @@ -30,7 +37,7 @@ Item { id: tank_lid color: root.tank_lid_color anchors { - bottom: tank_base.top + top: parent.top horizontalCenter: tank_base.horizontalCenter bottomMargin: -5 } @@ -42,9 +49,12 @@ Item { Rectangle { id: tank_base color: root.tank_base_color - anchors.centerIn: parent + anchors { + top: tank_lid.bottom + horizontalCenter: parent.horizontalCenter + } width: parent.width - height: parent.height + height: parent.height * 0.55 border.color: root.tank_base_border_color border.width: 5 radius: 4 @@ -72,74 +82,93 @@ Item { } } - Text { - id: water_level_reading - text: "Current Reading: " + (root.distance / 10)+ " cm" - font.pointSize: 16 - color: root.text_color + ScrollView { + id: rect_base_scroll_view anchors { margins: 5 top: tank_base.bottom + bottom: parent.bottom horizontalCenter: parent.horizontalCenter } - horizontalAlignment: Text.AlignHCenter + width: parent.width + + contentWidth: parent.width + contentHeight: settings_column.implicitHeight + Column { + id: settings_column + spacing: 10 + width: rect_base_scroll_view.width + Text { + id: water_level_reading + text: "Current Reading: " + (root.distance / 10)+ " cm" + font.pointSize: root.font_normal_text_size + color: root.text_color + visible: enable_current_reading + anchors { + horizontalCenter: parent.horizontalCenter + } + horizontalAlignment: Text.AlignHCenter + } + + Text { + id: water_level_percentage + text: "Percentage: " + root.percentage_filled.toFixed(2) + " %" + font.pointSize: root.font_normal_text_size + visible: enable_filling_status + color: root.text_color + anchors { + horizontalCenter: parent.horizontalCenter + } + horizontalAlignment: Text.AlignHCenter + } + + Text { + id: fill_status + text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging") + font.pointSize: root.font_normal_text_size + color: root.text_color + visible: enable_filling_status + anchors { + horizontalCenter: parent.horizontalCenter + } + horizontalAlignment: Text.AlignHCenter + } + Text { + id: rate_of_fill_label + text: "Rate of fill: " + root.rate_of_fill + " cm/s" + font.pointSize: root.font_normal_text_size + color: root.text_color + visible: enable_rate_of_fill + anchors { + horizontalCenter: parent.horizontalCenter + } + horizontalAlignment: Text.AlignHCenter + } + Text { + id: expected_to_fill_label + text: "Expected to fill in: " + root.expected_to_fill + " s" + font.pointSize: root.font_normal_text_size + color: root.text_color + visible: (root.rate_of_fill > 0 && enable_expected_to_fill === true) ? true : false + anchors { + horizontalCenter: parent.horizontalCenter + } + horizontalAlignment: Text.AlignHCenter + } + } } - Text { - id: water_level_percentage - text: "Percentage: " + root.percentage_filled + " %" - font.pointSize: 16 - color: root.text_color - anchors { - margins: 5 - top: water_level_reading.bottom - horizontalCenter: parent.horizontalCenter - } - horizontalAlignment: Text.AlignHCenter - } - - Text { - id: fill_status - text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging") - font.pointSize: 16 - color: root.text_color - anchors { - margins: 5 - top: water_level_percentage.bottom - horizontalCenter: parent.horizontalCenter - } - horizontalAlignment: Text.AlignHCenter - } - Text { - id: rate_of_fill_label - text: "Rate of fill: " + root.rate_of_fill + " cm/s" - font.pointSize: 16 - color: root.text_color - anchors { - margins: 5 - top: fill_status.bottom - horizontalCenter: parent.horizontalCenter - } - horizontalAlignment: Text.AlignHCenter - } - Text { - id: expected_to_fill_label - text: "Expected to fill in: " + root.expected_to_fill + " s" - font.pointSize: 16 - color: root.text_color - visible: (root.rate_of_fill > 0) ? true : false - anchors { - margins: 5 - top: rate_of_fill_label.bottom - horizontalCenter: parent.horizontalCenter - } - horizontalAlignment: Text.AlignHCenter - } } function setConfig(config: var) { min_thres_input = config.min; max_thres_input = config.max; alert_threshold = config.alert; + enable_current_reading = config.current_reading_enable; + enable_percentage_reading = config.percentage_enable; + enable_filling_status = config.filling_status_enable; + enable_rate_of_fill = config.rate_of_fill_enable; + enable_expected_to_fill = config.expected_to_fill_enable; + } function setDistance(distance) { diff --git a/configinterface.cpp b/configinterface.cpp index 258560f..8f40d45 100644 --- a/configinterface.cpp +++ b/configinterface.cpp @@ -2,6 +2,7 @@ #include #include +#include ConfigInterface::ConfigInterface(QObject *parent) : QObject{parent} @@ -37,6 +38,12 @@ void ConfigInterface::setFileName(const QString &newFileName) emit fileNameChanged(); } +void ConfigInterface::deleteConfig() +{ + std::filesystem::remove(std::filesystem::path(m_fileName.toStdString())); + emit configDeleted(); +} + void ConfigInterface::saveConfig(QJsonObject config) { QJsonDocument doc; diff --git a/configinterface.h b/configinterface.h index 606b496..e3cb75e 100644 --- a/configinterface.h +++ b/configinterface.h @@ -19,7 +19,9 @@ public: signals: void config(QJsonObject config); void fileNameChanged(); + void configDeleted(); public slots: + void deleteConfig(); void saveConfig(QJsonObject config); private: QString m_fileName; diff --git a/requestworker.cpp b/requestworker.cpp index c13d223..308c73b 100644 --- a/requestworker.cpp +++ b/requestworker.cpp @@ -4,6 +4,7 @@ #include #include #include +#include RequestWorker::RequestWorker(QObject *parent) : QObject{parent} @@ -63,6 +64,30 @@ void RequestWorker::stop() qDebug() << "Stopping" << timer->isActive(); } +QString RequestWorker::getRequestResponseString(QNetworkReply *rep, QString rep_data) +{ + if(!rep) { + return "No Reply"; + } + QNetworkRequest req = rep->request(); + QString errorString = rep->errorString(); + QString resp = "GET " + req.url().toString() + "\n"; + for(auto &key: req.rawHeaderList()) { + QByteArray value = req.rawHeader(key); + resp += key + ": " + value + "\n"; + } + qDebug() << "Raw header list size: " << req.rawHeaderList().size(); + QString ret; + ret = "Request:\n"; + ret += resp; + ret += "Response:\n"; + if(rep_data == "") + ret += errorString; + else + ret += rep_data; + return ret; +} + void RequestWorker::pollWiFi() { #ifdef Q_OS_ANDROID @@ -90,8 +115,53 @@ void RequestWorker::gotWiFiSSID(QString ssid) void RequestWorker::playAlarm() { - if(player_started) + if (player_started) return; + + // Step 1: Copy the qrc resource to a local cache file so Android can read it +#ifdef Q_OS_ANDROID + QString qrcPath = ":/sounds/echo.mp3"; + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/echo.mp3"; + + if (QFile::exists(cachePath)) { + QFile::remove(cachePath); + } + + if (QFile::copy(qrcPath, cachePath)) { + qDebug() << "Successfully copied resource to cache:" << cachePath; + } else { + qDebug() << "Failed to copy resource to cache path!"; + return; + } + + // Step 2: Use Android JNI to initialize and play via android.media.MediaPlayer + QJniEnvironment env; + + // Create new android.media.MediaPlayer() + QJniObject mediaPlayer("android/media/MediaPlayer"); + if (!mediaPlayer.isValid()) { + qDebug() << "Failed to create native Android MediaPlayer!"; + return; + } + + // Set data source using the local cache file path + QJniObject jPath = QJniObject::fromString(cachePath); + mediaPlayer.callMethod("setDataSource", "(Ljava/lang/String;)V", jPath.object()); + + // Enable looping + mediaPlayer.callMethod("setLooping", "(Z)V", true); + + // Prepare and start playback + mediaPlayer.callMethod("prepare"); + mediaPlayer.callMethod("start"); + + // Store a global reference so it doesn't get garbage collected immediately + m_androidMediaPlayer = new QJniObject(mediaPlayer.object()); + + player_started = true; + qDebug() << "Native Android alarm started successfully."; + return; +#endif if(player) { player->deleteLater(); player = nullptr; @@ -122,6 +192,7 @@ void RequestWorker::makeRequest() if(stop_timer) return; QNetworkRequest req; + req.setRawHeader("Testing", "Something"); req.setUrl(this->url); disconnect(manager, nullptr, nullptr, nullptr); connect(manager, &QNetworkAccessManager::finished, this, &RequestWorker::requestFinished); @@ -133,15 +204,16 @@ void RequestWorker::makeRequest() void RequestWorker::requestFinished(QNetworkReply *rep) { QByteArray data = rep->readAll(); - rep->deleteLater(); - rep = nullptr; auto now = std::chrono::high_resolution_clock::now(); auto time_diff = std::chrono::duration_cast(now - prev_time); if(data.size() == 0) { prev_time = now; timer->start(); - emit requestFailed(); + QString request_response_string = getRequestResponseString(rep); + emit requestFailed(request_response_string); emit setStatus("Invalid Response; Resending Request"); + rep->deleteLater(); + rep = nullptr; return; } unsigned int time_in_microseconds = time_diff.count(); @@ -151,10 +223,10 @@ void RequestWorker::requestFinished(QNetworkReply *rep) float rate_of_fill = ( float )(prev_distance - water_level) /(float) time_in_microseconds; rate_of_fill = rate_of_fill * 1'000'000; int expected_to_fill = (water_level - min_height) / rate_of_fill; - int percentage_filled = (float (max_height - water_level) / float (max_height)) *100; + int percentage_filled = (float (max_height - water_level) / float (max_height)) * 100; if(enable_alarm) { qDebug() << "Alarm enabled"; - if(percentage_filled >= alarm_thres) { + if(water_level <= alarm_thres) { playAlarm(); } } @@ -169,14 +241,19 @@ void RequestWorker::requestFinished(QNetworkReply *rep) qDebug() << "Percentage filled: " << percentage_filled; qDebug() << "Time difference: " << time_in_microseconds; prev_distance = water_level; - emit networkReply(data_water_level, rate_of_fill, expected_to_fill); + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + QString request_response_string = getRequestResponseString(rep, data); + request_response_string += "Timestamp:\n" + timestamp; + rep->deleteLater(); + rep = nullptr; + emit networkReply(data_water_level, rate_of_fill, expected_to_fill, request_response_string); if(stop_timer) { setStatus("Timer stopped."); return; } prev_time = now; timer->start(); - QString status = "Got Reply on: " + QDateTime::currentDateTime().toString("hh:mm:ss") + "; Waiting to Send."; + QString status = "Got Reply on: " + timestamp + "; Waiting to Send."; setStatus(status); } @@ -211,4 +288,10 @@ void RequestWorker::stopAlarm() audioOutput->deleteLater(); audioOutput = nullptr; } +#ifdef Q_OS_ANDROID + if(m_androidMediaPlayer) { + delete m_androidMediaPlayer; + m_androidMediaPlayer = nullptr; + } +#endif } diff --git a/requestworker.h b/requestworker.h index 6a87bbb..c759e44 100644 --- a/requestworker.h +++ b/requestworker.h @@ -13,6 +13,7 @@ #include #ifdef Q_OS_ANDROID #include "wifihelper.h" +#include #endif class RequestWorker : public QObject @@ -45,8 +46,8 @@ public: void setStatus(const QString &newStatus); signals: - void requestFailed(); - void networkReply(int distance, float rate_of_fill, int expected_to_fill); + void requestFailed(QString request_response_string); + void networkReply(int distance, float rate_of_fill, int expected_to_fill, QString data); void statusChanged(); void playMedia(); void connectionText(QString text, bool errored = false); @@ -57,6 +58,7 @@ public slots: void start(); void stop(); private slots: + QString getRequestResponseString(QNetworkReply *rep, QString rep_data = ""); void pollWiFi(); void gotWiFiSSID(QString ssid); void playAlarm(); @@ -64,6 +66,9 @@ private slots: void requestFinished(QNetworkReply *rep); private: QString m_status; +#ifdef Q_OS_ANDROID + QJniObject *m_androidMediaPlayer = nullptr; +#endif }; #endif // REQUESTWORKER_H diff --git a/wifihelper.cpp b/wifihelper.cpp index 739a9dd..835cea7 100644 --- a/wifihelper.cpp +++ b/wifihelper.cpp @@ -1,7 +1,9 @@ #include "wifihelper.h" #include #include +#ifdef Q_OS_ANDROID #include +#endif #include WiFiHelper::WiFiHelper(QObject *parent) @@ -11,6 +13,7 @@ WiFiHelper::WiFiHelper(QObject *parent) } QString WiFiHelper::getWifiSsid() { +#ifdef Q_OS_ANDROID // Get Android application context in Qt 6 QJniObject context = QNativeInterface::QAndroidApplication::context(); if (!context.isValid()) return QString(); @@ -46,9 +49,12 @@ QString WiFiHelper::getWifiSsid() { } return ssid; +#endif + return ""; } void WiFiHelper::requestPermissionsAndFetch() { +#ifdef Q_OS_ANDROID QLocationPermission locationPermission; locationPermission.setAccuracy(QLocationPermission::Accuracy::Precise); Qt::PermissionStatus status = qApp->checkPermission(locationPermission); @@ -67,4 +73,5 @@ void WiFiHelper::requestPermissionsAndFetch() { } }); } +#endif }