Skip to content
Merged
9 changes: 0 additions & 9 deletions src/app/modules/keycard_channel/constants.nim

This file was deleted.

2 changes: 0 additions & 2 deletions src/app/modules/keycard_channel/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import nimqml
import io_interface, view, controller
import app/global/global_singleton
import app/core/eventemitter
import ./constants

export io_interface
export constants

type
Module* = ref object of io_interface.AccessInterface
Expand Down
24 changes: 2 additions & 22 deletions src/app/modules/keycard_channel/view.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import nimqml

import ./io_interface
import ./constants

QtObject:
type
Expand All @@ -14,7 +13,6 @@ QtObject:
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.keycardChannelState = KEYCARD_CHANNEL_STATE_IDLE
result.setup()

proc load*(self: View) =
Expand All @@ -33,26 +31,8 @@ QtObject:
write = setKeycardChannelState
notify = keycardChannelStateChanged

# Constants for channel states (readonly properties for QML)
proc getStateIdle*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_IDLE
QtProperty[string] stateIdle:
read = getStateIdle

proc getStateWaitingForKeycard*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_WAITING_FOR_KEYCARD
QtProperty[string] stateWaitingForKeycard:
read = getStateWaitingForKeycard

proc getStateReading*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_READING
QtProperty[string] stateReading:
read = getStateReading

proc getStateError*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_ERROR
QtProperty[string] stateError:
read = getStateError
proc keycardDismissed*(self: View) {.slot.} =
self.setKeycardChannelState("")

proc setup(self: View) =
self.QObject.setup
Expand Down
222 changes: 222 additions & 0 deletions storybook/pages/KeycardChannelDrawerPage.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

import Storybook

import StatusQ.Core
import StatusQ.Core.Theme
import StatusQ.Controls
import StatusQ.Components

import shared.popups

