Create a QML module for settings

This commit is contained in:
James Graham
2024-03-26 13:23:43 +00:00
parent f772906324
commit ff5853a850
38 changed files with 66 additions and 48 deletions

13
src/settings/About.qml Normal file
View File

@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.coreaddons
FormCard.AboutPage {
title: i18nc("@title:window", "About NeoChat")
aboutData: AboutData
}

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import org.kde.kirigamiaddons.formcard as FormCard
FormCard.AboutKDE {
title: i18nc("@title:window", "About KDE")
}

View File

@@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.platform
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
FormCard.FormCardPage {
id: root
title: i18n("Edit Account")
property NeoChatConnection connection
KirigamiComponents.AvatarButton {
id: avatar
property OpenFileDialog fileDialog: null
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: Kirigami.Units.largeSpacing
// Square button
implicitWidth: Kirigami.Units.gridUnit * 5
implicitHeight: implicitWidth
padding: 0
source: root.connection && root.connection.localUser.avatarMediaId ? ("image://mxc/" + root.connection.localUser.avatarMediaId) : ""
name: root.connection.localUser.displayName
onClicked: {
if (fileDialog) {
return;
}
fileDialog = openFileDialog.createObject(this);
fileDialog.chosen.connect(receivedSource => {
if (!receivedSource) {
return;
}
source = receivedSource;
});
fileDialog.open();
}
QQC2.Button {
anchors {
bottom: parent.bottom
right: parent.right
}
visible: avatar.source.toString().length === 0
icon.name: "cloud-upload"
text: i18n("Upload new avatar")
display: QQC2.AbstractButton.IconOnly
onClicked: parent.clicked()
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Button {
anchors {
bottom: parent.bottom
right: parent.right
}
visible: avatar.source.toString().length !== 0
icon.name: "edit-clear"
text: i18n("Remove current avatar")
display: QQC2.AbstractButton.IconOnly
onClicked: avatar.source = ""
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Component {
id: openFileDialog
OpenFileDialog {
currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
parentWindow: root.Window.window
onAccepted: destroy()
onRejected: destroy()
}
}
}
FormCard.FormHeader {
title: i18n("User information")
}
FormCard.FormCard {
FormCard.FormTextFieldDelegate {
id: name
label: i18n("Name:")
text: root.connection ? root.connection.localUser.displayName : ""
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: accountLabel
label: i18n("Label:")
text: root.connection ? root.connection.label : ""
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Save")
onClicked: {
if (!root.connection.setAvatar(avatar.source)) {
showPassiveNotification("The Avatar could not be set");
}
if (root.connection.localUser.displayName !== name.text) {
root.connection.localUser.rename(name.text);
}
if (root.connection.label !== accountLabel.text) {
root.connection.label = accountLabel.text;
}
}
}
}
FormCard.FormHeader {
title: i18n("Password")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
visible: root.connection !== undefined && root.connection.canChangePassword === false
text: i18n("Your server doesn't support changing your password")
}
FormCard.FormDelegateSeparator {
visible: root.connection !== undefined && root.connection.canChangePassword === false
}
FormCard.FormTextFieldDelegate {
id: currentPassword
label: i18n("Current Password:")
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: newPassword
label: i18n("New Password:")
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: confirmPassword
label: i18n("Confirm new Password:")
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
onTextChanged: if (newPassword.text !== confirmPassword.text && confirmPassword.text.length > 0) {
confirmPassword.status = FormCard.AbstractFormDelegate.Status.Error;
confirmPassword.statusMessage = i18n("Passwords don't match");
} else {
confirmPassword.status = FormCard.AbstractFormDelegate.Status.Default;
confirmPassword.statusMessage = '';
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Save")
enabled: currentPassword.text.length > 0 && newPassword.text.length > 0 && confirmPassword.text.length > 0
onClicked: {
if (newPassword.text === confirmPassword.text) {
root.connection.changePassword(currentPassword.text, newPassword.text);
} else {
showPassiveNotification(i18n("Passwords do not match"));
}
}
}
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Server Information")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
text: i18n("Homeserver url")
description: root.connection.homeserver
}
/* TODO but needs first some api in Quotient
FormCard.FormTextDelegate {
text: i18n("Server file upload limit")
description: root.connection.homeserver
}
FormCard.FormTextDelegate {
text: i18n("Server name")
description: root.connection.homeserver
}
FormCard.FormTextDelegate {
text: i18n("Server version")
description: root.connection.homeserver
}*/
}
FormCard.FormHeader {
title: i18nc("@title", "Account Management")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
id: deactivateAccountButton
text: i18n("Deactivate Account")
onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ConfirmDeactivateAccountDialog.qml'), {
connection: root.connection
}, {
title: i18nc("@title", "Confirm Deactivating Account")
})
}
}
data: Connections {
target: root.connection
function onPasswordStatus(status) {
if (status === NeoChatConnection.Success) {
showPassiveNotification(i18n("Password changed successfully"));
} else if (status === NeoChatConnection.Wrong) {
showPassiveNotification(i18n("Wrong password entered"));
} else {
showPassiveNotification(i18n("Unknown problem while trying to change password"));
}
}
}
}

View File

@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2020 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 QtQuick.Window
import Qt.labs.platform
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.accounts
FormCard.FormCardPage {
id: root
title: i18n("Accounts")
FormCard.FormHeader {
title: i18n("Accounts")
}
FormCard.FormCard {
Repeater {
model: AccountRegistry
delegate: FormCard.AbstractFormDelegate {
id: accountDelegate
required property NeoChatConnection connection
Layout.fillWidth: true
onClicked: applicationWindow().pageStack.layers.push('AccountEditorPage.qml', {
connection: accountDelegate.connection
}, {
title: i18n("Account editor")
})
contentItem: RowLayout {
KirigamiComponents.Avatar {
name: accountDelegate.connection.localUser.displayName
source: accountDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + accountDelegate.connection.localUser.avatarMediaId) : ""
Layout.rightMargin: Kirigami.Units.largeSpacing
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
}
ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
Layout.fillWidth: true
text: accountDelegate.connection.localUser.displayName
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
color: Kirigami.Theme.textColor
}
QQC2.Label {
Layout.fillWidth: true
text: accountDelegate.connection.localUserId
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
}
}
QQC2.ToolButton {
text: i18n("Logout")
icon.name: "im-kick-user"
onClicked: confirmLogoutDialogComponent.createObject(applicationWindow().overlay).open()
}
Component {
id: confirmLogoutDialogComponent
ConfirmLogoutDialog {
connection: accountDelegate.connection
onAccepted: {
if (AccountRegistry.accountCount === 1) {
root.Window.window.close();
}
}
}
}
FormCard.FormArrow {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
direction: Qt.RightArrow
}
}
}
}
FormCard.FormDelegateSeparator {
below: addAccountDelegate
}
FormCard.FormButtonDelegate {
id: addAccountDelegate
text: i18n("Add Account")
icon.name: "list-add"
onClicked: applicationWindow().pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'WelcomePage.qml'))
}
}
property Connections connections: Connections {
target: Controller
function onConnectionAdded() {
if (pageStack.layers.depth > 2) {
pageStack.layers.pop();
}
}
}
}

View File

