Rework HBox-based pages and account settings

- Refactor everything about HBox, and adapt all the pages and popups
  that used it

- Replace HTabContainer by HTabbedBox

- Make boxes swippable

- Make esc presses in boxes click the cancel button

- Make all boxes and popups scrollable when needed

- Replace generic apply button icons in popups

- Fix tab focus for error and invite popups

- Rework (still WIP) the account settings page:
  - Use the standard tabbed design of other pages
  - Ditch the horizontal profile layout, hacky and impossible to extend
  - Add real-time coloring for the display name field

- Implement a device list in account settings (Sessions, still WIP)
This commit is contained in:
miruka
2020-06-25 08:32:08 -04:00
parent 72bd78c77e
commit da4a5ab5cd
66 changed files with 1594 additions and 1173 deletions

View File

@@ -4,16 +4,21 @@ import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
HFlickableColumnPage {
title: qsTr("Add an account")
header: HPageHeader {}
HPage {
id: page
HTabContainer {
tabModel: [
qsTr("Sign in"), qsTr("Register"), qsTr("Reset"),
]
HTabbedBox {
anchors.centerIn: parent
width: Math.min(implicitWidth, page.availableWidth)
height: Math.min(implicitHeight, page.availableHeight)
SignIn { Component.onCompleted: forceActiveFocus() }
header: HTabBar {
HTabButton { text: qsTr("Sign in") }
HTabButton { text: qsTr("Register") }
HTabButton { text: qsTr("Reset") }
}
SignIn {}
Register {}
Reset {}
}

View File

@@ -3,28 +3,30 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../Base/ButtonLayout"
HBox {
id: signInBox
clickButtonOnEnter: "ok"
HFlickableColumnPage {
function takeFocus() { registerButton.forceActiveFocus() }
buttonModel: [
{ name: "ok", text: qsTr("Register from Riot"), iconName: "register" },
]
buttonCallbacks: ({
ok: button => {
Qt.openUrlExternally("https://riot.im/app/#/register")
footer: ButtonLayout {
ApplyButton {
id: registerButton
text: qsTr("Register from Riot")
icon.name: "register"
onClicked: Qt.openUrlExternally("https://riot.im/app/#/register")
Layout.fillWidth: true
}
})
}
HLabel {
wrapMode: Text.Wrap
horizontalAlignment: Qt.AlignHCenter
text: qsTr(
"Not yet implemented\n\nYou can create a new " +
"account from another client such as Riot."
"Not implemented yet\n\n" +
"You can create a new account from another client such as Riot."
)
Layout.fillWidth: true

View File

@@ -3,32 +3,31 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../Base/ButtonLayout"
HBox {
id: signInBox
clickButtonOnEnter: "ok"
HFlickableColumnPage {
function takeFocus() { resetButton.forceActiveFocus() }
buttonModel: [
{
name: "ok",
text: qsTr("Reset password from Riot"),
iconName: "reset-password"
},
]
buttonCallbacks: ({
ok: button => {
Qt.openUrlExternally("https://riot.im/app/#/forgot_password")
footer: ButtonLayout {
ApplyButton {
id: resetButton
text: qsTr("Reset password from Riot")
icon.name: "reset-password"
onClicked:
Qt.openUrlExternally("https://riot.im/app/#/forgot_password")
Layout.fillWidth: true
}
})
}
HLabel {
wrapMode: Text.Wrap
horizontalAlignment: Qt.AlignHCenter
text: qsTr(
"Not yet implemented\n\nYou can reset your " +
"password using another client such as Riot."
"Not implemented yet\n\n" +
"You can reset your password from another client such as Riot."
)
Layout.fillWidth: true

View File

@@ -3,82 +3,10 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../Base/ButtonLayout"
HBox {
id: signInBox
clickButtonOnEnter: "apply"
onFocusChanged: idField.item.forceActiveFocus()
buttonModel: [
{
name: "apply",
text: qsTr("Sign in"),
enabled: canSignIn,
iconName: "sign-in",
loading: loginFuture !== null,
disableWhileLoading: false,
},
{ name: "cancel", text: qsTr("Cancel"), iconName: "cancel"},
]
buttonCallbacks: ({
apply: button => {
if (loginFuture) loginFuture.cancel()
signInTimeout.restart()
errorMessage.text = ""
const args = [
idField.item.text.trim(), passwordField.item.text,
undefined, serverField.item.text.trim(),
]
loginFuture = py.callCoro("login_client", args, userId => {
signInTimeout.stop()
errorMessage.text = ""
loginFuture = null
py.callCoro(
rememberAccount.checked ?
"saved_accounts.add": "saved_accounts.delete",
[userId]
)
pageLoader.showPage(
"AccountSettings/AccountSettings", {userId}
)
}, (type, args, error, traceback, uuid) => {
loginFuture = null
signInTimeout.stop()
let txt = qsTr(
"Invalid request, login type or unknown error: %1",
).arg(type)
type === "MatrixForbidden" ?
txt = qsTr("Invalid username or password") :
type === "MatrixUserDeactivated" ?
txt = qsTr("This account was deactivated") :
utils.showError(type, traceback, uuid)
errorMessage.text = txt
})
},
cancel: button => {
if (! loginFuture) return
signInTimeout.stop()
loginFuture.cancel()
loginFuture = null
}
})
HFlickableColumnPage {
id: page
property var loginFuture: null
@@ -90,6 +18,83 @@ HBox {
passwordField.item.text && ! serverField.item.error
function takeFocus() { idField.item.forceActiveFocus() }
function signIn() {
if (page.loginFuture) page.loginFuture.cancel()
signInTimeout.restart()
errorMessage.text = ""
const args = [
idField.item.text.trim(), passwordField.item.text,
undefined, serverField.item.text.trim(),
]
page.loginFuture = py.callCoro("login_client", args, userId => {
signInTimeout.stop()
errorMessage.text = ""
page.loginFuture = null
py.callCoro(
rememberAccount.checked ?
"saved_accounts.add": "saved_accounts.delete",
[userId]
)
pageLoader.showPage(
"AccountSettings/AccountSettings", {userId}
)
}, (type, args, error, traceback, uuid) => {
page.loginFuture = null
signInTimeout.stop()
let txt = qsTr(
"Invalid request, login type or unknown error: %1",
).arg(type)
type === "MatrixForbidden" ?
txt = qsTr("Invalid username or password") :
type === "MatrixUserDeactivated" ?
txt = qsTr("This account was deactivated") :
utils.showError(type, traceback, uuid)
errorMessage.text = txt
})
}
function cancel() {
if (! page.loginFuture) return
signInTimeout.stop()
page.loginFuture.cancel()
page.loginFuture = null
}
footer: ButtonLayout {
ApplyButton {
enabled: page.canSignIn
text: qsTr("Sign in")
icon.name: "sign-in"
loading: page.loginFuture !== null
disableWhileLoading: false
onClicked: page.signIn()
}
CancelButton {
onClicked: page.cancel()
}
}
Keys.onEscapePressed: page.cancel()
Timer {
id: signInTimeout
interval: 30 * 1000
@@ -120,10 +125,10 @@ HBox {
HButton {
icon.name: modelData
circle: true
checked: signInWith === modelData
checked: page.signInWith === modelData
enabled: modelData === "username"
autoExclusive: true
onClicked: signInWith = modelData
onClicked: page.signInWith = modelData
}
}
}
@@ -131,8 +136,8 @@ HBox {
HLabeledItem {
id: idField
label.text: qsTr(
signInWith === "email" ? "Email:" :
signInWith === "phone" ? "Phone:" :
page.signInWith === "email" ? "Email:" :
page.signInWith === "phone" ? "Phone:" :
"Username:"
)
@@ -157,9 +162,6 @@ HBox {
HLabeledItem {
id: serverField
label.text: qsTr("Homeserver:")
Layout.fillWidth: true
// 2019-11-11 https://www.hello-matrix.net/public_servers.php
readonly property var knownServers: [
@@ -182,6 +184,10 @@ HBox {
readonly property bool knownServerChosen:
knownServers.includes(item.cleanText)
label.text: qsTr("Homeserver:")
Layout.fillWidth: true
HTextField {
width: parent.width
text: "https://matrix.org"