Files
neochat/src/qml/Main.qml
2024-04-12 22:17:39 +02:00

449 lines
14 KiB
QML

// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
import org.kde.neochat.login
import org.kde.neochat.settings
Kirigami.ApplicationWindow {
id: root
property NeoChatConnection connection: Controller.activeConnection
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 15
visible: false // Will be overridden in Component.onCompleted
wideScreen: width > Kirigami.Units.gridUnit * 65
pageStack {
initialPage: WelcomePage {
showExisting: true
onConnectionChosen: root.load()
}
globalToolBar.canContainHandles: true
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar
showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0
}
}
onConnectionChanged: {
CustomEmojiModel.connection = root.connection;
MatrixImageProvider.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
if (ShareHandler.text && root.connection) {
root.handleShare();
}
}
Connections {
target: LoginHelper
function onLoaded() {
root.load();
}
}
Connections {
target: root.quitAction
function onTriggered() {
Qt.quit();
}
}
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
sourceComponent: GlobalMenu {
connection: root.connection
}
}
// This timer allows to batch update the window size change to reduce
// the io load and also work around the fact that x/y/width/height are
// changed when loading the page and overwrite the saved geometry from
// the previous session.
Timer {
id: saveWindowGeometryTimer
interval: 1000
onTriggered: WindowController.saveGeometry()
}
Connections {
id: saveWindowGeometryConnections
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
target: root
function onClosing() {
WindowController.saveGeometry();
}
function onWidthChanged() {
saveWindowGeometryTimer.restart();
}
function onHeightChanged() {
saveWindowGeometryTimer.restart();
}
function onXChanged() {
saveWindowGeometryTimer.restart();
}
function onYChanged() {
saveWindowGeometryTimer.restart();
}
}
Loader {
id: quickView
active: !Kirigami.Settings.isMobile
sourceComponent: QuickSwitcher {
connection: root.connection
}
}
Connections {
target: RoomManager
function onAskJoinRoom(room) {
joinRoomDialog.createObject(applicationWindow(), {
room: room,
connection: root.connection
}).open();
}
function onShowUserDetail(user) {
root.showUserDetail(user);
}
function goToEvent(event) {
if (event.length > 0) {
roomItem.goToEvent(event);
}
roomItem.forceActiveFocus();
}
function onAskDirectChatConfirmation(user) {
askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
user: user
}).open();
}
function onExternalUrl(url) {
let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(applicationWindow());
dialog.link = url;
dialog.open();
}
}
function pushReplaceLayer(page, args) {
if (pageStack.layers.depth === 2) {
pageStack.layers.replace(page, args);
} else {
pageStack.layers.push(page, args);
}
}
function openRoomDrawer() {
pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection
});
}
contextDrawer: RoomDrawer {
id: contextDrawer
// This is a memory for all user initiated actions on the drawer, i.e. clicking the button
// It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width
property bool drawerUserState: Config.autoRoomInfoDrawer
connection: root.connection
handleOpenIcon.source: "arrow-right"
handleClosedIcon.source: "arrow-left"
// Connect to the onClicked function of the RoomDrawer handle button
Connections {
target: contextDrawer.handle.children[0]
function onClicked() {
contextDrawer.drawerUserState = contextDrawer.drawerOpen;
}
}
modal: (!root.wideScreen || !enabled)
onEnabledChanged: drawerOpen = enabled && !modal
onModalChanged: {
if (Config.autoRoomInfoDrawer) {
drawerOpen = !modal && drawerUserState;
dim = false;
}
}
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3 && (pageStack.visibleItems.length > 1 || pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode
handleVisible: enabled
}
Component.onCompleted: {
CustomEmojiModel.connection = root.connection;
MatrixImageProvider.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);
if (ShareHandler.text && root.connection) {
root.handleShare()
}
if (Config.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && Config.systemTray) {
restoreWindowGeometryConnections.enabled = true; // To restore window size and position
} else {
visible = true;
saveWindowGeometryConnections.enabled = true;
}
}
Connections {
target: Config
function onBlurChanged() {
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);
}
function onCompactLayoutChanged() {
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);
}
}
// blur effect
color: Config.blur && !Config.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor
// we need to apply the translucency effect separately on top of the color
background: Rectangle {
color: Config.blur && !Config.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - Config.transparency) : "transparent"
}
Component {
id: roomListComponent
RoomListPage {
id: roomList
connection: root.connection
Shortcut {
sequences: ["Ctrl+PgUp", "Ctrl+Backtab", "Alt+Up"]
onActivated: {
roomList.goToPreviousRoom();
}
}
Shortcut {
sequences: ["Ctrl+PgDown", "Ctrl+Tab", "Alt+Down"]
onActivated: {
roomList.goToNextRoom();
}
}
Shortcut {
sequence: "Alt+Shift+Up"
onActivated: {
roomList.goToPreviousUnreadRoom();
}
}
Shortcut {
sequence: "Alt+Shift+Down"
onActivated: {
roomList.goToNextUnreadRoom();
}
}
}
}
Connections {
target: AccountRegistry
function onRowsRemoved() {
if (AccountRegistry.rowCount() === 0) {
pageStack.clear();
pageStack.push(Qt.createComponent('org.kde.neochat', 'WelcomePage'));
}
}
}
Connections {
target: Controller
function onErrorOccured(error, detail) {
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error);
}
}
Connections {
id: restoreWindowGeometryConnections
enabled: false
target: root
function onVisibleChanged() {
if (!visible) {
return;
}
Controller.restoreWindowGeometry(root);
restoreWindowGeometryConnections.enabled = false; // Only restore window geometry for the first time
saveWindowGeometryConnections.enabled = true;
}
}
Component {
id: keyVerificationDialogComponent
KeyVerificationDialog {}
}
Connections {
target: root.connection
function onDirectChatAvailable(directChat) {
RoomManager.resolveResource(directChat.id);
}
function onNewKeyVerificationSession(session) {
applicationWindow().pageStack.pushDialogLayer(keyVerificationDialogComponent, {
session: session
}, {
title: i18nc("@title:window", "Session Verification")
});
}
function onUserConsentRequired(url) {
let consent = consentSheetComponent.createObject(QQC2.ApplicationWindow.overlay);
consent.url = url;
consent.open();
}
}
Component {
id: consentSheetComponent
Kirigami.OverlaySheet {
id: consentSheet
property string url: ""
title: i18n("User consent")
QQC2.Label {
id: label
text: i18n("Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
wrapMode: Text.WordWrap
width: parent.width
}
footer: QQC2.Button {
text: i18n("Open")
onClicked: UrlHelper.openUrl(consentSheet.url)
}
}
}
Component {
id: createRoomDialog
CreateRoomDialog {
connection: root.connection
}
}
Component {
id: joinRoomDialog
JoinRoomDialog {}
}
Component {
id: askDirectChatConfirmationComponent
Kirigami.OverlaySheet {
id: askDirectChatConfirmation
required property var user
parent: QQC2.ApplicationWindow.overlay
title: i18n("Start a chat")
contentItem: QQC2.Label {
text: i18n("Do you want to start a chat with %1?", user.displayName)
wrapMode: Text.WordWrap
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
onAccepted: {
user.requestDirectChat();
askDirectChatConfirmation.close();
}
onRejected: askDirectChatConfirmation.close()
}
}
}
property Item hoverLinkIndicator: QQC2.Control {
parent: overlay.parent
property string text
opacity: linkText.text.length > 0 ? 1 : 0
z: 20
x: 0
Accessible.ignored: true
y: parent.height - implicitHeight
contentItem: QQC2.Label {
id: linkText
text: parent.text.startsWith("https://matrix.to/") ? "" : parent.text
Accessible.description: i18nc("@info screenreader", "The currently selected link")
}
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
Shortcut {
sequence: "Ctrl+Shift+,"
onActivated: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'NeoChatSettings'), {
connection: root.connection
}, {
title: i18n("Configure"),
width: Kirigami.Units.gridUnit * 50,
height: Kirigami.Units.gridUnit * 42
});
}
}
Connections {
target: ShareHandler
function onTextChanged(): void {
if (root.connection && ShareHandler.text.length > 0) {
handleShare();
}
}
}
function handleShare(): void {
const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Share"),
width: Kirigami.Units.gridUnit * 25
})
dialog.chosen.connect(function(targetRoomId) {
RoomManager.resolveResource(targetRoomId)
ShareHandler.room = targetRoomId
dialog.closeDialog()
})
}
function showUserDetail(user) {
userDetailDialog.createObject(root.QQC2.ApplicationWindow.window, {
room: RoomManager.currentRoom ? RoomManager.currentRoom : null,
user: RoomManager.currentRoom ? RoomManager.currentRoom.getUser(user.id) : QmlUtils.getUser(user),
connection: root.connection
}).open();
}
function load() {
pageStack.replace(roomListComponent);
RoomManager.loadInitialRoom();
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
connection: root.connection
});
roomPage.forceActiveFocus();
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
}