@@ -0,0 +1,356 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// 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.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.config
FormCard.FormCardPage {
id: root
title: i18nc("@title:window", "Appearance")
FormCard.FormHeader {
title: i18n("General theme")
}
FormCard.FormCard {
FormCard.AbstractFormDelegate {
id: timelineModeSetting
background: Item {}
contentItem: RowLayout {
Layout.alignment: Qt.AlignCenter
spacing: Kirigami.Units.largeSpacing
Item {
Layout.fillWidth: true
}
QQC2.ButtonGroup {
id: themeGroup
}
ThemeRadioButton {
thin: timelineModeSetting.width < Kirigami.Units.gridUnit * 22
innerObject: [
RowLayout {
Layout.fillWidth: true
KirigamiComponents.Avatar {
color: "#4a5bcc"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
QQC2.Control {
Layout.fillWidth: true
contentItem: ColumnLayout {
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Paul Müller"
color: "#4a5bcc"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
}
},
RowLayout {
Layout.fillWidth: true
KirigamiComponents.Avatar {
color: "#9f244b"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
QQC2.Control {
Layout.fillWidth: true
contentItem: ColumnLayout {
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Jean Paul"
color: "#9f244b"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta , quis sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
}
}
]
text: i18n("Bubbles")
checked: !Config.compactLayout
QQC2.ButtonGroup.group: themeGroup
enabled: !Config.isCompactLayoutImmutable
onToggled: {
Config.compactLayout = !checked;
Config.save();
}
}
ThemeRadioButton {
// Layout.alignment: Qt.AlignRight
thin: timelineModeSetting.width < Kirigami.Units.gridUnit * 22
innerObject: [
RowLayout {
Layout.fillWidth: true
KirigamiComponents.Avatar {
color: "#4a5bcc"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
ColumnLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Paul Müller"
color: "#4a5bcc"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur elit. Vivamus facilisis porta mauris, finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
},
RowLayout {
Layout.fillWidth: true
KirigamiComponents.Avatar {
color: "#9f244b"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
ColumnLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Jean Paul"
color: "#9f244b"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
}
]
text: i18n("Compact")
checked: Config.compactLayout
QQC2.ButtonGroup.group: themeGroup
enabled: !Config.isCompactLayoutImmutable
onToggled: {
Config.compactLayout = checked;
Config.save();
}
}
Item {
Layout.fillWidth: true
}
}
}
FormCard.FormDelegateSeparator {
below: compactRoomListDelegate
}
FormCard.FormCheckDelegate {
id: compactRoomListDelegate
text: i18n("Use compact room list")
checked: Config.compactRoomList
onToggled: {
Config.compactRoomList = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: compactRoomListDelegate
below: colorSchemeDelegate.item
visible: colorSchemeDelegate.visible
}
Loader {
id: colorSchemeDelegate
visible: item !== null
sourceComponent: Qt.createComponent('org.kde.neochat.settings', 'ColorScheme.qml')
Layout.fillWidth: true
}
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormCheckDelegate {
id: showFancyEffectsDelegate
text: i18n("Show fancy effects in chat")
checked: Config.showFancyEffects
enabled: !Config.isShowFancyEffectsImmutable
onToggled: {
Config.showFancyEffects = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: showFancyEffectsDelegate
below: hasWindowSystemDelegate
}
FormCard.FormCheckDelegate {
id: hasWindowSystemDelegate
visible: WindowController.hasWindowSystem
text: i18n("Use transparent chat page")
enabled: !Config.compactLayout && !Config.isBlurImmutable
checked: Config.blur
onToggled: {
Config.blur = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: hasWindowSystemDelegate
below: transparencyDelegate
}
FormCard.AbstractFormDelegate {
id: transparencyDelegate
visible: WindowController.hasWindowSystem && Config.blur
enabled: !Config.isTransparancyImmutable
background: Item {}
contentItem: ColumnLayout {
QQC2.Label {
text: i18n("Transparency")
Layout.fillWidth: true
}
QQC2.Slider {
enabled: !Config.compactLayout && Config.blur
from: 0
to: 1
stepSize: 0.05
value: Config.transparency
onMoved: {
Config.transparency = value;
Config.save();
}
Layout.fillWidth: true
HoverHandler {
id: sliderHover
}
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
}
QQC2.Label {
text: Math.round(Config.transparency * 100) + "%"
Layout.fillWidth: true
}
}
}
FormCard.FormDelegateSeparator {
above: transparencyDelegate
below: showLocalMessagesOnRightDelegate
visible: transparencyDelegate.visible
}
FormCard.FormCheckDelegate {
id: showLocalMessagesOnRightDelegate
text: i18n("Show your messages on the right")
checked: Config.showLocalMessagesOnRight
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
onToggled: {
Config.showLocalMessagesOnRight = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: showLocalMessagesOnRightDelegate
below: showLinkPreviewDelegate
}
FormCard.FormCheckDelegate {
id: showLinkPreviewDelegate
text: i18n("Show links preview in the chat messages")
checked: Config.showLinkPreview
onToggled: {
Config.showLinkPreview = checked;
Config.save();
}
}
}
FormCard.FormHeader {
title: i18n("Show Avatar")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
text: i18n("In chat")
checked: Config.showAvatarInTimeline
onToggled: {
Config.showAvatarInTimeline = checked;
Config.save();
}
enabled: !Config.isShowAvatarInTimelineImmutable
}
FormCard.FormCheckDelegate {
text: i18n("In sidebar")
checked: Config.showAvatarInRoomDrawer
enabled: !Config.isShowAvatarInRoomDrawerImmutable
onToggled: {
Config.showAvatarInRoomDrawer = checked;
Config.save();
}
}
}
}

View File

@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(settings STATIC)
qt_add_qml_module(settings
URI org.kde.neochat.settings
QML_FILES
NeoChatSettings.qml
RoomSettings.qml
About.qml
AboutKDE.qml
AccountsPage.qml
AccountEditorPage.qml
AppearanceSettingsPage.qml
DevicesPage.qml
EmoticonsPage.qml
EmoticonEditorPage.qml
GlobalNotificationsPage.qml
NeoChatGeneralPage.qml
NeoChatSecurityPage.qml
NetworkProxyPage.qml
Permissions.qml
PushNotification.qml
RoomGeneralPage.qml
RoomSecurityPage.qml
SonnetConfigPage.qml
ColorScheme.qml
DevicesCard.qml
DeviceDelegate.qml
EmoticonFormCard.qml
IgnoredUsersDialog.qml
NotificationRuleItem.qml
ThemeRadioButton.qml
)

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.neochat.config
FormCard.FormComboBoxDelegate {
id: root
text: i18n("Color theme")
textRole: "display"
valueRole: "display"
model: ColorSchemer.model
Component.onCompleted: currentIndex = ColorSchemer.indexForScheme(Config.colorScheme)
onCurrentValueChanged: {
ColorSchemer.apply(currentIndex);
Config.colorScheme = ColorSchemer.nameForIndex(currentIndex);
Config.save();
}
}

View File

@@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: 2020 - 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// 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.formcard as FormCard
import org.kde.neochat
FormCard.AbstractFormDelegate {
id: root
required property string id
required property string timestamp
required property string displayName
property bool editDeviceName: false
property bool showVerifyButton
onClicked: root.editDeviceName = true
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "network-connect"
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
}
ColumnLayout {
id: deviceLabel
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
visible: !root.editDeviceName
QQC2.Label {
Layout.fillWidth: true
text: root.displayName
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
}
QQC2.Label {
Layout.fillWidth: true
text: i18nc("@label", "%1, Last activity: %2", root.id, root.timestamp)
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
visible: text.length > 0
}
}
Kirigami.ActionTextField {
id: nameField
Accessible.description: i18n("New device name")
Layout.fillWidth: true
Layout.preferredHeight: deviceLabel.implicitHeight
visible: root.editDeviceName
text: root.displayName
rightActions: [
Kirigami.Action {
text: i18n("Cancel editing display name")
icon.name: "edit-delete-remove"
onTriggered: {
root.editDeviceName = false;
}
},
Kirigami.Action {
text: i18n("Confirm new display name")
icon.name: "checkmark"
visible: nameField.text !== root.displayName
onTriggered: {
devicesModel.setName(root.id, nameField.text);
}
}
]
onAccepted: devicesModel.setName(root.id, nameField.text)
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: editDeviceAction
text: i18n("Edit device name")
icon.name: "document-edit"
onTriggered: root.editDeviceName = true
}
QQC2.ToolTip {
text: editDeviceAction.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
visible: root.showVerifyButton
action: Kirigami.Action {
id: verifyDeviceAction
text: i18n("Verify device")
icon.name: "security-low-symbolic"
onTriggered: {
devicesModel.connection.startKeyVerificationSession(devicesModel.connection.localUserId, root.id);
}
}
QQC2.ToolTip {
text: verifyDeviceAction.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: logoutDeviceAction
text: i18n("Logout device")
icon.name: "edit-delete-remove"
onTriggered: {
passwordSheet.deviceId = root.id;
passwordSheet.open();
}
}
QQC2.ToolTip {
text: logoutDeviceAction.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2020 - 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
ColumnLayout {
id: root
required property string title
required property var type
required property bool showVerifyButton
visible: deviceRepeater.count > 0
FormCard.FormHeader {
title: root.title
}
FormCard.FormCard {
id: devicesCard
Repeater {
id: deviceRepeater
model: DevicesProxyModel {
sourceModel: devicesModel
type: root.type
}
Kirigami.LoadingPlaceholder {
visible: deviceModel.count === 0 // We can assume 0 means loading since there is at least one device
anchors.centerIn: parent
}
delegate: DeviceDelegate {
showVerifyButton: root.showVerifyButton
}
}
}
}

View File

@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2020 - 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// 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.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
title: i18n("Devices")
background: Kirigami.PlaceholderMessage {
text: i18n("Loading…")
visible: !thisDeviceCard.visible
}
required property NeoChatConnection connection
property DevicesModel devicesModel: DevicesModel {
id: devicesModel
connection: root.connection
}
DevicesCard {
id: thisDeviceCard
title: i18n("This Device")
type: DevicesModel.This
showVerifyButton: false
}
DevicesCard {
title: i18n("Verified Devices")
type: DevicesModel.Verified
showVerifyButton: true
}
DevicesCard {
title: i18n("Unverified Devices")
type: DevicesModel.Unverified
showVerifyButton: true
}
DevicesCard {
title: i18n("Devices without Encryption Support")
type: DevicesModel.Unencrypted
showVerifyButton: false
}
FormCard.AbstractFormDelegate {
Layout.fillWidth: true
visible: root.connection && devicesModel.count === 0 // We can assume 0 means loading since there is at least one device
contentItem: Kirigami.LoadingPlaceholder {}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
Layout.alignment: Qt.AlignHCenter
text: i18n("Please login to view the signed-in devices for your account.")
type: Kirigami.MessageType.Information
visible: !root.connection
}
property Kirigami.Dialog passwordSheet: Kirigami.Dialog {
id: passwordSheet
property string deviceId
preferredWidth: Kirigami.Units.gridUnit * 24
title: i18n("Remove device")
standardButtons: QQC2.Dialog.Cancel
FormCard.FormCard {
FormCard.FormTextFieldDelegate {
id: passwordField
label: i18n("Password:")
echoMode: TextInput.Password
}
}
customFooterActions: [
Kirigami.Action {
text: i18nc("As in 'Remove this device'", "Remove")
icon.name: "delete"
onTriggered: {
devicesModel.logout(passwordSheet.deviceId, passwordField.text);
passwordField.text = "";
passwordSheet.close();
}
}
]
}
}

View File

@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: 2023 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 Qt.labs.platform
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
required property string description
required property string index
required property string url
required property string shortcode
required property var model
required property var proxyModel
property bool newEmoticon: false
required property var emoticonType
title: emoticonType === EmoticonFormCard.Stickers ? (newEmoticon ? i18nc("@title", "Add Sticker") : i18nc("@title", "Edit Sticker")) : (newEmoticon ? i18nc("@title", "Add Emoji") : i18nc("@title", "Edit Emoji"))
FormCard.FormHeader {
title: emoticonType === EmoticonFormCard.Stickers ? i18n("Sticker") : i18n("Emoji")
}
FormCard.FormCard {
FormCard.AbstractFormDelegate {
background: Item {}
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
Image {
id: image
Layout.alignment: Qt.AlignRight
source: root.url
sourceSize.width: Kirigami.Units.gridUnit * 4
sourceSize.height: Kirigami.Units.gridUnit * 4
width: Kirigami.Units.gridUnit * 4
height: Kirigami.Units.gridUnit * 4
Kirigami.Icon {
source: emoticonType === EmoticonFormCard.Stickers ? "stickers" : "preferences-desktop-emoticons"
anchors.fill: parent
visible: parent.status !== Image.Ready
}
QQC2.Button {
icon.name: "edit-entry"
anchors.right: parent.right
anchors.bottom: parent.bottom
onClicked: mouseArea.clicked()
text: image.source != "" ? i18n("Change Image") : i18n("Set Image")
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
display: QQC2.Button.IconOnly
}
MouseArea {
id: mouseArea
anchors.fill: parent
property var fileDialog: null
cursorShape: Qt.PointingHandCursor
onClicked: {
if (fileDialog != null) {
return;
}
fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.Overlay);
fileDialog.chosen.connect(function (receivedSource) {
mouseArea.fileDialog = null;
if (!receivedSource) {
return;
}
parent.source = receivedSource;
});
fileDialog.onRejected.connect(function () {
mouseArea.fileDialog = null;
});
fileDialog.open();
}
}
}
Item {
Layout.fillWidth: true
}
}
}
FormCard.FormTextFieldDelegate {
id: shortcode
label: i18n("Shortcode:")
text: root.shortcode
}
FormCard.FormTextFieldDelegate {
id: description
label: i18n("Description:")
text: root.description
}
FormCard.FormButtonDelegate {
id: save
text: i18n("Save")
icon.name: "document-save"
enabled: !root.newEmoticon || (image.source.toString().length > 0 && shortcode.text && description.text)
onClicked: {
if (root.newEmoticon) {
model.addEmoticon(image.source, shortcode.text, description.text, emoticonType === EmoticonFormCard.Stickers ? "sticker" : "emoticon");
} else {
if (description.text !== root.description) {
root.model.setEmoticonBody(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, description.text);
}
if (shortcode.text !== root.shortcode) {
root.model.setEmoticonShortcode(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, shortcode.text);
}
if (image.source + "" !== root.url) {
root.model.setEmoticonImage(proxyModel.mapToSource(proxyModel.index(model.index, 0)).row, image.source);
}
}
root.closeDialog();
}
}
}
property Component openFileDialog: Component {
id: openFileDialog
OpenFileDialog {
currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
parentWindow: root.Window.window
}
}
}

View File

@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2023 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.formcard as FormCard
import org.kde.neochat
FormCard.FormCard {
id: root
enum EmoticonType {
Emojis,
Stickers
}
property var emoticonType
required property NeoChatConnection connection
Flow {
id: stickerFlow
Layout.fillWidth: true
Repeater {
model: EmoticonFilterModel {
id: emoticonFilterModel
sourceModel: AccountEmoticonModel {
id: stickerModel
connection: root.connection
}
showStickers: root.emoticonType === EmoticonFormCard.Stickers
showEmojis: root.emoticonType === EmoticonFormCard.Emojis
}
delegate: FormCard.AbstractFormDelegate {
id: stickerDelegate
width: stickerFlow.width / 4
height: width
onClicked: pageStack.pushDialogLayer(emoticonEditorPage, {
description: model.body ?? "",
index: model.index,
url: model.url,
shortcode: model.shortcode,
model: stickerModel,
proxyModel: emoticonFilterModel,
emoticonType: root.emoticonType
}, {
title: root.emoticonType === EmoticonFormCard.Emojis ? i18nc("@title", "Edit Emoji") : i18nc("@title", "Edit Sticker")
})
contentItem: ColumnLayout {
Image {
source: model.url
Layout.fillWidth: true
sourceSize.height: parent.width * 0.8
fillMode: Image.PreserveAspectFit
autoTransform: true
Kirigami.Icon {
source: root.emoticonType === EmoticonFormCard.Emojis ? "preferences-desktop-emoticons" : "stickers"
anchors.fill: parent
visible: parent.status !== Image.Ready
}
}
QQC2.Label {
id: descriptionLabel
text: model.body ?? i18nc("As in 'This sticker/emoji has no description'", "No Description")
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
wrapMode: Text.Wrap
maximumLineCount: 2
elide: Text.ElideRight
}
}
QQC2.Button {
icon.name: "edit-delete"
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Kirigami.Units.smallSpacing
z: 2
onClicked: stickerModel.deleteEmoticon(emoticonFilterModel.mapToSource(emoticonFilterModel.index(model.index, 0)).row)
}
}
}
FormCard.AbstractFormDelegate {
width: stickerFlow.width / 4
height: width
onClicked: pageStack.pushDialogLayer(emoticonEditorPage, {
description: "",
index: -1,
url: "",
shortcode: "",
model: stickerModel,
proxyModel: emoticonFilterModel,
newEmoticon: true,
emoticonType: root.emoticonType
}, {
title: root.emoticonType === EmoticonFormCard.Emojis ? i18nc("@title", "Add Emoji") : i18nc("@title", "Add Sticker")
})
contentItem: ColumnLayout {
spacing: 0
Kirigami.Icon {
source: "list-add"
Layout.fillWidth: true
}
QQC2.Label {
text: root.emoticonType === EmoticonFormCard.Emojis ? i18n("Add Emoji") : i18n("Add Sticker")
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
required property NeoChatConnection connection
title: i18nc("@title", "Stickers & Emojis")
FormCard.FormHeader {
title: i18n("Emojis")
}
EmoticonFormCard {
emoticonType: EmoticonFormCard.Emojis
connection: root.connection
}
FormCard.FormHeader {
title: i18n("Stickers")
}
EmoticonFormCard {
emoticonType: EmoticonFormCard.Stickers
connection: root.connection
}
property Component emoticonEditorPage: Component {
id: emoticonEditorPage
EmoticonEditorPage {}
}
}

View File

@@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kitemmodels
import org.kde.neochat
FormCard.FormCardPage {
id: root
required property NeoChatConnection connection
title: i18nc("@title:window", "Notifications")
property PushRuleModel pushRuleModel: PushRuleModel {
connection: root.connection
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormCheckDelegate {
text: i18n("Enable notifications for this account")
description: i18n("Whether push notifications are generated by your Matrix server")
checked: root.pushRuleModel.globalNotificationsEnabled
enabled: root.pushRuleModel.globalNotificationsSet
onToggled: {
root.pushRuleModel.globalNotificationsEnabled = checked;
}
}
}
FormCard.FormHeader {
title: i18n("Room Notifications")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
return sectionRole == PushRuleSection.Room;
}
}
delegate: ruleDelegate
}
}
FormCard.FormHeader {
title: i18n("@Mentions")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
return sectionRole == PushRuleSection.Mentions;
}
}
delegate: ruleDelegate
}
}
FormCard.FormHeader {
title: i18n("Keywords")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
return sectionRole == PushRuleSection.Keywords;
}
}
delegate: ruleDelegate
}
FormCard.AbstractFormDelegate {
Layout.fillWidth: true
contentItem: RowLayout {
Kirigami.ActionTextField {
id: keywordAddField
Layout.fillWidth: true
placeholderText: i18n("Keyword…")
enabled: NotificationsManager.keywordNotificationAction !== PushRuleAction.Unknown
rightActions: Kirigami.Action {
icon.name: "edit-clear"
visible: keywordAddField.text.length > 0
onTriggered: {
keywordAddField.text = "";
}
}
onAccepted: {
root.pushRuleModel.addKeyword(keywordAddField.text);
keywordAddField.text = "";
}
}
QQC2.Button {
id: addButton
text: i18n("Add keyword")
Accessible.name: text
icon.name: "list-add"
display: QQC2.AbstractButton.IconOnly
enabled: NotificationsManager.keywordNotificationAction !== PushRuleAction.Unknown
onClicked: {
root.pushRuleModel.addKeyword(keywordAddField.text);
keywordAddField.text = "";
}
QQC2.ToolTip {
text: addButton.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
FormCard.FormHeader {
title: i18n("Invites")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
return sectionRole == PushRuleSection.Invites;
}
}
delegate: ruleDelegate
}
}
FormCard.FormHeader {
title: i18n("Unknown")
visible: unknownModel.rowCount() > 0
}
FormCard.FormCard {
visible: unknownModel.rowCount() > 0
Repeater {
model: KSortFilterProxyModel {
id: unknownModel
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
return sectionRole == PushRuleSection.Unknown;
}
}
delegate: ruleDelegate
}
}
property Component ruleDelegate: Component {
id: ruleDelegate
NotificationRuleItem {
onDeleteRule: {
root.pushRuleModel.removeKeyword(id);
}
onActionChanged: action => root.pushRuleModel.setPushRuleAction(id, action)
}
}
}

View File

@@ -0,0 +1,71 @@
// 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.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
required property NeoChatConnection connection
title: i18nc("@title", "Ignored Users")
width: Kirigami.Units.gridUnit * 16
height: Kirigami.Units.gridUnit * 32
FormCard.FormHeader {
title: i18nc("@title:group", "Ignored Users")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
text: i18nc("Placeholder message when no user is ignored", "You are not ignoring any users")
visible: repeater.count === 0
}
Repeater {
id: repeater
model: root.connection.ignoredUsers()
delegate: FormCard.AbstractFormDelegate {
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
background: null
contentItem: RowLayout {
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: modelData
elide: Text.ElideRight
Accessible.ignored: true // base class sets this text on root already
}
QQC2.ToolButton {
text: i18nc("@action:button", "Unignore this user")
icon.name: "list-remove-symbolic"
onClicked: root.connection.removeFromIgnoredUsers(root.connection.user(modelData))
display: QQC2.Button.IconOnly
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
}
}
}
}
}
Connections {
target: root.connection
function onIgnoredUsersListChanged() {
repeater.model = root.connection.ignoredUsers();
}
}
}

