Skip to content

Commit

Permalink
feat(Onboarding) Implement new Login screen
Browse files Browse the repository at this point in the history
- implement the new UI and frontend logic of the Login screen
- integrate it (as a separate page) into the OnboardingLayout
- add SB pages
- add an integration QML test
- add some TODOs and FIXMEs for the existing and new external flows,
which will be covered separately in followup PRs

Fixes #17057
  • Loading branch information
caybro committed Jan 22, 2025
1 parent 8d7d933 commit 435e210
Show file tree
Hide file tree
Showing 23 changed files with 1,863 additions and 28 deletions.
281 changes: 281 additions & 0 deletions storybook/pages/LoginScreenPage.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import QtQml.Models 2.15

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

import Models 1.0
import Storybook 1.0

import AppLayouts.Onboarding.enums 1.0
import AppLayouts.Onboarding2.pages 1.0
import AppLayouts.Onboarding2.stores 1.0

import utils 1.0

SplitView {
id: root
orientation: Qt.Vertical

Logs { id: logs }

OnboardingStore {
id: store

// keycard
property int keycardState: Onboarding.KeycardState.NoPCSCService
property int keycardRemainingPinAttempts: ctrlUnlockWithPuk.checked ? 1 : 5

function setPin(pin: string) { // -> bool
logs.logEvent("OnboardingStore.setPin", ["pin"], arguments)
const valid = pin === ctrlPin.text
if (!valid)
keycardRemainingPinAttempts-- // SIMULATION: decrease the remaining PIN attempts
if (keycardRemainingPinAttempts <= 0) { // SIMULATION: "lock" the keycard
keycardState = Onboarding.KeycardState.Locked
keycardRemainingPinAttempts = ctrlUnlockWithPuk.checked ? 1 : 5
}
return valid
}

// password signals
signal accountLoginError(string error, bool wrongPassword)

// biometrics signals
signal obtainingPasswordSuccess(string password)
signal obtainingPasswordError(string errorDescription, string errorType /* Constants.keychain.errorType.* */, bool wrongFingerprint)
}

LoginScreen {
id: loginScreen
SplitView.fillWidth: true
SplitView.fillHeight: true

loginAccountsModel: ListModel {
readonly property var data: [
{
keycardCreatedAccount: false,
colorId: 1,
colorHash: [{colorId: 3, segmentLength: 2}, {colorId: 7, segmentLength: 1}, {colorId: 4, segmentLength: 2}],
username: "Bob",
thumbnailImage: Theme.png("collectibles/Doodles"),
keyUid: "uid_1"
},
{
keycardCreatedAccount: false,
colorId: 2,
colorHash: [{colorId: 9, segmentLength: 1}, {colorId: 7, segmentLength: 3}, {colorId: 10, segmentLength: 2}],
username: "John",
thumbnailImage: Theme.png("collectibles/CryptoPunks"),
keyUid: "uid_2"
},
{
keycardCreatedAccount: true,
colorId: 3,
colorHash: [],
username: "8️⃣6️⃣.eth",
thumbnailImage: "",
keyUid: "uid_4"
},
{
keycardCreatedAccount: true,
colorId: 4,
colorHash: [{colorId: 2, segmentLength: 4}, {colorId: 6, segmentLength: 3}, {colorId: 11, segmentLength: 1}],
username: "Very long username that should eventually elide on the right side",
thumbnailImage: Theme.png("collectibles/SuperRare"),
keyUid: "uid_3"
}
]
Component.onCompleted: append(data)
}
onboardingStore: store
biometricsAvailable: ctrlBiometrics.checked
isBiometricsLogin: localAccountSettings.storeToKeychainValue === Constants.keychain.storedValue.store
onBiometricsRequested: biometricsPopup.open()
onLoginRequested: (keyUid, method, data) => {
logs.logEvent("onLoginRequested", ["keyUid", "method", "data"], arguments)

// SIMULATION: emit an error in case of wrong password
if (method === Onboarding.LoginMethod.Password && data.password !== ctrlPassword.text) {
onboardingStore.accountLoginError("The impossible has happened", Math.random() < 0.5)
}
}
onOnboardingCreateProfileFlowRequested: logs.logEvent("onOnboardingCreateProfileFlowRequested")
onOnboardingLoginFlowRequested: logs.logEvent("onOnboardingLoginFlowRequested")
onUnlockWithSeedphraseRequested: logs.logEvent("onUnlockWithSeedphraseRequested")
onUnlockWithPukRequested: logs.logEvent("onUnlockWithPukRequested")
onLostKeycard: logs.logEvent("onLostKeycard")

// mocks
QtObject {
id: localAccountSettings
readonly property string storeToKeychainValue: ctrlTouchIdUser.checked ? Constants.keychain.storedValue.store : ""
}
onSelectedProfileKeyIdChanged: biometricsPopup.visible = Qt.binding(() => ctrlBiometrics.checked && ctrlTouchIdUser.checked && store.keycardState === Onboarding.KeycardState.NotEmpty)
}

Dialog {
id: biometricsPopup
width: 300
margins: 40
visible: ctrlBiometrics.checked && ctrlTouchIdUser.checked && store.keycardState === Onboarding.KeycardState.NotEmpty
x: root.Window.width - width
closePolicy: Popup.NoAutoClose
contentItem: ColumnLayout {
spacing: 10
StatusIcon {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 40
Layout.preferredHeight: 40
icon: "touch-id"
color: Theme.palette.baseColor1
}
Label {
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
text: "Status Desktop"
font.pixelSize: 20
}
Label {
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
text: "Status Desktop is trying to authenticate you.\n\nTouch ID or enter your password to allow this."
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
type: StatusBaseButton.Type.Primary
focusPolicy: Qt.NoFocus
text: "Use password..."
onClicked: {
store.obtainingPasswordError("Password required instead of touch ID.", Constants.keychain.errorType.keychain, false)
biometricsPopup.close()
}
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
focusPolicy: Qt.NoFocus
text: "Cancel"
onClicked: {
store.obtainingPasswordError("Touch ID canceled, try entering password instead.", Constants.keychain.errorType.keychain, false)
biometricsPopup.close()
}
}
Item { Layout.preferredHeight: 20 }
StatusButton {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
type: StatusBaseButton.Type.Success
focusPolicy: Qt.NoFocus
text: "Simulate correct fingerprint"
onClicked: {
store.obtainingPasswordSuccess(loginScreen.selectedProfileIsKeycard ? ctrlPin.text : ctrlPassword.text)
biometricsPopup.close()
}
}
StatusButton {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
type: StatusBaseButton.Type.Danger
focusPolicy: Qt.NoFocus
text: "Simulate wrong fingerprint"
onClicked: {
biometricsPopup.close()
store.obtainingPasswordError("Wrong fingerprint provided.", Constants.keychain.errorType.keychain, true)
}
}
}
}

LogsAndControlsPanel {
id: logsAndControlsPanel

SplitView.minimumHeight: 180
SplitView.preferredHeight: 180

logsView.logText: logs.logText

ColumnLayout {
anchors.fill: parent

Label {
text: "Selected user ID: %1".arg(loginScreen.selectedProfileKeyId || "N/A")
}

RowLayout {
Layout.fillWidth: true
Label {
text: "Password:\t"
}
TextField {
id: ctrlPassword
text: "0123456789"
placeholderText: "Example password"
selectByMouse: true
}
Switch {
id: ctrlBiometrics
text: "Biometrics available"
checked: true
}
Switch {
id: ctrlTouchIdUser
text: "Touch ID login"
enabled: ctrlBiometrics.checked
checked: ctrlBiometrics.checked
}
Switch {
id: ctrlUnlockWithPuk
text: "Unlock with PUK available"
checked: true
}
}

RowLayout {
Layout.fillWidth: true
Label {
text: "Keycard PIN:\t"
}
TextField {
id: ctrlPin
text: "111111"
inputMask: "999999"
selectByMouse: true
}
Label {
text: "State:"
}
ComboBox {
Layout.preferredWidth: 300
id: ctrlKeycardState
focusPolicy: Qt.NoFocus
textRole: "text"
valueRole: "value"
model: [
{ value: Onboarding.KeycardState.NoPCSCService, text: "NoPCSCService" },
{ value: Onboarding.KeycardState.PluginReader, text: "PluginReader" },
{ value: Onboarding.KeycardState.InsertKeycard, text: "InsertKeycard" },
{ value: Onboarding.KeycardState.ReadingKeycard, text: "ReadingKeycard" },
{ value: Onboarding.KeycardState.WrongKeycard, text: "WrongKeycard" },
{ value: Onboarding.KeycardState.NotKeycard, text: "NotKeycard" },
{ value: Onboarding.KeycardState.MaxPairingSlotsReached, text: "MaxPairingSlotsReached" },
{ value: Onboarding.KeycardState.Locked, text: "Locked" },
{ value: Onboarding.KeycardState.NotEmpty, text: "NotEmpty" },
{ value: Onboarding.KeycardState.Empty, text: "Empty" }
]
onActivated: store.keycardState = currentValue
Component.onCompleted: currentIndex = Qt.binding(() => indexOfValue(store.keycardState))
}
}
}
}
}

// category: Onboarding
// status: good
// https://www.figma.com/design/Lw4nPYQcZOPOwTgETiiIYo/Desktop-Onboarding-Redesign?node-id=801-42615&m=dev
Loading

0 comments on commit 435e210

Please sign in to comment.