From 1217771fd058e3f9c1b669e4ecce3965f6d4ed3c Mon Sep 17 00:00:00 2001 From: Noelia Date: Thu, 10 Feb 2022 11:06:41 +0100 Subject: [PATCH] feat(StatusPinInput): Introduce `StatusPinInput` control It creates `StatusPinInput` control that allows customzing its circle diameter, circle spacing and pin length. It contains a `TextInput` object that will provide the component, input text management like validation rules. It incorporates a blinking animation when the control is focused and feedback (mouse shape changed) when hovering it. It adds new page in sandbox to play with `StatusPinInput` control. It adds component documentation. Also it creates 2 new `StatusValidator` controls with their corresponding documentation: - `StatusRegularExpressionValidator` which wraps a QML type `RegularExpressionValidator`. - `StatusIntValidator` which wraps a QML type `IntValidator`. Closes #524 --- doc/src/images/status_pin_input.png | Bin 0 -> 2162 bytes doc/src/statusqcontrols.qdoc | 3 + sandbox/main.qml | 5 + sandbox/pages/StatusPinInputPage.qml | 75 ++++++ src/StatusQ/Controls/StatusPinInput.qml | 234 ++++++++++++++++++ .../Validators/StatusIntValidator.qml | 57 +++++ .../StatusRegularExpressionValidator.qml | 60 +++++ .../Controls/Validators/StatusValidator.qml | 1 + src/StatusQ/Controls/Validators/qmldir | 2 + src/StatusQ/Controls/qmldir | 1 + statusq.qrc | 3 + 11 files changed, 441 insertions(+) create mode 100644 doc/src/images/status_pin_input.png create mode 100644 sandbox/pages/StatusPinInputPage.qml create mode 100644 src/StatusQ/Controls/StatusPinInput.qml create mode 100644 src/StatusQ/Controls/Validators/StatusIntValidator.qml create mode 100644 src/StatusQ/Controls/Validators/StatusRegularExpressionValidator.qml diff --git a/doc/src/images/status_pin_input.png b/doc/src/images/status_pin_input.png new file mode 100644 index 0000000000000000000000000000000000000000..3b2563dfa1059d2e042bc44ab67d1e5e405be1a6 GIT binary patch literal 2162 zcmZWq3p|ti8-KUC9Y!Wy-0HPjG;A!_VJ(~7uiHUL!zMEf+xeSP#^;cviwG4_QLP-L zq&i|p61qFC{mbPj_en`*C;7jt{_;8J{d}JHdEV!FzR&mfdw##?^B(l}bW&B;Rt5k- z)y0|W3+YZsER__XccX7z4gkQmvMCgA7YYURPT)qcqgeo8m=n%mXgC`iwuXi>7_E>s1s-IiwY(P8pGJkQ26tDIZ&qYwdP%(C~5!qe$doYDWQi{|&rwxF<$0ugot` z7&-bmt^M4?_t_fqP7I}TtY8E)Ic~UCYps@tQg*IJuhD@+Sk zx@uh7_`!WQL)|W2o%TdwPur2yAu@AYm=F2|EPEgz>(A`NVDz*EiLChZ%i^N$W3jlh zO+52ay13X_x45{NsD(hxnw0i)0j1j^N&!RKhoFx7u>xEoJUjp_WGex%18hJJvS5&C zLjnMZ;~9V=q%|O+mco~!ou!B++ftUejYe^Cfix{VfyIjDMRNHUSPuSBsV+7>fFI!D zP7LS9;6j<)FcwY_6DNZJb^;<~#<2LIpdcnXmPZuWqrYYlAzS9gqrtB!e2zUjz{4A) za1&UdCC(IQiY6(8AZVAsj3D|_w||F2U-sxoK0l6#$0sHx;u5#uxCy)QW;Ql9cvAwN zKrn$aOn6DL{7`{OEN{cNBLA;LW%0rj*l~O|Hx`uD4GrV|!na4GWkgHQw{@}v>>o_A zyzkqBHi(x+@Mbtu{8DWQYA5p&z1ad*^bRUJ25Jw)L9#Hn`f*D~s za#`e`$e($v1PV6>V&s#49L#s{^5A!%9bUHaWsUgO<*#0-%_Lz65J>snN(Y(j)FjxKU-u_JQ@<1vT)rrpJW6p7`Dqqg~-{MJH(v2MBXjJvB zWUOylZRED81Ks)NF)K&iU2KatjW;KDDDE4@xe}A!tn24avkBa3bN@q4nrINN2~Toe zjX}XsnLo99W93kw#<8=RrYBtGn1)?KC3VP2Ep)}e33*XuxlG83b}!d}X}MZ}a4MXj ziMrr>P>3woR)?WsT3Jdo5tW?btC|Vq2$7Dzis)#NB2u6Voyi&KCOQ^%>?|FDCT|2O zs8H+)B~9YN!I$*DkcN<2?(C}5rw2^!bdo2Sb;UX+&n2h#S?(8gx+Km1wkxEnuRW@( z+bYUW)QLR0ea-JYKkP_FBc5A+CfKP5zi@Z7WU_g~jEY;(hfm`*9u2#$l@A1|9*UR| z6nF$r)+&Bfh!#{|87f?~9}7w`3VauKefmW0<(OE@FDnq~4$RB_^{S4#w{JeM8vlFP z%StnA{#mZ$Ub9=L+EyfPl1>dR=${fLPo6JalW!H1RDO!RfLKV*5>h5UGzLTPW+Q4hGyZ25>UmrhQjGIQ@^{2F0{Jdd7FK0Er?8(rI=1woeXtMe5 z5mE7^VVlB9?S{Flu$=mcw3BDNzSOoQaz1d_TfS`EGaGM0%J?{uDobH6RLsk;{dCH{ z;{Tc)uQ0eqQnO91913dGmAt|XOD@)UJel6L=ExAuNUQfJ{^9HUZRgA0JYHWf%8j%Lb>p{NMd!wcMkfyI zC+SvQwaG!gnE7QnKXFS(y-VVXKGcEW|Kvwp(l%3WY#SP5Ty-mxPpe&(| z2_vmc6?xEFNjDmv!F*OkA08;SjyP*AZn>(^QD7JK^s&Xv!Cdz>eCKQdU5 z_*GZb2Od7(f53se?)kwpcLLevx8B%441GU0-|ZxG<$sy#zmy(-e59}L(-<&fDwS%F zTXndt&m|q$dh_<;4)L3oh2kdy+Gh>w)XZIA~TM(E8v8 zAVod|2qYB= (d.currentPinIndex + 1)) { + repeater.itemAt(d.currentPinIndex).innerState = "FILLED" + + // Update next: + d.currentPinIndex++ + if(d.currentPinIndex < root.pinLen) + repeater.itemAt(d.currentPinIndex).innerState = "NEXT" + + } + // Modify state of current removed character position: + else if (text.length <= (d.currentPinIndex + 1)) { + if(d.currentPinIndex < root.pinLen) + repeater.itemAt(d.currentPinIndex).innerState = "EMPTY" + d.currentPinIndex-- + repeater.itemAt(d.currentPinIndex).innerState = "NEXT" + } + + // Some component validations: + if(text.length !== d.currentPinIndex) + console.error("StatusPinInput input management error. Current pin length must be "+ text.length + "and is " + d.currentPinIndex) + } + onFocusChanged: { if(!focus) { d.deactivateBlink () } } + } + + // Pin input visual objects: + Row { + spacing: root.circleSpacing + + Repeater { + id: repeater + model: root.pinLen + + Rectangle { + id: background + property string innerState: "EMPTY" + property alias blinkingAnimation: blinkingAnimation + property alias innerOpacity: inner.opacity + + width: root.circleDiameter + height: width + color: Theme.palette.primaryColor2 + radius: 0.5 * width + + Rectangle { + id: inner + state: background.innerState + anchors.centerIn: parent + height: width + color: Theme.palette.primaryColor1 + radius: 0.5 * width + states: [ + State { + name: "NEXT" + StateChangeScript { script: { if(inputText.focus) blinkingAnimation.start() } } + PropertyChanges {target: inner; width: root.circleDiameter / 2} + }, + State { + name: "FILLED" + StateChangeScript { script: if(blinkingAnimation.running) blinkingAnimation.stop() } + PropertyChanges {target: inner; width: root.circleDiameter} + PropertyChanges {target: inner; opacity: 1} + }, + State { + name: "EMPTY" + StateChangeScript { script: if(blinkingAnimation.running) blinkingAnimation.stop() } + PropertyChanges {target: inner; width: 0} + PropertyChanges {target: inner; opacity: 1} + } + ] + + // Animation on transitions + Behavior on width { NumberAnimation { duration: 200 } } + + // Animation on "cursor" blinking + SequentialAnimation { + id: blinkingAnimation + loops: Animation.Infinite + NumberAnimation { target: inner; property: "opacity"; to: 0; duration: 800 } + NumberAnimation { target: inner; property: "opacity"; to: 1; duration: 800 } + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + // MouseArea behavior: + onClicked: { + inputText.forceActiveFocus() + d.activateBlink () + } + onContainsMouseChanged: { if(containsMouse) { cursorShape = Qt.PointingHandCursor } } + } +} diff --git a/src/StatusQ/Controls/Validators/StatusIntValidator.qml b/src/StatusQ/Controls/Validators/StatusIntValidator.qml new file mode 100644 index 00000000..5ccc9f24 --- /dev/null +++ b/src/StatusQ/Controls/Validators/StatusIntValidator.qml @@ -0,0 +1,57 @@ +import QtQuick 2.14 + +import StatusQ.Controls 0.1 + +/*! + \qmltype StatusIntValidator + \inherits StatusValidator + \inqmlmodule StatusQ.Controls.Validators + \since StatusQ.Controls.Validators 0.1 + \brief The StatusIntValidator type provides a validator for integer values. + + The \c StatusIntValidator type provides a validator for integer values. + + It is a wrapper of QML type \l{https://doc.qt.io/qt-5/qml-qtquick-intvalidator.html}{IntValidator}. + + Example of how to use it: + + \qml + StatusIntValidator { + bottom: 0 + top: 125 + errorMessage: qsTr("This is an invalid numeric value") + } + \endqml + + For a list of components available see StatusQ. +*/ +StatusValidator { + id: root + + /*! + \qmlproperty string StatusIntValidator::bottom + This property holds the validator's lowest acceptable value. By default, this property's value is derived from the lowest signed integer available (typically -2147483647). + */ + property int bottom + + /*! + \qmlproperty string StatusIntValidator::locale + This property holds the name of the locale used to interpret the number. + */ + property string locale + + /*! + \qmlproperty string StatusIntValidator::top + This property holds the validator's highest acceptable value. By default, this property's value is derived from the highest signed integer available (typically 2147483647). + */ + property int top + + name: "intValidator" + errorMessage: "Please enter a valid numeric value." + validatorObj: IntValidator { bottom: root.bottom; locale: root.locale; top: root.top } + + validate: function (t) { + // Basic validation management + return root.validatorObj.validate() === IntValidator.Acceptable + } +} diff --git a/src/StatusQ/Controls/Validators/StatusRegularExpressionValidator.qml b/src/StatusQ/Controls/Validators/StatusRegularExpressionValidator.qml new file mode 100644 index 00000000..8a6b0157 --- /dev/null +++ b/src/StatusQ/Controls/Validators/StatusRegularExpressionValidator.qml @@ -0,0 +1,60 @@ +import QtQuick 2.14 + +import StatusQ.Controls 0.1 + +/*! + \qmltype StatusRegularExpressionValidator + \inherits StatusValidator + \inqmlmodule StatusQ.Controls.Validators + \since StatusQ.Controls.Validators 0.1 + \brief The StatusRegularExpressionValidator type provides a validator for regular expressions. + + The \c StatusRegularExpressionValidator type provides a validator, that counts as valid any string which matches a specified regular expression. + + It is a wrapper of QML type \l{https://doc.qt.io/qt-5/qml-qtquick-regularexpressionvalidator.html}{RegularExpressionValidator}. + + Example of how to use it: + + \qml + StatusRegularExpressionValidator { + regularExpression: /[0-9A-Za-z@]+/ + errorMessage: qsTr("Please enter a valid regular expression.") + } + \endqml + + For a list of components available see StatusQ. +*/ +StatusValidator { + id: root + + /*! + \qmlproperty string StatusRegularExpressionValidator::regularExpression + This property holds the regular expression used for validation. + + Note that this property should be a regular expression in JS syntax, e.g /a/ for the regular expression matching "a". + + By default, this property contains a regular expression with the pattern .* that matches any string. + + Some more examples of regular expressions: + + > A list of numbers with one to three positions separated by a comma: + \qml + /\d{1,3}(?:,\d{1,3})+$/ + \endqml + + > An amount consisting of up to 3 numbers before the decimal point, and 1 to 2 after the decimal point: + \qml + /(\d{1,3})([.,]\d{1,2})?$/ + \endqml + */ + property var regularExpression + + name: "regex" + errorMessage: "Please enter a valid regular expression." + validatorObj: RegularExpressionValidator { regularExpression: root.regularExpression } + + validate: function (t) { + // Basic validation management + return root.validatorObj.validate() === RegularExpressionValidator.Acceptable + } +} diff --git a/src/StatusQ/Controls/Validators/StatusValidator.qml b/src/StatusQ/Controls/Validators/StatusValidator.qml index 6e4353cd..ba62778d 100644 --- a/src/StatusQ/Controls/Validators/StatusValidator.qml +++ b/src/StatusQ/Controls/Validators/StatusValidator.qml @@ -6,6 +6,7 @@ QtObject { property string name: "" property string errorMessage: "invalid input" + property var validatorObj property var validate: function (value) { return true diff --git a/src/StatusQ/Controls/Validators/qmldir b/src/StatusQ/Controls/Validators/qmldir index 6299a5da..55dcb715 100644 --- a/src/StatusQ/Controls/Validators/qmldir +++ b/src/StatusQ/Controls/Validators/qmldir @@ -4,7 +4,9 @@ StatusAddressValidator 0.1 StatusAddressValidator.qml StatusAddressOrEnsValidator 0.1 StatusAddressOrEnsValidator.qml StatusAsyncValidator 0.1 StatusAsyncValidator.qml StatusAsyncEnsValidator 0.1 StatusAsyncEnsValidator.qml +StatusIntValidator 0.1 StatusIntValidator.qml StatusMinLengthValidator 0.1 StatusMinLengthValidator.qml StatusMaxLengthValidator 0.1 StatusMaxLengthValidator.qml +StatusRegularExpressionValidator 0.1 StatusRegularExpressionValidator.qml StatusUrlValidator 0.1 StatusUrlValidator.qml StatusValidator 0.1 StatusValidator.qml diff --git a/src/StatusQ/Controls/qmldir b/src/StatusQ/Controls/qmldir index d745f8b3..20971b9b 100644 --- a/src/StatusQ/Controls/qmldir +++ b/src/StatusQ/Controls/qmldir @@ -25,6 +25,7 @@ StatusSelect 0.1 StatusSelect.qml StatusBaseInput 0.1 StatusBaseInput.qml StatusInput 0.1 StatusInput.qml StatusPickerButton 0.1 StatusPickerButton.qml +StatusPinInput 0.1 StatusPinInput.qml StatusProgressBar 0.1 StatusProgressBar.qml StatusPasswordStrengthIndicator 0.1 StatusPasswordStrengthIndicator.qml StatusSwitchTabButton 0.1 StatusSwitchTabButton.qml diff --git a/statusq.qrc b/statusq.qrc index a0aff12c..00821eaf 100644 --- a/statusq.qrc +++ b/statusq.qrc @@ -326,5 +326,8 @@ src/StatusQ/Components/StatusContactVerificationIcons.qml src/StatusQ/Controls/StatusIdenticonRing.qml src/StatusQ/Core/StatusIdenticonRingSettings.qml + src/StatusQ/Controls/StatusPinInput.qml + src/StatusQ/Controls/Validators/StatusRegularExpressionValidator.qml + src/StatusQ/Controls/Validators/StatusIntValidator.qml