View File

@@ -0,0 +1,219 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.neochat.config
FormCard.FormCardPage {
title: i18nc("@title:window", "General")
FormCard.FormHeader {
title: i18n("General settings")
visible: Qt.platform.os !== "android"
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
id: closeDelegate
text: i18n("Show in System Tray")
checked: Config.systemTray
visible: Controller.supportSystemTray
enabled: !Config.isSystemTrayImmutable
onToggled: {
Config.systemTray = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: closeDelegate
below: minimizeDelegate
}
FormCard.FormCheckDelegate {
id: minimizeDelegate
text: i18n("Minimize to system tray on startup")
checked: Config.minimizeToSystemTrayOnStartup
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
enabled: Config.systemTray && !Config.isMinimizeToSystemTrayOnStartupImmutable
onToggled: {
Config.minimizeToSystemTrayOnStartup = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: minimizeDelegate
below: automaticallyDelegate
}
FormCard.FormCheckDelegate {
id: automaticallyDelegate
text: i18n("Automatically hide/unhide the room information when resizing the window")
checked: Config.autoRoomInfoDrawer
enabled: !Config.isAutoRoomInfoDrawerImmutable
visible: Qt.platform.os !== "android"
onToggled: {
Config.autoRoomInfoDrawer = checked;
Config.save();
}
}
}
FormCard.FormHeader {
title: i18n("Room list sort order")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18nc("As in 'sort something based on last activity'", "Activity")
checked: Config.sortOrder === 1
enabled: !Config.isSortOrderImmutable
onToggled: {
Config.sortOrder = 1
Config.save()
}
}
FormCard.FormRadioDelegate {
text: i18nc("As in 'sort something alphabetically'", "Alphabetical")
checked: Config.sortOrder === 0
enabled: !Config.isSortOrderImmutable
onToggled: {
Config.sortOrder = 0
Config.save()
}
}
}
FormCard.FormHeader {
title: i18n("Timeline Events")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
id: showDeletedMessages
text: i18n("Show deleted messages")
checked: Config.showDeletedMessages
enabled: !Config.isShowDeletedMessagesImmutable
onToggled: {
Config.showDeletedMessages = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: showDeletedMessages
below: showStateEvents
}
FormCard.FormCheckDelegate {
id: showStateEvents
text: i18n("Show state events")
checked: Config.showStateEvent
enabled: !Config.isShowStateEventImmutable
onToggled: {
Config.showStateEvent = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
visible: Config.showStateEvent
above: showStateEvents
below: showLeaveJoinEventDelegate
}
FormCard.FormCheckDelegate {
id: showLeaveJoinEventDelegate
visible: Config.showStateEvent
text: i18n("Show leave and join events")
checked: Config.showLeaveJoinEvent
enabled: !Config.isShowLeaveJoinEventImmutable
onToggled: {
Config.showLeaveJoinEvent = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
visible: Config.showStateEvent
above: showLeaveJoinEventDelegate
below: showNameDelegate
}
FormCard.FormCheckDelegate {
id: showNameDelegate
visible: Config.showStateEvent
text: i18n("Show name change events")
checked: Config.showRename
enabled: !Config.isShowRenameImmutable
onToggled: {
Config.showRename = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
visible: Config.showStateEvent
above: showNameDelegate
below: showAvatarChangeDelegate
}
FormCard.FormCheckDelegate {
id: showAvatarChangeDelegate
visible: Config.showStateEvent
text: i18n("Show avatar update events")
checked: Config.showAvatarUpdate
enabled: !Config.isShowAvatarUpdateImmutable
onToggled: {
Config.showAvatarUpdate = checked;
Config.save();
}
}
}
FormCard.FormHeader {
title: i18nc("Chat Editor", "Editor")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
id: quickEditCheckbox
text: i18n("Use s/text/replacement syntax to edit your last message")
checked: Config.allowQuickEdit
enabled: !Config.isAllowQuickEditImmutable
onToggled: {
Config.allowQuickEdit = checked;
Config.save();
}
}
FormCard.FormDelegateSeparator {
above: quickEditCheckbox
below: typingNotificationsDelegate
}
FormCard.FormCheckDelegate {
id: typingNotificationsDelegate
text: i18n("Send typing notifications")
checked: Config.typingNotifications
enabled: !Config.isTypingNotificationsImmutable
onToggled: {
Config.typingNotifications = checked;
Config.save();
}
}
}
FormCard.FormHeader {
title: i18n("Developer Settings")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
text: i18n("Enable developer tools")
checked: Config.developerTools
enabled: !Config.isDeveloperToolsImmutable
onToggled: {
Config.developerTools = checked;
Config.save();
}
}
}
}

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
required property NeoChatConnection connection
title: i18nc("@title", "Security")
FormCard.FormHeader {
title: i18nc("@title", "Keys")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
text: connection.deviceKey
description: i18n("Device key")
}
FormCard.FormTextDelegate {
text: connection.encryptionKey
description: i18n("Encryption key")
}
FormCard.FormTextDelegate {
text: connection.deviceId
description: i18n("Device id")
}
}
FormCard.FormHeader {
title: i18nc("@title:group", "Ignored Users")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Manage ignored users")
onClicked: pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
title: i18nc("@title:window", "Ignored Users")
});
}
}
Component {
id: ignoredUsersDialogComponent
IgnoredUsersDialog {
connection: root.connection
}
}
}