SplitView {
id: root

orientation: Qt.Horizontal

Logs { id: logs }

// Helper timers for test scenarios
Timer {
id: timer1
interval: 1500
onTriggered: {
if (root.currentScenario === "success") {
logs.logEvent("Changing to reading state")
stateCombo.currentIndex = 2 // reading
timer2.start()
} else if (root.currentScenario === "error") {
logs.logEvent("Changing to reading state")
stateCombo.currentIndex = 2 // reading
timer2.start()
} else if (root.currentScenario === "quick") {
logs.logEvent("Quick change to reading")
stateCombo.currentIndex = 2 // reading
timer2.start()
}
}
}

Timer {
id: timer2
interval: root.currentScenario === "quick" ? 300 : 1500
onTriggered: {
if (root.currentScenario === "success") {
logs.logEvent("Changing to idle state (success)")
stateCombo.currentIndex = 0 // idle (will trigger success)
} else if (root.currentScenario === "error") {
logs.logEvent("Changing to error state")
stateCombo.currentIndex = 3 // error
} else if (root.currentScenario === "quick") {
logs.logEvent("Quick change to idle (success)")
stateCombo.currentIndex = 0 // idle
}
root.currentScenario = ""
}
}

property string currentScenario: ""

Item {
SplitView.fillWidth: true
SplitView.fillHeight: true

KeycardChannelDrawer {
id: drawer

currentState: stateCombo.currentValue
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside

onDismissed: {
logs.logEvent("KeycardChannelDrawer::dismissed()")
}
}
}

LogsAndControlsPanel {
id: logsAndControlsPanel

SplitView.preferredWidth: 350
SplitView.fillHeight: true

logsView.logText: logs.logText

ColumnLayout {
Layout.fillWidth: true
spacing: Theme.padding

// State control section
RowLayout {
Layout.fillWidth: true
spacing: Theme.halfPadding

Label {
Layout.preferredWidth: 120
text: "Current state:"
}

ComboBox {
id: stateCombo
Layout.fillWidth: true

textRole: "text"
valueRole: "value"

model: ListModel {
ListElement { text: "Idle"; value: "idle" }
ListElement { text: "Waiting for Keycard"; value: "waiting-for-keycard" }
ListElement { text: "Reading"; value: "reading" }
ListElement { text: "Error"; value: "error" }
}

currentIndex: 0
}
}

// State info display
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: infoColumn.implicitHeight + Theme.padding * 2
color: Theme.palette.baseColor5
radius: Theme.radius
border.width: 1
border.color: Theme.palette.baseColor2

ColumnLayout {
id: infoColumn
anchors.fill: parent
anchors.margins: Theme.padding
spacing: Theme.halfPadding

StatusBaseText {
Layout.fillWidth: true
text: "State Information"
font.bold: true
font.pixelSize: Theme.primaryTextFontSize
}

StatusBaseText {
Layout.fillWidth: true
text: "Current: %1".arg(stateCombo.currentText)
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.baseColor1
}

StatusBaseText {
Layout.fillWidth: true
text: "Opened: %1".arg(drawer.opened ? "Yes" : "No")
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.baseColor1
}
}
}

// Scenario buttons section
Label {
Layout.fillWidth: true
Layout.topMargin: Theme.padding
text: "Test Scenarios:"
font.bold: true
}

Button {
Layout.fillWidth: true
text: "Simulate Success Flow"
onClicked: {
logs.logEvent("Starting success flow simulation")
root.currentScenario = "success"
stateCombo.currentIndex = 1 // waiting-for-keycard
timer1.start()
}
}

Button {
Layout.fillWidth: true
text: "Simulate Error Flow"
onClicked: {
logs.logEvent("Starting error flow simulation")
root.currentScenario = "error"
stateCombo.currentIndex = 1 // waiting-for-keycard
timer1.start()
}
}

Button {
Layout.fillWidth: true
text: "Simulate Quick State Changes"
onClicked: {
logs.logEvent("Testing state queue with rapid changes")
root.currentScenario = "quick"
stateCombo.currentIndex = 1 // waiting-for-keycard
timer1.interval = 300
timer1.start()
}
}

Button {
Layout.fillWidth: true
text: "Open Drawer Manually"
onClicked: {
logs.logEvent("Manually opening drawer")
drawer.open()
}
}

Button {
Layout.fillWidth: true
text: "Clear Logs"
onClicked: logs.clear()
}

Item {
Layout.fillHeight: true
}
}
}
}

// category: Popups
// status: good

2 changes: 1 addition & 1 deletion storybook/qmlTests/tests/tst_OnboardingLayout.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1471,7 +1471,7 @@ Item {
tryCompare(userSelector, "keycardCreatedAccount", true)

// Verify keycardRequested signal WAS emitted when switching to keycard profile
tryCompare(keycardRequestedSpy, "count", 1)
tryCompare(keycardRequestedSpy, "count", 0)
}

function test_keycardRequested_notEmittedForPasswordFlow_data() {
Expand Down
17 changes: 13 additions & 4 deletions ui/StatusQ/src/StatusQ/Controls/StatusPinInput.qml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ Item {
*/
property int additionalSpacing: 0

/*!
\qmlproperty flags StatusPinInput::inputMethodHints
This property allows you to customize the input method hints for the virtual keyboard.
The default value is Qt.ImhDigitsOnly which allows only digits input.
*/
property int inputMethodHints: Qt.ImhDigitsOnly

signal pinEditedManually()

QtObject {
Expand Down Expand Up @@ -158,8 +165,6 @@ Item {
Convenient method to force active focus in case it gets stolen by any other component.
*/
function forceFocus() {
if (Utils.isMobile)
return
inputText.forceActiveFocus()
d.activateBlink()
}
Expand Down Expand Up @@ -208,9 +213,13 @@ Item {
TextInput {
id: inputText
objectName: "pinInputTextInput"
visible: false
focus: !Utils.isMobile
visible: true
// Set explicit dimensions for Android keyboard input to work
width: 1
height: 1
opacity: 0
maximumLength: root.pinLen
inputMethodHints: root.inputMethodHints
validator: d.statusValidator.validatorObj
onTextChanged: {
// Modify state of current introduced character position:
Expand Down
Loading