Add QR code scanner

This commit is contained in:
Tobias Fella
2023-12-16 16:46:30 +01:00
parent fbb4b962fa
commit c3fd2428a2
12 changed files with 319 additions and 41 deletions

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
Kirigami.Dialog {
id: root
property url link
width: Kirigami.Units.gridUnit * 24
height: Kirigami.Units.gridUnit * 8
title: i18nc("@title", "Open Url")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
contentItem: QQC2.Label {
text: i18nc("Do you want to open <link>", "Do you want to open <b>%1</b>?", root.link)
wrapMode: QQC2.Label.Wrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
onAccepted: {
Qt.openUrlExternally(root.link);
root.close();
}
onRejected: {
root.close();
}
}

View File

@@ -68,6 +68,16 @@ RowLayout {
}
}
property Kirigami.Action scanAction: Kirigami.Action {
text: i18n("Scan a QR Code")
icon.name: "view-barcode-qr"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "QrScannerPage.qml"), {
connection: root.connection
}, {
title: i18nc("@title", "Scan a QR Code")
})
}
/**
* @brief Emitted when the text is changed in the search field.
*/
@@ -130,6 +140,9 @@ RowLayout {
QQC2.MenuItem {
action: spaceAction
}
QQC2.MenuItem {
action: scanAction
}
}
}
Component {
@@ -177,6 +190,11 @@ RowLayout {
onClicked: menuRoot.close()
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
action: scanAction
onClicked: menuRoot.close()
Layout.fillWidth: true
}
}
}
}

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.prison
import org.kde.neochat
Kirigami.Dialog {
id: root
property string room
property NeoChatConnection connection
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title", "Join Room")
contentItem: ColumnLayout {
spacing: 0
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
name: root.room.slice(1, -1)
initialsMode: KirigamiComponents.Avatar.UseInitials
}
Kirigami.Heading {
level: 1
Layout.fillWidth: true
font.bold: true
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: root.room
textFormat: Text.PlainText
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Join room")
icon.name: "irc-join-channel"
onClicked: {
RoomManager.resolveResource(root.room, "join");
root.close();
}
}
}
}

58
src/qml/QrScannerPage.qml Normal file
View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtMultimedia
import org.kde.kirigami as Kirigami
import org.kde.prison.scanner as Prison
import org.kde.neochat
Kirigami.Page {
id: root
title: i18nc("@title", "Scan a QR Code")
required property NeoChatConnection connection
padding: 0
Component.onCompleted: camera.start()
Connections {
target: root.QQC2.ApplicationWindow.window
function onClosing() {
root.destroy();
}
}
VideoOutput {
id: viewFinder
anchors.centerIn: parent
}
Prison.VideoScanner {
id: scanner
property string previousText: ""
formats: Prison.Format.QRCode | Prison.Format.Aztec
onResultChanged: {
if (result.text.length > 0 && result.text != scanner.previousText) {
RoomManager.resolveResource(result.text, "");
scanner.previousText = result.text;
}
root.closeDialog();
}
videoSink: viewFinder.videoSink
}
CaptureSession {
camera: Camera {
id: camera
}
imageCapture: ImageCapture {
id: imageCapture
}
videoOutput: viewFinder
}
}

View File