View File

@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.settings as KirigamiSettings
import QtQuick.Layouts
import org.kde.neochat
KirigamiSettings.CategorizedSettings {
id: root
property NeoChatConnection connection
objectName: "settingsPage"
actions: [
KirigamiSettings.SettingAction {
actionName: "general"
text: i18n("General")
icon.name: "org.kde.neochat"
page: Qt.resolvedUrl("NeoChatGeneralPage.qml")
},
KirigamiSettings.SettingAction {
actionName: "appearance"
text: i18n("Appearance")
icon.name: "preferences-desktop-theme-global"
page: Qt.resolvedUrl("AppearanceSettingsPage.qml")
},
KirigamiSettings.SettingAction {
actionName: "notifications"
text: i18n("Notifications")
icon.name: "preferences-desktop-notification"
page: Qt.resolvedUrl("GlobalNotificationsPage.qml")
initialProperties: {
return {
connection: root.connection
};
}
},
KirigamiSettings.SettingAction {
actionName: "security"
text: i18n("Security")
icon.name: "preferences-security"
page: Qt.resolvedUrl("NeoChatSecurityPage.qml")
initialProperties: {
return {
connection: root.connection
};
}
},
KirigamiSettings.SettingAction {
actionName: "accounts"
text: i18n("Accounts")
icon.name: "preferences-system-users"
page: Qt.resolvedUrl("AccountsPage.qml")
},
KirigamiSettings.SettingAction {
actionName: "emoticons"
text: i18n("Stickers & Emojis")
icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("EmoticonsPage.qml")
initialProperties: {
return {
connection: root.connection
};
}
},
KirigamiSettings.SettingAction {
actionName: "spellChecking"
text: i18n("Spell Checking")
icon.name: "tools-check-spelling"
page: Qt.resolvedUrl("SonnetConfigPage.qml")
visible: Qt.platform.os !== "android"
},
KirigamiSettings.SettingAction {
actionName: "networkProxy"
text: i18n("Network Proxy")
icon.name: "network-connect"
page: Qt.resolvedUrl("NetworkProxyPage.qml")
},
KirigamiSettings.SettingAction {
actionName: "devices"
text: i18n("Devices")
icon.name: "computer"
page: Qt.resolvedUrl("DevicesPage.qml")
initialProperties: {
return {
connection: root.connection
};
}
},
KirigamiSettings.SettingAction {
actionName: "aboutNeochat"
text: i18n("About NeoChat")
icon.name: "help-about"
page: Qt.resolvedUrl("About.qml")
},
KirigamiSettings.SettingAction {
actionName: "aboutKDE"
text: i18n("About KDE")
icon.name: "kde"
page: Qt.resolvedUrl("AboutKDE.qml")
}
]
}

