diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 3b95781..7636e46 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -27,6 +27,7 @@ qt_add_qml_module(applinux QML_FILES Main.qml BatteryIndicator.qml + SegmentedControl.qml ) # Add the resource file diff --git a/linux/Main.qml b/linux/Main.qml index 065a335..a756d48 100644 --- a/linux/Main.qml +++ b/linux/Main.qml @@ -21,7 +21,7 @@ ApplicationWindow { Column { spacing: 5 - opacity: airPodsTrayApp.isLeftPodInEar ? 1 : 0.5 + opacity: airPodsTrayApp.leftPodInEar ? 1 : 0.5 visible: airPodsTrayApp.battery.leftPodAvailable Image { @@ -36,7 +36,6 @@ ApplicationWindow { } BatteryIndicator { - visible: airPodsTrayApp.leftPodAvailable batteryLevel: airPodsTrayApp.battery.leftPodLevel isCharging: airPodsTrayApp.battery.leftPodCharging darkMode: true @@ -46,7 +45,7 @@ ApplicationWindow { Column { spacing: 5 - opacity: airPodsTrayApp.isRightPodInEar ? 1 : 0.5 + opacity: airPodsTrayApp.rightPodInEar ? 1 : 0.5 visible: airPodsTrayApp.battery.rightPodAvailable Image { @@ -62,7 +61,6 @@ ApplicationWindow { } BatteryIndicator { - visible: airPodsTrayApp.rightPodAvailable batteryLevel: airPodsTrayApp.battery.rightPodLevel isCharging: airPodsTrayApp.battery.rightPodCharging darkMode: true @@ -87,7 +85,6 @@ ApplicationWindow { } BatteryIndicator { - visible: airPodsTrayApp.caseAvailable batteryLevel: airPodsTrayApp.battery.caseLevel isCharging: airPodsTrayApp.battery.caseCharging darkMode: true @@ -95,19 +92,21 @@ ApplicationWindow { } } + SegmentedControl { + id: noiseControlMode + // width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"] + currentIndex: airPodsTrayApp.noiseControlMode + onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex + } + Text { id: earDetectionStatus text: "Ear Detection Status: " + airPodsTrayApp.earDetectionStatus color: "#ffffff" } - ComboBox { - id: noiseControlMode - model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"] - currentIndex: airPodsTrayApp.noiseControlMode - onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex - } - Switch { id: caToggle text: "Conversational Awareness" @@ -116,6 +115,7 @@ ApplicationWindow { } Slider { + id: noiseLevelSlider visible: airPodsTrayApp.adaptiveModeActive from: 0 to: 100 @@ -125,8 +125,8 @@ ApplicationWindow { property Timer debounceTimer: Timer { interval: 500 // 500ms delay after last change onTriggered: { - if (!parent.pressed) { - airPodsTrayApp.setAdaptiveNoiseLevel(parent.value) + if (!noiseLevelSlider.pressed) { + airPodsTrayApp.setAdaptiveNoiseLevel(noiseLevelSlider.value) } } } diff --git a/linux/SegmentedControl.qml b/linux/SegmentedControl.qml new file mode 100644 index 0000000..77b0a4c --- /dev/null +++ b/linux/SegmentedControl.qml @@ -0,0 +1,104 @@ +pragma ComponentBehavior: Bound + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Control { + id: root + + // Properties + property var model: ["Option 1", "Option 2"] // Default model + property int currentIndex: 0 + + // Colors using system palette + readonly property color backgroundColor: palette.light + readonly property color selectedColor: palette.highlight + readonly property color textColor: palette.buttonText + readonly property color selectedTextColor: palette.highlightedText + + // System palette + SystemPalette { + id: palette + } + + // Internal properties + padding: 6 + implicitHeight: 32 + // Removed: implicitWidth: Math.max(200, model.length * 100) + + // Set focus policy to enable keyboard navigation + focusPolicy: Qt.StrongFocus + activeFocusOnTab: true + + // Styling + background: Rectangle { + radius: height / 2 + color: root.backgroundColor + border.width: root.activeFocus ? 1 : 0 + border.color: root.selectedColor + } + + contentItem: Row { + spacing: root.padding + + Repeater { + model: root.model + + delegate: Button { + id: segmentButton + required property int index + required property string modelData + text: modelData + // Removed: width: (root.availableWidth - (root.model.length - 1) * root.padding) / root.model.length + height: root.availableHeight + focusPolicy: Qt.NoFocus // Let the root control handle focus + + background: Rectangle { + radius: height / 2 + color: root.currentIndex === segmentButton.index ? root.selectedColor : "transparent" + border.width: 0 + + Behavior on color { + ColorAnimation { + duration: 600 + easing.type: Easing.OutQuad + } + } + } + + onClicked: { + if (root.currentIndex !== index) { + root.currentIndex = index; + } + } + } + } + } + + // Handle key events for navigation + Keys.onPressed: event => { + if (event.key === Qt.Key_Left) { + if (root.currentIndex > 0) { + root.currentIndex--; + event.accepted = true; + } + } else if (event.key === Qt.Key_Right) { + if (root.currentIndex < root.model.length - 1) { + root.currentIndex++; + event.accepted = true; + } + } else if (event.key === Qt.Key_Home) { + root.currentIndex = 0; + event.accepted = true; + } else if (event.key === Qt.Key_End) { + root.currentIndex = root.model.length - 1; + event.accepted = true; + } else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) { + const index = event.key - Qt.Key_1; + if (index <= root.model.length) { + root.currentIndex = index; + event.accepted = true; + } + } + } +} diff --git a/linux/battery.hpp b/linux/battery.hpp index 3174afb..4bd45a1 100644 --- a/linux/battery.hpp +++ b/linux/battery.hpp @@ -104,7 +104,12 @@ public: // Set primary and secondary pods based on order if (!podsInPacket.isEmpty()) { - primaryPod = podsInPacket[0]; // First pod is primary + Component newPrimaryPod = podsInPacket[0]; // First pod is primary + if (newPrimaryPod != primaryPod) + { + primaryPod = newPrimaryPod; + emit primaryChanged(); + } } if (podsInPacket.size() >= 2) { @@ -166,6 +171,7 @@ public: signals: void batteryStatusChanged(); + void primaryChanged(); private: bool isStatus(Component component, BatteryStatus status) const diff --git a/linux/main.cpp b/linux/main.cpp index ffa6def..5dad02d 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -25,8 +25,8 @@ class AirPodsTrayApp : public QObject { Q_PROPERTY(bool oneOrMorePodsInCase READ oneOrMorePodsInCase NOTIFY earDetectionStatusChanged) Q_PROPERTY(QString podIcon READ podIcon NOTIFY modelChanged) Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged) - Q_PROPERTY(bool isLeftPodInEar READ isLeftPodInEar NOTIFY earDetectionStatusChanged) - Q_PROPERTY(bool isRightPodInEar READ isRightPodInEar NOTIFY earDetectionStatusChanged) + Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged) + Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged) public: AirPodsTrayApp(bool debugMode) @@ -55,6 +55,8 @@ public: mediaController->initializeMprisInterface(); mediaController->followMediaChanges(); + connect(m_battery, &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged); + // load conversational awareness state setConversationalAwareness(loadConversationalAwarenessState()); @@ -476,6 +478,9 @@ private slots: m_model = parseModelNumber(modelNumber); + emit modelChanged(); + m_model = parseModelNumber(modelNumber); + emit modelChanged(); emit deviceNameChanged(m_deviceName); @@ -598,10 +603,13 @@ private slots: char secondary = data[7]; m_primaryInEar = primary == 0x00; m_secoundaryInEar = secondary == 0x00; + m_primaryInEar = primary == 0x00; + m_secoundaryInEar = secondary == 0x00; m_earDetectionStatus = QString("Primary: %1, Secondary: %2") .arg(getEarStatus(primary), getEarStatus(secondary)); LOG_INFO("Ear detection status: " << m_earDetectionStatus); emit earDetectionStatusChanged(m_earDetectionStatus); + emit primaryChanged(); } // Battery Status else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS)) @@ -854,6 +862,7 @@ signals: void adaptiveNoiseLevelChanged(int level); void deviceNameChanged(const QString &name); void modelChanged(); + void primaryChanged(); private: QSystemTrayIcon *trayIcon;