@@ -238,9 +238,6 @@ Kirigami.Page {
Connections {
target: RoomManager
function onShowUserDetail(user) {
root.showUserDetail(user);
}
function onShowEventSource(eventId) {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
@@ -286,18 +283,6 @@ Kirigami.Page {
}
}
function showUserDetail(user) {
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
room: root.currentRoom,
user: root.currentRoom.getUser(user.id)
}).open();
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {

View File

@@ -16,9 +16,13 @@ import org.kde.neochat
Kirigami.Dialog {
id: root
// This dialog is sometimes used outside the context of a room, e.g., when scanning a user's QR code.
// Make sure that code is prepared to deal with this property being null
property NeoChatRoom room
property var user
property NeoChatConnection connection
parent: applicationWindow().overlay
leftPadding: 0
@@ -102,19 +106,19 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser
visible: !root.user.isLocalUser && !!root.user.object
action: Kirigami.Action {
text: room.connection.isIgnored(root.user.object) ? i18n("Unignore this user") : i18n("Ignore this user")
text: !!root.user.object && root.connection.isIgnored(root.user.object) ? i18n("Unignore this user") : i18n("Ignore this user")
icon.name: "im-invisible-user"
onTriggered: {
root.close();
room.connection.isIgnored(root.user.object) ? room.connection.removeFromIgnoredUsers(root.user.object) : room.connection.addToIgnoredUsers(root.user.object);
root.connection.isIgnored(root.user.object) ? root.connection.removeFromIgnoredUsers(root.user.object) : root.connection.addToIgnoredUsers(root.user.object);
}
}
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser && room.canSendState("kick") && room.containsUser(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.room.connection.localUser.id)
visible: root.room && !root.user.isLocalUser && room.canSendState("kick") && room.containsUser(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.connection.localUser.id)
action: Kirigami.Action {
text: i18n("Kick this user")
@@ -127,10 +131,10 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser && room.canSendState("invite") && !room.containsUser(root.user.id)
visible: root.room && !root.user.isLocalUser && room.canSendState("invite") && !room.containsUser(root.user.id)
action: Kirigami.Action {
enabled: !room.isUserBanned(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.user.id)
text: i18n("Invite this user")
icon.name: "list-add-user"
onTriggered: {
@@ -141,7 +145,7 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.room.connection.localUser.id)
visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.room.connection.localUser.id)
action: Kirigami.Action {
text: i18n("Ban this user")
@@ -161,7 +165,7 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser && room.canSendState("ban") && room.isUserBanned(root.user.id)
visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && room.isUserBanned(root.user.id)
action: Kirigami.Action {
text: i18n("Unban this user")
@@ -175,7 +179,7 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: room.canSendState("m.room.power_levels")
visible: root.room && room.canSendState("m.room.power_levels")
action: Kirigami.Action {
text: i18n("Set user power level")
icon.name: "visibility"
@@ -199,7 +203,7 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: root.user.isLocalUser || room.canSendState("redact")
visible: root.room && (root.user.isLocalUser || room.canSendState("redact"))
action: Kirigami.Action {
text: i18n("Remove recent messages by this user")
@@ -221,10 +225,10 @@ Kirigami.Dialog {
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser
action: Kirigami.Action {
text: root.room.connection.directChatExists(root.user.object) ? i18nc("%1 is the name of the user.", "Chat with %1", root.user.escapedDisplayName) : i18n("Invite to private chat")
text: root.connection.directChatExists(root.user.object) ? i18nc("%1 is the name of the user.", "Chat with %1", root.user.escapedDisplayName) : i18n("Invite to private chat")
icon.name: "document-send"
onTriggered: {
root.room.connection.openOrCreateDirectChat(root.user.object);
root.connection.openOrCreateDirectChat(root.user.object);
root.close();
}
}

View File

@@ -136,6 +136,17 @@ Kirigami.ApplicationWindow {
}
}
function onAskJoinRoom(room) {
joinRoomDialog.createObject(applicationWindow(), {
room: room,
connection: root.connection
}).open();
}
function onShowUserDetail(user) {
root.showUserDetail(user);
}
function onPushSpaceHome(room) {
root.spaceHomePage = pageStack.push(Qt.createComponent('org.kde.neochat', 'SpaceHomePage.qml'));
root.spaceHomePage.forceActiveFocus();
@@ -189,6 +200,11 @@ Kirigami.ApplicationWindow {
user: user
}).open();
}
function onExternalUrl(url) {
let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog.qml").createObject(applicationWindow());
dialog.link = url;
dialog.open();
}
}
function pushReplaceLayer(page, args) {
@@ -404,6 +420,11 @@ Kirigami.ApplicationWindow {
RoomWindow {}
}
Component {
id: joinRoomDialog
JoinRoomDialog {}
}
Component {
id: askDirectChatConfirmationComponent
@@ -481,4 +502,16 @@ Kirigami.ApplicationWindow {
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();
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
}