View File

@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later OR LicenseRef-KDE-Accepted-LGPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat.config
FormCard.FormCardPage {
id: root
title: i18nc("@title:window", "Proxy")
property int currentType
property bool proxyConfigChanged: false
FormCard.FormHeader {
title: i18n("Network Proxy")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18n("System Default")
checked: currentType === 0
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 0;
}
}
FormCard.FormRadioDelegate {
text: i18n("HTTP")
checked: currentType === 1
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 1;
}
}
FormCard.FormRadioDelegate {
text: i18n("Socks5")
checked: currentType === 2
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 2;
}
}
}
FormCard.FormHeader {
title: i18n("Proxy Settings")
}
FormCard.FormCard {
FormCard.FormTextFieldDelegate {
id: hostField
label: i18n("Host")
text: Config.proxyHost
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
FormCard.FormSpinBoxDelegate {
id: portField
label: i18n("Port")
value: Config.proxyPort
from: 0
to: 65536
textFromValue: function (value, locale) {
return value; // it will add a thousands separator if we don't do this, not sure why
}
onValueChanged: {
proxyConfigChanged = true;
}
}
FormCard.FormTextFieldDelegate {
id: userField
label: i18n("User")
text: Config.proxyUser
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
FormCard.FormTextFieldDelegate {
id: passwordField
label: i18n("Password")
text: Config.proxyPassword
echoMode: TextInput.Password
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
}
footer: QQC2.ToolBar {
height: visible ? implicitHeight : 0
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18n("Apply")
enabled: currentType !== Config.proxyType || proxyConfigChanged
onClicked: {
Config.proxyType = currentType;
Config.proxyHost = hostField.text;
Config.proxyPort = portField.value;
Config.proxyUser = userField.text;
Config.proxyPassword = passwordField.text;
Config.save();
proxyConfigChanged = false;
ProxyController.setApplicationProxy();
}
}
}
}
Component.onCompleted: {
currentType = Config.proxyType;
}
}

View File

@@ -0,0 +1,195 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.AbstractFormDelegate {
id: root
required property string id
required property string name
required property int ruleAction
required property bool highlightable
required property bool deletable
readonly property bool notificationsOn: isNotificationRuleOn(ruleAction)
readonly property bool notificationsOnModifiable: !deletable
readonly property bool highlightOn: isNotificationRuleHighlight(ruleAction)
signal actionChanged(int action)
signal deleteRule
enabled: ruleAction !== PushRuleAction.Unknown
text: name
onClicked: {
notificationAction = nextNotificationRuleAction(notificationAction);
}
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
Layout.minimumWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
Layout.minimumHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
text: notificationsOn ? "" : "●"
color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
visible: notificationsOn
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightOn && highlightable ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightOn && highlightable ? 1 : 0.3
radius: height / 2
}
}
QQC2.Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: root.text
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
}
RowLayout {
Layout.alignment: Qt.AlignRight
QQC2.Button {
id: onButton
text: onButton.checked ? i18n("Disable notifications") : i18n("Enable notifications")
Accessible.name: text
icon.name: checked ? "notifications" : "notifications-disabled"
display: QQC2.AbstractButton.IconOnly
visible: root.notificationsOnModifiable
checkable: true
checked: root.notificationsOn
enabled: root.enabled
down: checked
onToggled: {
root.actionChanged(root.notifcationRuleAction());
}
QQC2.ToolTip {
text: onButton.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Button {
id: noisyButton
text: noisyButton.checked ? i18n("Mute notifications") : i18n("Unmute notifications")
Accessible.name: text
icon.name: checked ? "audio-volume-high" : "audio-volume-muted"
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: isNotificationRuleNoisy(root.ruleAction)
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
down: checked
onToggled: {
root.actionChanged(root.notifcationRuleAction());
}
QQC2.ToolTip {
text: noisyButton.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Button {
id: highlightButton
text: highlightButton.checked ? i18nc("As in clicking this button will switch off highlights for messages that match this rule", "Disable message highlights") : i18nc("As in clicking this button will switch on highlights for messages that match this rule", "Enable message highlights")
Accessible.name: text
icon.name: "draw-highlight"
display: QQC2.AbstractButton.IconOnly
visible: root.highlightable
checkable: true
checked: root.highlightOn
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
down: checked
onToggled: {
root.actionChanged(root.notifcationRuleAction());
}
QQC2.ToolTip {
text: highlightButton.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Button {
id: deleteButton
Accessible.name: i18n("Delete keyword")
icon.name: "edit-delete-remove"
visible: root.deletable
onClicked: {
root.deleteRule();
}
}
}
}
function notifcationRuleAction() {
if (onButton.checked) {
if (noisyButton.checked && highlightButton.checked && root.highlightable) {
return PushRuleAction.NoisyHighlight;
} else if (noisyButton.checked) {
return PushRuleAction.Noisy;
} else if (highlightButton.checked && root.highlightable) {
return PushRuleAction.Highlight;
} else {
return PushRuleAction.On;
}
} else {
return PushRuleAction.Off;
}
}
function nextNotificationRuleAction(action) {
let finished = false;
if (action == PushRuleAction.NoisyHighlight) {
action = PushRuleAction.Off;
} else {
action += 1;
}
while (!finished) {
if (action == PushRuleAction.Off && !root.notificationsOnModifiable) {
action = PushRuleAction.On;
} else if (action == PushRuleAction.Noisy) {
action = PushRuleAction.Highlight;
} else if (action == PushRuleAction.Highlight && !root.highlightable) {
action = PushRuleAction.Off;
} else {
finished = true;
}
}
actionChanged(action);
}
function isNotificationRuleOn(action) {
return action == PushRuleAction.On || action == PushRuleAction.Noisy || action == PushRuleAction.Highlight || action == PushRuleAction.NoisyHighlight;
}
function isNotificationRuleNoisy(action) {
return action == PushRuleAction.Noisy || action == PushRuleAction.NoisyHighlight;
}
function isNotificationRuleHighlight(action) {
return action == PushRuleAction.Highlight || action == PushRuleAction.NoisyHighlight;
}
}

View File

@@ -0,0 +1,469 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kitemmodels
import org.kde.neochat
FormCard.FormCardPage {
id: root
property NeoChatRoom room
title: i18nc('@title:window', 'Permissions')
property UserListModel userListModel: UserListModel {
id: userListModel
room: root.room
}
property ListModel powerLevelModel: ListModel {
id: powerLevelModel
}
FormCard.FormHeader {
title: i18n("Privileged Users")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: userListModel
sortRoleName: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRowCallback: function (source_row, source_parent) {
let powerLevelRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), UserListModel.PowerLevelRole);
return powerLevelRole > 0;
}
}
delegate: FormCard.FormTextDelegate {
text: name
textItem.textFormat: Text.PlainText
description: userId
contentItem.children: RowLayout {
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
id: powerLevelLabel
visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= model.powerLevel && model.userId != room.localUser.id)
text: powerLevelString
color: Kirigami.Theme.disabledTextColor
}
QQC2.ComboBox {
focusPolicy: Qt.NoFocus // provided by parent
model: powerLevelModel
textRole: "text"
valueRole: "powerLevel"
visible: !powerLevelLabel.visible
Component.onCompleted: {
/**
* This is very silly but the only way to populate the model with
* translated strings. Done here because the model needs to be filled
* before the first delegate sets it's current index.
*/
if (powerLevelModel.count == 0) {
powerLevelModel.append({
"text": i18n("Member (0)"),
"powerLevel": 0
});
powerLevelModel.append({
"text": i18n("Moderator (50)"),
"powerLevel": 50
});
powerLevelModel.append({
"text": i18n("Admin (100)"),
"powerLevel": 100
});
}
currentIndex = indexOfValue(powerLevel);
}
onActivated: {
room.setUserPowerLevel(userId, currentValue);
}
}
}
}
}
FormCard.FormDelegateSeparator {
below: userListSearchCard
}
FormCard.AbstractFormDelegate {
id: userListSearchCard
visible: room.canSendState("m.room.power_levels")
contentItem: Kirigami.SearchField {
id: userListSearchField
autoAccept: false
Layout.fillWidth: true
Keys.onUpPressed: userListView.decrementCurrentIndex()
Keys.onDownPressed: userListView.incrementCurrentIndex()
onAccepted: {
let currentUser = userListView.itemAtIndex(userListView.currentIndex);
currentUser.action.trigger();
}
}
QQC2.Popup {
id: userListSearchPopup
x: userListSearchField.x
y: userListSearchField.y - height
width: userListSearchField.width
height: {
let maxHeight = userListSearchField.mapToGlobal(userListSearchField.x, userListSearchField.y).y - Kirigami.Units.largeSpacing * 3;
let minHeight = Kirigami.Units.gridUnit * 2 + userListSearchPopup.padding * 2;
let filterContentHeight = userListView.contentHeight + userListSearchPopup.padding * 2;
return Math.max(Math.min(filterContentHeight, maxHeight), minHeight);
}
padding: Kirigami.Units.smallSpacing
leftPadding: Kirigami.Units.smallSpacing / 2
rightPadding: Kirigami.Units.smallSpacing / 2
modal: false
onClosed: userListSearchField.text = ""
background: Kirigami.ShadowedRectangle {
property color borderColor: Kirigami.Theme.textColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
radius: 4
color: Kirigami.Theme.backgroundColor
border {
color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
width: 1
}
shadow {
xOffset: 0
yOffset: 4
color: Qt.rgba(0, 0, 0, 0.3)
size: 8
}
}
contentItem: QQC2.ScrollView {
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView {
id: userListView
clip: true
model: UserFilterModel {
id: userListFilterModel
sourceModel: userListModel
filterText: userListSearchField.text
onFilterTextChanged: {
if (filterText.length > 0 && !userListSearchPopup.visible) {
userListSearchPopup.open();
} else if (filterText.length <= 0 && userListSearchPopup.visible) {
userListSearchPopup.close();
}
}
}
delegate: Delegates.RoundedItemDelegate {
id: userListItem
required property string userId
required property string avatar
required property string name
required property int powerLevel
required property string powerLevelString
text: name
contentItem: RowLayout {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: userListItem.avatar ? ("image://" + userListItem.avatar) : ""
name: userListItem.name
}
Delegates.SubtitleContentItem {
itemDelegate: userListItem
subtitle: userListItem.userId
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
Layout.fillWidth: true
}
QQC2.Label {
visible: userListItem.powerLevel > 0
text: userListItem.powerLevelString
color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
}
action: Kirigami.Action {
id: editPowerLevelAction
onTriggered: {
userListSearchPopup.close();
let dialog = powerLevelDialog.createObject(applicationWindow().overlay, {
room: root.room,
userId: userListItem.userId,
powerLevel: userListItem.powerLevel
});
dialog.open();
}
}
Component {
id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog
}
}
}
}
}
}
}
}
FormCard.FormHeader {
visible: room.canSendState("m.room.power_levels")
title: i18n("Default permissions")
}
FormCard.FormCard {
visible: room.canSendState("m.room.power_levels")
FormCard.FormComboBoxDelegate {
text: i18n("Default user power level")
description: i18n("This is power level for all new users when joining the room")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.defaultUserPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.defaultUserPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Default power level to set the room state")
description: i18n("This is used for all state events that do not have their own entry here")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.statePowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.statePowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Default power level to send messages")
description: i18n("This is used for all message events that do not have their own entry here")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.defaultEventPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.defaultEventPowerLevel = currentValue;
}
}
}
FormCard.FormHeader {
visible: room.canSendState("m.room.power_levels")
title: i18n("Basic permissions")
}
FormCard.FormCard {
visible: room.canSendState("m.room.power_levels")
FormCard.FormComboBoxDelegate {
text: i18n("Invite users")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.invitePowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.invitePowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Kick users")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.kickPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.kickPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Ban users")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.banPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.banPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Remove message sent by other users")
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.redactPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.redactPowerLevel = currentValue;
}
}
}
FormCard.FormHeader {
visible: room.canSendState("m.room.power_levels")
title: i18n("Event permissions")
}
FormCard.FormCard {
visible: room.canSendState("m.room.power_levels")
FormCard.FormComboBoxDelegate {
text: i18n("Change user permissions")
description: "m.room.power_levels"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.powerLevelPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.powerLevelPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Change the room name")
description: "m.room.name"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.namePowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.namePowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Change the room avatar")
description: "m.room.avatar"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.avatarPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.avatarPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Change the room canonical alias")
description: "m.room.canonical_alias"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.canonicalAliasPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.canonicalAliasPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Change the room topic")
description: "m.room.topic"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.topicPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.topicPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Enable encryption for the room")
description: "m.room.encryption"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.encryptionPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.encryptionPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Change the room history visibility")
description: "m.room.history_visibility"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.historyVisibilityPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.historyVisibilityPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Set pinned events")
description: "m.room.pinned_events"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.pinnedEventsPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.pinnedEventsPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Upgrade the room")
description: "m.room.tombstone"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.tombstonePowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.tombstonePowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Set the room server access control list (ACL)")
description: "m.room.server_acl"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.serverAclPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.serverAclPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
visible: room.isSpace
text: i18n("Set the children of this space")
description: "m.space.child"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.spaceChildPowerLevel = currentValue;
}
}
FormCard.FormComboBoxDelegate {
text: i18n("Set the parent space of this room")
description: "m.space.parent"
textRole: "text"
valueRole: "powerLevel"
model: powerLevelModel
Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel)
onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) {
room.spaceParentPowerLevel = currentValue;
}
}
}
}

