[Settings]: Added change password view (#13284)

* [Settings]: Added change password view

Closes #10037

Adding configuration options to PasswordView

* feat(ChangePassword): Integrate ConfirmChangePasswordModal

1. Integrate with backend
2. Clean unused components

* feat: Add support to restart application

1. Adding restart app support in DOtherSide
2. Integrating nimqml
3. Expose in qml in Utils

* chore: Move changeDatabasePassword call to threadpool

* chore(squish): Fix failing tests due to settings index changes

---------

Co-authored-by: Alex Jbanca <alexjb@status.im>
This commit is contained in:
Alexandra Betouni 2024-02-09 13:31:37 +02:00 committed by GitHub
parent d29e5406de
commit 480985ca4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 573 additions and 295 deletions

View File

@ -110,6 +110,9 @@ QtObject:
proc downloadImageByUrl*(self: Utils, url: string, path: string) {.slot.} =
downloadImageByUrl(url, path)
proc restartApplication*(self: Utils) {.slot.} =
restartApplication()
proc generateQRCodeSVG*(self: Utils, text: string, border: int = 0): string =
var qr0: array[0..qrcodegen_BUFFER_LEN_MAX, uint8]
var tempBuffer: array[0..qrcodegen_BUFFER_LEN_MAX, uint8]

View File

@ -67,6 +67,9 @@ method mnemonicBackedUp*(self: Module) =
self.view.emitMnemonicBackedUpSignal()
method onPasswordChanged*(self: Module, success: bool, errorMsg: string) =
if singletonInstance.localAccountSettings.getStoreToKeychainValue() != LS_VALUE_NEVER:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
self.view.emitPasswordChangedSignal(success, errorMsg)
method getMnemonic*(self: Module): string =

View File

@ -0,0 +1,22 @@
import ../../../backend/privacy as status_privacy
type
ChangeDatabasePasswordTaskArg = ref object of QObjectTaskArg
accountId: string
currentPassword: string
newPassword: string
const changeDatabasePasswordTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[ChangeDatabasePasswordTaskArg](argEncoded)
let output = %* {
"error": "",
"result": ""
}
try:
let result = status_privacy.changeDatabasePassword(arg.accountId, arg.currentPassword, arg.newPassword)
output["result"] = %result.result
except Exception as e:
output["error"] = %e.msg
arg.finish(output)

View File

@ -5,12 +5,15 @@ import ../settings/service as settings_service
import ../accounts/service as accounts_service
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../backend/eth as status_eth
import ../../../backend/privacy as status_privacy
import ../../common/utils as common_utils
include ./async_tasks
logScope:
topics = "privacy-service"
@ -27,6 +30,7 @@ QtObject:
events: EventEmitter
settingsService: settings_service.Service
accountsService: accounts_service.Service
threadpool: threadpool.ThreadPool
proc delete*(self: Service) =
self.QObject.delete
@ -58,6 +62,36 @@ QtObject:
except Exception as e:
error "error: ", procName="getDefaultAccount", errName = e.name, errDesription = e.msg
proc onChangeDatabasePasswordResponse(self: Service, responseStr: string) {.slot.} =
var data = OperationSuccessArgs(success: false, errorMsg: "")
try:
let response = responseStr.parseJson
# nim runtime error
let error = response["error"].getStr
if error != "":
data.errorMsg = error
self.events.emit(SIGNAL_PASSWORD_CHANGED, data)
return;
let result = response["result"]
if(result.contains("error")):
let errMsg = result["error"].getStr
if(errMsg.len == 0):
data.success = true
else:
# backend runtime error
data.errorMsg = errMsg
error "error: ", procName="changePassword", errDesription = errMsg
except Exception as e:
error "error: ", procName="changePassword", errName = e.name, errDesription = e.msg
data.errorMsg = e.msg
self.events.emit(SIGNAL_PASSWORD_CHANGED, data)
proc changePassword*(self: Service, password: string, newPassword: string) =
try:
var data = OperationSuccessArgs(success: false, errorMsg: "")
@ -76,16 +110,15 @@ QtObject:
return
let loggedInAccount = self.accountsService.getLoggedInAccount()
let response = status_privacy.changeDatabasePassword(loggedInAccount.keyUid, common_utils.hashPassword(password), common_utils.hashPassword(newPassword))
if(response.result.contains("error")):
let errMsg = response.result["error"].getStr
if(errMsg.len == 0):
data.success = true
else:
error "error: ", procName="changePassword", errDesription = errMsg
self.events.emit(SIGNAL_PASSWORD_CHANGED, data)
let arg = ChangeDatabasePasswordTaskArg(
tptr: cast[ByteAddress](changeDatabasePasswordTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onChangeDatabasePasswordResponse",
accountId: loggedInAccount.keyUid,
currentPassword: common_utils.hashPassword(password),
newPassword: common_utils.hashPassword(newPassword)
)
self.threadpool.start(arg)
except Exception as e:
error "error: ", procName="changePassword", errName = e.name, errDesription = e.msg

View File

@ -0,0 +1,88 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import AppLayouts.Profile.popups 1.0
import Storybook 1.0
import utils 1.0
SplitView {
id: root
PopupBackground {
id: popupBg
property var popupIntance: null
SplitView.fillWidth: true
SplitView.fillHeight: true
Button {
id: reopenButton
anchors.centerIn: parent
text: "Reopen"
enabled: globalUtilsMock.ready
onClicked: modal.open()
}
QtObject {
id: globalUtilsMock
property bool ready: false
property var globalUtils: QtObject {
function restartApplication() {
if (popupBg.popupIntance)
popupBg.popupIntance.close()
}
}
Component.onCompleted: {
Utils.globalUtilsInst = globalUtilsMock.globalUtils
globalUtilsMock.ready = true
}
}
ConfirmChangePasswordModal {
id: modal
visible: true
onChangePasswordRequested: {
passwordChangedTimer.start()
}
Component.onCompleted: {
popupBg.popupIntance = modal
}
Timer {
id: passwordChangedTimer
interval: 2000
repeat: false
onTriggered: {
if (successFlow.checked) {
modal.passwordSuccessfulyChanged()
} else {
modal.close()
}
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
SplitView.preferredWidth: 300
ColumnLayout {
CheckBox {
id: successFlow
text: "%1 in 2 seconds".arg(successFlow.checked ? "Success" : "Error")
checked: true
}
}
}
}
// category: Popups
// https://www.figma.com/file/d0G7m8X6ELjQlFOEKQpn1g/Profile-WIP?type=design&node-id=11-111195&mode=design&t=j3guZtz78wkceVda-0

View File

@ -12,21 +12,22 @@ _EXTRA_MENU_ITEM_OBJ_NAME = "-ExtraMenuItem"
# These values are used to determine the dynamic `objectName` of the subsection item instead of using "design" properties like `text`.
class SettingsSubsection(Enum):
PROFILE: str = "0" + _MAIN_MENU_ITEM_OBJ_NAME
CONTACTS: str = "1" + _MAIN_MENU_ITEM_OBJ_NAME
ENS_USERNAMES: str = "2" + _MAIN_MENU_ITEM_OBJ_NAME
MESSAGING: str = "3" + _APP_MENU_ITEM_OBJ_NAME
WALLET: str = "4" + _APP_MENU_ITEM_OBJ_NAME
APPEARANCE: str = "5" + _SETTINGS_MENU_ITEM_OBJ_NAME
LANGUAGE: str = "6" + _SETTINGS_MENU_ITEM_OBJ_NAME
NOTIFICATIONS: str = "7" + _SETTINGS_MENU_ITEM_OBJ_NAME
DEVICE_SETTINGS: str = "8" + _SETTINGS_MENU_ITEM_OBJ_NAME
BROWSER: str = "9" + _APP_MENU_ITEM_OBJ_NAME
ADVANCED: str = "10" + _SETTINGS_MENU_ITEM_OBJ_NAME
ABOUT: str = "11" + _EXTRA_MENU_ITEM_OBJ_NAME
COMMUNITY: str = "12" + _APP_MENU_ITEM_OBJ_NAME
KEYCARD: str = "13" + _MAIN_MENU_ITEM_OBJ_NAME
SIGNOUT: str = "16" + _EXTRA_MENU_ITEM_OBJ_NAME
BACKUP_SEED: str = "17" + _MAIN_MENU_ITEM_OBJ_NAME
PASSWORD: str = "1" + _MAIN_MENU_ITEM_OBJ_NAME
CONTACTS: str = "2" + _MAIN_MENU_ITEM_OBJ_NAME
ENS_USERNAMES: str = "3" + _MAIN_MENU_ITEM_OBJ_NAME
MESSAGING: str = "4" + _APP_MENU_ITEM_OBJ_NAME
WALLET: str = "5" + _APP_MENU_ITEM_OBJ_NAME
APPEARANCE: str = "6" + _SETTINGS_MENU_ITEM_OBJ_NAME
LANGUAGE: str = "7" + _SETTINGS_MENU_ITEM_OBJ_NAME
NOTIFICATIONS: str = "8" + _SETTINGS_MENU_ITEM_OBJ_NAME
DEVICE_SETTINGS: str = "9" + _SETTINGS_MENU_ITEM_OBJ_NAME
BROWSER: str = "10" + _APP_MENU_ITEM_OBJ_NAME
ADVANCED: str = "11" + _SETTINGS_MENU_ITEM_OBJ_NAME
ABOUT: str = "12" + _EXTRA_MENU_ITEM_OBJ_NAME
COMMUNITY: str = "13" + _APP_MENU_ITEM_OBJ_NAME
KEYCARD: str = "14" + _MAIN_MENU_ITEM_OBJ_NAME
SIGNOUT: str = "17" + _EXTRA_MENU_ITEM_OBJ_NAME
BACKUP_SEED: str = "18" + _MAIN_MENU_ITEM_OBJ_NAME
# Main:
navBarListView_Settings_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_navBarListView_ListView, "objectName": "Settings-navbar", "type": "StatusNavBarTabButton", "visible": True}

View File

@ -93,6 +93,7 @@ Rectangle {
property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent
property alias errorIcon: errorIcon
property alias statusListItemTagsRowLayout: statusListItemSubtitleTagsRow
property bool showLoadingIndicator: false
property int subTitleBadgeLoaderAlignment: Qt.AlignVCenter
@ -113,7 +114,9 @@ Rectangle {
}
return Math.max(64, statusListItemTitleArea.height + 90)
}
color: {
color: bgColor
property color bgColor: {
if (sensor.containsMouse || root.highlighted) {
switch(type) {
case StatusListItem.Type.Primary:
@ -153,6 +156,8 @@ Rectangle {
acceptedButtons: Qt.NoButton
hoverEnabled: true
StatusSmartIdenticon {
id: iconOrImage
anchors.left: parent.left
@ -160,9 +165,9 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
asset: root.asset
name: root.title
active: root.asset.isLetterIdenticon ||
active: ((root.asset.isLetterIdenticon ||
!!root.asset.name ||
!!root.asset.emoji
!!root.asset.emoji) && !root.showLoadingIndicator)
badge.border.color: root.color
ringSettings: root.ringSettings
loading: root.loading
@ -170,6 +175,19 @@ Rectangle {
onClicked: root.iconClicked(mouse)
}
Loader {
id: loadingIndicator
anchors.left: parent.left
anchors.leftMargin: root.leftPadding
anchors.top: statusListItemTitleArea.top
active: root.showLoadingIndicator
sourceComponent: StatusLoadingIndicator {
width: 24
height: 24
color: Theme.palette.baseColor1
}
}
Item {
id: statusListItemTitleArea
@ -181,9 +199,9 @@ Rectangle {
return !root.titleAsideText && !isIconsRowVisible ? statusListItemTitleArea.right : undefined
}
anchors.left: iconOrImage.active ? iconOrImage.right : parent.left
anchors.left: iconOrImage.active ? iconOrImage.right : loadingIndicator.active ? loadingIndicator.right : parent.left
anchors.right: statusListItemLabel.visible ? statusListItemLabel.left : statusListItemComponentsSlot.left
anchors.leftMargin: iconOrImage.active ? 16 : root.leftPadding
anchors.leftMargin: iconOrImage.active ? 16 : loadingIndicator.active ? 6 : root.leftPadding
anchors.rightMargin: Math.max(root.rightPadding, titleIconsRow.requiredWidth)
anchors.verticalCenter: bottomModel.length === 0 ? parent.verticalCenter : undefined
@ -291,7 +309,7 @@ Rectangle {
Loader {
id: subTitleBadgeLoader
Layout.alignment: root.subTitleBadgeLoaderAlignment
visible: sourceComponent
visible: sourceComponent && !root.showLoadingIndicator
}
StatusTextWithLoadingState {

View File

@ -59,6 +59,7 @@ Loader {
root.asset.bgColor
image.fillMode: Image.PreserveAspectCrop
}
Loader {
anchors.centerIn: parent
active: root.asset.imgStatus === Image.Error ||

View File

@ -7,7 +7,6 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
import utils 1.0
import shared.views 1.0
import "../../Profile/views"
import "../controls"
import "../stores"
@ -44,6 +43,7 @@ Item {
PasswordView {
id: view
Layout.preferredWidth: root.width - 2 * Style.current.bigPadding
Layout.maximumWidth: 460
Layout.fillHeight: true
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
highSizeIntro: true

View File

@ -6,7 +6,7 @@ import QtQuick.Window 2.15
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.stores 1.0
import shared.stores 1.0 as SharedStores
import shared.popups.keycard 1.0
import shared.stores.send 1.0
@ -38,7 +38,7 @@ StatusSectionLayout {
required property TransactionStore transactionStore
required property WalletAssetsStore walletAssetsStore
required property CollectiblesStore collectiblesStore
required property CurrenciesStore currencyStore
required property SharedStores.CurrenciesStore currencyStore
backButtonName: root.store.backButtonName
notificationCount: activityCenterStore.unreadNotificationsCount
@ -147,6 +147,19 @@ StatusSectionLayout {
}
}
Loader {
active: false
asynchronous: true
sourceComponent: ChangePasswordView {
implicitWidth: parent.width
implicitHeight: parent.height
privacyStore: root.store.privacyStore
passwordStrengthScoreFunction: SharedStores.RootStore.getPasswordStrengthScore
contentWidth: d.contentWidth
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.password)
}
}
Loader {
active: false
asynchronous: true

View File

@ -1,115 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import utils 1.0
import shared 1.0
import shared.views 1.0
import shared.panels 1.0
import shared.controls 1.0
import shared.stores 1.0
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import "../views"
StatusModal {
id: root
property var privacyStore
signal passwordChanged()
function onChangePasswordResponse(success, errorMsg) {
if (success) {
if (Qt.platform.os === Constants.mac && localAccountSettings.storeToKeychainValue !== Constants.keychain.storedValue.never) {
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.notNow;
}
passwordChanged()
}
else {
view.reset()
view.errorMsgText = errorMsg
console.warn("TODO: Display error message when change password action failure! ")
}
d.passwordProcessing = "";
submitBtn.loading = false;
}
QtObject {
id: d
// We temporarly store the password during "changePassword" call
// to store it to KeyChain after successfull change operation.
property string passwordProcessing: ""
function submit() {
submitBtn.loading = true
// ChangePassword operation blocks the UI so loading = true; will never have any affect until changePassword/createPassword is done.
// Getting around it with a small pause (timer) in order to get the desired behavior
pause.start()
}
}
Connections {
target: root.privacyStore.privacyModule
function onPasswordChanged(success: bool, errorMsg: string) {
onChangePasswordResponse(success, errorMsg)
}
}
width: 480
height: 546
closePolicy: submitBtn.loading? Popup.NoAutoClose : Popup.CloseOnEscape | Popup.CloseOnPressOutside
hasCloseButton: !submitBtn.loading
headerSettings.title: qsTr("Change password")
onOpened: view.reset()
PasswordView {
id: view
anchors {
fill: parent
topMargin: Style.current.padding
bottomMargin: Style.current.padding
leftMargin: Style.current.padding
rightMargin: Style.current.padding
}
passwordStrengthScoreFunction: RootStore.getPasswordStrengthScore
titleVisible: false
introText: qsTr("Change password used to unlock Status on this device & sign transactions.")
fixIntroTextWidth: true
createNewPsw: false
onReturnPressed: if(submitBtn.enabled) d.submit()
}
rightButtons: [
StatusButton {
id: submitBtn
objectName: "changePasswordModalSubmitButton"
text: qsTr("Change password and restart Status")
enabled: !submitBtn.loading && view.ready
property Timer sim: Timer {
id: pause
interval: 20
onTriggered: {
// Change current password call action to the backend
d.passwordProcessing = view.newPswText
root.privacyStore.changePassword(view.currentPswText, view.newPswText)
}
}
onClicked: { d.submit() }
}
]
// By clicking anywhere outside password entries fields or focusable element in the view, it is needed to check if passwords entered matches
MouseArea {
anchors.fill: parent
z: view.zBehind // Behind focusable components in the view
onClicked: { view.checkPasswordMatches() }
}
}

View File

@ -1,56 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import StatusQ.Core 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
StatusModal {
id: root
width: 400
height: 248
closePolicy: Popup.NoAutoClose
hasCloseButton: false
showHeader: false
contentItem: ColumnLayout {
anchors.fill: parent
anchors.margins: 45
spacing: Style.current.halfPadding
StatusIcon {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 26
Layout.preferredHeight: 26
icon: "checkmark"
color: Style.current.green
}
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 18
text: qsTr("<b>Password changed</b>")
color: Theme.palette.directColor1
}
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 13
color: Theme.palette.baseColor1
text: qsTr("You need to sign in again using the new password.")
}
StatusButton {
id: submitBtn
objectName:"changePasswordSuccessModalSignOutAndQuitButton"
Layout.alignment: Qt.AlignHCenter
text: qsTr("Sign out & Quit")
onClicked: {
//quits the app TODO: change this to logout instead when supported
Qt.quit();
}
}
}
}

View File

@ -38,7 +38,7 @@ ModalPopup {
type: StatusBaseButton.Type.Danger
text: qsTr("Restart")
anchors.bottom: parent.bottom
onClicked: Qt.quit()
onClicked: Utils.restartApplication();
}
}
}

View File

@ -0,0 +1,136 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.12
import QtQml.Models 2.15
import utils 1.0
import shared 1.0
import shared.views 1.0
import shared.panels 1.0
import shared.controls 1.0
import shared.stores 1.0
import StatusQ.Core 0.1
import StatusQ.Popups.Dialog 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import "../views"
StatusDialog {
id: root
signal changePasswordRequested()
// Currently this modal handles only the happy path
// The error is handled in the caller
function passwordSuccessfulyChanged() {
d.dbEncryptionInProgress = false;
d.passwordChanged = true;
}
QtObject {
id: d
function reset() {
d.dbEncryptionInProgress = false;
d.passwordChanged = false;
}
property bool passwordChanged: false
property bool dbEncryptionInProgress: false
}
onClosed: {
d.reset();
}
width: 480
height: 546
closePolicy: d.passwordChanged || d.dbEncryptionInProgress ? Popup.NoAutoClose : Popup.CloseOnEscape | Popup.CloseOnPressOutside
Column {
anchors.fill: parent
anchors.margins: 16
spacing: 20
StatusBaseText {
width: parent.width
wrapMode: Text.WordWrap
text: qsTr("Your data must now be re-encrypted with your new password. This process may take some time, during which you wont be able to interact with the app. Do not quit the app or turn off your device. Doing so will lead to data corruption, loss of your Status profile and the inability to restart Status.")
}
Item {
width: parent.width
height: 76
Rectangle {
anchors.fill: parent
visible: d.passwordChanged
border.color: Theme.palette.successColor1
color: Theme.palette.successColor1
radius: 8
opacity: .1
}
StatusListItem {
id: listItem
anchors.fill: parent
sensor.enabled: false
visible: (d.dbEncryptionInProgress || d.passwordChanged)
title: !d.dbEncryptionInProgress ? qsTr("Re-encryption complete") :
qsTr("Re-encrypting your data with your new password...")
subTitle: !d.dbEncryptionInProgress ? qsTr("Restart Status and log in using your new password") :
qsTr("Do not quit the app of turn off your device")
statusListItemSubTitle.customColor: !d.passwordChanged ? Style.current.red : Theme.palette.successColor1
statusListItemIcon.active: d.passwordChanged
asset.name: "checkmark-circle"
asset.width: 24
asset.height: 24
asset.bgWidth: 0
asset.bgHeight: 0
asset.color: Theme.palette.successColor1
showLoadingIndicator: (d.dbEncryptionInProgress && !d.passwordChanged)
asset.isLetterIdenticon: false
border.width: !d.passwordChanged ? 1 : 0
border.color: Theme.palette.baseColor5
color: d.passwordChanged ? "transparent" : bgColor
}
}
}
header: StatusDialogHeader {
visible: true
headline.title: qsTr("Change password")
actions.closeButton.visible: !(d.passwordChanged || d.dbEncryptionInProgress)
actions.closeButton.onClicked: root.close()
}
footer: StatusDialogFooter {
id: footer
leftButtons: ObjectModel {
StatusFlatButton {
text: qsTr("Cancel")
visible: !d.dbEncryptionInProgress && !d.passwordChanged
textColor: Style.current.darkGrey
onClicked: { root.close(); }
}
}
rightButtons: ObjectModel {
StatusButton {
id: submitBtn
objectName: "changePasswordModalSubmitButton"
text: !d.dbEncryptionInProgress && !d.passwordChanged ? qsTr("Re-encrypt data using new password") : qsTr("Restart status")
enabled: !d.dbEncryptionInProgress
onClicked: {
if (d.passwordChanged) {
Utils.restartApplication();
} else {
d.dbEncryptionInProgress = true
root.changePasswordRequested()
}
}
}
}
}
}

View File

@ -8,3 +8,4 @@ RemoveKeypairPopup 1.0 RemoveKeypairPopup.qml
TokenListPopup 1.0 TokenListPopup.qml
WalletKeypairAccountMenu 1.0 WalletKeypairAccountMenu.qml
WalletAddressMenu 1.0 WalletAddressMenu.qml
ConfirmChangePasswordModal 1.0 ConfirmChangePasswordModal.qml

View File

@ -94,6 +94,9 @@ QtObject {
append({subsection: Constants.settingsSubsection.profile,
text: qsTr("Profile"),
icon: "profile"})
append({subsection: Constants.settingsSubsection.password,
text: qsTr("Password"),
icon: "profile"})
append({subsection: Constants.settingsSubsection.keycard,
text: qsTr("Keycard"),
icon: "keycard"})

View File

@ -0,0 +1,102 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.12
import shared.panels 1.0
import shared.controls 1.0
import shared.stores 1.0
import shared.views 1.0
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import AppLayouts.Profile.popups 1.0
SettingsContentBase {
id: root
property var privacyStore
property var passwordStrengthScoreFunction: function () {}
//TODO https://github.com/status-im/status-desktop/issues/13302
// titleRowComponentLoader.sourceComponent: Item {
// implicitWidth: 226
// implicitHeight: 38
// StatusSwitch {
// LayoutMirroring.enabled: true
// text: qsTr("Enable biometrics")
// onToggled: {
// //
// }
// }
// }
ColumnLayout {
PasswordView {
id: choosePasswordForm
width: 507
height: 660
createNewPsw: false
title: qsTr("Change your password.")
titleSize: 17
contentAlignment: Qt.AlignLeft
passwordStrengthScoreFunction: root.passwordStrengthScoreFunction
onReadyChanged: {
submitBtn.enabled = ready
}
onReturnPressed: {
if (ready) {
confirmPasswordChangePopup.open();
}
}
}
RowLayout {
Layout.fillWidth: true
StatusLinkText {
text: qsTr("Clear & cancel")
onClicked: {
choosePasswordForm.reset();
}
}
Item { Layout.fillWidth: true }
StatusButton {
id: submitBtn
Layout.alignment: Qt.AlignRight
objectName: "changePasswordModalSubmitButton"
text: qsTr("Change password")
enabled: choosePasswordForm.ready
onClicked: { confirmPasswordChangePopup.open(); }
}
}
ConfirmChangePasswordModal {
id: confirmPasswordChangePopup
onChangePasswordRequested: {
root.privacyStore.changePassword(choosePasswordForm.currentPswText, choosePasswordForm.newPswText);
}
Connections {
target: root.privacyStore.privacyModule
function onPasswordChanged(success: bool, errorMsg: string) {
if (success) {
confirmPasswordChangePopup.passwordSuccessfulyChanged()
return
}
choosePasswordForm.reset()
choosePasswordForm.errorMsgText = errorMsg
confirmPasswordChangePopup.close()
}
}
}
}
}

View File

@ -233,19 +233,6 @@ SettingsContentBase {
socialLinksModel: root.profileStore.temporarySocialLinksModel
}
Component {
id: changePasswordModal
ChangePasswordModal {
privacyStore: root.privacyStore
onPasswordChanged: Global.openPopup(successPopup)
}
}
Component {
id: successPopup
ChangePasswordSuccessModal {}
}
Component {
id: profilePreview
ProfileDialog {

View File

@ -1,7 +1,8 @@
AboutView 1.0 AboutView.qml
LanguageView 1.0 LanguageView.qml
AppearanceView 1.0 AppearanceView.qml
NotificationsView 1.0 NotificationsView.qml
CommunitiesView 1.0 CommunitiesView.qml
BrowserView 1.0 BrowserView.qml
ChangePasswordView 1.0 ChangePasswordView.qml
CommunitiesView 1.0 CommunitiesView.qml
LanguageView 1.0 LanguageView.qml
NotificationsView 1.0 NotificationsView.qml
SyncingView 1.0 SyncingView.qml

View File

@ -22,11 +22,11 @@ Item {
spacing: Style.current.padding
PasswordView {
Layout.minimumWidth: 460
Layout.fillWidth: true
Layout.fillHeight: true
passwordStrengthScoreFunction: RootStore.getPasswordStrengthScore
highSizeIntro: true
fixIntroTextWidth: true
newPswText: root.sharedKeycardModule.getNewPassword()
confirmationPswText: root.sharedKeycardModule.getNewPassword()

View File

@ -19,11 +19,13 @@ ColumnLayout {
property bool createNewPsw: true
property string title: qsTr("Create a password")
property bool titleVisible: true
property real titleSize: 22
property string introText: qsTr("Create a password to unlock Status on this device & sign transactions.")
property string recoverText: qsTr("You will not be able to recover this password if it is lost.")
property string strengthenText: qsTr("Minimum %n character(s). To strengthen your password consider including:", "", Constants.minPasswordLength)
property bool highSizeIntro: false
property bool fixIntroTextWidth: false
property int contentAlignment: Qt.AlignHCenter
property var passwordStrengthScoreFunction: function () {}
@ -80,8 +82,6 @@ ColumnLayout {
readonly property var validatorRegexp: /^[!-~]{0,64}$/
readonly property string validatorErrMessage: qsTr("Only letters, numbers, underscores and hyphens allowed")
readonly property int defaultInputWidth: 416
// Password strength categorization / validation
function lowerCaseValidator(text) { return (/[a-z]/.test(text)) }
function upperCaseValidator(text) { return (/[A-Z]/.test(text)) }
@ -142,16 +142,17 @@ ColumnLayout {
function isTooShort() { return newPswInput.text.length < Constants.minPasswordLength }
}
implicitWidth: 460
spacing: Style.current.bigPadding
z: root.zFront
// View visual content:
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
Layout.alignment: root.contentAlignment
visible: root.titleVisible
text: root.title
font.pixelSize: 22
font.pixelSize: root.titleSize
font.bold: true
color: Theme.palette.directColor1
}
@ -159,16 +160,16 @@ ColumnLayout {
ColumnLayout {
id: introColumn
Layout.preferredWidth: root.fixIntroTextWidth ? d.defaultInputWidth : parent.width
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.alignment: root.contentAlignment
spacing: 4
StatusBaseText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.alignment: root.contentAlignment
text: root.introText
horizontalAlignment: Text.AlignHCenter
horizontalAlignment: root.contentAlignment
font.pixelSize: root.highSizeIntro ? Style.current.primaryTextFontSize : Style.current.tertiaryTextFontSize
wrapMode: Text.WordWrap
color: Theme.palette.baseColor1
@ -176,10 +177,10 @@ ColumnLayout {
StatusBaseText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.alignment: root.contentAlignment
text: root.recoverText
horizontalAlignment: Text.AlignHCenter
horizontalAlignment: root.contentAlignment
font.pixelSize: root.highSizeIntro ? Style.current.primaryTextFontSize : Style.current.tertiaryTextFontSize
wrapMode: Text.WordWrap
color: Theme.palette.dangerColor1
@ -194,8 +195,8 @@ ColumnLayout {
z: root.zFront
visible: !root.createNewPsw
Layout.preferredWidth: d.defaultInputWidth
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.alignment: root.contentAlignment
placeholderText: qsTr("Current password")
echoMode: showPassword ? TextInput.Normal : TextInput.Password
rightPadding: showHideCurrentIcon.width + showHideCurrentIcon.anchors.rightMargin + Style.current.padding / 2
@ -219,7 +220,8 @@ ColumnLayout {
ColumnLayout {
spacing: 4
z: root.zFront
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.alignment: root.contentAlignment
StatusPasswordInput {
id: newPswInput
@ -227,8 +229,8 @@ ColumnLayout {
property bool showPassword
Layout.preferredWidth: d.defaultInputWidth
Layout.alignment: Qt.AlignHCenter
Layout.alignment: root.contentAlignment
Layout.fillWidth: true
placeholderText: qsTr("New password")
echoMode: showPassword ? TextInput.Normal : TextInput.Password
rightPadding: showHideNewIcon.width + showHideNewIcon.anchors.rightMargin + Style.current.padding / 2
@ -269,6 +271,7 @@ ColumnLayout {
StatusPasswordStrengthIndicator {
id: strengthInditactor
Layout.fillWidth: true
value: Math.min(Constants.minPasswordLength, newPswInput.text.length)
from: 0
to: Constants.minPasswordLength
@ -280,46 +283,66 @@ ColumnLayout {
}
}
StatusBaseText {
id: strengthenTxt
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
text: root.strengthenText
font.pixelSize: 12
color: Theme.palette.baseColor1
clip: true
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 80
border.color: Theme.palette.baseColor2
border.width: 1
radius: Style.current.radius
implicitHeight: strengthColumn.implicitHeight
implicitWidth: strengthColumn.implicitWidth
RowLayout {
spacing: Style.current.padding
Layout.alignment: Qt.AlignHCenter
ColumnLayout {
id: strengthColumn
anchors.fill: parent
anchors.margins: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
spacing: Style.current.padding
StatusBaseText {
id: lowerCaseTxt
text: "• " + qsTr("Lower case")
font.pixelSize: 12
color: d.containsLower ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: strengthenTxt
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
text: root.strengthenText
font.pixelSize: 12
color: Theme.palette.baseColor1
clip: true
}
StatusBaseText {
id: upperCaseTxt
text: "• " + qsTr("Upper case")
font.pixelSize: 12
color: d.containsUpper ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
RowLayout {
spacing: Style.current.padding
Layout.alignment: Qt.AlignHCenter
StatusBaseText {
id: numbersTxt
text: "• " + qsTr("Numbers")
font.pixelSize: 12
color: d.containsNumbers ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: lowerCaseTxt
text: "• " + qsTr("Lower case")
font.pixelSize: 12
color: d.containsLower ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: symbolsTxt
text: "• " + qsTr("Symbols")
font.pixelSize: 12
color: d.containsSymbols ? Theme.palette.successColor1 : Theme.palette.baseColor1
StatusBaseText {
id: upperCaseTxt
text: "• " + qsTr("Upper case")
font.pixelSize: 12
color: d.containsUpper ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: numbersTxt
text: "• " + qsTr("Numbers")
font.pixelSize: 12
color: d.containsNumbers ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: symbolsTxt
text: "• " + qsTr("Symbols")
font.pixelSize: 12
color: d.containsSymbols ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
}
}
}
@ -330,8 +353,8 @@ ColumnLayout {
property bool showPassword
z: root.zFront
Layout.preferredWidth: d.defaultInputWidth
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.alignment: root.contentAlignment
placeholderText: qsTr("Confirm password")
echoMode: showPassword ? TextInput.Normal : TextInput.Password
rightPadding: showHideConfirmIcon.width + showHideConfirmIcon.anchors.rightMargin + Style.current.padding / 2
@ -376,7 +399,7 @@ ColumnLayout {
StatusBaseText {
id: errorTxt
Layout.alignment: Qt.AlignHCenter
Layout.alignment: root.contentAlignment
Layout.fillHeight: true
font.pixelSize: 12
color: Theme.palette.dangerColor1

View File

@ -331,25 +331,26 @@ QtObject {
readonly property QtObject settingsSubsection: QtObject {
readonly property int profile: 0
readonly property int contacts: 1
readonly property int ensUsernames: 2
readonly property int messaging: 3
readonly property int wallet: 4
readonly property int appearance: 5
readonly property int language: 6
readonly property int notifications: 7
readonly property int syncingSettings: 8
readonly property int browserSettings: 9
readonly property int advanced: 10
readonly property int about: 11
readonly property int communitiesSettings: 12
readonly property int keycard: 13
readonly property int about_terms: 14 // a subpage under "About"
readonly property int about_privacy: 15 // a subpage under "About"
readonly property int password: 1
readonly property int contacts: 2
readonly property int ensUsernames: 3
readonly property int messaging: 4
readonly property int wallet:5
readonly property int appearance: 6
readonly property int language: 7
readonly property int notifications: 8
readonly property int syncingSettings: 9
readonly property int browserSettings: 10
readonly property int advanced: 11
readonly property int about: 12
readonly property int communitiesSettings: 13
readonly property int keycard: 14
readonly property int about_terms: 15 // a subpage under "About"
readonly property int about_privacy: 16 // a subpage under "About"
// special treatment; these do not participate in the main settings' StackLayout
readonly property int signout: 16
readonly property int backUpSeed: 17
readonly property int signout: 17
readonly property int backUpSeed: 18
}
readonly property QtObject walletSettingsSubsection: QtObject {

View File

@ -17,6 +17,10 @@ QtObject {
readonly property int maxImgSizeBytes: Constants.maxUploadFilesizeMB * 1048576 /* 1 MB in bytes */
readonly property int communityIdLength: 68
function restartApplication() {
globalUtilsInst.restartApplication()
}
function isDigit(value) {
return /^\d$/.test(value);
}

View File

@ -127,6 +127,9 @@ DOS_API void DOS_CALL dos_qguiapplication_icon(const char *filename);
/// \note A QGuiApplication should have been already created through dos_qguiapplication_create()
DOS_API void DOS_CALL dos_qguiapplication_quit(void);
/// @brief Calls the QGuiApplication::quit() function of the current QGuiApplication and QProcess::startDetached to spawn another process
DOS_API void DOS_CALL dos_qguiapplication_restart(void);
/// \brief Free the memory of the current QGuiApplication
/// \note A QGuiApplication should have been already created through dos_qguiapplication_create()
DOS_API void DOS_CALL dos_qguiapplication_delete(void);

View File

@ -371,6 +371,12 @@ void dos_qguiapplication_quit()
QMetaObject::invokeMethod(qGuiApp, "quit", Qt::QueuedConnection);
}
void dos_qguiapplication_restart()
{
QProcess::startDetached(QCoreApplication::applicationFilePath());
dos_qguiapplication_quit();
}
void dos_qguiapplication_icon(const char *filename)
{
qGuiApp->setWindowIcon(QIcon(filename));

2
vendor/nimqml vendored

@ -1 +1 @@
Subproject commit 2d733c5ec6977edac451c4d0017911b59d01a310
Subproject commit 13a8890db484d3ff40b410c2ff4b3e3bd2e0e880