diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4581df94..7dd4d245 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -331,24 +331,13 @@ set(SRC_FILES
src/timeline/RoomlistModel.cpp
# UI components
- src/ui/DropShadow.cpp
- src/ui/FlatButton.cpp
- src/ui/Label.cpp
- src/ui/LoadingIndicator.cpp
src/ui/MxcAnimatedImage.cpp
src/ui/MxcMediaProxy.cpp
src/ui/NhekoCursorShape.cpp
src/ui/NhekoDropArea.cpp
src/ui/NhekoGlobalObject.cpp
- src/ui/OverlayModal.cpp
- src/ui/OverlayWidget.cpp
- src/ui/RaisedButton.cpp
- src/ui/Ripple.cpp
- src/ui/RippleOverlay.cpp
src/ui/RoomSettings.cpp
- src/ui/SnackBar.cpp
src/ui/TextField.cpp
- src/ui/TextLabel.cpp
src/ui/Theme.cpp
src/ui/ThemeManager.cpp
src/ui/ToggleButton.cpp
@@ -395,7 +384,6 @@ set(SRC_FILES
src/RoomDirectoryModel.cpp
src/RoomsModel.cpp
src/Utils.cpp
- src/WelcomePage.cpp
src/main.cpp
third_party/blurhash/blurhash.cpp
@@ -414,7 +402,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
- GIT_TAG 6a7eaa5006b1a18e132be7655e490d9819158dca
+ GIT_TAG 9781553b0186f2db9036d2abbde83c28828eb2b9
)
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
@@ -538,23 +526,13 @@ qt5_wrap_cpp(MOC_HEADERS
src/timeline/RoomlistModel.h
# UI components
- src/ui/FlatButton.h
- src/ui/Label.h
- src/ui/LoadingIndicator.h
src/ui/MxcAnimatedImage.h
src/ui/MxcMediaProxy.h
- src/ui/Menu.h
src/ui/NhekoCursorShape.h
src/ui/NhekoDropArea.h
src/ui/NhekoGlobalObject.h
- src/ui/OverlayWidget.h
- src/ui/RaisedButton.h
- src/ui/Ripple.h
- src/ui/RippleOverlay.h
src/ui/RoomSettings.h
- src/ui/SnackBar.h
src/ui/TextField.h
- src/ui/TextLabel.h
src/ui/Theme.h
src/ui/ThemeManager.h
src/ui/ToggleButton.h
@@ -596,7 +574,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/UsersModel.h
src/RoomDirectoryModel.h
src/RoomsModel.h
- src/WelcomePage.h
src/ReadReceiptsModel.h
)
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 946ca7b6..700b6d02 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -191,7 +191,7 @@ modules:
buildsystem: cmake-ninja
name: mtxclient
sources:
- - commit: 6a7eaa5006b1a18e132be7655e490d9819158dca
+ - commit: 9781553b0186f2db9036d2abbde83c28828eb2b9
#tag: v0.6.1
type: git
url: https://github.com/Nheko-Reborn/mtxclient.git
diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts
index 29a04355..c499874b 100644
--- a/resources/langs/nheko_de.ts
+++ b/resources/langs/nheko_de.ts
@@ -319,7 +319,7 @@
Failed to kick %1 from %2: %3
- Kontte %1 nicht aus %2 entfernen: %3
+ Konnte %1 nicht aus %2 entfernen: %3
diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml
index 33db1b1a..e3aa3e48 100644
--- a/resources/qml/ChatPage.qml
+++ b/resources/qml/ChatPage.qml
@@ -97,6 +97,7 @@ Rectangle {
implicitHeight: chatPage.height
collapsed: parent.collapsed
+ anchors.fill: parent
}
Binding {
diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml
index af077124..05f2c82f 100644
--- a/resources/qml/MatrixTextField.qml
+++ b/resources/qml/MatrixTextField.qml
@@ -8,29 +8,139 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import im.nheko 1.0
-TextField {
- id: input
- property alias backgroundColor: backgroundRect.color
+ColumnLayout {
+ id: c
+ property color backgroundColor: Nheko.colors.base
+ property alias color: labelC.color
+ property alias textPadding: input.padding
+ property alias text: input.text
+ property alias label: labelC.text
+ property alias placeholderText: input.placeholderText
+ property alias font: input.font
+ property alias echoMode: input.echoMode
+ property alias selectByMouse: input.selectByMouse
- palette: Nheko.colors
- color: Nheko.colors.text
+ Timer {
+ id: timer
+ interval: 350
+ onTriggered: editingFinished()
+ }
+
+ onTextChanged: timer.restart()
+
+ signal textEdited
+ signal accepted
+ signal editingFinished
+
+ function forceActiveFocus() {
+ input.forceActiveFocus();
+ }
+
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.visible: hover.hovered
+
+ spacing: 0
+
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: labelC.contentHeight
+ Layout.margins: input.padding
+ Layout.bottomMargin: Nheko.paddingSmall
+ visible: labelC.text
+
+ z: 1
+
+ Label {
+ id: labelC
+
+ y: contentHeight + input.padding + Nheko.paddingSmall
+ enabled: false
+
+ palette: Nheko.colors
+ color: Nheko.colors.text
+ font.pixelSize: input.font.pixelSize
+ font.weight: Font.DemiBold
+ font.letterSpacing: input.font.pixelSize * 0.02
+ width: parent.width
+
+ state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
+
+ states: State {
+ name: "focused"
+
+ PropertyChanges {
+ target: labelC
+ y: 0
+ }
+
+ PropertyChanges {
+ target: input
+ opacity: 1
+ }
+
+ }
+
+ transitions: Transition {
+ from: ""
+ to: "focused"
+ reversible: true
+
+ NumberAnimation {
+ target: labelC
+ properties: "y"
+ duration: 210
+ easing.type: Easing.InCubic
+ alwaysRunToEnd: true
+ }
+
+ NumberAnimation {
+ target: input
+ properties: "opacity"
+ duration: 210
+ easing.type: Easing.InCubic
+ alwaysRunToEnd: true
+ }
+
+ }
+ }
+ }
+
+ TextField {
+ id: input
+ Layout.fillWidth: true
+
+ palette: Nheko.colors
+ color: labelC.color
+ opacity: labelC.text ? 0 : 1
+
+ onTextEdited: c.textEdited()
+ onAccepted: c.accepted()
+ onEditingFinished: c.editingFinished()
+
+
+ background: Rectangle {
+ id: backgroundRect
+
+ color: labelC.text ? "transparent" : backgroundColor
+ }
+
+ }
Rectangle {
id: blueBar
- anchors.top: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
+ Layout.fillWidth: true
+
color: Nheko.colors.highlight
height: 1
- width: parent.width
Rectangle {
id: blackBar
- anchors.verticalCenter: blueBar.verticalCenter
+ anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
- height: parent.height + 1
+ height: parent.height*2
width: 0
color: Nheko.colors.text
@@ -50,11 +160,12 @@ TextField {
to: "focused"
reversible: true
+
NumberAnimation {
target: blackBar
properties: "width"
- duration: 500
- easing.type: Easing.InOutQuad
+ duration: 310
+ easing.type: Easing.InCubic
alwaysRunToEnd: true
}
@@ -64,10 +175,8 @@ TextField {
}
- background: Rectangle {
- id: backgroundRect
-
- color: Nheko.colors.base
+ HoverHandler {
+ id: hover
+ enabled: c.ToolTip.text
}
-
}
diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml
index e6286bc6..fb3818df 100644
--- a/resources/qml/PrivacyScreen.qml
+++ b/resources/qml/PrivacyScreen.qml
@@ -5,6 +5,7 @@
import QtGraphicalEffects 1.0
import QtQuick 2.12
+import QtQuick.Window 2.2
import im.nheko 1.0
Item {
@@ -15,7 +16,7 @@ Item {
Connections {
function onFocusChanged() {
- if (TimelineManager.isWindowFocused) {
+ if (MainWindow.active) {
screenSaverTimer.stop();
screenSaver.state = "Invisible";
} else {
@@ -32,7 +33,7 @@ Item {
id: screenSaverTimer
interval: screenTimeout * 1000
- running: true
+ running: !MainWindow.active
onTriggered: {
screenSaver.state = "Visible";
}
diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml
index 174951a0..8747c47d 100644
--- a/resources/qml/QuickSwitcher.qml
+++ b/resources/qml/QuickSwitcher.qml
@@ -11,7 +11,6 @@ Popup {
id: quickSwitcher
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
- property int textMargin: Math.round(textHeight / 8)
background: null
width: Math.round(parent.width / 2)
@@ -34,7 +33,6 @@ Popup {
anchors.fill: parent
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
- padding: textMargin
color: Nheko.colors.text
onTextEdited: {
completerPopup.completer.searchString = text;
@@ -60,7 +58,7 @@ Popup {
id: completerPopup
x: roomTextInput.x
- y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
+ y: roomTextInput.y + quickSwitcher.textHeight
visible: roomTextInput.length > 0
width: parent.width
completerName: "room"
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 6e7b683f..da205950 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -385,7 +385,7 @@ Page {
header: ColumnLayout {
spacing: 0
- Rectangle {
+ Pane {
id: userInfoPanel
function openUserProfile() {
@@ -396,12 +396,15 @@ Page {
userProfile.show();
}
- color: Nheko.colors.window
+
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
- Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
+ //Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
+ padding: Nheko.paddingMedium
Layout.minimumHeight: 40
+ background: Rectangle {color: Nheko.colors.window}
+
InputDialog {
id: statusDialog
@@ -442,14 +445,12 @@ Page {
gesturePolicy: TapHandler.ReleaseWithinBounds
}
- RowLayout {
+ contentItem: RowLayout {
id: userInfoGrid
property var profile: Nheko.currentUser
spacing: Nheko.paddingMedium
- anchors.fill: parent
- anchors.margins: Nheko.paddingMedium
Avatar {
id: avatar
@@ -614,19 +615,17 @@ Page {
Layout.fillWidth: true
}
- Rectangle {
- color: Nheko.colors.window
+ Pane {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
- Layout.preferredHeight: buttonRow.implicitHeight
Layout.minimumHeight: 40
- RowLayout {
- id: buttonRow
+ horizontalPadding: Nheko.paddingMedium
+ verticalPadding: 0
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Nheko.paddingMedium
+ background: Rectangle {color: Nheko.colors.window}
+ contentItem: RowLayout {
+ id: buttonRow
ImageButton {
Layout.fillWidth: true
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index e4b164e4..88d3e7c6 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -9,6 +9,7 @@ import "./dialogs"
import "./emoji"
import "./pages"
import "./voip"
+import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
@@ -17,10 +18,12 @@ import QtQuick.Window 2.15
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
-Page {
+Pane {
id: timelineRoot
palette: Nheko.colors
+ background: null
+ padding: 0
FontMetrics {
id: fontMetrics
@@ -153,11 +156,15 @@ Page {
}
+ Shortcut {
+ sequence: StandardKey.Quit
+ onActivated: Qt.quit()
+ }
+
Shortcut {
sequence: "Ctrl+K"
onActivated: {
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
- TimelineManager.focusTimeline();
quickSwitch.open();
}
}
@@ -165,7 +172,6 @@ Page {
Shortcut {
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
sequences: ["Alt+A", "Ctrl+Shift+A"]
- context: Qt.ApplicationShortcut
onActivated: Rooms.nextRoomWithActivity()
}
@@ -366,9 +372,51 @@ Page {
id: mainWindow
anchors.fill: parent
- initialItem: ChatPage {
- //anchors.fill: parent
+ initialItem: welcomePage
+ }
+
+ Component {
+ id: welcomePage
+
+ WelcomePage {
}
}
+ Component {
+ id: chatPage
+
+ ChatPage {
+ }
+ }
+
+ Component {
+ id: loginPage
+
+ LoginPage {
+ }
+ }
+
+ Component {
+ id: registerPage
+
+ RegisterPage {
+ }
+ }
+
+ Snackbar { id: snackbar }
+
+ Connections {
+ function onSwitchToChatPage() {
+ mainWindow.replace(null, chatPage);
+ }
+ function onSwitchToLoginPage(error) {
+ mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
+ }
+ function onShowNotification(msg) {
+ snackbar.showNotification(msg);
+ console.log("New snack: " + msg);
+ }
+ target: MainWindow
+ }
+
}
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 1c0115e2..c9a8d0d2 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -12,7 +12,7 @@ import im.nheko 1.0
import "./delegates"
-Rectangle {
+Pane {
id: topBar
property bool showBackButton: false
@@ -28,7 +28,11 @@ Rectangle {
Layout.fillWidth: true
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
z: 3
- color: Nheko.colors.window
+
+ padding: 0
+ background: Rectangle {
+ color: Nheko.colors.window
+ }
TapHandler {
onSingleTapped: {
@@ -65,248 +69,250 @@ Rectangle {
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
}
- GridLayout {
- id: topLayout
+ contentItem: Item {
+ GridLayout {
+ id: topLayout
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Nheko.paddingMedium
- anchors.verticalCenter: parent.verticalCenter
- columnSpacing: Nheko.paddingSmall
- rowSpacing: Nheko.paddingSmall
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: Nheko.paddingMedium
+ anchors.verticalCenter: parent.verticalCenter
+ columnSpacing: Nheko.paddingSmall
+ rowSpacing: Nheko.paddingSmall
- ImageButton {
- id: backToRoomsButton
+ ImageButton {
+ id: backToRoomsButton
- Layout.column: 0
- Layout.row: 0
- Layout.rowSpan: 2
- Layout.alignment: Qt.AlignVCenter
- Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
- Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
- visible: showBackButton
- image: ":/icons/icons/ui/angle-arrow-left.svg"
- ToolTip.visible: hovered
- ToolTip.text: qsTr("Back to room list")
- onClicked: Rooms.resetCurrentRoom()
- }
-
- Avatar {
- Layout.column: 1
- Layout.row: 0
- Layout.rowSpan: 2
- Layout.alignment: Qt.AlignVCenter
- width: Nheko.avatarSize
- height: Nheko.avatarSize
- url: avatarUrl.replace("mxc://", "image://MxcImage/")
- roomid: roomId
- userid: isDirect ? directChatOtherUserId : ""
- displayName: roomName
- enabled: false
- }
-
- Label {
- Layout.fillWidth: true
- Layout.column: 2
- Layout.row: 0
- color: Nheko.colors.text
- font.pointSize: fontMetrics.font.pointSize * 1.1
- text: roomName
- maximumLineCount: 1
- elide: Text.ElideRight
- textFormat: Text.RichText
- }
-
- MatrixText {
- id: roomTopicC
- Layout.fillWidth: true
- Layout.column: 2
- Layout.row: 1
- Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
- selectByMouse: false
- enabled: false
- clip: true
- text: roomTopic
- }
-
- EncryptionIndicator {
- Layout.column: 3
- Layout.row: 0
- Layout.rowSpan: 2
- Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
- Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
- sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio
- sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio
- visible: isEncrypted
- encrypted: isEncrypted
- trust: trustlevel
- ToolTip.text: {
- if (!encrypted)
- return qsTr("This room is not encrypted!");
-
- switch (trust) {
- case Crypto.Verified:
- return qsTr("This room contains only verified devices.");
- case Crypto.TOFU:
- return qsTr("This room contains verified devices and devices which have never changed their master key.");
- default:
- return qsTr("This room contains unverified devices!");
- }
+ Layout.column: 0
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
+ Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
+ visible: showBackButton
+ image: ":/icons/icons/ui/angle-arrow-left.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Back to room list")
+ onClicked: Rooms.resetCurrentRoom()
}
- }
- ImageButton {
- id: pinButton
+ Avatar {
+ Layout.column: 1
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ url: avatarUrl.replace("mxc://", "image://MxcImage/")
+ roomid: roomId
+ userid: isDirect ? directChatOtherUserId : ""
+ displayName: roomName
+ enabled: false
+ }
- property bool pinsShown: !Settings.hiddenPins.includes(roomId)
+ Label {
+ Layout.fillWidth: true
+ Layout.column: 2
+ Layout.row: 0
+ color: Nheko.colors.text
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+ text: roomName
+ maximumLineCount: 1
+ elide: Text.ElideRight
+ textFormat: Text.RichText
+ }
- visible: !!room && room.pinnedMessages.length > 0
- Layout.column: 4
- Layout.row: 0
- Layout.rowSpan: 2
- Layout.alignment: Qt.AlignVCenter
- Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
- Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
- image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
- ToolTip.visible: hovered
- ToolTip.text: qsTr("Show or hide pinned messages")
- onClicked: {
- var ps = Settings.hiddenPins;
- if (pinsShown) {
- ps.push(roomId);
- } else {
- const index = ps.indexOf(roomId);
- if (index > -1) {
- ps.splice(index, 1);
+ MatrixText {
+ id: roomTopicC
+ Layout.fillWidth: true
+ Layout.column: 2
+ Layout.row: 1
+ Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
+ selectByMouse: false
+ enabled: false
+ clip: true
+ text: roomTopic
+ }
+
+ EncryptionIndicator {
+ Layout.column: 3
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
+ Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
+ sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio
+ sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio
+ visible: isEncrypted
+ encrypted: isEncrypted
+ trust: trustlevel
+ ToolTip.text: {
+ if (!encrypted)
+ return qsTr("This room is not encrypted!");
+
+ switch (trust) {
+ case Crypto.Verified:
+ return qsTr("This room contains only verified devices.");
+ case Crypto.TOFU:
+ return qsTr("This room contains verified devices and devices which have never changed their master key.");
+ default:
+ return qsTr("This room contains unverified devices!");
}
}
- Settings.hiddenPins = ps;
}
- }
+ ImageButton {
+ id: pinButton
- ImageButton {
- id: roomOptionsButton
+ property bool pinsShown: !Settings.hiddenPins.includes(roomId)
- visible: !!room
- Layout.column: 5
- Layout.row: 0
- Layout.rowSpan: 2
- Layout.alignment: Qt.AlignVCenter
- Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
- Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
- image: ":/icons/icons/ui/options.svg"
- ToolTip.visible: hovered
- ToolTip.text: qsTr("Room options")
- onClicked: roomOptionsMenu.open(roomOptionsButton)
-
- Platform.Menu {
- id: roomOptionsMenu
-
- Platform.MenuItem {
- visible: room ? room.permissions.canInvite() : false
- text: qsTr("Invite users")
- onTriggered: TimelineManager.openInviteUsers(roomId)
- }
-
- Platform.MenuItem {
- text: qsTr("Members")
- onTriggered: TimelineManager.openRoomMembers(room)
- }
-
- Platform.MenuItem {
- text: qsTr("Leave room")
- onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
- }
-
- Platform.MenuItem {
- text: qsTr("Settings")
- onTriggered: TimelineManager.openRoomSettings(roomId)
+ visible: !!room && room.pinnedMessages.length > 0
+ Layout.column: 4
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
+ Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
+ image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Show or hide pinned messages")
+ onClicked: {
+ var ps = Settings.hiddenPins;
+ if (pinsShown) {
+ ps.push(roomId);
+ } else {
+ const index = ps.indexOf(roomId);
+ if (index > -1) {
+ ps.splice(index, 1);
+ }
+ }
+ Settings.hiddenPins = ps;
}
}
- }
+ ImageButton {
+ id: roomOptionsButton
- ScrollView {
- id: pinnedMessages
+ visible: !!room
+ Layout.column: 5
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
+ Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
+ image: ":/icons/icons/ui/options.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Room options")
+ onClicked: roomOptionsMenu.open(roomOptionsButton)
- Layout.row: 2
- Layout.column: 2
- Layout.columnSpan: 3
+ Platform.Menu {
+ id: roomOptionsMenu
- Layout.fillWidth: true
- Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
-
- visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
- clip: true
-
- palette: Nheko.colors
- ScrollBar.horizontal.visible: false
-
- ListView {
-
- spacing: Nheko.paddingSmall
- model: room ? room.pinnedMessages : undefined
- delegate: RowLayout {
- required property string modelData
-
- width: ListView.view.width
- height: implicitHeight
-
- Reply {
- property var e: room ? room.getDump(modelData, "") : {}
- Layout.fillWidth: true
- Layout.preferredHeight: height
-
- userColor: TimelineManager.userColor(e.userId, Nheko.colors.window)
- blurhash: e.blurhash ?? ""
- body: e.body ?? ""
- formattedBody: e.formattedBody ?? ""
- eventId: e.eventId ?? ""
- filename: e.filename ?? ""
- filesize: e.filesize ?? ""
- proportionalHeight: e.proportionalHeight ?? 1
- type: e.type ?? MtxEvent.UnknownMessage
- typeString: e.typeString ?? ""
- url: e.url ?? ""
- originalWidth: e.originalWidth ?? 0
- isOnlyEmoji: e.isOnlyEmoji ?? false
- userId: e.userId ?? ""
- userName: e.userName ?? ""
- encryptionError: e.encryptionError ?? ""
+ Platform.MenuItem {
+ visible: room ? room.permissions.canInvite() : false
+ text: qsTr("Invite users")
+ onTriggered: TimelineManager.openInviteUsers(roomId)
}
- ImageButton {
- id: deletePinButton
-
- Layout.preferredHeight: 16
- Layout.preferredWidth: 16
- Layout.alignment: Qt.AlignTop | Qt.AlignLeft
- visible: room.permissions.canChange(MtxEvent.PinnedEvents)
-
- hoverEnabled: true
- image: ":/icons/icons/ui/dismiss.svg"
- ToolTip.visible: hovered
- ToolTip.text: qsTr("Unpin")
-
- onClicked: room.unpin(modelData)
+ Platform.MenuItem {
+ text: qsTr("Members")
+ onTriggered: TimelineManager.openRoomMembers(room)
}
+
+ Platform.MenuItem {
+ text: qsTr("Leave room")
+ onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
+ }
+
+ Platform.MenuItem {
+ text: qsTr("Settings")
+ onTriggered: TimelineManager.openRoomSettings(roomId)
+ }
+
}
+ }
- ScrollHelper {
- flickable: parent
- anchors.fill: parent
- enabled: !Settings.mobileMode
+ ScrollView {
+ id: pinnedMessages
+
+ Layout.row: 2
+ Layout.column: 2
+ Layout.columnSpan: 3
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
+
+ visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
+ clip: true
+
+ palette: Nheko.colors
+ ScrollBar.horizontal.visible: false
+
+ ListView {
+
+ spacing: Nheko.paddingSmall
+ model: room ? room.pinnedMessages : undefined
+ delegate: RowLayout {
+ required property string modelData
+
+ width: ListView.view.width
+ height: implicitHeight
+
+ Reply {
+ property var e: room ? room.getDump(modelData, "") : {}
+ Layout.fillWidth: true
+ Layout.preferredHeight: height
+
+ userColor: TimelineManager.userColor(e.userId, Nheko.colors.window)
+ blurhash: e.blurhash ?? ""
+ body: e.body ?? ""
+ formattedBody: e.formattedBody ?? ""
+ eventId: e.eventId ?? ""
+ filename: e.filename ?? ""
+ filesize: e.filesize ?? ""
+ proportionalHeight: e.proportionalHeight ?? 1
+ type: e.type ?? MtxEvent.UnknownMessage
+ typeString: e.typeString ?? ""
+ url: e.url ?? ""
+ originalWidth: e.originalWidth ?? 0
+ isOnlyEmoji: e.isOnlyEmoji ?? false
+ userId: e.userId ?? ""
+ userName: e.userName ?? ""
+ encryptionError: e.encryptionError ?? ""
+ }
+
+ ImageButton {
+ id: deletePinButton
+
+ Layout.preferredHeight: 16
+ Layout.preferredWidth: 16
+ Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+ visible: room.permissions.canChange(MtxEvent.PinnedEvents)
+
+ hoverEnabled: true
+ image: ":/icons/icons/ui/dismiss.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Unpin")
+
+ onClicked: room.unpin(modelData)
+ }
+ }
+
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
}
}
}
- }
- CursorShape {
- anchors.fill: parent
- anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
- cursorShape: Qt.PointingHandCursor
+ CursorShape {
+ anchors.fill: parent
+ anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
+ cursorShape: Qt.PointingHandCursor
+ }
}
}
diff --git a/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml
index 8ca3f104..72184d28 100644
--- a/resources/qml/components/FlatButton.qml
+++ b/resources/qml/components/FlatButton.qml
@@ -12,7 +12,7 @@ import im.nheko 1.0
Button {
id: control
- implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.5)
+ implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
hoverEnabled: true
@@ -42,7 +42,7 @@ Button {
background: Rectangle {
//height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2
- radius: height / 6
+ radius: height / 8
color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index c00a0bdb..90dc9ac4 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -21,7 +21,6 @@ ApplicationWindow {
minimumHeight: stack.implicitHeight
width: stack.implicitWidth
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(dialog)
StackView {
id: stack
diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index 3ba04d94..eb420fce 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -12,8 +12,6 @@ import QtQuick.Layouts 1.12
import im.nheko 1.0
ApplicationWindow {
- //Component.onCompleted: Nheko.reparent(win)
-
id: win
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
@@ -175,39 +173,37 @@ ApplicationWindow {
}
}
- MatrixText {
- visible: imagePack.roomid
- text: qsTr("State key")
- }
-
MatrixTextField {
+ id: statekeyField
+
visible: imagePack.roomid
Layout.fillWidth: true
+ Layout.columnSpan: 2
+ label: qsTr("State key")
text: imagePack.statekey
onTextEdited: imagePack.statekey = text
}
- MatrixText {
- text: qsTr("Packname")
- }
-
MatrixTextField {
Layout.fillWidth: true
+ Layout.columnSpan: 2
+ label: qsTr("Packname")
text: imagePack.packname
onTextEdited: imagePack.packname = text
}
- MatrixText {
- text: qsTr("Attribution")
- }
-
MatrixTextField {
Layout.fillWidth: true
+ Layout.columnSpan: 2
+ label: qsTr("Attribution")
text: imagePack.attribution
onTextEdited: imagePack.attribution = text
}
MatrixText {
+ Layout.margins: statekeyField.textPadding
+ font.weight: Font.DemiBold
+ font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Emoji")
}
@@ -218,6 +214,9 @@ ApplicationWindow {
}
MatrixText {
+ Layout.margins: statekeyField.textPadding
+ font.weight: Font.DemiBold
+ font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Sticker")
}
@@ -253,27 +252,28 @@ ApplicationWindow {
Layout.alignment: Qt.AlignHCenter
}
- MatrixText {
- text: qsTr("Shortcode")
- }
-
MatrixTextField {
Layout.fillWidth: true
+ Layout.columnSpan: 2
+ label: qsTr("Shortcode")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
}
- MatrixText {
- text: qsTr("Body")
- }
-
MatrixTextField {
+ id: bodyField
+
Layout.fillWidth: true
+ Layout.columnSpan: 2
+ label: qsTr("Body")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
}
MatrixText {
+ Layout.margins: bodyField.textPadding
+ font.weight: Font.DemiBold
+ font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Emoji")
}
@@ -284,6 +284,9 @@ ApplicationWindow {
}
MatrixText {
+ Layout.margins: bodyField.textPadding
+ font.weight: Font.DemiBold
+ font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Use as Sticker")
}
@@ -294,6 +297,9 @@ ApplicationWindow {
}
MatrixText {
+ Layout.margins: bodyField.textPadding
+ font.weight: Font.DemiBold
+ font.letterSpacing: font.pixelSize * 0.02
text: qsTr("Remove from pack")
}
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index fa079855..18c32c41 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -28,7 +28,6 @@ ApplicationWindow {
color: Nheko.colors.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(win)
Component {
id: packEditor
diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml
index 63ca3181..cf1474dc 100644
--- a/resources/qml/dialogs/InputDialog.qml
+++ b/resources/qml/dialogs/InputDialog.qml
@@ -18,7 +18,6 @@ ApplicationWindow {
modality: Qt.NonModal
flags: Qt.Dialog
- Component.onCompleted: Nheko.reparent(inputDialog)
width: 350
height: fontMetrics.lineSpacing * 7
diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml
index 917bc856..e7dd4e3a 100644
--- a/resources/qml/dialogs/InviteDialog.qml
+++ b/resources/qml/dialogs/InviteDialog.qml
@@ -37,7 +37,6 @@ ApplicationWindow {
palette: Nheko.colors
color: Nheko.colors.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(inviteDialogRoot)
Shortcut {
sequence: "Ctrl+Enter"
diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml
index 9ce6bcf1..e49f538d 100644
--- a/resources/qml/dialogs/JoinRoomDialog.qml
+++ b/resources/qml/dialogs/JoinRoomDialog.qml
@@ -17,7 +17,6 @@ ApplicationWindow {
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
palette: Nheko.colors
color: Nheko.colors.window
- Component.onCompleted: Nheko.reparent(joinRoomRoot)
width: 350
height: fontMetrics.lineSpacing * 7
diff --git a/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml
index 399b11d5..9c36c98f 100644
--- a/resources/qml/dialogs/PhoneNumberInputDialog.qml
+++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml
@@ -19,7 +19,6 @@ ApplicationWindow {
modality: Qt.NonModal
flags: Qt.Dialog
- Component.onCompleted: Nheko.reparent(inputDialog)
width: 350
height: fontMetrics.lineSpacing * 7
diff --git a/resources/qml/dialogs/RawMessageDialog.qml b/resources/qml/dialogs/RawMessageDialog.qml
index 34104394..774b078b 100644
--- a/resources/qml/dialogs/RawMessageDialog.qml
+++ b/resources/qml/dialogs/RawMessageDialog.qml
@@ -17,7 +17,6 @@ ApplicationWindow {
palette: Nheko.colors
color: Nheko.colors.window
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(rawMessageRoot)
Shortcut {
sequence: StandardKey.Cancel
diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml
index aced4374..da87996e 100644
--- a/resources/qml/dialogs/ReadReceipts.qml
+++ b/resources/qml/dialogs/ReadReceipts.qml
@@ -22,7 +22,6 @@ ApplicationWindow {
palette: Nheko.colors
color: Nheko.colors.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(readReceiptsRoot)
Shortcut {
sequence: StandardKey.Cancel
diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml
index f458ac51..36c29a0b 100644
--- a/resources/qml/dialogs/RoomDirectory.qml
+++ b/resources/qml/dialogs/RoomDirectory.qml
@@ -22,7 +22,6 @@ ApplicationWindow {
color: Nheko.colors.window
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(roomDirectoryWindow)
title: qsTr("Explore Public Rooms")
Shortcut {
@@ -189,7 +188,6 @@ ApplicationWindow {
Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
- padding: Nheko.paddingMedium
color: Nheko.colors.text
placeholderText: qsTr("Search for public rooms")
onTextChanged: searchTimer.restart()
@@ -200,7 +198,6 @@ ApplicationWindow {
Layout.minimumWidth: 0.3 * header.width
Layout.maximumWidth: 0.3 * header.width
- padding: Nheko.paddingMedium
color: Nheko.colors.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml
index 89cce414..55d5488b 100644
--- a/resources/qml/dialogs/RoomMembers.qml
+++ b/resources/qml/dialogs/RoomMembers.qml
@@ -24,7 +24,6 @@ ApplicationWindow {
palette: Nheko.colors
color: Nheko.colors.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(roomMembersRoot)
Shortcut {
sequence: StandardKey.Cancel
diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml
index c9f2b1a1..48d2e2b7 100644
--- a/resources/qml/dialogs/RoomSettings.qml
+++ b/resources/qml/dialogs/RoomSettings.qml
@@ -23,7 +23,6 @@ ApplicationWindow {
color: Nheko.colors.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(roomSettingsDialog)
title: qsTr("Room Settings")
Shortcut {
diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml
index 29ce2c3f..73c4e68b 100644
--- a/resources/qml/dialogs/UserProfile.qml
+++ b/resources/qml/dialogs/UserProfile.qml
@@ -13,9 +13,6 @@ import QtQuick.Window 2.13
import im.nheko 1.0
ApplicationWindow {
- // this does not work in ApplicationWindow, just in Window
- //transientParent: Nheko.mainwindow()
-
id: userProfileDialog
property var profile
@@ -29,7 +26,6 @@ ApplicationWindow {
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
- Component.onCompleted: Nheko.reparent(userProfileDialog)
Shortcut {
sequence: StandardKey.Cancel
diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml
new file mode 100644
index 00000000..4d3a52b3
--- /dev/null
+++ b/resources/qml/pages/LoginPage.qml
@@ -0,0 +1,182 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.15
+import im.nheko 1.0
+import "../components/"
+import "../ui/"
+import "../"
+
+Item {
+ id: loginPage
+ property int maxExpansion: 400
+
+ property string error: login.error
+
+ Login {
+ id: login
+ }
+
+ ScrollView {
+ id: scroll
+
+ clip: false
+ palette: Nheko.colors
+ ScrollBar.horizontal.visible: false
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ height: Math.min(loginPage.height, col.implicitHeight)
+ anchors.margins: Nheko.paddingLarge
+
+ contentWidth: availableWidth
+
+ ColumnLayout {
+ id: col
+
+ spacing: Nheko.paddingMedium
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter
+ source: "qrc:/logos/login.png"
+ height: 128
+ width: 128
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingLarge
+
+ Layout.fillWidth: true
+ MatrixTextField {
+ id: matrixIdLabel
+ label: qsTr("Matrix ID")
+ placeholderText: qsTr("e.g @joe:matrix.org")
+ onEditingFinished: login.mxid = text
+
+ ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
+ Keys.forwardTo: [pwBtn, ssoBtn]
+ }
+
+
+ Spinner {
+ height: matrixIdLabel.height/2
+ Layout.alignment: Qt.AlignBottom
+
+ visible: running
+ running: login.lookingUpHs
+ foreground: Nheko.colors.mid
+ }
+ }
+
+ MatrixText {
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: login.mxidError
+ visible: text
+ }
+
+ MatrixTextField {
+ id: passwordLabel
+ Layout.fillWidth: true
+ label: qsTr("Password")
+ echoMode: TextInput.Password
+ ToolTip.text: qsTr("Your password.")
+ visible: login.passwordSupported
+ Keys.forwardTo: [pwBtn, ssoBtn]
+ }
+
+ MatrixTextField {
+ id: deviceNameLabel
+ Layout.fillWidth: true
+ label: qsTr("Device name")
+ placeholderText: login.initialDeviceName()
+ ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
+ Keys.forwardTo: [pwBtn, ssoBtn]
+ }
+
+ MatrixTextField {
+ id: hsLabel
+ enabled: visible
+ visible: login.homeserverNeeded
+
+ Layout.fillWidth: true
+ label: qsTr("Homeserver address")
+ placeholderText: qsTr("server.my:8787")
+ text: login.homeserver
+ onEditingFinished: login.homeserver = text
+ ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
+ Keys.forwardTo: [pwBtn, ssoBtn]
+ }
+
+ Item {
+ height: Nheko.avatarSize
+ Layout.fillWidth: true
+
+ Spinner {
+ height: parent.height
+ anchors.centerIn: parent
+
+ visible: running
+ running: login.loggingIn
+ foreground: Nheko.colors.mid
+ }
+ }
+
+ MatrixText {
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: loginPage.error
+ visible: text
+ }
+
+ FlatButton {
+ id: pwBtn
+ visible: login.passwordSupported
+ enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("LOGIN")
+ function pwLogin() {
+ login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
+ }
+ onClicked: pwBtn.pwLogin()
+ Keys.onEnterPressed: pwBtn.pwLogin()
+ Keys.onReturnPressed: pwBtn.pwLogin()
+ Keys.enabled: pwBtn.enabled && login.passwordSupported
+ }
+ FlatButton {
+ id: ssoBtn
+ visible: login.ssoSupported
+ enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("SSO LOGIN")
+ function ssoLogin() {
+ login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
+ }
+ onClicked: ssoBtn.ssoLogin()
+ Keys.onEnterPressed: ssoBtn.ssoLogin()
+ Keys.onReturnPressed: ssoBtn.ssoLogin()
+ Keys.enabled: ssoBtn.enabled && !login.passwordSupported
+ }
+
+ }
+ }
+
+ ImageButton {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: Nheko.paddingMedium
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ image: ":/icons/icons/ui/angle-arrow-left.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Back")
+ onClicked: mainWindow.pop()
+ }
+}
diff --git a/resources/qml/pages/RegisterPage.qml b/resources/qml/pages/RegisterPage.qml
new file mode 100644
index 00000000..44836ccb
--- /dev/null
+++ b/resources/qml/pages/RegisterPage.qml
@@ -0,0 +1,215 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.15
+import im.nheko 1.0
+import "../components/"
+import "../ui/"
+import "../"
+
+Item {
+ id: registrationPage
+ property int maxExpansion: 400
+
+ property string error: regis.error
+
+ Registration {
+ id: regis
+ }
+
+ ScrollView {
+ id: scroll
+
+ clip: false
+ palette: Nheko.colors
+ ScrollBar.horizontal.visible: false
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ height: Math.min(registrationPage.height, col.implicitHeight)
+ anchors.margins: Nheko.paddingLarge
+
+ contentWidth: availableWidth
+
+ ColumnLayout {
+ id: col
+
+ spacing: Nheko.paddingMedium
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter
+ source: "qrc:/logos/login.png"
+ height: 128
+ width: 128
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingLarge
+
+ Layout.fillWidth: true
+ MatrixTextField {
+ id: hsLabel
+ label: qsTr("Homeserver")
+ placeholderText: qsTr("your.server")
+ onEditingFinished: regis.setServer(text)
+
+ ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.")
+ }
+
+
+ Spinner {
+ height: hsLabel.height/2
+ Layout.alignment: Qt.AlignBottom
+
+ visible: running
+ running: regis.lookingUpHs
+ foreground: Nheko.colors.mid
+ }
+ }
+
+ MatrixText {
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: regis.hsError
+ visible: text
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingLarge
+
+ visible: regis.supported
+
+ Layout.fillWidth: true
+ MatrixTextField {
+ id: usernameLabel
+ Layout.fillWidth: true
+ label: qsTr("Username")
+ ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.")
+ onEditingFinished: regis.checkUsername(text)
+ }
+ Spinner {
+ height: usernameLabel.height/2
+ Layout.alignment: Qt.AlignBottom
+
+ visible: running
+ running: regis.lookingUpUsername
+ foreground: Nheko.colors.mid
+ }
+
+ Image {
+ width: usernameLabel.height/2
+ height: width
+ Layout.preferredHeight: usernameLabel.height/2
+ Layout.preferredWidth: usernameLabel.height/2
+ Layout.alignment: Qt.AlignBottom
+ source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error)
+ visible: regis.usernameAvailable || regis.usernameUnavailable
+ ToolTip.visible: ma.hovered
+ ToolTip.text: qsTr("Back")
+ sourceSize.height: height * Screen.devicePixelRatio
+ sourceSize.width: width * Screen.devicePixelRatio
+ HoverHandler {
+ id: ma
+ }
+ }
+ }
+
+ MatrixText {
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: regis.usernameError
+ visible: text
+ }
+
+
+ MatrixTextField {
+ visible: regis.supported
+ id: passwordLabel
+ Layout.fillWidth: true
+ label: qsTr("Password")
+ echoMode: TextInput.Password
+ ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.")
+ }
+
+ MatrixTextField {
+ visible: regis.supported
+ id: passwordConfirmationLabel
+ Layout.fillWidth: true
+ label: qsTr("Password confirmation")
+ echoMode: TextInput.Password
+ }
+
+ MatrixText {
+ visible: regis.supported
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
+ }
+
+ MatrixTextField {
+ visible: regis.supported
+ id: deviceNameLabel
+ Layout.fillWidth: true
+ label: qsTr("Device name")
+ placeholderText: regis.initialDeviceName()
+ ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
+ }
+
+ Item {
+ height: Nheko.avatarSize
+ Layout.fillWidth: true
+
+ Spinner {
+ height: parent.height
+ anchors.centerIn: parent
+
+ visible: running
+ running: regis.registering
+ foreground: Nheko.colors.mid
+ }
+ }
+
+ MatrixText {
+ textFormat: Text.PlainText
+ color: Nheko.theme.error
+ text: registrationPage.error
+ visible: text
+ }
+
+ FlatButton {
+ id: regisBtn
+ visible: regis.supported
+ enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("REGISTER")
+ function register() {
+ regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text)
+ }
+ onClicked: regisBtn.register()
+ Keys.onEnterPressed: regisBtn.register()
+ Keys.onReturnPressed: regisBtn.register()
+ Keys.enabled: regisBtn.enabled && regis.supported
+ }
+ }
+ }
+
+ ImageButton {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: Nheko.paddingMedium
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ image: ":/icons/icons/ui/angle-arrow-left.svg"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Back")
+ onClicked: mainWindow.pop()
+ }
+}
+
diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml
new file mode 100644
index 00000000..e1ecc31d
--- /dev/null
+++ b/resources/qml/pages/WelcomePage.qml
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.15
+import im.nheko 1.0
+import "../components/"
+
+ColumnLayout {
+ Item {
+ Layout.fillHeight: true
+ }
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter
+ source: "qrc:/logos/splash.png"
+ height: 256
+ width: 256
+ }
+
+ Label {
+ Layout.margins: Nheko.paddingLarge
+ Layout.bottomMargin: 0
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
+ color: Nheko.colors.text
+ font.pointSize: fontMetrics.font.pointSize*2
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ }
+ Label {
+ Layout.margins: Nheko.paddingLarge
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ text: qsTr("Enjoy your stay!")
+ color: Nheko.colors.text
+ font.pointSize: fontMetrics.font.pointSize*1.5
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ FlatButton {
+ Layout.margins: Nheko.paddingLarge
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("REGISTER")
+ onClicked: {
+ mainWindow.push(registerPage);
+ }
+ }
+ FlatButton {
+ Layout.margins: Nheko.paddingLarge
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("LOGIN")
+ onClicked: {
+ mainWindow.push(loginPage);
+ }
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ Item {
+ Layout.fillHeight: true
+ }
+}
diff --git a/resources/qml/ui/Snackbar.qml b/resources/qml/ui/Snackbar.qml
new file mode 100644
index 00000000..80c0d888
--- /dev/null
+++ b/resources/qml/ui/Snackbar.qml
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0
+
+Popup {
+ id: snackbar
+
+ property var messages: []
+ property string currentMessage: ""
+
+ function showNotification(msg) {
+ messages.push(msg);
+ currentMessage = messages[0];
+ if (!visible) {
+ open();
+ dismissTimer.start();
+ }
+ }
+
+ Timer {
+ id: dismissTimer
+ interval: 10000
+ onTriggered: snackbar.close()
+ }
+
+ onAboutToHide: {
+ messages.shift();
+ }
+ onClosed: {
+ if (messages.length > 0) {
+ currentMessage = messages[0];
+ open();
+ dismissTimer.restart();
+ }
+ }
+
+ parent: Overlay.overlay
+ opacity: 0
+ y: -100
+ x: (parent.width - width)/2
+ padding: Nheko.paddingLarge
+
+ contentItem: Label {
+ color: Nheko.colors.light
+ width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400)
+ text: snackbar.currentMessage
+ font.bold: true
+ }
+
+ background: Rectangle {
+ radius: Nheko.paddingLarge
+ color: Nheko.colors.dark
+ opacity: 0.8
+ }
+
+ enter: Transition {
+ NumberAnimation {
+ target: snackbar
+ property: "opacity"
+ from: 0.0
+ to: 1.0
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
+ NumberAnimation {
+ target: snackbar
+ properties: "y"
+ from: -100
+ to: 100
+ duration: 1000
+ easing.type: Easing.OutCubic
+ }
+ }
+ exit: Transition {
+ NumberAnimation {
+ target: snackbar
+ property: "opacity"
+ from: 1.0
+ to: 0.0
+ duration: 300
+ easing.type: Easing.InCubic
+ }
+ NumberAnimation {
+ target: snackbar
+ properties: "y"
+ to: -100
+ from: 100
+ duration: 300
+ easing.type: Easing.InCubic
+ }
+ }
+}
+
+
diff --git a/resources/res.qrc b/resources/res.qrc
index a2ee393f..2fba5f4c 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -110,6 +110,9 @@
qml/TypingIndicator.qml
qml/NotificationWarning.qml
qml/pages/UserSettingsPage.qml
+ qml/pages/WelcomePage.qml
+ qml/pages/LoginPage.qml
+ qml/pages/RegisterPage.qml
qml/components/AdaptiveLayout.qml
qml/components/AdaptiveLayoutElement.qml
qml/components/AvatarListTile.qml
@@ -154,6 +157,7 @@
qml/ui/NhekoSlider.qml
qml/ui/Ripple.qml
qml/ui/Spinner.qml
+ qml/ui/Snackbar.qml
qml/ui/animations/BlinkAnimation.qml
qml/ui/media/MediaControls.qml
qml/voip/ActiveCallBar.qml
diff --git a/src/Cache.cpp b/src/Cache.cpp
index d1723d98..b55d53a6 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -325,7 +325,7 @@ static void
fatalSecretError()
{
QMessageBox::critical(
- ChatPage::instance(),
+ nullptr,
QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
QCoreApplication::translate(
"SecretStorage",
@@ -391,6 +391,7 @@ Cache::loadSecrets(std::vector> toLoad)
&QKeychain::ReadPasswordJob::finished,
this,
[this, name, toLoad, job](QKeychain::Job *) mutable {
+ nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first);
const QString secret = job->textData();
if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
@@ -413,6 +414,7 @@ Cache::loadSecrets(std::vector> toLoad)
// You can't start a job from the finish signal of a job.
QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); });
});
+ nhlog::db()->debug("Reading '{}'", name_);
job->start();
}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index bfaa6389..cdaf7260 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -22,7 +22,6 @@
#include "Utils.h"
#include "encryption/DeviceVerificationFlow.h"
#include "encryption/Olm.h"
-#include "ui/OverlayModal.h"
#include "ui/Theme.h"
#include "ui/UserProfile.h"
#include "voip/CallManager.h"
@@ -44,8 +43,8 @@ Q_DECLARE_METATYPE(mtx::presence::PresenceState)
Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
Q_DECLARE_METATYPE(SecretsToDecrypt)
-ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
- : QWidget(parent)
+ChatPage::ChatPage(QSharedPointer userSettings, QObject *parent)
+ : QObject(parent)
, isConnected_(true)
, userSettings_{userSettings}
, notificationsManager(this)
@@ -61,14 +60,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
qRegisterMetaType();
qRegisterMetaType();
- topLayout_ = new QHBoxLayout(this);
- topLayout_->setSpacing(0);
- topLayout_->setContentsMargins(0, 0, 0, 0);
-
view_manager_ = new TimelineViewManager(callManager_, this);
- topLayout_->addWidget(view_manager_->getWidget());
-
connect(this,
&ChatPage::downloadedSecrets,
this,
@@ -154,7 +147,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
[this](const QString &roomid, const QString &eventid) {
Q_UNUSED(eventid)
view_manager_->rooms()->setCurrentRoom(roomid);
- activateWindow();
+ MainWindow::instance()->requestActivate();
});
connect(¬ificationsManager,
&NotificationsManager::sendNotificationReply,
@@ -162,17 +155,9 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
[this](const QString &roomid, const QString &eventid, const QString &body) {
view_manager_->rooms()->setCurrentRoom(roomid);
view_manager_->queueReply(roomid, eventid, body);
- activateWindow();
+ MainWindow::instance()->requestActivate();
});
- connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
- // ensure the qml context is shutdown before we destroy all other singletons
- // Otherwise Qml will try to access the room list or settings, after they have been
- // destroyed
- topLayout_->removeWidget(view_manager_->getWidget());
- delete view_manager_->getWidget();
- });
-
connect(
this,
&ChatPage::initializeViews,
@@ -183,8 +168,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
&ChatPage::initializeEmptyViews,
view_manager_,
&TimelineViewManager::initializeRoomlist);
- connect(
- this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
view_manager_->sync(sync);
@@ -201,7 +184,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
// TODO: Replace this once we have proper pushrules support. This is a horrible hack
if (prevNotificationCount < notificationCount) {
if (userSettings_->hasAlertOnNotification())
- QApplication::alert(this);
+ MainWindow::instance()->alert(0);
}
prevNotificationCount = notificationCount;
@@ -331,7 +314,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
} else if (cacheVersion == cache::CacheVersion::Older) {
if (!cache::runMigrations()) {
QMessageBox::critical(
- this,
+ nullptr,
tr("Cache migration failed!"),
tr("Migrating the cache to the current version failed. "
"This can have different reasons. Please open an "
@@ -344,7 +327,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
return;
} else if (cacheVersion == cache::CacheVersion::Newer) {
QMessageBox::critical(
- this,
+ nullptr,
tr("Incompatible cache version"),
tr("The cache on your disk is newer than this version of Nheko "
"supports. Please update Nheko or clear your cache."));
@@ -690,7 +673,7 @@ ChatPage::joinRoomVia(const std::string &room_id,
if (promptForConfirmation &&
QMessageBox::Yes !=
QMessageBox::question(
- this,
+ nullptr,
tr("Confirm join"),
tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
return;
@@ -776,7 +759,7 @@ ChatPage::inviteUser(QString userid, QString reason)
{
auto room = currentRoom();
- if (QMessageBox::question(this,
+ if (QMessageBox::question(nullptr,
tr("Confirm invite"),
tr("Do you really want to invite %1 (%2)?")
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -787,6 +770,8 @@ ChatPage::inviteUser(QString userid, QString reason)
userid.toStdString(),
[this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
if (err) {
+ nhlog::net()->error(
+ "Failed to invite {} to {}: {}", userid.toStdString(), room.toStdString(), *err);
emit showNotification(
tr("Failed to invite %1 to %2: %3")
.arg(userid, room, QString::fromStdString(err->matrix_error.error)));
@@ -800,7 +785,7 @@ ChatPage::kickUser(QString userid, QString reason)
{
auto room = currentRoom();
- if (QMessageBox::question(this,
+ if (QMessageBox::question(nullptr,
tr("Confirm kick"),
tr("Do you really want to kick %1 (%2)?")
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -825,7 +810,7 @@ ChatPage::banUser(QString userid, QString reason)
auto room = currentRoom();
if (QMessageBox::question(
- this,
+ nullptr,
tr("Confirm ban"),
tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) !=
QMessageBox::Yes)
@@ -849,7 +834,7 @@ ChatPage::unbanUser(QString userid, QString reason)
{
auto room = currentRoom();
- if (QMessageBox::question(this,
+ if (QMessageBox::question(nullptr,
tr("Confirm unban"),
tr("Do you really want to unban %1 (%2)?")
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -1064,8 +1049,6 @@ ChatPage::initiateLogout()
emit loggedOut();
});
-
- emit showOverlayProgressBar();
}
template
@@ -1083,7 +1066,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
const SecretsToDecrypt &secrets)
{
QString text = QInputDialog::getText(
- ChatPage::instance(),
+ nullptr,
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
keyDesc.name.empty()
? QCoreApplication::translate(
@@ -1115,7 +1098,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
if (!decryptionKey) {
QMessageBox::information(
- ChatPage::instance(),
+ nullptr,
QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
QCoreApplication::translate("CrossSigningSecrets",
"Failed to decrypt secrets with the "
@@ -1209,7 +1192,7 @@ ChatPage::startChat(QString userid)
if (QMessageBox::Yes !=
QMessageBox::question(
- this,
+ nullptr,
tr("Confirm invite"),
tr("Do you really want to start a private chat with %1?").arg(userid)))
return;
@@ -1395,7 +1378,7 @@ ChatPage::handleMatrixUri(const QUrl &uri)
bool
ChatPage::isRoomActive(const QString &room_id)
{
- return isActiveWindow() && currentRoom() == room_id;
+ return MainWindow::instance()->isActive() && currentRoom() == room_id;
}
QString
diff --git a/src/ChatPage.h b/src/ChatPage.h
index ae55c923..e4b9e4e8 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -8,7 +8,6 @@
#include
#include
-#include
#include
#include
@@ -18,17 +17,15 @@
#include
#include
-#include
#include
#include
+#include
#include
-#include
#include "CacheCryptoStructs.h"
#include "CacheStructs.h"
#include "notifications/Manager.h"
-class OverlayModal;
class TimelineViewManager;
class UserSettings;
class NotificationsManager;
@@ -51,12 +48,12 @@ struct Rooms;
using SecretsToDecrypt = std::map;
-class ChatPage : public QWidget
+class ChatPage : public QObject
{
Q_OBJECT
public:
- ChatPage(QSharedPointer userSettings, QWidget *parent = nullptr);
+ ChatPage(QSharedPointer userSettings, QObject *parent = nullptr);
// Initialize all the components of the UI.
void bootstrap(QString userid, QString homeserver, QString token);
@@ -112,7 +109,6 @@ signals:
void showNotification(const QString &msg);
void showLoginPage(const QString &msg);
void showUserSettingsPage();
- void showOverlayProgressBar();
void ownProfileOk();
void setUserDisplayName(const QString &name);
@@ -143,7 +139,6 @@ signals:
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
- void chatFocusChanged(const bool focused);
//! Signals for device verificaiton
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
@@ -201,8 +196,6 @@ private:
template
void connectCallMessage();
- QHBoxLayout *topLayout_;
-
TimelineViewManager *view_manager_;
QTimer connectivityTimer_;
diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index 4c28f364..6bed446e 100644
--- a/src/LoginPage.cpp
+++ b/src/LoginPage.cpp
@@ -5,11 +5,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include
-#include
-#include
-#include
-#include
-#include
#include
#include
@@ -18,247 +13,94 @@
#include "Config.h"
#include "Logging.h"
#include "LoginPage.h"
+#include "MainWindow.h"
#include "MatrixClient.h"
#include "SSOHandler.h"
#include "UserSettingsPage.h"
-#include "ui/FlatButton.h"
-#include "ui/LoadingIndicator.h"
-#include "ui/OverlayModal.h"
-#include "ui/RaisedButton.h"
-#include "ui/TextField.h"
Q_DECLARE_METATYPE(LoginPage::LoginMethod)
using namespace mtx::identifiers;
-LoginPage::LoginPage(QWidget *parent)
- : QWidget(parent)
+LoginPage::LoginPage(QObject *parent)
+ : QObject(parent)
, inferredServerAddress_()
{
- qRegisterMetaType("LoginPage::LoginMethod");
-
- top_layout_ = new QVBoxLayout();
-
- top_bar_layout_ = new QHBoxLayout();
- top_bar_layout_->setSpacing(0);
- top_bar_layout_->setContentsMargins(0, 0, 0, 0);
-
- back_button_ = new FlatButton(this);
- back_button_->setMinimumSize(QSize(30, 30));
-
- top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
- top_bar_layout_->addStretch(1);
-
- QIcon icon;
- icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
-
- back_button_->setIcon(icon);
- back_button_->setIconSize(QSize(32, 32));
-
- QIcon logo;
- logo.addFile(QStringLiteral(":/logos/login.png"));
-
- logo_ = new QLabel(this);
- logo_->setPixmap(logo.pixmap(128));
-
- logo_layout_ = new QHBoxLayout();
- logo_layout_->setContentsMargins(0, 0, 0, 20);
- logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
-
- form_wrapper_ = new QHBoxLayout();
- form_widget_ = new QWidget();
- form_widget_->setMinimumSize(QSize(350, 200));
-
- form_layout_ = new QVBoxLayout();
- form_layout_->setSpacing(20);
- form_layout_->setContentsMargins(0, 0, 0, 30);
- form_widget_->setLayout(form_layout_);
-
- form_wrapper_->addStretch(1);
- form_wrapper_->addWidget(form_widget_);
- form_wrapper_->addStretch(1);
-
- matrixid_input_ = new TextField(this);
- matrixid_input_->setLabel(tr("Matrix ID"));
- matrixid_input_->setRegexp(QRegularExpression(QStringLiteral("@.+?:.{3,}")));
- matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
- matrixid_input_->setToolTip(
- tr("Your login name. A mxid should start with @ followed by the user id. After the user "
- "id you need to include your server name after a :.\nYou can also put your homeserver "
- "address there, if your server doesn't support .well-known lookup.\nExample: "
- "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
- "field to enter the server manually."));
-
- spinner_ = new LoadingIndicator(this);
- spinner_->setFixedHeight(40);
- spinner_->setFixedWidth(40);
- spinner_->hide();
-
- errorIcon_ = new QLabel(this);
- errorIcon_->setPixmap(QPixmap(QStringLiteral(":/icons/icons/error.png")));
- errorIcon_->hide();
-
- matrixidLayout_ = new QHBoxLayout();
- matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
-
- QFont font;
-
- error_matrixid_label_ = new QLabel(this);
- error_matrixid_label_->setFont(font);
- error_matrixid_label_->setWordWrap(true);
-
- password_input_ = new TextField(this);
- password_input_->setLabel(tr("Password"));
- password_input_->setEchoMode(QLineEdit::Password);
- password_input_->setToolTip(tr("Your password."));
-
- deviceName_ = new TextField(this);
- deviceName_->setLabel(tr("Device name"));
- deviceName_->setToolTip(
- tr("A name for this device, which will be shown to others, when verifying your devices. "
- "If none is provided a default is used."));
-
- serverInput_ = new TextField(this);
- serverInput_->setLabel(tr("Homeserver address"));
- serverInput_->setPlaceholderText(tr("server.my:8787"));
- serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
- "client API.\nExample: https://server.my:8787"));
- serverInput_->hide();
-
- serverLayout_ = new QHBoxLayout();
- serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
-
- form_layout_->addLayout(matrixidLayout_);
- form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
- form_layout_->addWidget(password_input_);
- form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
- form_layout_->addLayout(serverLayout_);
-
- error_matrixid_label_->hide();
-
- button_layout_ = new QHBoxLayout();
- button_layout_->setSpacing(20);
- button_layout_->setContentsMargins(0, 0, 0, 30);
-
- login_button_ = new RaisedButton(tr("LOGIN"), this);
- login_button_->setMinimumSize(150, 65);
- login_button_->setFontSize(20);
- login_button_->setCornerRadius(3);
-
- sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
- sso_login_button_->setMinimumSize(150, 65);
- sso_login_button_->setFontSize(20);
- sso_login_button_->setCornerRadius(3);
- sso_login_button_->setVisible(false);
-
- button_layout_->addStretch(1);
- button_layout_->addWidget(login_button_);
- button_layout_->addWidget(sso_login_button_);
- button_layout_->addStretch(1);
-
- error_label_ = new QLabel(this);
- error_label_->setFont(font);
- error_label_->setWordWrap(true);
-
- top_layout_->addLayout(top_bar_layout_);
- top_layout_->addStretch(1);
- top_layout_->addLayout(logo_layout_);
- top_layout_->addLayout(form_wrapper_);
- top_layout_->addStretch(1);
- top_layout_->addLayout(button_layout_);
- top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
- top_layout_->addStretch(1);
-
- setLayout(top_layout_);
+ [[maybe_unused]] static auto ignored =
+ qRegisterMetaType("LoginPage::LoginMethod");
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
+ connect(
+ this,
+ &LoginPage::loginOk,
+ this,
+ [this](const mtx::responses::Login &res) {
+ loggingIn_ = false;
+ emit loggingInChanged();
- connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
- connect(login_button_, &RaisedButton::clicked, this, [this]() {
- onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
- });
- connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
- onLoginButtonClicked(LoginMethod::SSO);
- });
- connect(this,
- &LoginPage::showErrorMessage,
- this,
- static_cast(&LoginPage::showError),
- Qt::QueuedConnection);
- connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
- connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
+ http::client()->set_user(res.user_id);
+ MainWindow::instance()->showChatPage();
+ },
+ Qt::QueuedConnection);
}
void
LoginPage::showError(const QString &msg)
{
- auto rect = QFontMetrics(font()).boundingRect(msg);
- int width = rect.width();
- int height = rect.height();
- error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
- error_label_->setText(msg);
+ loggingIn_ = false;
+ emit loggingInChanged();
+
+ error_ = msg;
+ emit errorOccurred();
}
void
-LoginPage::showError(QLabel *label, const QString &msg)
+LoginPage::setHomeserver(QString hs)
{
- auto rect = QFontMetrics(font()).boundingRect(msg);
- int width = rect.width();
- int height = rect.height();
- label->setFixedHeight((int)qCeil(width / 200.0) * height);
- label->setText(msg);
+ if (hs != homeserver_) {
+ homeserver_ = hs;
+ homeserverValid_ = false;
+ emit homeserverChanged();
+ http::client()->set_server(hs.toStdString());
+ checkHomeserverVersion();
+ }
}
void
LoginPage::onMatrixIdEntered()
{
- error_label_->setText(QLatin1String(""));
+ clearErrors();
+
+ homeserverValid_ = false;
+ emit homeserverChanged();
User user;
+ try {
+ user = parse(mxid_.toStdString());
+ } catch (const std::exception &) {
+ mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ emit mxidErrorChanged();
+ return;
+ }
- if (!matrixid_input_->isValid()) {
- error_matrixid_label_->show();
- showError(error_matrixid_label_,
- tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
+ if (user.hostname().empty() || user.localpart().empty()) {
+ mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ emit mxidErrorChanged();
return;
} else {
- error_matrixid_label_->setText(QLatin1String(""));
- error_matrixid_label_->hide();
+ nhlog::net()->debug("hostname: {}", user.hostname());
}
- try {
- user = parse(matrixid_input_->text().toStdString());
- } catch (const std::exception &) {
- showError(error_matrixid_label_,
- tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
- return;
- }
-
- QString homeServer = QString::fromStdString(user.hostname());
- if (homeServer != inferredServerAddress_) {
- serverInput_->hide();
- serverLayout_->removeWidget(errorIcon_);
- errorIcon_->hide();
- if (serverInput_->isVisible()) {
- matrixidLayout_->removeWidget(spinner_);
- serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->start();
- } else {
- serverLayout_->removeWidget(spinner_);
- matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->start();
- }
-
- inferredServerAddress_ = homeServer;
- serverInput_->setText(homeServer);
+ if (user.hostname() != inferredServerAddress_.toStdString()) {
+ homeserverNeeded_ = false;
+ lookingUpHs_ = true;
+ emit lookingUpHsChanged();
http::client()->set_server(user.hostname());
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
+ homeserver_ = QString::fromStdString(user.hostname());
+ emit homeserverChanged();
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
@@ -286,6 +128,7 @@ LoginPage::onMatrixIdEntered()
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
+ emit homeserverChanged();
checkHomeserverVersion();
});
}
@@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered()
void
LoginPage::checkHomeserverVersion()
{
+ clearErrors();
+
+ try {
+ User user = parse(mxid_.toStdString());
+ } catch (const std::exception &) {
+ mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ emit mxidErrorChanged();
+ return;
+ }
+
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
@@ -318,107 +171,78 @@ LoginPage::checkHomeserverVersion()
if (err || flows.flows.empty())
emit versionOkCb(true, false);
- bool ssoSupported_ = false;
- bool passwordSupported_ = false;
+ bool ssoSupported = false;
+ bool passwordSupported = false;
for (const auto &flow : flows.flows) {
if (flow.type == mtx::user_interactive::auth_types::sso) {
- ssoSupported_ = true;
+ ssoSupported = true;
} else if (flow.type == mtx::user_interactive::auth_types::password) {
- passwordSupported_ = true;
+ passwordSupported = true;
}
}
- emit versionOkCb(passwordSupported_, ssoSupported_);
+ emit versionOkCb(passwordSupported, ssoSupported);
});
});
}
-void
-LoginPage::onServerAddressEntered()
-{
- error_label_->setText(QLatin1String(""));
- http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
- http::client()->set_server(serverInput_->text().toStdString());
- checkHomeserverVersion();
-
- serverLayout_->removeWidget(errorIcon_);
- errorIcon_->hide();
- serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->start();
-}
-
void
LoginPage::versionError(const QString &error)
{
- showError(error_label_, error);
- serverInput_->show();
+ showError(error);
- spinner_->stop();
- serverLayout_->removeWidget(spinner_);
- serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
- errorIcon_->show();
- matrixidLayout_->removeWidget(spinner_);
+ homeserverNeeded_ = true;
+ lookingUpHs_ = false;
+ homeserverValid_ = false;
+ emit lookingUpHsChanged();
+ emit versionLookedUp();
}
void
-LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
+LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
{
- passwordSupported = passwordSupported_;
- ssoSupported = ssoSupported_;
+ passwordSupported_ = passwordSupported;
+ ssoSupported_ = ssoSupported;
- serverLayout_->removeWidget(spinner_);
- matrixidLayout_->removeWidget(spinner_);
- spinner_->stop();
-
- password_input_->setVisible(passwordSupported);
- password_input_->setEnabled(passwordSupported);
- sso_login_button_->setVisible(ssoSupported);
- login_button_->setVisible(passwordSupported);
-
- if (serverInput_->isVisible())
- serverInput_->hide();
+ lookingUpHs_ = false;
+ homeserverValid_ = true;
+ emit homeserverChanged();
+ emit lookingUpHsChanged();
+ emit versionLookedUp();
}
void
-LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
+LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
+ QString userid,
+ QString password,
+ QString deviceName)
{
- error_label_->setText(QLatin1String(""));
+ clearErrors();
+
User user;
- if (!matrixid_input_->isValid()) {
- error_matrixid_label_->show();
- showError(error_matrixid_label_,
- tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
- return;
- } else {
- error_matrixid_label_->setText(QLatin1String(""));
- error_matrixid_label_->hide();
- }
-
try {
- user = parse(matrixid_input_->text().toStdString());
+ user = parse(userid.toStdString());
} catch (const std::exception &) {
- showError(error_matrixid_label_,
- tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
+ mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ emit mxidErrorChanged();
return;
}
if (loginMethod == LoginMethod::Password) {
- if (password_input_->text().isEmpty())
- return showError(error_label_, tr("Empty password"));
+ if (password.isEmpty())
+ return showError(tr("Empty password"));
http::client()->login(
user.localpart(),
- password_input_->text().toStdString(),
- deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
- : deviceName_->text().toStdString(),
+ password.toStdString(),
+ deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(),
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
auto error = err->matrix_error.error;
if (error.empty())
error = err->parse_error;
- showErrorMessage(error_label_, QString::fromStdString(error));
- emit errorOccurred();
+ showError(QString::fromStdString(error));
return;
}
@@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
});
} else {
auto sso = new SSOHandler();
- connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
- mtx::requests::Login req{};
- req.token = token;
- req.type = mtx::user_interactive::auth_types::token;
- req.device_id = deviceName_->text().trimmed().isEmpty()
- ? initialDeviceName()
- : deviceName_->text().toStdString();
- http::client()->login(
- req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
- if (err) {
- showErrorMessage(error_label_,
- QString::fromStdString(err->matrix_error.error));
- emit errorOccurred();
- return;
- }
+ connect(
+ sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) {
+ mtx::requests::Login req{};
+ req.token = token;
+ req.type = mtx::user_interactive::auth_types::token;
+ req.device_id =
+ deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString();
+ http::client()->login(
+ req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
+ if (err) {
+ showError(QString::fromStdString(err->matrix_error.error));
+ emit errorOccurred();
+ return;
+ }
- if (res.well_known) {
- http::client()->set_server(res.well_known->homeserver.base_url);
- nhlog::net()->info("Login requested to user server: " +
- res.well_known->homeserver.base_url);
- }
+ if (res.well_known) {
+ http::client()->set_server(res.well_known->homeserver.base_url);
+ nhlog::net()->info("Login requested to user server: " +
+ res.well_known->homeserver.base_url);
+ }
- emit loginOk(res);
- });
- sso->deleteLater();
- });
+ emit loginOk(res);
+ });
+ sso->deleteLater();
+ });
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
- showErrorMessage(error_label_, tr("SSO login failed"));
+ showError(tr("SSO login failed"));
emit errorOccurred();
sso->deleteLater();
});
@@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
}
- emit loggingIn();
-}
-
-void
-LoginPage::reset()
-{
- matrixid_input_->clear();
- password_input_->clear();
- password_input_->show();
- serverInput_->clear();
-
- spinner_->stop();
- errorIcon_->hide();
- serverLayout_->removeWidget(spinner_);
- serverLayout_->removeWidget(errorIcon_);
- matrixidLayout_->removeWidget(spinner_);
-
- inferredServerAddress_.clear();
-}
-
-void
-LoginPage::onBackButtonClicked()
-{
- emit backButtonClicked();
-}
-
-void
-LoginPage::paintEvent(QPaintEvent *)
-{
- QStyleOption opt;
- opt.initFrom(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+ loggingIn_ = true;
+ emit loggingInChanged();
}
diff --git a/src/LoginPage.h b/src/LoginPage.h
index fbfd8710..9a1b9653 100644
--- a/src/LoginPage.h
+++ b/src/LoginPage.h
@@ -6,16 +6,7 @@
#pragma once
-#include
-
-class FlatButton;
-class LoadingIndicator;
-class OverlayModal;
-class RaisedButton;
-class TextField;
-class QLabel;
-class QVBoxLayout;
-class QHBoxLayout;
+#include
namespace mtx {
namespace responses {
@@ -23,62 +14,61 @@ struct Login;
}
}
-class LoginPage : public QWidget
+class LoginPage : public QObject
{
Q_OBJECT
+ Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged)
+ Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
+
+ Q_PROPERTY(QString mxidError READ mxidError NOTIFY mxidErrorChanged)
+ Q_PROPERTY(QString error READ error NOTIFY errorOccurred)
+ Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
+ Q_PROPERTY(bool homeserverValid READ homeserverValid NOTIFY lookingUpHsChanged)
+ Q_PROPERTY(bool loggingIn READ loggingIn NOTIFY loggingInChanged)
+ Q_PROPERTY(bool passwordSupported READ passwordSupported NOTIFY versionLookedUp)
+ Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp)
+ Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp)
+
public:
enum class LoginMethod
{
Password,
SSO,
};
+ Q_ENUM(LoginMethod)
- LoginPage(QWidget *parent = nullptr);
+ LoginPage(QObject *parent = nullptr);
- void reset();
+ Q_INVOKABLE QString initialDeviceName() const
+ {
+ return QString::fromStdString(initialDeviceName_());
+ }
-signals:
- void backButtonClicked();
- void loggingIn();
- void errorOccurred();
+ bool lookingUpHs() const { return lookingUpHs_; }
+ bool loggingIn() const { return loggingIn_; }
+ bool passwordSupported() const { return passwordSupported_; }
+ bool ssoSupported() const { return ssoSupported_; }
+ bool homeserverNeeded() const { return homeserverNeeded_; }
+ bool homeserverValid() const { return homeserverValid_; }
- //! Used to trigger the corresponding slot outside of the main thread.
- void versionErrorCb(const QString &err);
- void versionOkCb(bool passwordSupported, bool ssoSupported);
+ QString homeserver() { return homeserver_; }
+ QString mxid() { return mxid_; }
- void loginOk(const mtx::responses::Login &res);
- void showErrorMessage(QLabel *label, const QString &msg);
+ QString error() { return error_; }
+ QString mxidError() { return mxidError_; }
-protected:
- void paintEvent(QPaintEvent *event) override;
+ void setHomeserver(QString hs);
+ void setMxid(QString id)
+ {
+ if (id != mxid_) {
+ mxid_ = id;
+ emit matrixIdChanged();
+ onMatrixIdEntered();
+ }
+ }
-public slots:
- // Displays errors produced during the login.
- void showError(const QString &msg);
- void showError(QLabel *label, const QString &msg);
-
-private slots:
- // Callback for the back button.
- void onBackButtonClicked();
-
- // Callback for the login button.
- void onLoginButtonClicked(LoginMethod loginMethod);
-
- // Callback for probing the server found in the mxid
- void onMatrixIdEntered();
-
- // Callback for probing the manually entered server
- void onServerAddressEntered();
-
- // Callback for errors produced during server probing
- void versionError(const QString &error_message);
- // Callback for successful server probing
- void versionOk(bool passwordSupported, bool ssoSupported);
-
-private:
- void checkHomeserverVersion();
- std::string initialDeviceName()
+ static std::string initialDeviceName_()
{
#if defined(Q_OS_MAC)
return "Nheko on macOS";
@@ -93,33 +83,65 @@ private:
#endif
}
- QVBoxLayout *top_layout_;
+signals:
+ void loggingInChanged();
+ void errorOccurred();
- QHBoxLayout *top_bar_layout_;
- QHBoxLayout *logo_layout_;
- QHBoxLayout *button_layout_;
+ //! Used to trigger the corresponding slot outside of the main thread.
+ void versionErrorCb(const QString &err);
+ void versionOkCb(bool passwordSupported, bool ssoSupported);
- QLabel *logo_;
- QLabel *error_label_;
- QLabel *error_matrixid_label_;
+ void loginOk(const mtx::responses::Login &res);
+
+ void onServerAddressEntered();
+
+ void matrixIdChanged();
+ void homeserverChanged();
+
+ void mxidErrorChanged();
+ void lookingUpHsChanged();
+ void versionLookedUp();
+ void versionLookupFinished();
+
+public slots:
+ // Displays errors produced during the login.
+ void showError(const QString &msg);
+
+ // Callback for the login button.
+ void onLoginButtonClicked(LoginMethod loginMethod,
+ QString userid,
+ QString password,
+ QString deviceName);
+
+ // Callback for errors produced during server probing
+ void versionError(const QString &error_message);
+ // Callback for successful server probing
+ void versionOk(bool passwordSupported, bool ssoSupported);
+
+private:
+ void checkHomeserverVersion();
+ void onMatrixIdEntered();
+ void clearErrors()
+ {
+ error_.clear();
+ mxidError_.clear();
+ emit errorOccurred();
+ emit mxidErrorChanged();
+ }
- QHBoxLayout *serverLayout_;
- QHBoxLayout *matrixidLayout_;
- LoadingIndicator *spinner_;
- QLabel *errorIcon_;
QString inferredServerAddress_;
- FlatButton *back_button_;
- RaisedButton *login_button_, *sso_login_button_;
+ QString mxid_;
+ QString homeserver_;
- QWidget *form_widget_;
- QHBoxLayout *form_wrapper_;
- QVBoxLayout *form_layout_;
+ QString mxidError_;
+ QString error_;
- TextField *matrixid_input_;
- TextField *password_input_;
- TextField *deviceName_;
- TextField *serverInput_;
- bool passwordSupported = true;
- bool ssoSupported = false;
+ bool passwordSupported_ = true;
+ bool ssoSupported_ = false;
+
+ bool lookingUpHs_ = false;
+ bool loggingIn_ = false;
+ bool homeserverNeeded_ = false;
+ bool homeserverValid_ = false;
};
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 5bfce89e..83504d86 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -5,91 +5,87 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include
-#include
#include
-#include
-#include
#include
#include
+#include "BlurhashProvider.h"
#include "Cache.h"
#include "Cache_p.h"
#include "ChatPage.h"
+#include "Clipboard.h"
+#include "ColorImageProvider.h"
+#include "CombinedImagePackModel.h"
+#include "CompletionProxyModel.h"
#include "Config.h"
+#include "EventAccessors.h"
+#include "ImagePackListModel.h"
+#include "InviteesModel.h"
#include "JdenticonProvider.h"
#include "Logging.h"
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "MemberList.h"
+#include "MxcImageProvider.h"
+#include "ReadReceiptsModel.h"
#include "RegisterPage.h"
+#include "RoomDirectoryModel.h"
+#include "RoomsModel.h"
+#include "SingleImagePackModel.h"
#include "TrayIcon.h"
#include "UserSettingsPage.h"
+#include "UsersModel.h"
#include "Utils.h"
-#include "WelcomePage.h"
-#include "ui/LoadingIndicator.h"
-#include "ui/OverlayModal.h"
-#include "ui/SnackBar.h"
+#include "emoji/EmojiModel.h"
+#include "emoji/Provider.h"
+#include "encryption/DeviceVerificationFlow.h"
+#include "encryption/SelfVerificationStatus.h"
+#include "timeline/DelegateChooser.h"
+#include "timeline/TimelineViewManager.h"
+#include "ui/MxcAnimatedImage.h"
+#include "ui/MxcMediaProxy.h"
+#include "ui/NhekoCursorShape.h"
+#include "ui/NhekoDropArea.h"
+#include "ui/NhekoGlobalObject.h"
+#include "ui/UIA.h"
#include "voip/WebRTCSession.h"
#include "dialogs/CreateRoom.h"
+Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
+Q_DECLARE_METATYPE(std::vector)
+Q_DECLARE_METATYPE(std::vector)
+
MainWindow *MainWindow::instance_ = nullptr;
-MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
+MainWindow::MainWindow(QWindow *parent)
+ : QQuickView(parent)
, userSettings_{UserSettings::instance()}
{
instance_ = this;
- QMainWindow::setWindowTitle(0);
+ MainWindow::setWindowTitle(0);
setObjectName(QStringLiteral("MainWindow"));
-
- modal_ = new OverlayModal(this);
-
+ setResizeMode(QQuickView::SizeRootObjectToView);
+ setMinimumHeight(400);
+ setMinimumWidth(400);
restoreWindowSize();
- QFont font;
- font.setStyleStrategy(QFont::PreferAntialias);
- setFont(font);
+ chat_page_ = new ChatPage(userSettings_, this);
+ registerQmlTypes();
+
+ setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color());
+ setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
- welcome_page_ = new WelcomePage(this);
- login_page_ = new LoginPage(this);
- register_page_ = new RegisterPage(this);
- chat_page_ = new ChatPage(userSettings_, this);
-
- // Initialize sliding widget manager.
- pageStack_ = new QStackedWidget(this);
- pageStack_->addWidget(welcome_page_);
- pageStack_->addWidget(login_page_);
- pageStack_->addWidget(register_page_);
- pageStack_->addWidget(chat_page_);
-
- setCentralWidget(pageStack_);
-
- connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
- connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
-
- connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
- connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
- connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
- connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
- connect(
- register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
- connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
-
- connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
- connect(
- chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
+ connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
- connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
- login_page_->showError(msg);
- showLoginPage();
- });
+ connect(chat_page_, &ChatPage::showLoginPage, this, &MainWindow::switchToLoginPage);
+ connect(chat_page_, &ChatPage::showNotification, this, &MainWindow::showNotification);
connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
connect(trayIcon_,
@@ -97,20 +93,6 @@ MainWindow::MainWindow(QWidget *parent)
this,
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
- connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
-
- connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
-
- connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
- http::client()->set_user(res.user_id);
- showChatPage();
- });
-
- connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
-
- QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
- connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
-
trayIcon_->setVisible(userSettings_->tray());
// load cache on event loop
@@ -133,11 +115,171 @@ MainWindow::MainWindow(QWidget *parent)
user_id.toStdString());
}
+ nhlog::ui()->info("User already signed in, showing chat page");
showChatPage();
}
});
}
+void
+MainWindow::registerQmlTypes()
+{
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType>();
+
+ qRegisterMetaType>();
+
+ qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
+ "im.nheko",
+ 1,
+ 0,
+ "MtxEvent",
+ QStringLiteral("Can't instantiate enum!"));
+ qmlRegisterUncreatableMetaObject(
+ olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!"));
+ qmlRegisterUncreatableMetaObject(crypto::staticMetaObject,
+ "im.nheko",
+ 1,
+ 0,
+ "Crypto",
+ QStringLiteral("Can't instantiate enum!"));
+ qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
+ "im.nheko",
+ 1,
+ 0,
+ "VerificationStatus",
+ QStringLiteral("Can't instantiate enum!"));
+
+ qmlRegisterType("im.nheko", 1, 0, "DelegateChoice");
+ qmlRegisterType("im.nheko", 1, 0, "DelegateChooser");
+ qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea");
+ qmlRegisterType("im.nheko", 1, 0, "CursorShape");
+ qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage");
+ qmlRegisterType("im.nheko", 1, 0, "MxcMedia");
+ qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel");
+ qmlRegisterType("im.nheko", 1, 0, "Login");
+ qmlRegisterType("im.nheko", 1, 0, "Registration");
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "DeviceVerificationFlow",
+ QStringLiteral("Can't create verification flow from QML!"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "UserProfileModel",
+ QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "MemberList",
+ QStringLiteral("MemberList needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "RoomSettingsModel",
+ QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "ImagePackListModel",
+ QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "SingleImagePackModel",
+ QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "InviteesModel",
+ QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "ReadReceiptsProxy",
+ QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side"));
+
+ qmlRegisterSingletonType(
+ "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return new Clipboard();
+ });
+ qmlRegisterSingletonType(
+ "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return new Nheko();
+ });
+ qmlRegisterSingletonType(
+ "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return new UserSettingsModel();
+ });
+
+ qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data());
+
+ qRegisterMetaType();
+ qRegisterMetaType>();
+
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "FilteredCommunitiesModel",
+ QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
+
+ qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel");
+ qmlRegisterUncreatableType(
+ "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
+ qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
+ "im.nheko.EmojiModel",
+ 1,
+ 0,
+ "EmojiCategory",
+ QStringLiteral("Error: Only enums"));
+
+ qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel");
+
+ qmlRegisterSingletonType(
+ "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = new SelfVerificationStatus();
+ QObject::connect(ChatPage::instance(),
+ &ChatPage::initializeEmptyViews,
+ ptr,
+ &SelfVerificationStatus::invalidate);
+ return ptr;
+ });
+ qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", this);
+ qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
+ qmlRegisterSingletonInstance(
+ "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
+
+ imgProvider = new MxcImageProvider();
+ engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
+ engine()->addImageProvider(QStringLiteral("colorimage"), new ColorImageProvider());
+ engine()->addImageProvider(QStringLiteral("blurhash"), new BlurhashProvider());
+ if (JdenticonProvider::isAvailable())
+ engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider());
+
+ QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit);
+}
+
void
MainWindow::setWindowTitle(int notificationCount)
{
@@ -148,20 +290,19 @@ MainWindow::setWindowTitle(int notificationCount)
if (notificationCount > 0) {
name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount));
}
- QMainWindow::setWindowTitle(name);
+ QQuickView::setTitle(name);
}
bool
MainWindow::event(QEvent *event)
{
auto type = event->type();
- if (type == QEvent::WindowActivate) {
- emit focusChanged(true);
- } else if (type == QEvent::WindowDeactivate) {
- emit focusChanged(false);
+
+ if (type == QEvent::Close) {
+ closeEvent(static_cast(event));
}
- return QMainWindow::event(event);
+ return QQuickView::event(event);
}
void
@@ -188,31 +329,6 @@ MainWindow::saveCurrentWindowSize()
settings->setValue(QStringLiteral("window/height"), current.height());
}
-void
-MainWindow::removeOverlayProgressBar()
-{
- QTimer *timer = new QTimer(this);
- timer->setSingleShot(true);
-
- connect(timer, &QTimer::timeout, this, [this, timer]() {
- timer->deleteLater();
-
- if (modal_)
- modal_->hide();
-
- if (spinner_)
- spinner_->stop();
- });
-
- // FIXME: Snackbar doesn't work if it's initialized in the constructor.
- QTimer::singleShot(0, this, [this]() {
- snackBar_ = new SnackBar(this);
- connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
- });
-
- timer->start(50);
-}
-
void
MainWindow::showChatPage()
{
@@ -227,19 +343,13 @@ MainWindow::showChatPage()
userSettings_.data()->setDeviceId(device_id);
userSettings_.data()->setHomeserver(homeserver);
- showOverlayProgressBar();
-
- pageStack_->setCurrentWidget(chat_page_);
-
- pageStack_->removeWidget(welcome_page_);
- pageStack_->removeWidget(login_page_);
- pageStack_->removeWidget(register_page_);
-
- login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token);
connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
+
emit reload();
+ nhlog::ui()->info("Switching to chat page");
+ emit switchToChatPage();
}
void
@@ -247,7 +357,7 @@ MainWindow::closeEvent(QCloseEvent *event)
{
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(
- this, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
+ nullptr, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
QMessageBox::Yes) {
event->ignore();
return;
@@ -289,23 +399,11 @@ MainWindow::hasActiveUser()
settings->contains(prefix + "auth/user_id");
}
-void
-MainWindow::showOverlayProgressBar()
-{
- spinner_ = new LoadingIndicator(this);
- spinner_->setFixedHeight(100);
- spinner_->setFixedWidth(100);
- spinner_->setObjectName(QStringLiteral("ChatPageLoadSpinner"));
- spinner_->start();
-
- showSolidOverlayModal(spinner_);
-}
-
void
MainWindow::openCreateRoomDialog(
std::function callback)
{
- auto dialog = new dialogs::CreateRoom(this);
+ auto dialog = new dialogs::CreateRoom(nullptr);
connect(dialog,
&dialogs::CreateRoom::createRoom,
this,
@@ -314,76 +412,19 @@ MainWindow::openCreateRoomDialog(
showDialog(dialog);
}
-void
-MainWindow::showTransparentOverlayModal(QWidget *content, QFlags flags)
-{
- modal_->setWidget(content);
- modal_->setColor(QColor(30, 30, 30, 150));
- modal_->setDismissible(true);
- modal_->setContentAlignment(flags);
- modal_->raise();
- modal_->show();
-}
-
-void
-MainWindow::showSolidOverlayModal(QWidget *content, QFlags flags)
-{
- modal_->setWidget(content);
- modal_->setColor(QColor(30, 30, 30));
- modal_->setDismissible(false);
- modal_->setContentAlignment(flags);
- modal_->raise();
- modal_->show();
-}
-
-bool
-MainWindow::hasActiveDialogs() const
-{
- return modal_ && modal_->isVisible();
-}
-
bool
MainWindow::pageSupportsTray() const
{
- return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
-}
-
-void
-MainWindow::hideOverlay()
-{
- if (modal_)
- modal_->hide();
+ return !http::client()->access_token().empty();
}
inline void
MainWindow::showDialog(QWidget *dialog)
{
- utils::centerWidget(dialog, this);
+ dialog->setWindowFlags(Qt::WindowType::Dialog | Qt::WindowType::WindowCloseButtonHint |
+ Qt::WindowType::WindowTitleHint);
dialog->raise();
dialog->show();
-}
-
-void
-MainWindow::showWelcomePage()
-{
- removeOverlayProgressBar();
- pageStack_->addWidget(welcome_page_);
- pageStack_->setCurrentWidget(welcome_page_);
-}
-
-void
-MainWindow::showLoginPage()
-{
- if (modal_)
- modal_->hide();
-
- pageStack_->addWidget(login_page_);
- pageStack_->setCurrentWidget(login_page_);
-}
-
-void
-MainWindow::showRegisterPage()
-{
- pageStack_->addWidget(register_page_);
- pageStack_->setCurrentWidget(register_page_);
+ utils::centerWidget(dialog, this);
+ dialog->window()->windowHandle()->setTransientParent(this);
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 458eb054..7bc94328 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -8,26 +8,21 @@
#include
-#include
+#include
#include
-#include
#include
#include "UserSettingsPage.h"
-#include "ui/OverlayModal.h"
#include "jdenticoninterface.h"
class ChatPage;
class RegisterPage;
-class LoginPage;
class WelcomePage;
-class LoadingIndicator;
-class OverlayModal;
-class SnackBar;
class TrayIcon;
class UserSettings;
+class MxcImageProvider;
namespace mtx {
namespace requests {
@@ -42,17 +37,12 @@ class MemberList;
class ReCaptcha;
}
-class MainWindow : public QMainWindow
+class MainWindow : public QQuickView
{
Q_OBJECT
- Q_PROPERTY(int x READ x CONSTANT)
- Q_PROPERTY(int y READ y CONSTANT)
- Q_PROPERTY(int width READ width CONSTANT)
- Q_PROPERTY(int height READ height CONSTANT)
-
public:
- explicit MainWindow(QWidget *parent = nullptr);
+ explicit MainWindow(QWindow *parent = nullptr);
static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize();
@@ -61,69 +51,51 @@ public:
openCreateRoomDialog(std::function callback);
void openJoinRoomDialog(std::function callback);
- void hideOverlay();
- void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter);
- void
- showTransparentOverlayModal(QWidget *content,
- QFlags flags = Qt::AlignTop | Qt::AlignHCenter);
+ MxcImageProvider *imageProvider() { return imgProvider; }
+
+ //! Show the chat page and start communicating with the given access token.
+ void showChatPage();
protected:
- void closeEvent(QCloseEvent *event) override;
+ void closeEvent(QCloseEvent *event);
bool event(QEvent *event) override;
private slots:
//! Handle interaction with the tray icon.
void iconActivated(QSystemTrayIcon::ActivationReason reason);
- //! Show the welcome page in the main window.
- void showWelcomePage();
-
- //! Show the login page in the main window.
- void showLoginPage();
-
- //! Show the register page in the main window.
- void showRegisterPage();
-
- //! Show the chat page and start communicating with the given access token.
- void showChatPage();
-
- void showOverlayProgressBar();
- void removeOverlayProgressBar();
-
virtual void setWindowTitle(int notificationCount);
signals:
- void focusChanged(const bool focused);
void reload();
void secretsChanged();
+ void showNotification(QString msg);
+
+ void switchToChatPage();
+ void switchToWelcomePage();
+ void switchToLoginPage(QString error);
+
private:
void showDialog(QWidget *dialog);
bool hasActiveUser();
void restoreWindowSize();
- //! Check if there is an open dialog.
- bool hasActiveDialogs() const;
//! Check if the current page supports the "minimize to tray" functionality.
bool pageSupportsTray() const;
+ void registerQmlTypes();
+
static MainWindow *instance_;
//! The initial welcome screen.
WelcomePage *welcome_page_;
- //! The login screen.
- LoginPage *login_page_;
//! The register page.
RegisterPage *register_page_;
- //! A stacked widget that handles the transitions between widgets.
- QStackedWidget *pageStack_;
//! The main chat area.
ChatPage *chat_page_;
QSharedPointer userSettings_;
//! Tray icon that shows the unread message count.
TrayIcon *trayIcon_;
- //! Notifications display.
- SnackBar *snackBar_ = nullptr;
- //! Overlay modal used to project other widgets.
- OverlayModal *modal_ = nullptr;
- LoadingIndicator *spinner_ = nullptr;
+
+ MxcImageProvider *imgProvider = nullptr;
};
diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp
index d089ac96..5b2ebc78 100644
--- a/src/RegisterPage.cpp
+++ b/src/RegisterPage.cpp
@@ -4,312 +4,83 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
+#include
#include
#include
#include
#include "Config.h"
#include "Logging.h"
+#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "RegisterPage.h"
-#include "ui/FlatButton.h"
-#include "ui/RaisedButton.h"
-#include "ui/TextField.h"
#include "ui/UIA.h"
-#include "dialogs/FallbackAuth.h"
-#include "dialogs/ReCaptcha.h"
-
-Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized)
-Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
-
-RegisterPage::RegisterPage(QWidget *parent)
- : QWidget(parent)
+RegisterPage::RegisterPage(QObject *parent)
+ : QObject(parent)
{
- qRegisterMetaType();
- qRegisterMetaType();
- top_layout_ = new QVBoxLayout();
-
- back_layout_ = new QHBoxLayout();
- back_layout_->setSpacing(0);
- back_layout_->setContentsMargins(5, 5, -1, -1);
-
- back_button_ = new FlatButton(this);
- back_button_->setMinimumSize(QSize(30, 30));
-
- QIcon icon;
- icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
-
- back_button_->setIcon(icon);
- back_button_->setIconSize(QSize(32, 32));
-
- back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
- back_layout_->addStretch(1);
-
- QIcon logo;
- logo.addFile(QStringLiteral(":/logos/register.png"));
-
- logo_ = new QLabel(this);
- logo_->setPixmap(logo.pixmap(128));
-
- logo_layout_ = new QHBoxLayout();
- logo_layout_->setContentsMargins(0, 0, 0, 0);
- logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
-
- form_wrapper_ = new QHBoxLayout();
- form_widget_ = new QWidget();
- form_widget_->setMinimumSize(QSize(350, 300));
-
- form_layout_ = new QVBoxLayout();
- form_layout_->setSpacing(20);
- form_layout_->setContentsMargins(0, 0, 0, 40);
- form_widget_->setLayout(form_layout_);
-
- form_wrapper_->addStretch(1);
- form_wrapper_->addWidget(form_widget_);
- form_wrapper_->addStretch(1);
-
- username_input_ = new TextField();
- username_input_->setLabel(tr("Username"));
- username_input_->setRegexp(QRegularExpression(QStringLiteral("[a-z0-9._=/-]+")));
- username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
- "characters a-z, 0-9, ., _, =, -, and /."));
-
- password_input_ = new TextField();
- password_input_->setLabel(tr("Password"));
- password_input_->setRegexp(QRegularExpression(QStringLiteral("^.{8,}$")));
- password_input_->setEchoMode(QLineEdit::Password);
- password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
- "for password strength may depend on your server."));
-
- password_confirmation_ = new TextField();
- password_confirmation_->setLabel(tr("Password confirmation"));
- password_confirmation_->setEchoMode(QLineEdit::Password);
-
- server_input_ = new TextField();
- server_input_->setLabel(tr("Homeserver"));
- server_input_->setRegexp(QRegularExpression(QStringLiteral(".+")));
- server_input_->setToolTip(
- tr("A server that allows registration. Since matrix is decentralized, you need to first "
- "find a server you can register on or host your own."));
-
- error_username_label_ = new QLabel(this);
- error_username_label_->setWordWrap(true);
- error_username_label_->hide();
-
- error_password_label_ = new QLabel(this);
- error_password_label_->setWordWrap(true);
- error_password_label_->hide();
-
- error_password_confirmation_label_ = new QLabel(this);
- error_password_confirmation_label_->setWordWrap(true);
- error_password_confirmation_label_->hide();
-
- error_server_label_ = new QLabel(this);
- error_server_label_->setWordWrap(true);
- error_server_label_->hide();
-
- form_layout_->addWidget(username_input_, Qt::AlignHCenter);
- form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
- form_layout_->addWidget(password_input_, Qt::AlignHCenter);
- form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
- form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
- form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
- form_layout_->addWidget(server_input_, Qt::AlignHCenter);
- form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
-
- button_layout_ = new QHBoxLayout();
- button_layout_->setSpacing(0);
- button_layout_->setContentsMargins(0, 0, 0, 0);
-
- error_label_ = new QLabel(this);
- error_label_->setWordWrap(true);
-
- register_button_ = new RaisedButton(tr("REGISTER"), this);
- register_button_->setMinimumSize(350, 65);
- register_button_->setFontSize(conf::btn::fontSize);
- register_button_->setCornerRadius(conf::btn::cornerRadius);
-
- button_layout_->addStretch(1);
- button_layout_->addWidget(register_button_);
- button_layout_->addStretch(1);
-
- top_layout_->addLayout(back_layout_);
- top_layout_->addLayout(logo_layout_);
- top_layout_->addLayout(form_wrapper_);
- top_layout_->addStretch(1);
- top_layout_->addLayout(button_layout_);
- top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
- top_layout_->addStretch(1);
- setLayout(top_layout_);
-
- connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
- connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
-
- connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
- connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
- connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(password_confirmation_,
- &TextField::editingFinished,
- this,
- &RegisterPage::checkPasswordConfirmation);
- connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
-
- connect(
- this,
- &RegisterPage::serverError,
- this,
- [this](const QString &msg) {
- server_input_->setValid(false);
- showError(error_server_label_, msg);
- },
- Qt::QueuedConnection);
-
- connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
- connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
- connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
+ connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); });
}
void
-RegisterPage::onBackButtonClicked()
+RegisterPage::setError(QString err)
{
- emit backButtonClicked();
+ registrationError_ = err;
+ emit errorChanged();
+ registering_ = false;
+ emit registeringChanged();
+}
+void
+RegisterPage::setHsError(QString err)
+{
+ hsError_ = err;
+ emit hsErrorChanged();
+ lookingUpHs_ = false;
+ emit lookingUpHsChanged();
+}
+
+QString
+RegisterPage::initialDeviceName() const
+{
+ return QString::fromStdString(LoginPage::initialDeviceName_());
}
void
-RegisterPage::showError(const QString &msg)
+RegisterPage::setServer(QString server)
{
- emit errorOccurred();
- auto rect = QFontMetrics(font()).boundingRect(msg);
- int width = rect.width();
- int height = rect.height();
- error_label_->setFixedHeight(qCeil(width / 200.0) * height);
- error_label_->setText(msg);
-}
+ if (server == lastServer)
+ return;
-void
-RegisterPage::showError(QLabel *label, const QString &msg)
-{
- emit errorOccurred();
- auto rect = QFontMetrics(font()).boundingRect(msg);
- int width = rect.width();
- int height = rect.height();
- label->setFixedHeight((int)qCeil(width / 200.0) * height);
- label->setText(msg);
- label->show();
-}
+ lastServer = server;
-bool
-RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
-{
- if (t_field->isValid()) {
- label->hide();
- return true;
- } else {
- showError(label, msg);
- return false;
- }
-}
+ http::client()->set_server(server.toStdString());
+ http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
-bool
-RegisterPage::checkUsername()
-{
- return checkOneField(error_username_label_,
- username_input_,
- tr("The username must not be empty, and must contain only the "
- "characters a-z, 0-9, ., _, =, -, and /."));
-}
+ hsError_.clear();
+ emit hsErrorChanged();
+ supported_ = false;
+ lookingUpHs_ = true;
+ emit lookingUpHsChanged();
-bool
-RegisterPage::checkPassword()
-{
- return checkOneField(
- error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
-}
-
-bool
-RegisterPage::checkPasswordConfirmation()
-{
- if (password_input_->text() == password_confirmation_->text()) {
- error_password_confirmation_label_->hide();
- password_confirmation_->setValid(true);
- return true;
- } else {
- showError(error_password_confirmation_label_, tr("Passwords don't match"));
- password_confirmation_->setValid(false);
- return false;
- }
-}
-
-bool
-RegisterPage::checkServer()
-{
- // This doesn't check that the server is reachable,
- // just that the input is not obviously wrong.
- return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
-}
-
-void
-RegisterPage::onRegisterButtonClicked()
-{
- if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
- auto server = server_input_->text().toStdString();
-
- http::client()->set_server(server);
- http::client()->verify_certificates(
- !UserSettings::instance()->disableCertificateValidation());
-
- // This starts a chain of `emit`s which ends up doing the
- // registration. Signals are used rather than normal function
- // calls so that the dialogs used in UIA work correctly.
- //
- // The sequence of events looks something like this:
- //
- // doKnownLookup
- // v
- // doVersionsCheck
- // v
- // doRegistration -> loops the UIAHandler until complete
-
- emit wellKnownLookup();
-
- emit registering();
- }
-}
-
-void
-RegisterPage::doWellKnownLookup()
-{
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
nhlog::net()->info("Autodiscovery: No .well-known.");
// Check that the homeserver can be reached
- emit versionsCheck();
+ versionsCheck();
return;
}
if (!err->parse_error.empty()) {
- emit serverError(tr("Autodiscovery failed. Received malformed response."));
+ setHsError(tr("Autodiscovery failed. Received malformed response."));
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
+ emit hsErrorChanged();
return;
}
- emit serverError(tr("Autodiscovery failed. Unknown error when "
- "requesting .well-known."));
+ setHsError(tr("Autodiscovery failed. Unknown error when requesting .well-known."));
nhlog::net()->error("Autodiscovery failed. Unknown error when "
"requesting .well-known. {} {}",
err->status_code,
@@ -319,98 +90,140 @@ RegisterPage::doWellKnownLookup()
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
+ emit hsErrorChanged();
// Check that the homeserver can be reached
- emit versionsCheck();
+ versionsCheck();
});
}
void
-RegisterPage::doVersionsCheck()
+RegisterPage::versionsCheck()
{
// Make a request to /_matrix/client/versions to check the address
// given is a Matrix homeserver.
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
- emit serverError(tr("The required endpoints were not found. Possibly "
- "not a Matrix server."));
+ setHsError(
+ tr("The required endpoints were not found. Possibly not a Matrix server."));
+ emit hsErrorChanged();
return;
}
if (!err->parse_error.empty()) {
- emit serverError(tr("Received malformed response. Make sure the homeserver "
- "domain is valid."));
+ setHsError(
+ tr("Received malformed response. Make sure the homeserver domain is valid."));
+ emit hsErrorChanged();
return;
}
- emit serverError(tr("An unknown error occured. Make sure the "
- "homeserver domain is valid."));
+ setHsError(tr("An unknown error occured. Make sure the homeserver domain is valid."));
+ emit hsErrorChanged();
return;
}
- // Attempt registration without an `auth` dict
- emit registration();
+ http::client()->registration(
+ [this](const mtx::responses::Register &, mtx::http::RequestErr e) {
+ nhlog::net()->debug("Registration check: {}", e);
+
+ if (!e) {
+ setHsError(tr("Server does not support querying registration flows!"));
+ emit hsErrorChanged();
+ return;
+ }
+ if (e->status_code != 401) {
+ setHsError(tr("Server does not support registration."));
+ emit hsErrorChanged();
+ return;
+ }
+
+ supported_ = true;
+ lookingUpHs_ = false;
+ emit lookingUpHsChanged();
+ });
});
}
void
-RegisterPage::doRegistration()
+RegisterPage::checkUsername(QString name)
{
- // These inputs should still be alright, but check just in case
- if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
- auto username = username_input_->text().toStdString();
- auto password = password_input_->text().toStdString();
- connect(UIA::instance(), &UIA::error, this, [this](QString msg) {
- showError(msg);
- disconnect(UIA::instance(), &UIA::error, this, nullptr);
- });
- http::client()->registration(
- username,
- password,
- ::UIA::instance()->genericHandler(QStringLiteral("Registration")),
- registrationCb());
- }
-}
+ usernameAvailable_ = usernameUnavailable_ = false;
+ usernameError_.clear();
+ lookingUpUsername_ = true;
+ emit lookingUpUsernameChanged();
-mtx::http::Callback
-RegisterPage::registrationCb()
-{
- // Return a function to be used as the callback when an attempt at
- // registration is made.
- return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
- if (!err) {
- http::client()->set_user(res.user_id);
- http::client()->set_access_token(res.access_token);
- emit registerOk();
- disconnect(UIA::instance(), &UIA::error, this, nullptr);
- return;
- }
+ http::client()->register_username_available(
+ name.toStdString(),
+ [this](const mtx::responses::Available &available, mtx::http::RequestErr e) {
+ if (e) {
+ if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_INVALID_USERNAME) {
+ usernameError_ = tr("Invalid username.");
+ } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_USER_IN_USE) {
+ usernameError_ = tr("Name already in use.");
+ } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_EXCLUSIVE) {
+ usernameError_ = tr("Part of the reserved namespace.");
+ } else {
+ }
- // The server requires registration flows.
- if (err->status_code == 401) {
- if (err->matrix_error.unauthorized.flows.empty()) {
- nhlog::net()->warn("failed to retrieve registration flows: "
- "status_code({}), matrix_error({}) ",
- static_cast(err->status_code),
- err->matrix_error.error);
- showError(QString::fromStdString(err->matrix_error.error));
- }
- return;
- }
-
- nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
- static_cast(err->status_code),
- err->matrix_error.error);
-
- showError(QString::fromStdString(err->matrix_error.error));
- };
+ usernameAvailable_ = false;
+ usernameUnavailable_ = true;
+ } else {
+ usernameAvailable_ = available.available;
+ usernameUnavailable_ = !available.available;
+ }
+ lookingUpUsername_ = false;
+ emit lookingUpUsernameChanged();
+ });
}
void
-RegisterPage::paintEvent(QPaintEvent *)
+RegisterPage::startRegistration(QString username, QString password, QString devicename)
{
- QStyleOption opt;
- opt.initFrom(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+ // These inputs should still be alright, but check just in case
+ if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) {
+ registrationError_.clear();
+ emit errorChanged();
+ registering_ = true;
+ emit registeringChanged();
+
+ connect(UIA::instance(), &UIA::error, this, [this](QString msg) {
+ setError(msg);
+ disconnect(UIA::instance(), &UIA::error, this, nullptr);
+ });
+ http::client()->registration(
+ username.toStdString(),
+ password.toStdString(),
+ ::UIA::instance()->genericHandler(QStringLiteral("Registration")),
+ [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
+ registering_ = false;
+ emit registeringChanged();
+
+ if (!err) {
+ http::client()->set_user(res.user_id);
+ http::client()->set_access_token(res.access_token);
+ emit registerOk();
+ disconnect(UIA::instance(), &UIA::error, this, nullptr);
+ return;
+ }
+
+ // The server requires registration flows.
+ if (err->status_code == 401 && err->matrix_error.unauthorized.flows.empty()) {
+ nhlog::net()->warn("failed to retrieve registration flows: "
+ "status_code({}), matrix_error({}) ",
+ static_cast(err->status_code),
+ err->matrix_error.error);
+ setError(QString::fromStdString(err->matrix_error.error));
+ disconnect(UIA::instance(), &UIA::error, this, nullptr);
+ return;
+ }
+
+ nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
+ static_cast(err->status_code),
+ err->matrix_error.error);
+
+ setError(QString::fromStdString(err->matrix_error.error));
+ disconnect(UIA::instance(), &UIA::error, this, nullptr);
+ },
+ devicename.isEmpty() ? LoginPage::initialDeviceName_() : devicename.toStdString());
+ }
}
diff --git a/src/RegisterPage.h b/src/RegisterPage.h
index f76313c8..67e2a22e 100644
--- a/src/RegisterPage.h
+++ b/src/RegisterPage.h
@@ -6,88 +6,69 @@
#pragma once
-#include
-
-#include
+#include
+#include
#include
#include
-class FlatButton;
-class RaisedButton;
-class TextField;
-class QLabel;
-class QVBoxLayout;
-class QHBoxLayout;
-
-class RegisterPage : public QWidget
+class RegisterPage : public QObject
{
Q_OBJECT
-public:
- RegisterPage(QWidget *parent = nullptr);
+ Q_PROPERTY(QString error READ error NOTIFY errorChanged)
+ Q_PROPERTY(QString hsError READ hsError NOTIFY hsErrorChanged)
+ Q_PROPERTY(QString usernameError READ usernameError NOTIFY lookingUpUsernameChanged)
+ Q_PROPERTY(bool registering READ registering NOTIFY registeringChanged)
+ Q_PROPERTY(bool supported READ supported NOTIFY lookingUpHsChanged)
+ Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
+ Q_PROPERTY(bool lookingUpUsername READ lookingUpUsername NOTIFY lookingUpUsernameChanged)
+ Q_PROPERTY(bool usernameAvailable READ usernameAvailable NOTIFY lookingUpUsernameChanged)
+ Q_PROPERTY(bool usernameUnavailable READ usernameUnavailable NOTIFY lookingUpUsernameChanged)
-protected:
- void paintEvent(QPaintEvent *event) override;
+public:
+ RegisterPage(QObject *parent = nullptr);
+
+ Q_INVOKABLE void setServer(QString server);
+ Q_INVOKABLE void checkUsername(QString name);
+ Q_INVOKABLE void startRegistration(QString username, QString password, QString deviceName);
+ Q_INVOKABLE QString initialDeviceName() const;
+
+ bool registering() const { return registering_; }
+ bool supported() const { return supported_; }
+ bool lookingUpHs() const { return lookingUpHs_; }
+ bool lookingUpUsername() const { return lookingUpUsername_; }
+ bool usernameAvailable() const { return usernameAvailable_; }
+ bool usernameUnavailable() const { return usernameUnavailable_; }
+
+ QString error() const { return registrationError_; }
+ QString usernameError() const { return usernameError_; }
+ QString hsError() const { return hsError_; }
signals:
- void backButtonClicked();
- void errorOccurred();
+ void errorChanged();
+ void hsErrorChanged();
- //! Used to trigger the corresponding slot outside of the main thread.
- void serverError(const QString &err);
+ void registeringChanged();
+ void lookingUpHsChanged();
+ void lookingUpUsernameChanged();
- void wellKnownLookup();
- void versionsCheck();
- void registration();
-
- void registering();
void registerOk();
-private slots:
- void onBackButtonClicked();
- void onRegisterButtonClicked();
-
- // function for showing different errors
- void showError(const QString &msg);
- void showError(QLabel *label, const QString &msg);
-
- bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
- bool checkUsername();
- bool checkPassword();
- bool checkPasswordConfirmation();
- bool checkServer();
-
- void doWellKnownLookup();
- void doVersionsCheck();
- void doRegistration();
- mtx::http::Callback registrationCb();
-
private:
- QVBoxLayout *top_layout_;
+ void versionsCheck();
- QHBoxLayout *back_layout_;
- QHBoxLayout *logo_layout_;
- QHBoxLayout *button_layout_;
+ void setHsError(QString err);
+ void setError(QString err);
- QLabel *logo_;
- QLabel *error_label_;
- QLabel *error_username_label_;
- QLabel *error_password_label_;
- QLabel *error_password_confirmation_label_;
- QLabel *error_server_label_;
- QLabel *error_registration_token_label_;
+ QString registrationError_, hsError_, usernameError_;
- FlatButton *back_button_;
- RaisedButton *register_button_;
+ bool registering_;
+ bool supported_;
+ bool lookingUpHs_;
+ bool lookingUpUsername_;
+ bool usernameAvailable_;
+ bool usernameUnavailable_;
- QWidget *form_widget_;
- QHBoxLayout *form_wrapper_;
- QVBoxLayout *form_layout_;
-
- TextField *username_input_;
- TextField *password_input_;
- TextField *password_confirmation_;
- TextField *server_input_;
- TextField *registration_token_input_;
+ QString lastServer;
};
diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp
index d83156a4..28da9558 100644
--- a/src/TrayIcon.cpp
+++ b/src/TrayIcon.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include "TrayIcon.h"
@@ -100,7 +101,7 @@ MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State s
return result;
}
-TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
+TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
: QSystemTrayIcon(parent)
{
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@@ -110,13 +111,13 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
setIcon(QIcon(icon_));
#endif
- QMenu *menu = new QMenu(parent);
+ QMenu *menu = new QMenu();
setContextMenu(menu);
viewAction_ = new QAction(tr("Show"), this);
quitAction_ = new QAction(tr("Quit"), this);
- connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
+ connect(viewAction_, &QAction::triggered, parent, &QWindow::show);
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
menu->addAction(viewAction_);
diff --git a/src/TrayIcon.h b/src/TrayIcon.h
index 17bf5eff..554a4a0a 100644
--- a/src/TrayIcon.h
+++ b/src/TrayIcon.h
@@ -40,7 +40,7 @@ class TrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
- TrayIcon(const QString &filename, QWidget *parent);
+ TrayIcon(const QString &filename, QWindow *parent);
public slots:
void setUnreadCount(int count);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index c43733fb..a0aa8f84 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -5,25 +5,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include
-#include
#include
#include
-#include
-#include
+#include
#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
#include
#include
-#include
#include
#include "Cache.h"
@@ -33,8 +22,7 @@
#include "UserSettingsPage.h"
#include "Utils.h"
#include "encryption/Olm.h"
-#include "ui/FlatButton.h"
-#include "ui/ToggleButton.h"
+#include "ui/Theme.h"
#include "voip/CallDevices.h"
#include "config/nheko.h"
@@ -1518,7 +1506,7 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
QString homeFolder =
QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
auto filepath = QFileDialog::getOpenFileName(
- MainWindow::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
+ nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
if (!filepath.isEmpty()) {
i->setRingtone(filepath);
i->setRingtone(filepath);
@@ -1600,11 +1588,11 @@ UserSettingsModel::importSessionKeys()
{
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
const QString fileName = QFileDialog::getOpenFileName(
- MainWindow::instance(), tr("Open Sessions File"), homeFolder, QLatin1String(""));
+ nullptr, tr("Open Sessions File"), homeFolder, QLatin1String(""));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
- QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
+ QMessageBox::warning(nullptr, tr("Error"), file.errorString());
return;
}
@@ -1612,7 +1600,7 @@ UserSettingsModel::importSessionKeys()
auto payload = std::string(bin.data(), bin.size());
bool ok;
- auto password = QInputDialog::getText(MainWindow::instance(),
+ auto password = QInputDialog::getText(nullptr,
tr("File Password"),
tr("Enter the passphrase to decrypt the file:"),
QLineEdit::Password,
@@ -1622,8 +1610,7 @@ UserSettingsModel::importSessionKeys()
return;
if (password.isEmpty()) {
- QMessageBox::warning(
- MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
+ QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
return;
}
@@ -1631,7 +1618,7 @@ UserSettingsModel::importSessionKeys()
auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
cache::importSessionKeys(std::move(sessions));
} catch (const std::exception &e) {
- QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
+ QMessageBox::warning(nullptr, tr("Error"), e.what());
}
}
void
@@ -1639,7 +1626,7 @@ UserSettingsModel::exportSessionKeys()
{
// Open password dialog.
bool ok;
- auto password = QInputDialog::getText(MainWindow::instance(),
+ auto password = QInputDialog::getText(nullptr,
tr("File Password"),
tr("Enter passphrase to encrypt your session keys:"),
QLineEdit::Password,
@@ -1649,19 +1636,18 @@ UserSettingsModel::exportSessionKeys()
return;
if (password.isEmpty()) {
- QMessageBox::warning(
- MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
+ QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
return;
}
// Open file dialog to save the file.
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
const QString fileName = QFileDialog::getSaveFileName(
- MainWindow::instance(), tr("File to save the exported session keys"), homeFolder);
+ nullptr, tr("File to save the exported session keys"), homeFolder);
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
- QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
+ QMessageBox::warning(nullptr, tr("Error"), file.errorString());
return;
}
@@ -1679,7 +1665,7 @@ UserSettingsModel::exportSessionKeys()
out << prefix << newline << b64 << newline << suffix << newline;
file.close();
} catch (const std::exception &e) {
- QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
+ QMessageBox::warning(nullptr, tr("Error"), e.what());
}
}
void
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index a44e0030..e9b8763d 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -7,12 +7,9 @@
#pragma once
#include
-#include
-#include
#include
#include
#include
-#include
#include "JdenticonProvider.h"
#include
diff --git a/src/Utils.cpp b/src/Utils.cpp
index a9cfde22..0ac37d8e 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
@@ -770,20 +771,17 @@ utils::luminance(const QColor &col)
}
void
-utils::centerWidget(QWidget *widget, QWidget *parent)
+utils::centerWidget(QWidget *widget, QWindow *parent)
{
+ if (parent) {
+ widget->window()->windowHandle()->setTransientParent(parent);
+ return;
+ }
+
auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint {
return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
hostRect.center().y() - (childRect.height() * 0.5));
};
-
- if (parent) {
- widget->move(parent->window()->frameGeometry().topLeft() +
- parent->window()->rect().center() - widget->rect().center());
- return;
- }
-
- // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry()));
widget->move(findCenter(QGuiApplication::primaryScreen()->geometry()));
}
diff --git a/src/Utils.h b/src/Utils.h
index 87ce1c34..0b6034ac 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -290,7 +290,7 @@ luminance(const QColor &col);
//! Center a widget in relation to another widget.
void
-centerWidget(QWidget *widget, QWidget *parent);
+centerWidget(QWidget *widget, QWindow *parent);
void
restoreCombobox(QComboBox *combo, const QString &value);
diff --git a/src/WelcomePage.cpp b/src/WelcomePage.cpp
deleted file mode 100644
index 5d540f4e..00000000
--- a/src/WelcomePage.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Konstantinos Sideris
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include
-#include
-#include
-#include
-
-#include "Config.h"
-#include "WelcomePage.h"
-#include "ui/RaisedButton.h"
-#include "ui/TextLabel.h"
-
-WelcomePage::WelcomePage(QWidget *parent)
- : QWidget(parent)
-{
- auto topLayout_ = new QVBoxLayout(this);
- topLayout_->setSpacing(20);
- topLayout_->setAlignment(Qt::AlignCenter);
-
- QFont headingFont;
- headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
- QFont subTitleFont;
- subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
-
- QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
-
- auto logo_ = new QLabel(this);
- logo_->setPixmap(icon.pixmap(256));
- logo_->setAlignment(Qt::AlignCenter);
-
- QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
- QString main(tr("Enjoy your stay!"));
-
- auto intoTxt_ = new TextLabel(heading, this);
- intoTxt_->setFont(headingFont);
- intoTxt_->setAlignment(Qt::AlignCenter);
-
- auto subTitle = new TextLabel(main, this);
- subTitle->setFont(subTitleFont);
- subTitle->setAlignment(Qt::AlignCenter);
-
- topLayout_->addStretch(1);
- topLayout_->addWidget(logo_);
- topLayout_->addWidget(intoTxt_);
- topLayout_->addWidget(subTitle);
-
- auto btnLayout_ = new QHBoxLayout();
- btnLayout_->setSpacing(20);
- btnLayout_->setContentsMargins(0, 20, 0, 20);
-
- const int fontHeight = QFontMetrics{subTitleFont}.height();
- const int buttonHeight = fontHeight * 2.5;
- const int buttonWidth = fontHeight * 8;
-
- auto registerBtn = new RaisedButton(tr("REGISTER"), this);
- registerBtn->setMinimumSize(buttonWidth, buttonHeight);
- registerBtn->setFontSize(subTitleFont.pointSizeF());
- registerBtn->setCornerRadius(conf::btn::cornerRadius);
-
- auto loginBtn = new RaisedButton(tr("LOGIN"), this);
- loginBtn->setMinimumSize(buttonWidth, buttonHeight);
- loginBtn->setFontSize(subTitleFont.pointSizeF());
- loginBtn->setCornerRadius(conf::btn::cornerRadius);
-
- btnLayout_->addStretch(1);
- btnLayout_->addWidget(registerBtn);
- btnLayout_->addWidget(loginBtn);
- btnLayout_->addStretch(1);
-
- topLayout_->addLayout(btnLayout_);
- topLayout_->addStretch(1);
-
- connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
- connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
-}
-
-void
-WelcomePage::paintEvent(QPaintEvent *)
-{
- QStyleOption opt;
- opt.initFrom(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
diff --git a/src/WelcomePage.h b/src/WelcomePage.h
deleted file mode 100644
index 9d5da8ba..00000000
--- a/src/WelcomePage.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include
-
-class WelcomePage : public QWidget
-{
- Q_OBJECT
-
-public:
- explicit WelcomePage(QWidget *parent = nullptr);
-
-protected:
- void paintEvent(QPaintEvent *) override;
-
-signals:
- // Notify that the user wants to login in.
- void userLogin();
-
- // Notify that the user wants to register.
- void userRegister();
-};
diff --git a/src/main.cpp b/src/main.cpp
index 2ae631cf..24fc8415 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -279,6 +280,7 @@ main(int argc, char *argv[])
font.setPointSizeF(settings.lock()->fontSize());
app.setFont(font);
+ settings.lock()->applyTheme();
if (QLocale().language() == QLocale::C)
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom));
@@ -296,9 +298,10 @@ main(int argc, char *argv[])
app.installTranslator(&appTranslator);
MainWindow w;
+ // QQuickView w;
// Move the MainWindow to the center
- w.move(screenCenter(w.width(), w.height()));
+ // w.move(screenCenter(w.width(), w.height()));
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
w.show();
@@ -314,7 +317,7 @@ main(int argc, char *argv[])
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
w.show();
w.raise();
- w.activateWindow();
+ w.requestActivate();
});
// It seems like handling the message in a blocking manner is a no-go. I have no idea how to
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 5284ce0e..18e224b2 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -266,8 +266,8 @@ void
InputBar::openFileSelection()
{
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
- const auto fileName = QFileDialog::getOpenFileName(
- ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
+ const auto fileName =
+ QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
if (fileName.isEmpty())
return;
@@ -659,7 +659,7 @@ InputBar::command(const QString &command, QString args)
void
InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats)
{
- auto *previewDialog_ = new dialogs::PreviewUploadOverlay(ChatPage::instance());
+ auto *previewDialog_ = new dialogs::PreviewUploadOverlay(nullptr);
previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
// Force SVG to _not_ be handled as an image, but as raw data
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index eb453462..aa81f501 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -8,6 +8,7 @@
#include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h"
+#include "MainWindow.h"
#include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "TimelineModel.h"
@@ -275,7 +276,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
connect(newRoom.data(),
&TimelineModel::newEncryptedImage,
- manager->imageProvider(),
+ MainWindow::instance()->imageProvider(),
&MxcImageProvider::addEncryptionInfo);
connect(newRoom.data(),
&TimelineModel::forwardToRoom,
@@ -509,7 +510,7 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
// room_model->addEvents(room.timeline);
connect(room_model.data(),
&TimelineModel::newCallEvent,
- manager->callManager(),
+ ChatPage::instance()->callManager(),
&CallManager::syncEvent,
Qt::UniqueConnection);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 7c9df403..6b380f79 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index)
if (index != oldIndex)
emit currentIndexChanged(index);
- if (!ChatPage::instance()->isActiveWindow())
+ if (MainWindow::instance() != QGuiApplication::focusWindow())
return;
if (!currentId.startsWith('m')) {
@@ -1495,7 +1495,7 @@ TimelineModel::saveMedia(const QString &eventId) const
const QString openLocation = downloadsFolder + "/" + originalFilename;
const QString filename =
- QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString);
+ QFileDialog::getSaveFileName(nullptr, dialogTitle, openLocation, filterString);
if (filename.isEmpty())
return false;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index e689e2fa..0abd102b 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -5,10 +5,10 @@
#include "TimelineViewManager.h"
+#include
#include
#include
#include
-#include
#include
#include
#include
@@ -45,10 +45,6 @@
#include "ui/NhekoGlobalObject.h"
#include "ui/UIA.h"
-Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
-Q_DECLARE_METATYPE(std::vector)
-Q_DECLARE_METATYPE(std::vector)
-
namespace msgs = mtx::events::msg;
namespace {
@@ -102,19 +98,6 @@ void
TimelineViewManager::updateColorPalette()
{
userColors.clear();
-
- if (ChatPage::instance()->userSettings()->theme() == QLatin1String("light")) {
- view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
- view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
- QPalette());
- } else if (ChatPage::instance()->userSettings()->theme() == QLatin1String("dark")) {
- view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
- view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
- QPalette());
- } else {
- view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
- view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), nullptr);
- }
}
QColor
@@ -126,112 +109,15 @@ TimelineViewManager::userColor(QString id, QColor background)
return userColors.value(idx);
}
-TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
+TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent)
: QObject(parent)
- , imgProvider(new MxcImageProvider())
- , colorImgProvider(new ColorImageProvider())
- , blurhashProvider(new BlurhashProvider())
- , jdenticonProvider(new JdenticonProvider())
, rooms_(new RoomlistModel(this))
, communities_(new CommunitiesModel(this))
- , callManager_(callManager)
, verificationManager_(new VerificationManager(this))
, presenceEmitter(new PresenceEmitter(this))
{
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
-
- qRegisterMetaType>();
-
- qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
- "im.nheko",
- 1,
- 0,
- "MtxEvent",
- QStringLiteral("Can't instantiate enum!"));
- qmlRegisterUncreatableMetaObject(
- olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!"));
- qmlRegisterUncreatableMetaObject(crypto::staticMetaObject,
- "im.nheko",
- 1,
- 0,
- "Crypto",
- QStringLiteral("Can't instantiate enum!"));
- qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
- "im.nheko",
- 1,
- 0,
- "VerificationStatus",
- QStringLiteral("Can't instantiate enum!"));
-
- qmlRegisterType("im.nheko", 1, 0, "DelegateChoice");
- qmlRegisterType("im.nheko", 1, 0, "DelegateChooser");
- qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea");
- qmlRegisterType("im.nheko", 1, 0, "CursorShape");
- qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage");
- qmlRegisterType("im.nheko", 1, 0, "MxcMedia");
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "DeviceVerificationFlow",
- QStringLiteral("Can't create verification flow from QML!"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "UserProfileModel",
- QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "MemberList",
- QStringLiteral("MemberList needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "RoomSettingsModel",
- QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "ImagePackListModel",
- QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "SingleImagePackModel",
- QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "InviteesModel",
- QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "ReadReceiptsProxy",
- QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side"));
-
static auto self = this;
- qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance());
qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
- qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
qmlRegisterSingletonType(
"im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
auto ptr = new FilteredRoomlistModel(self->rooms_);
@@ -247,79 +133,15 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
return ptr;
});
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_);
- qmlRegisterSingletonInstance(
- "im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data());
- qmlRegisterSingletonInstance(
- "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
- qmlRegisterSingletonType(
- "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new Clipboard();
- });
- qmlRegisterSingletonType(
- "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new Nheko();
- });
- qmlRegisterSingletonType(
- "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new UserSettingsModel();
- });
qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
- qmlRegisterSingletonType(
- "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = new SelfVerificationStatus();
- QObject::connect(ChatPage::instance(),
- &ChatPage::initializeEmptyViews,
- ptr,
- &SelfVerificationStatus::invalidate);
- return ptr;
- });
- qRegisterMetaType();
- qRegisterMetaType>();
-
- qmlRegisterUncreatableType(
- "im.nheko",
- 1,
- 0,
- "FilteredCommunitiesModel",
- QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
-
- qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel");
- qmlRegisterUncreatableType(
- "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
- qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
- "im.nheko.EmojiModel",
- 1,
- 0,
- "EmojiCategory",
- QStringLiteral("Error: Only enums"));
-
- qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel");
-
-#ifdef USE_QUICK_VIEW
- view = new QQuickView(parent);
- container = QWidget::createWindowContainer(view, parent);
-#else
- view = new QQuickWidget(parent);
- container = view;
- view->setResizeMode(QQuickWidget::SizeRootObjectToView);
- container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
- nhlog::ui()->debug("Status changed to {}", status);
- });
-#endif
- container->setMinimumSize(200, 200);
updateColorPalette();
- view->engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
- view->engine()->addImageProvider(QStringLiteral("colorimage"), colorImgProvider);
- view->engine()->addImageProvider(QStringLiteral("blurhash"), blurhashProvider);
- if (JdenticonProvider::isAvailable())
- view->engine()->addImageProvider(QStringLiteral("jdenticon"), jdenticonProvider);
- view->setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
- connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
+ connect(UserSettings::instance().get(),
+ &UserSettings::themeChanged,
+ this,
+ &TimelineViewManager::updateColorPalette);
connect(parent,
&ChatPage::receivedRoomDeviceVerificationRequest,
verificationManager_,
@@ -336,6 +158,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
isInitialSync_ = true;
emit initialSyncChanged(true);
});
+ connect(qobject_cast(QApplication::instance()),
+ &QApplication::focusWindowChanged,
+ this,
+ &TimelineViewManager::focusChanged);
+}
+
+bool
+TimelineViewManager::isWindowFocused() const
+{
+ return MainWindow::instance() == QApplication::focusWindow();
}
void
@@ -379,7 +211,8 @@ void
TimelineViewManager::setVideoCallItem()
{
WebRTCSession::instance().setVideoItem(
- view->rootObject()->findChild(QStringLiteral("videoCallItem")));
+ MainWindow::instance()->rootObject()->findChild(
+ QStringLiteral("videoCallItem")));
}
void
@@ -401,7 +234,7 @@ TimelineViewManager::showEvent(const QString &room_id, const QString &event_id)
if (auto room = rooms_->getRoomById(room_id)) {
if (rooms_->currentRoom() != room) {
rooms_->setCurrentRoom(room_id);
- container->setFocus();
+ MainWindow::instance()->requestActivate();
nhlog::ui()->info("Activated room {}", room_id.toStdString());
}
@@ -439,7 +272,7 @@ TimelineViewManager::saveMedia(QString mxcUrl)
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
- const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation);
+ const QString filename = QFileDialog::getSaveFileName(nullptr, {}, openLocation);
if (filename.isEmpty())
return;
@@ -590,12 +423,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
return nullptr;
}
-void
-TimelineViewManager::focusTimeline()
-{
- getWidget()->setFocus();
-}
-
void
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
QString roomId)
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 455702f4..13ab5dbb 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -8,8 +8,6 @@
#include
#include
#include
-#include
-#include
#include
#include
@@ -43,23 +41,19 @@ class TimelineViewManager : public QObject
Q_PROPERTY(
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
- Q_PROPERTY(
- bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
+ Q_PROPERTY(bool isWindowFocused READ isWindowFocused NOTIFY focusChanged)
public:
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
- QWidget *getWidget() const { return container; }
void sync(const mtx::responses::Sync &sync_);
- MxcImageProvider *imageProvider() { return imgProvider; }
- CallManager *callManager() { return callManager_; }
VerificationManager *verificationManager() { return verificationManager_; }
void clearAll() { rooms_->clear(); }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
- bool isWindowFocused() const { return isWindowFocused_; }
+ bool isWindowFocused() const;
Q_INVOKABLE void openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId);
Q_INVOKABLE void openImagePackSettings(QString roomid);
Q_INVOKABLE void saveMedia(QString mxcUrl);
@@ -98,14 +92,8 @@ public slots:
void updateReadReceipts(const QString &room_id, const std::vector &event_ids);
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
void initializeRoomlist();
- void chatFocusChanged(bool focused)
- {
- isWindowFocused_ = focused;
- emit focusChanged();
- }
void showEvent(const QString &room_id, const QString &event_id);
- void focusTimeline();
void updateColorPalette();
void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
@@ -122,26 +110,12 @@ public slots:
RoomlistModel *rooms() { return rooms_; }
private:
-#ifdef USE_QUICK_VIEW
- QQuickView *view;
-#else
- QQuickWidget *view;
-#endif
- QWidget *container;
-
- MxcImageProvider *imgProvider;
- ColorImageProvider *colorImgProvider;
- BlurhashProvider *blurhashProvider;
- JdenticonProvider *jdenticonProvider;
-
- bool isInitialSync_ = true;
- bool isWindowFocused_ = false;
+ bool isInitialSync_ = true;
RoomlistModel *rooms_ = nullptr;
CommunitiesModel *communities_ = nullptr;
// don't move this above the rooms_
- CallManager *callManager_ = nullptr;
VerificationManager *verificationManager_ = nullptr;
PresenceEmitter *presenceEmitter = nullptr;
diff --git a/src/ui/DropShadow.cpp b/src/ui/DropShadow.cpp
deleted file mode 100644
index 039d6558..00000000
--- a/src/ui/DropShadow.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "DropShadow.h"
-
-#include
-#include
-
-void
-DropShadow::draw(QPainter &painter,
- qint16 margin,
- qreal radius,
- QColor start,
- QColor end,
- qreal startPosition,
- qreal endPosition0,
- qreal endPosition1,
- qreal width,
- qreal height)
-{
- painter.setPen(Qt::NoPen);
-
- QLinearGradient gradient;
- gradient.setColorAt(startPosition, start);
- gradient.setColorAt(endPosition0, end);
-
- // Right
- QPointF right0(width - margin, height / 2);
- QPointF right1(width, height / 2);
- gradient.setStart(right0);
- gradient.setFinalStop(right1);
- painter.setBrush(QBrush(gradient));
- // Deprecated in 5.13: painter.drawRoundRect(
- // QRectF(QPointF(width - margin * radius, margin), QPointF(width, height -
- // margin)), 0.0, 0.0);
- painter.drawRoundedRect(
- QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), 0.0, 0.0);
-
- // Left
- QPointF left0(margin, height / 2);
- QPointF left1(0, height / 2);
- gradient.setStart(left0);
- gradient.setFinalStop(left1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(
- QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0);
-
- // Top
- QPointF top0(width / 2, margin);
- QPointF top1(width / 2, 0);
- gradient.setStart(top0);
- gradient.setFinalStop(top1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0);
-
- // Bottom
- QPointF bottom0(width / 2, height - margin);
- QPointF bottom1(width / 2, height);
- gradient.setStart(bottom0);
- gradient.setFinalStop(bottom1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(
- QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0);
-
- // BottomRight
- QPointF bottomright0(width - margin, height - margin);
- QPointF bottomright1(width, height);
- gradient.setStart(bottomright0);
- gradient.setFinalStop(bottomright1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(bottomright0, bottomright1), 0.0, 0.0);
-
- // BottomLeft
- QPointF bottomleft0(margin, height - margin);
- QPointF bottomleft1(0, height);
- gradient.setStart(bottomleft0);
- gradient.setFinalStop(bottomleft1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0);
-
- // TopLeft
- QPointF topleft0(margin, margin);
- QPointF topleft1(0, 0);
- gradient.setStart(topleft0);
- gradient.setFinalStop(topleft1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(topleft0, topleft1), 0.0, 0.0);
-
- // TopRight
- QPointF topright0(width - margin, margin);
- QPointF topright1(width, 0);
- gradient.setStart(topright0);
- gradient.setFinalStop(topright1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(topright0, topright1), 0.0, 0.0);
-
- // Widget
- painter.setBrush(QBrush(QColor(0xff, 0xff, 0xff)));
- painter.setRenderHint(QPainter::Antialiasing);
- painter.drawRoundedRect(
- QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), radius, radius);
-}
diff --git a/src/ui/DropShadow.h b/src/ui/DropShadow.h
deleted file mode 100644
index 1810a1fe..00000000
--- a/src/ui/DropShadow.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include
-
-class QPainter;
-
-class DropShadow
-{
-public:
- static void draw(QPainter &painter,
- qint16 margin,
- qreal radius,
- QColor start,
- QColor end,
- qreal startPosition,
- qreal endPosition0,
- qreal endPosition1,
- qreal width,
- qreal height);
-};
diff --git a/src/ui/FlatButton.cpp b/src/ui/FlatButton.cpp
deleted file mode 100644
index da322378..00000000
--- a/src/ui/FlatButton.cpp
+++ /dev/null
@@ -1,730 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include
-#include
-#include
-#include
-#include