View File

@@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// 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.formcard as FormCard
import org.kde.kitemmodels
import org.kde.neochat
FormCard.FormCardPage {
id: root
property NeoChatRoom room
property PushRuleModel pushRuleModel: PushRuleModel {
connection: root.room.connection
}
title: i18nc('@title:window', 'Notifications')
FormCard.FormHeader {
title: i18n("Room notifications setting")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18n("Follow global setting")
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState !== PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Default;
}
}
FormCard.FormRadioDelegate {
text: i18nc("As in 'notify for all messages'", "All")
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState !== PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.All;
}
}
FormCard.FormRadioDelegate {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'", "@Mentions and Keywords")
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState !== PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.MentionKeyword;
}
}
FormCard.FormRadioDelegate {
text: i18nc("As in 'do not notify for any messages'", "Off")
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState !== PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Mute;
}
}
}
FormCard.FormHeader {
title: i18n("Keywords")
}
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
filterRowCallback: function (source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole);
let roomIdRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.RoomIdRole);
return sectionRole == PushRuleSection.RoomKeywords && roomIdRole == root.room.id;
}
}
delegate: ruleDelegate
Component {
id: ruleDelegate
NotificationRuleItem {
onDeleteRule: {
root.pushRuleModel.removeKeyword(id);
}
onActionChanged: action => root.pushRuleModel.setPushRuleAction(id, action)
}
}
}
FormCard.AbstractFormDelegate {
Layout.fillWidth: true
contentItem: RowLayout {
Kirigami.ActionTextField {
id: keywordAddField
Layout.fillWidth: true
placeholderText: i18n("Keyword…")
enabled: NotificationsManager.keywordNotificationAction !== PushRuleAction.Unknown
rightActions: Kirigami.Action {
icon.name: "edit-clear"
visible: keywordAddField.text.length > 0
onTriggered: {
keywordAddField.text = "";
}
}
onAccepted: {
root.pushRuleModel.addKeyword(keywordAddField.text, root.room.id);
keywordAddField.text = "";
}
}
QQC2.Button {
id: addButton
text: i18n("Add keyword")
Accessible.name: text
icon.name: "list-add"
display: QQC2.AbstractButton.IconOnly
enabled: NotificationsManager.keywordNotificationAction !== PushRuleAction.Unknown && keywordAddField.text.length > 0
onClicked: {
root.pushRuleModel.addKeyword(keywordAddField.text, root.room.id);
keywordAddField.text = "";
}
QQC2.ToolTip {
text: addButton.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
}

View File

@@ -0,0 +1,425 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
FormCard.FormCardPage {
id: root
property NeoChatRoom room
required property NeoChatConnection connection
title: i18n("General")
FormCard.FormHeader {
title: i18n("Room Information")
}
FormCard.FormCard {
FormCard.AbstractFormDelegate {
background: null
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
KirigamiComponents.Avatar {
id: avatar
Layout.alignment: Qt.AlignRight
name: room.name
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
implicitWidth: Kirigami.Units.iconSizes.enormous
implicitHeight: Kirigami.Units.iconSizes.enormous
}
QQC2.Button {
Layout.alignment: Qt.AlignLeft
enabled: room.canSendState("m.room.avatar")
visible: enabled
icon.name: "cloud-upload"
text: i18n("Update avatar")
display: QQC2.AbstractButton.IconOnly
onClicked: {
const fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay);
fileDialog.chosen.connect(function (path) {
if (!path)
return;
room.changeAvatar(path);
});
fileDialog.open();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
Item {
Layout.fillWidth: true
}
}
}
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18n("Room name:")
text: room.name
readOnly: !room.canSendState("m.room.name")
}
FormCard.AbstractFormDelegate {
id: roomTopicField
background: Item {}
contentItem: ColumnLayout {
QQC2.Label {
id: roomTopicLabel
text: i18n("Room topic:")
Layout.fillWidth: true
}
QQC2.TextArea {
id: roomTopicTextArea
Accessible.description: roomTopicLabel.text
Layout.fillWidth: true
wrapMode: TextEdit.Wrap
text: room.topic
readOnly: !room.canSendState("m.room.topic")
onTextChanged: roomTopicField.text = text
}
}
}
FormCard.AbstractFormDelegate {
visible: !roomNameField.readOnly || !roomTopicTextArea.readOnly
background: Item {}
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
QQC2.Button {
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
enabled: room.name !== roomNameField.text || room.topic !== roomTopicField.text
text: i18n("Save")
onClicked: {
if (room.name != roomNameField.text) {
room.setName(roomNameField.text);
}
if (room.topic != roomTopicField.text) {
room.setTopic(roomTopicField.text);
}
}
}
}
}
FormCard.FormTextDelegate {
id: roomIdDelegate
text: i18n("Room ID")
description: room.id
contentItem.children: QQC2.Button {
visible: roomIdDelegate.hovered
text: i18n("Copy room ID to clipboard")
icon.name: "edit-copy"
display: QQC2.AbstractButton.IconOnly
onClicked: {
Clipboard.saveText(room.id);
}
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
}
FormCard.FormTextDelegate {
text: i18n("Room version")
description: room.version
contentItem.children: QQC2.Button {
visible: room.canSwitchVersions()
enabled: room.version < room.maxRoomVersion
text: i18n("Upgrade Room")
icon.name: "arrow-up-double"
onClicked: {
if (room.canSwitchVersions()) {
roomUpgradeDialog.currentRoomVersion = room.version;
roomUpgradeDialog.open();
}
}
QQC2.ToolTip {
text: text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
FormCard.FormHeader {
title: i18n("Aliases")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
visible: room.aliases.length <= 0
text: i18n("No canonical alias set")
}
Repeater {
id: altAliasRepeater
model: room.aliases.slice().reverse()
delegate: FormCard.FormTextDelegate {
text: modelData
description: room.canonicalAlias.length > 0 && modelData === room.canonicalAlias ? "Canonical alias" : ""
contentItem.children: [
QQC2.ToolButton {
id: setCanonicalAliasButton
visible: modelData !== room.canonicalAlias && room.canSendState("m.room.canonical_alias")
text: i18n("Make this alias the room's canonical alias")
icon.name: "checkmark"
display: QQC2.AbstractButton.IconOnly
onClicked: {
room.setCanonicalAlias(modelData);
}
QQC2.ToolTip {
text: setCanonicalAliasButton.text
delay: Kirigami.Units.toolTipDelay
}
},
QQC2.ToolButton {
id: deleteButton
visible: room.canSendState("m.room.canonical_alias")
text: i18n("Delete alias")
icon.name: "edit-delete-remove"
display: QQC2.AbstractButton.IconOnly
onClicked: {
room.unmapAlias(modelData);
}
QQC2.ToolTip {
text: deleteButton.text
delay: Kirigami.Units.toolTipDelay
}
}
]
}
}
FormCard.AbstractFormDelegate {
visible: room.canSendState("m.room.canonical_alias")
contentItem: RowLayout {
Kirigami.ActionTextField {
id: aliasAddField
Layout.fillWidth: true
placeholderText: i18n("#new_alias:server.org")
rightActions: Kirigami.Action {
icon.name: "edit-clear"
visible: aliasAddField.text.length > 0
onTriggered: {
aliasAddField.text = "";
}
}
onAccepted: {
room.mapAlias(aliasAddField.text);
}
}
QQC2.Button {
id: addButton
text: i18n("Add new alias")
Accessible.name: text
icon.name: "list-add"
display: QQC2.AbstractButton.IconOnly
enabled: aliasAddField.text.length > 0
onClicked: {
room.mapAlias(aliasAddField.text);
}
QQC2.ToolTip {
text: addButton.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
FormCard.FormHeader {
title: i18n("URL Previews")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
text: i18n("Enable URL previews by default for room members")
checked: room.defaultUrlPreviewState
visible: room.canSendState("org.matrix.room.preview_urls")
onToggled: {
room.defaultUrlPreviewState = checked;
}
}
FormCard.FormCheckDelegate {
text: i18n("Enable URL previews")
// Most users won't see the above setting so tell them the default.
description: room.defaultUrlPreviewState ? i18n("URL previews are enabled by default in this room") : i18n("URL previews are disabled by default in this room")
checked: room.urlPreviewEnabled
onToggled: {
room.urlPreviewEnabled = checked;
}
}
}
FormCard.FormHeader {
title: i18n("Official Parent Spaces")
}
FormCard.FormCard {
Repeater {
id: officalParentRepeater
model: root.room.parentIds
delegate: FormCard.FormTextDelegate {
id: officalParentDelegate
required property string modelData
property NeoChatRoom space: root.connection.room(modelData)
text: {
if (space) {
return space.displayName;
} else {
return modelData;
}
}
description: {
if (space) {
if (space.canonicalAlias.length > 0) {
return space.canonicalAlias;
} else {
return modelData;
}
} else {
return "";
}
}
contentItem.children: RowLayout {
QQC2.Label {
visible: root.room.canonicalParent === officalParentDelegate.modelData
text: i18n("Canonical")
}
QQC2.ToolButton {
visible: root.room.canSendState("m.space.parent") && root.room.canonicalParent !== officalParentDelegate.modelData
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: canonicalParentAction
text: i18n("Make canonical parent")
icon.name: "checkmark"
onTriggered: root.room.canonicalParent = officalParentDelegate.modelData
}
QQC2.ToolTip {
text: canonicalParentAction.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
visible: officalParentDelegate?.space.canSendState("m.space.child") && root.room.canSendState("m.space.parent")
display: QQC2.AbstractButton.IconOnly
text: i18n("Remove parent")
icon.name: "edit-delete-remove"
onClicked: root.room.removeParent(officalParentDelegate.modelData)
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
}
FormCard.FormTextDelegate {
visible: officalParentRepeater.count <= 0
text: i18n("This room has no official parent spaces.")
}
FormCard.FormButtonDelegate {
visible: root.room.canSendState("m.space.parent")
text: i18nc("@action:button", "Add new official parent")
onClicked: selectParentDialog.createObject(applicationWindow().overlay).open()
Component {
id: selectParentDialog
SelectParentDialog {
room: root.room
}
}
}
}
Kirigami.InlineMessage {
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
Layout.alignment: Qt.AlignHCenter
text: i18n("This room continues another conversation.")
type: Kirigami.MessageType.Information
visible: room.predecessorId && room.connection.room(room.predecessorId)
actions: Kirigami.Action {
text: i18n("See older messages…")
onTriggered: {
RoomManager.resolveResource(room.predecessorId);
root.close();
}
}
}
Kirigami.InlineMessage {
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
Layout.alignment: Qt.AlignHCenter
text: i18n("This room has been replaced.")
type: Kirigami.MessageType.Information
visible: room.successorId && room.connection.room(room.successorId)
actions: Kirigami.Action {
text: i18n("See new room…")
onTriggered: {
RoomManager.resolveResource(room.successorId);
root.close();
}
}
}
property Component openFileDialog: Component {
id: openFileDialog
OpenFileDialog {
parentWindow: root.Window.window
}
}
property Kirigami.Dialog roomUpgradeDialog: Kirigami.Dialog {
id: roomUpgradeDialog
property var currentRoomVersion
width: Kirigami.Units.gridUnit * 16
title: i18n("Upgrade the Room")
ColumnLayout {
FormCard.FormSpinBoxDelegate {
id: spinBox
label: i18n("Select new version")
from: room.version
to: room.maxRoomVersion
value: room.version
}
}
customFooterActions: [
Kirigami.Action {
text: i18n("Confirm")
icon.name: "dialog-ok"
onTriggered: {
room.switchVersion(spinBox.value);
roomUpgradeDialog.close();
}
}
]
}
}

View File

@@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
property NeoChatRoom room
property string needUpgradeRoom: i18n("You need to upgrade this room to a newer version to enable this setting.")
title: i18n("Security")
FormCard.FormHeader {
title: i18nc("@option:check", "Encryption")
}
FormCard.FormCard {
FormCard.FormSwitchDelegate {
id: enableEncryptionSwitch
text: i18n("Enable encryption")
description: i18nc("option:check", "Once enabled, encryption cannot be disabled.")
enabled: room.canEncryptRoom
checked: room.usesEncryption
onToggled: if (checked) {
let dialog = confirmEncryptionDialog.createObject(applicationWindow().overlay, {
room: room
});
dialog.open();
}
}
}
FormCard.FormHeader {
title: i18nc("@option:check", "Access")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Private (invite only)")
description: i18n("Only invited people can join.")
checked: room.joinRule === "invite"
enabled: room.canSendState("m.room.join_rules")
onCheckedChanged: if (checked && room.joinRule != "invite") {
root.room.joinRule = "invite";
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Space members")
description: i18n("Anyone in the selected spaces can find and join.") + (!["8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
checked: room.joinRule === "restricted"
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version)
onCheckedChanged: if (checked && room.joinRule != "restricted") {
selectSpacesDialog.createObject(applicationWindow().overlay).open();
}
contentItem.children: QQC2.Button {
visible: root.room.joinRule === "restricted"
text: i18n("Select spaces")
icon.name: "list-add"
onClicked: selectSpacesDialog.createObject(applicationWindow().overlay).open()
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
Component {
id: selectSpacesDialog
SelectSpacesDialog {
room: root.room
}
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Knock")
description: i18n("People not in the room need to request an invite to join the room.") + (!["7", "8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
checked: room.joinRule === "knock"
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
onCheckedChanged: if (checked && room.joinRule != "knock") {
root.room.joinRule = "knock";
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Public")
description: i18nc("@option:check", "Anyone can find and join.")
checked: room.joinRule === "public"
enabled: room.canSendState("m.room.join_rules")
onCheckedChanged: if (checked && root.room.joinRule != "public") {
root.room.joinRule = "public";
}
}
}
FormCard.FormHeader {
title: i18nc("@option:check", "Message history visibility")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Anyone")
description: i18nc("@option:check", "Anyone, regardless of whether they have joined, can view history.")
checked: room.historyVisibility === "world_readable"
enabled: room.canSendState("m.room.history_visibility")
onCheckedChanged: if (checked) {
room.historyVisibility = "world_readable";
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Members only")
description: i18nc("@option:check", "All members can view the entire message history, even before they joined.")
checked: room.historyVisibility === "shared"
enabled: room.canSendState("m.room.history_visibility")
onCheckedChanged: if (checked) {
room.historyVisibility = "shared";
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Members only (since invite)")
description: i18nc("@option:check", "New members can view the message history from the point they were invited to the room.")
checked: room.historyVisibility === "invited"
enabled: room.canSendState("m.room.history_visibility")
onCheckedChanged: if (checked) {
room.historyVisibility = "invited";
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:check", "Members only (since joining)")
description: i18nc("@option:check", "New members can view the message history from the point they joined the room.")
checked: room.historyVisibility === "joined"
enabled: room.canSendState("m.room.history_visibility")
onCheckedChanged: if (checked) {
room.historyVisibility = "joined";
}
}
}
property Component confirmEncryptionDialog: Component {
id: confirmEncryptionDialog
ConfirmEncryptionDialog {
onClosed: {
// At the point this is executed, the state in the room is not yet changed.
// The value will be updated when room.onEncryption() emitted.
// This is in case if user simply closed the dialog.
enableEncryptionSwitch.checked = false;
}
}
}
property Connections connections: Connections {
target: room
onEncryption: {
enableEncryptionSwitch.checked = room.usesEncryption;
}
}
}

View File

@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.settings as KirigamiSettings
import QtQuick.Layouts
import org.kde.neochat
KirigamiSettings.CategorizedSettings {
id: root
property NeoChatRoom room
required property NeoChatConnection connection
objectName: "settingsPage"
actions: [
KirigamiSettings.SettingAction {
actionName: "general"
text: i18n("General")
icon.name: "settings-configure"
page: Qt.resolvedUrl("RoomGeneralPage.qml")
initialProperties: {
return {
room: root.room,
connection: root.connection
};
}
},
KirigamiSettings.SettingAction {
actionName: "security"
text: i18n("Security")
icon.name: "security-low"
page: Qt.resolvedUrl("RoomSecurityPage.qml")
initialProperties: {
return {
room: root.room
};
}
},
KirigamiSettings.SettingAction {
actionName: "permissions"
text: i18n("Permissions")
icon.name: "visibility"
page: Qt.resolvedUrl("Permissions.qml")
initialProperties: {
return {
room: root.room
};
}
},
KirigamiSettings.SettingAction {
actionName: "notifications"
text: i18n("Notifications")
icon.name: "notifications"
page: Qt.resolvedUrl("PushNotification.qml")
initialProperties: {
return {
room: root.room
};
}
}
]
}

View File

@@ -0,0 +1,253 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
import QtQml
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.sonnet as Sonnet
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.delegates as Delegates
Kirigami.ScrollablePage {
id: root
FormCard.FormHeader {
title: i18nc("@title", "Spellchecking")
}
FormCard.FormCard {
id: card
property Sonnet.Settings settings: Sonnet.Settings {
id: settings
}
FormCard.FormCheckDelegate {
id: enable
checked: settings.checkerEnabledByDefault
text: i18n("Enable automatic spell checking")
onCheckedChanged: {
settings.checkerEnabledByDefault = checked;
settings.save();
}
}
FormCard.FormDelegateSeparator {
below: enable
above: skipUppercase
}
FormCard.FormCheckDelegate {
id: skipUppercase
checked: settings.skipUppercase
text: i18n("Ignore uppercase words")
onCheckedChanged: {
settings.skipUppercase = checked;
settings.save();
}
}
FormCard.FormDelegateSeparator {
below: skipUppercase
above: skipRunTogether
}
FormCard.FormCheckDelegate {
id: skipRunTogether
checked: settings.skipRunTogether
text: i18n("Ignore hyphenated words")
onCheckedChanged: {
settings.skipRunTogether = checked;
settings.save();
}
}
FormCard.FormDelegateSeparator {
below: skipRunTogether
above: autodetectLanguageCheckbox
}
FormCard.FormCheckDelegate {
id: autodetectLanguageCheckbox
checked: settings.autodetectLanguage
text: i18n("Detect language automatically")
onCheckedChanged: {
settings.autodetectLanguage = checked;
settings.save();
}
}
FormCard.FormDelegateSeparator {
below: autodetectLanguageCheckbox
above: selectedDefaultLanguage
}
FormCard.FormComboBoxDelegate {
id: selectedDefaultLanguage
text: i18n("Selected default language:")
model: isEmpty ? [
{
"display": i18n("None")
}
] : settings.dictionaryModel
textRole: "display"
displayMode: Kirigami.Settings.isMobile ? FormCard.FormComboBoxDelegate.Dialog : FormCard.FormComboBoxDelegate.Page
valueRole: "languageCode"
property bool isEmpty: false
Component.onCompleted: {
if (settings.dictionaryModel.rowCount() === 0) {
isEmpty = true;
} else {
currentIndex = indexOfValue(settings.defaultLanguage);
}
}
onActivated: settings.defaultLanguage = currentValue
}
FormCard.FormDelegateSeparator {
below: selectedDefaultLanguage
above: spellCheckingLanguage
}
FormCard.FormButtonDelegate {
id: spellCheckingLanguage
text: i18n("Additional spell checking languages")
description: i18n("%1 will provide spell checking and suggestions for the languages listed here when autodetection is enabled.", Qt.application.displayName)
enabled: autodetectLanguageCheckbox.checked
onClicked: pageStack.pushDialogLayer(spellCheckingLanguageList, {}, {
width: pageStack.width - Kirigami.Units.gridUnit * 5,
height: pageStack.height - Kirigami.Units.gridUnit * 5
})
}
FormCard.FormDelegateSeparator {
below: spellCheckingLanguageList
above: personalDictionary
}
FormCard.FormButtonDelegate {
id: personalDictionary
text: i18n("Open Personal Dictionary")
onClicked: pageStack.pushDialogLayer(dictionaryPage, {}, {
width: pageStack.width - Kirigami.Units.gridUnit * 5,
height: pageStack.height - Kirigami.Units.gridUnit * 5
})
}
property Component spellCheckingLanguageList: Component {
id: spellCheckingLanguageList
Kirigami.ScrollablePage {
id: scroll
title: i18nc("@title:window", "Spell checking languages")
ListView {
clip: true
model: settings.dictionaryModel
delegate: QQC2.CheckDelegate {
onClicked: model.checked = checked
Accessible.description: model.isDefault ? i18n("Default Language") : ''
checked: model.checked
width: scroll.width
contentItem: RowLayout {
QQC2.Label {
id: label
Layout.fillWidth: true
text: model.display
}
Kirigami.Icon {
source: "favorite"
Layout.rightMargin: Kirigami.Units.largeSpacing * 2
visible: model.isDefault
HoverHandler {
id: hover
}
QQC2.ToolTip {
visible: hover.hovered
text: i18n("Default Language")
}
}
}
}
}
}
}
}
Component {
id: dictionaryPage
Kirigami.ScrollablePage {
title: i18n("Spell checking dictionary")
footer: QQC2.ToolBar {
contentItem: RowLayout {
QQC2.TextField {
id: dictionaryField
Layout.fillWidth: true
Accessible.name: placeholderText
placeholderText: i18n("Add a new word to your personal dictionary…")
}
QQC2.Button {
text: i18nc("@action:button", "Add word")
icon.name: "list-add"
enabled: dictionaryField.text.length > 0
onClicked: {
add(dictionaryField.text);
dictionaryField.clear();
if (instantApply) {
settings.save();
}
}
Layout.rightMargin: Kirigami.Units.largeSpacing
}
}
}
ListView {
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
bottomMargin: Math.round(Kirigami.Units.smallSpacing / 2)
model: settings.currentIgnoreList
delegate: Delegates.RoundedItemDelegate {
id: wordDelegate
required property var modelData
text: modelData
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Delegates.DefaultContentItem {
itemDelegate: wordDelegate
Layout.fillWidth: true
}
QQC2.ToolButton {
text: i18nc("@action:button", "Delete word")
icon.name: "delete"
display: QQC2.ToolButton.IconOnly
onClicked: {
remove(wordDelegate.modelData);
if (instantApply) {
settings.save();
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.ToolTip.toolTipDelay
}
}
}
}
}
}
function add(word: string) {
const dictionary = settings.currentIgnoreList;
dictionary.push(word);
settings.currentIgnoreList = dictionary;
}
function remove(word: string) {
settings.currentIgnoreList = settings.currentIgnoreList.filter((value, _, _) => {
return value !== word;
});
}
}

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Marco Martin <mart@kde.org>
// Copyright 2018 Furkan Tokac <furkantokac34@gmail.com>
// Copyright 2019 Nate Graham <nate@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
QQC2.RadioButton {
id: root
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
property alias innerObject: contentLayout.children
property bool thin
contentItem: ColumnLayout {
Kirigami.ShadowedRectangle {
implicitWidth: implicitHeight * 1.6
implicitHeight: root.thin ? Kirigami.Units.gridUnit * 5 : Kirigami.Units.gridUnit * 6
radius: Kirigami.Units.smallSpacing
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
shadow.xOffset: 0
shadow.yOffset: 2
shadow.size: 10
shadow.color: Qt.rgba(0, 0, 0, 0.3)
color: {
if (root.checked) {
return Kirigami.Theme.highlightColor;
} else if (root.hovered) {
// Match appearance of hovered list items
return Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.5);
} else {
return Kirigami.Theme.backgroundColor;
}
}
ColumnLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
clip: true
}
}
QQC2.Label {
id: label
Layout.fillWidth: true
text: root.text
horizontalAlignment: Text.AlignHCenter
}
}
indicator: Item {}
background: Item {}
}