Compare commits

...

17 Commits

Author SHA1 Message Date
Joshua Goins
fb63ac5642 Fix a crash when sitting around with no room open
This doesn't check if m_room is null before accessing it's connection.
You can easily reproduce this by sitting around leaving NeoChat open
without entering a room.
2024-09-06 04:55:12 -04:00
Joshua Goins
bff69e21ad Add icons for the menus in the message context menu
In Qt6 we can now assign icons to menus, so let's add what's missing.
2024-09-02 17:28:03 +00:00
Joshua Goins
e117cc0cfb Move the "Show User" action to DelegateContextMenu, add it for media
This was missing from the media context menu, and should be added. Since
it's shared between messages and files, it's now a common action.
2024-09-02 17:28:03 +00:00
Joshua Goins
3af1a88e05 Don't show the "Web Search" action in the media context menu
All this does is try to web search for the filename of the image, which
is useless. Let's hide it entirely in this case.
2024-09-02 17:28:03 +00:00
Joshua Goins
aa116a35f5 Web Shortcuts: kcmshell5 does not exist anymore on Plasma 6
This is hardcoded, but it's probably a safe assumption to think most
people running modern NeoChat are using Plasma 6 anyway.
2024-09-02 17:15:37 +00:00
Joshua Goins
ac232d7f55 Change the "configure room" button icon to something more fitting
This button doesn't actually configure anything, you can do plenty of
actions like "mark as read" and such. Since the button isn't solely for
configuration, we should use an overflow menu icon instead.
2024-09-02 17:02:36 +00:00
Joshua Goins
928911e33c Settings: Overhaul the Security page
This improves the organization of this page, which is starting to become
a bit of a mess. The "Hide images and video events" option is moved
here, and the page is rebranded accordingly for "Security & Safety".
Unnecessary headings are removed, and the ignored users button is moved
to the top of the page.

Explanations for the import/keys buttons are added. The key display
is removed as it's not useful for the user (because they don't know what
to do with it) nor developers (because you can't copy it.) We can add
it back somewhere else.

This has the added benefit of making the whole page fit in the default
settings window size too.
2024-09-02 16:13:12 +00:00
Joshua Goins
e3c30f5bb3 Settings: Hide "minimize to system tray" when system tray is disabled
We do this for the state events setting, so let's do it here too.
2024-09-02 16:01:51 +00:00
Tobias Fella
4b51855528 Add name for Avatar in RecommendedSpaceDialog 2024-09-02 17:26:14 +02:00
Tobias Fella
79c27db0a9 Improve InviteUserPage 2024-09-02 16:33:45 +02:00
Tobias Fella
78e42ab352 Make it possible to invite users that were previously in the room by command 2024-09-02 15:54:40 +02:00
Tobias Fella
93909c45ee Refactor dialogs for reporting, banning, and removing messages 2024-09-02 15:18:32 +02:00
Joshua Goins
eda0bf4b23 LocationChooser: Add a "Locate" button to locate yourself
The map centers on London by default, but for the other people living
outside it may find it hard to figure out where they are. This adds a
button that calls into QtPositioning to center the map over where you
are.
2024-09-02 13:09:21 +00:00
Joshua Goins
4f4b10e0b6 LocationChooser: Add a "Re-center" action to find the pinned location
It's somewhat easy to scroll or pan away from the pin you placed, so
let's add an action to re-center the map if you want to find it again.
2024-09-02 13:09:21 +00:00
Joshua Goins
2b374a8bec Add an icon when you only have invites but no direct messages
There's an edge case with the friends icon, where it will display a
blank circle if you only have pending invites but no actual direct
messages. Now an icon is added to make it clear there is pending invites
and it's not a visual bug.
2024-09-02 12:40:54 +00:00
Tobias Fella
7821de4a8d Don't end kick messages without a reason with a ":"
BUG: 492512
2024-09-02 14:39:17 +02:00
Tobias Fella
86b88c851f Close AccountSwitchDialog when logging in a new connection
Otherwise, the dialog is still opened after login has finished
2024-09-02 14:38:33 +02:00
22 changed files with 269 additions and 344 deletions

View File

@@ -243,10 +243,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/MessageDelegateContextMenu.qml
qml/FileDelegateContextMenu.qml
qml/MessageSourceSheet.qml
qml/ReportSheet.qml
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/RoomSearchPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
@@ -291,6 +288,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/AvatarNotification.qml
qml/ReasonDialog.qml
DEPENDENCIES
QtCore
QtQuick

View File

@@ -366,9 +366,13 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
}
return (e.senderId() != e.userId())
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
: i18n("left the room");
if (e.senderId() == e.userId()) {
return i18n("left the room");
}
if (const auto &reason = e.contentJson()["reason"_ls].toString().toHtmlEscaped(); !reason.isEmpty()) {
return i18n("has put %1 out of the room: %2", subjectName, reason);
}
return i18n("has put %1 out of the room", subjectName);
case Membership::Ban:
if (e.senderId() != e.userId()) {
if (e.reason().isEmpty()) {

View File

@@ -211,7 +211,7 @@ QList<ActionsModel::Action> actions{
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
return QString();
}
if (room->members().contains(room->member(text))) {
if (room->joinedMemberIds().contains(text)) {
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
return QString();
}

View File

@@ -578,22 +578,21 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
}
if (linkPreviewer->loaded()) {
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
} else {
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
for (auto &component : m_components) {
if (component.attributes["link"_ls].toUrl() == link) {
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
beginResetModel();
component.type = MessageComponentType::LinkPreview;
endResetModel();
}
}
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
for (auto &component : m_components) {
if (component.attributes["link"_ls].toUrl() == link) {
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
beginResetModel();
component.type = MessageComponentType::LinkPreview;
endResetModel();
}
}
});
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
}
}
});
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
}
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)

View File

@@ -84,21 +84,19 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
return;
}
if (m_currentRoom) {
// HACK: Reset the model to a null room first to make sure QML dismantles
// last room's objects before the room is actually changed
beginResetModel();
m_currentRoom->disconnect(this);
m_currentRoom = nullptr;
endResetModel();
// HACK: Reset the model to a null room first to make sure QML dismantles
// last room's objects before the room is actually changed
beginResetModel();
m_currentRoom->disconnect(this);
m_currentRoom = nullptr;
endResetModel();
// Don't clear the member objects until the model has been fully reset and all
// refs cleared.
m_memberObjects.clear();
m_contentModels.clear();
m_reactionModels.clear();
m_readMarkerModels.clear();
}
// Don't clear the member objects until the model has been fully reset and all
// refs cleared.
m_memberObjects.clear();
m_contentModels.clear();
m_reactionModels.clear();
m_readMarkerModels.clear();
beginResetModel();
m_currentRoom = room;

View File

@@ -125,7 +125,7 @@ void WebShortcutModel::trigger(const QString &data)
void WebShortcutModel::configureWebShortcuts()
{
#ifdef HAVE_KIO
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"), this);
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), QStringList() << QStringLiteral("webshortcuts"), this);
job->exec();
#endif
}

View File

@@ -66,7 +66,7 @@ Kirigami.Dialog {
if (switchUserButton.checked) {
switchUserButton.checked = false;
}
accountView.currentIndex = Controller.activeConnectionIndex;
root.close();
}
Keys.onUpPressed: {
accountView.currentIndex = accountView.count - 1;

View File

@@ -70,6 +70,11 @@ Loader {
*/
property list<Kirigami.Action> actions
/**
* @brief Whether the web search menu should be shown or not.
*/
property bool enableWebSearch: true
/**
* Some common actions shared between menus
*/
@@ -82,16 +87,23 @@ Loader {
component RemoveMessageAction: Kirigami.Action {
visible: author.isLocalMember || currentRoom.canSendState("redact")
text: i18n("Remove")
text: i18nc("@action:button", "Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
room: currentRoom,
eventId: eventId
}, {
title: i18nc("@title", "Remove Message"),
width: Kirigami.Units.gridUnit * 25
})
onTriggered: {
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Message"),
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title:dialog", "Remove Message"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
currentRoom.redactEvent(root.eventId, reason);
});
}
}
component ReplyMessageAction: Kirigami.Action {
@@ -108,13 +120,28 @@ Loader {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: !author.isLocalMember
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), {
room: currentRoom,
eventId: eventId
}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
onTriggered: {
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report Message"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this message"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
currentRoom.reportEvent(root.eventId, reason);
});
}
}
component ShowUserAction: Kirigami.Action {
text: i18nc("@action:inmenu", "Show User")
icon.name: "username-copy"
onTriggered: {
RoomManager.resolveResource(author.id)
}
}
Component {
@@ -128,6 +155,7 @@ Loader {
id: menuItem
visible: modelData.visible
title: modelData.text
icon: modelData.icon
Instantiator {
model: modelData.children
@@ -158,7 +186,8 @@ Loader {
QQC2.Menu {
id: webshortcutmenu
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
property bool isVisible: webshortcutmodel.enabled
icon.name: "search-symbolic"
property bool isVisible: webshortcutmodel.enabled && root.enableWebSearch
Component.onCompleted: {
webshortcutmenu.parent.visible = isVisible;
}

View File

@@ -32,6 +32,9 @@ DelegateContextMenu {
*/
required property var progressInfo
// Web search isn't useful for images
enableWebSearch: false
/**
* @brief The main list of menu item actions.
*
@@ -67,15 +70,23 @@ DelegateContextMenu {
text: i18n("Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
room: currentRoom,
eventId: eventId
}, {
title: i18nc("@title", "Remove Message"),
width: Kirigami.Units.gridUnit * 25
})
onTriggered: {
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Message"),
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title:dialog", "Remove Message"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
currentRoom.redactEvent(root.eventId, reason);
});
}
},
DelegateContextMenu.ReportMessageAction {},
DelegateContextMenu.ShowUserAction {},
DelegateContextMenu.ViewSourceAction {}
]

View File

@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
@@ -11,111 +13,77 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
Kirigami.ScrollablePage {
SearchPage {
id: root
property NeoChatRoom room
title: i18n("Invite a User")
title: i18nc("@title:dialog", "Invite a User")
actions: [
Kirigami.Action {
icon.name: "dialog-close"
text: i18nc("@action", "Cancel")
onTriggered: root.closeDialog()
}
]
header: RowLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
searchFieldPlaceholder: i18nc("@info:placeholder", "Find a user…")
noResultPlaceholderMessage: i18nc("@info:placeholder", "No users found")
Kirigami.SearchField {
id: identifierField
property bool isUserId: text.match(/@(.+):(.+)/g)
Layout.fillWidth: true
headerTrailing: QQC2.Button {
icon.name: "list-add"
display: QQC2.Button.IconOnly
enabled: root.model.searchText.match(/@(.+):(.+)/g) && !root.room.containsUser(root.model.searchText)
placeholderText: i18n("Find a user...")
onAccepted: userDictListModel.search()
}
text: i18nc("@action:button", "Invite this User")
QQC2.Button {
visible: identifierField.isUserId
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: root.room.containsUser(root.model.searchText) ? i18nc("@info:tooltip", "User is either already a member or has been invited") : text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
text: i18n("Add")
highlighted: true
onClicked: {
room.inviteToRoom(identifierField.text);
}
}
onClicked: root.room.inviteToRoom(root.model.searchText);
}
ListView {
id: userDictListView
model: UserDirectoryListModel {
id: userDictListModel
clip: true
connection: root.room.connection
}
model: UserDirectoryListModel {
id: userDictListModel
modelDelegate: Delegates.RoundedItemDelegate {
id: delegate
connection: root.room.connection
searchText: identifierField.text
}
required property string userId
required property string displayName
required property url avatarUrl
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
text: displayName
visible: userDictListView.count < 1
contentItem: RowLayout {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatarUrl
name: delegate.displayName
}
text: i18n("No users available")
}
Delegates.SubtitleContentItem {
itemDelegate: delegate
subtitle: delegate.userId
labelItem.textFormat: Text.PlainText
}
delegate: Delegates.RoundedItemDelegate {
id: delegate
QQC2.ToolButton {
id: inviteButton
required property string userId
required property string displayName
required property url avatarUrl
readonly property bool inRoom: root.room && root.room.containsUser(delegate.userId)
property bool inRoom: room && room.containsUser(userId)
icon.name: "document-send"
text: i18nc("@action:button", "Send invitation")
opacity: inRoom ? 0.5 : 1
enabled: !inRoom
text: displayName
contentItem: RowLayout {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatarUrl
name: delegate.displayName
onClicked: {
inviteButton.enabled = false;
root.room.inviteToRoom(delegate.userId);
}
Delegates.SubtitleContentItem {
itemDelegate: delegate
subtitle: delegate.userId
labelItem.textFormat: Text.PlainText
}
QQC2.ToolButton {
id: inviteButton
icon.name: "document-send"
text: i18n("Send invitation")
checkable: true
checked: inRoom
opacity: inRoom ? 0.5 : 1
onToggled: {
if (inRoom) {
checked = true;
} else {
room.inviteToRoom(delegate.userId);
applicationWindow().pageStack.layers.pop();
}
}
QQC2.ToolTip.text: !inRoom ? text : i18n("User is either already a member or has been invited")
QQC2.ToolTip.visible: inviteButton.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolTip.text: !inRoom ? text : i18nc("@info:tooltip", "User is either already a member or has been invited")
QQC2.ToolTip.visible: inviteButton.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}

View File

@@ -30,9 +30,36 @@ Components.AbstractMaximizeComponent {
root.close();
}
enabled: !!root.location
},
Kirigami.Action {
text: i18nc("@action:intoolbar Re-center the map onto the set location", "Re-Center")
icon.name: "snap-bounding-box-center-symbolic"
onTriggered: mapView.map.fitViewportToMapItems([mapView.locationMapItem])
enabled: root.location !== undefined
},
Kirigami.Action {
text: i18nc("@action:intoolbar Determine the device's location", "Locate")
icon.name: "mark-location-symbolic"
enabled: positionSource.valid
onTriggered: positionSource.update()
}
]
PositionSource {
id: positionSource
active: false
onPositionChanged: {
const coord = position.coordinate;
mapView.gpsMapItem.latitude = coord.latitude;
mapView.gpsMapItem.longitude = coord.longitude;
mapView.map.addMapItem(mapView.gpsMapItem);
mapView.map.fitViewportToMapItems([mapView.gpsMapItem])
}
}
content: MapView {
id: mapView
map.plugin: OsmLocationPlugin.plugin
@@ -54,6 +81,15 @@ Components.AbstractMaximizeComponent {
author: null
}
readonly property LocationMapItem gpsMapItem: LocationMapItem {
latitude: 0.0
longitude: 0.0
isLive: true
heading: NaN
asset: ""
author: null
}
Connections {
target: mapView.map
function onCopyrightLinkActivated(link: string) {

View File

@@ -66,13 +66,7 @@ DelegateContextMenu {
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
},
DelegateContextMenu.ReportMessageAction {},
Kirigami.Action {
text: i18nc("@action:inmenu", "Show User")
icon.name: "username-copy"
onTriggered: {
RoomManager.resolveResource(author.id)
}
},
DelegateContextMenu.ShowUserAction {},
DelegateContextMenu.ViewSourceAction {},
Kirigami.Action {
text: i18n("Copy Link")

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
@@ -12,10 +12,11 @@ import org.kde.neochat
Kirigami.Page {
id: root
property NeoChatRoom room
property string userId
required property string placeholder
required property string actionText
required property string icon
title: i18n("Ban User")
signal accepted(reason: string)
leftPadding: 0
rightPadding: 0
@@ -24,7 +25,7 @@ Kirigami.Page {
QQC2.TextArea {
id: reason
placeholderText: i18n("Reason for banning this user")
placeholderText: root.placeholder
anchors.fill: parent
wrapMode: TextEdit.Wrap
@@ -40,11 +41,11 @@ Kirigami.Page {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban")
icon.name: "im-ban-user"
text: root.actionText
icon.name: root.icon
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
root.room.ban(root.userId, reason.text);
root.accepted(reason.text);
root.closeDialog();
}
}

View File

@@ -33,6 +33,7 @@ Kirigami.Dialog {
spacing: Kirigami.Units.largeSpacing * 4
Avatar {
source: root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar)
name: SpaceHierarchyCache.recommendedSpaceDisplayName
}
ColumnLayout {
Layout.fillWidth: true

View File

@@ -1,63 +0,0 @@
// SPDX-FileCopyrightText: 2022 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.neochat
Kirigami.Page {
id: root
property NeoChatRoom room
property string eventId
property string userId: ""
title: userId.length > 0 ? i18n("Remove Messages") : i18n("Remove Message")
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
QQC2.TextArea {
id: reason
placeholderText: userId.length > 0 ? i18n("Reason for removing this user's recent messages") : i18n("Reason for removing this message")
anchors.fill: parent
wrapMode: TextEdit.Wrap
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
footer: QQC2.ToolBar {
QQC2.DialogButtonBox {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove")
icon.name: "delete"
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
if (root.userId.length > 0) {
root.room.deleteMessagesByUser(root.userId, reason.text);
} else {
root.room.redactEvent(root.eventId, reason.text);
}
root.closeDialog();
}
}
QQC2.Button {
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.closeDialog()
}
}
}
}

View File

@@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: 2022 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.neochat
Kirigami.Page {
id: root
property NeoChatRoom room
property string eventId
title: i18n("Report Message")
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
QQC2.TextArea {
id: reason
placeholderText: i18n("Reason for reporting this message")
anchors.fill: parent
wrapMode: TextEdit.Wrap
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
footer: QQC2.ToolBar {
QQC2.DialogButtonBox {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
root.room.reportEvent(eventId, reason.text);
root.closeDialog();
}
}
QQC2.Button {
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.closeDialog()
}
}
}
}

View File

@@ -145,7 +145,7 @@ Delegates.RoundedItemDelegate {
text: i18n("Configure room")
display: QQC2.Button.IconOnly
icon.name: "configure"
icon.name: "overflow-menu-symbolic"
onClicked: createRoomListContextMenu()
}
}

View File

@@ -154,7 +154,7 @@ QQC2.Control {
visible: true
Kirigami.Theme.colorSet: Kirigami.Theme.Button
Kirigami.Theme.inherit: false
color: root.connection.directChatsHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
color: root.connection.directChatsHaveHighlightNotifications || root.connection.directChatInvites ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2
}
@@ -162,6 +162,13 @@ QQC2.Control {
id: directChatNotificationCountTextMetrics
text: directChatNotificationCountLabel.text
}
Kirigami.Icon {
anchors.fill: parent
source: "list-add-symbolic"
visible: root.connection.directChatInvites && root.connection.directChatNotifications === 0
}
}
}

View File

@@ -150,13 +150,18 @@ Kirigami.Dialog {
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
(root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'BanSheet'), {
room: root.room,
userId: root.user.id
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title", "Ban User"),
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
}
@@ -204,17 +209,22 @@ Kirigami.Dialog {
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
action: Kirigami.Action {
text: i18n("Remove recent messages by this user")
text: i18nc("@action:button", "Remove recent messages by this user")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
room: root.room,
userId: root.user.id
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
}
}

View File

@@ -45,7 +45,7 @@ FormCard.FormCardPage {
id: minimizeDelegate
text: i18n("Minimize to system tray on startup")
checked: NeoChatConfig.minimizeToSystemTrayOnStartup
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile && NeoChatConfig.systemTray
enabled: NeoChatConfig.systemTray && !NeoChatConfig.isMinimizeToSystemTrayOnStartupImmutable
onToggled: {
NeoChatConfig.minimizeToSystemTrayOnStartup = checked;
@@ -56,6 +56,7 @@ FormCard.FormCardPage {
FormCard.FormDelegateSeparator {
above: minimizeDelegate
below: automaticallyDelegate
visible: minimizeDelegate.visible
}
FormCard.FormCheckDelegate {
@@ -193,23 +194,6 @@ FormCard.FormCardPage {
NeoChatConfig.save();
}
}
FormCard.FormDelegateSeparator {
above: showAvatarChangeDelegate
below: hideImagesDelegate
}
FormCard.FormCheckDelegate {
id: hideImagesDelegate
text: i18nc("@label:checkbox", "Hide image and video events by default")
description: i18nc("@info", "When this option is enabled, images and videos are only shown after a button is clicked.")
checked: NeoChatConfig.hideImages
enabled: !NeoChatConfig.isHideImagesImmutable
onToggled: {
NeoChatConfig.hideImages = checked;
NeoChatConfig.save();
}
}
}
FormCard.FormHeader {
title: i18nc("Chat Editor", "Editor")

View File

@@ -3,6 +3,7 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
@@ -15,7 +16,7 @@ FormCard.FormCardPage {
required property NeoChatConnection connection
title: i18nc("@title", "Security")
title: i18nc("@title", "Security & Safety")
header: KirigamiComponents.Banner {
id: banner
@@ -24,13 +25,39 @@ FormCard.FormCardPage {
type: Kirigami.MessageType.Error
}
FormCard.FormHeader {
title: i18nc("@title:group", "Invitations")
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormButtonDelegate {
id: ignoredUsersDelegate
text: i18nc("@action:button", "Ignored Users")
icon.name: "im-invisible-user"
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
title: i18nc("@title:window", "Ignored Users")
});
}
FormCard.FormDelegateSeparator {
above: ignoredUsersDelegate
below: hideImagesDelegate
}
FormCard.FormCheckDelegate {
id: hideImagesDelegate
text: i18nc("@label:checkbox", "Hide images and videos by default")
description: i18nc("@info", "When this option is enabled, images and videos are only shown after a button is clicked.")
checked: NeoChatConfig.hideImages
enabled: !NeoChatConfig.isHideImagesImmutable
onToggled: {
NeoChatConfig.hideImages = checked;
NeoChatConfig.save();
}
}
FormCard.FormDelegateSeparator {
above: hideImagesDelegate
below: rejectInvitationsDelegate
}
FormCard.FormCheckDelegate {
id: rejectInvitationsDelegate
text: i18nc("@option:check", "Reject invitations from unknown users")
description: connection.canCheckMutualRooms ? i18n("If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18n("Your server does not support this setting.")
description: connection.canCheckMutualRooms ? i18nc("@info", "If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18nc("@info", "Your server does not support this setting.")
checked: NeoChatConfig.rejectUnknownInvites
enabled: !NeoChatConfig.isRejectUnknownInvitesImmutable && connection.canCheckMutualRooms
onToggled: {
@@ -40,42 +67,15 @@ FormCard.FormCardPage {
}
}
FormCard.FormHeader {
title: i18nc("@title:group", "Ignored Users")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Manage ignored users")
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
title: i18nc("@title:window", "Ignored Users")
});
}
}
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", "Encryption")
visible: Controller.csSupported
title: i18nc("@title", "Encryption Keys")
}
FormCard.FormCard {
visible: Controller.csSupported
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Import Encryption Keys")
id: importKeysDelegate
text: i18nc("@action:button", "Import Keys")
description: i18nc("@info", "Import encryption keys from a backup.")
icon.name: "document-import"
onClicked: {
let dialog = root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ImportKeysDialog"), {
@@ -91,8 +91,14 @@ FormCard.FormCardPage {
banner.visible = false;
}
}
FormCard.FormDelegateSeparator {
above: importKeysDelegate
below: exportKeysDelegate
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Export Encryption Keys")
id: exportKeysDelegate
text: i18nc("@action:button", "Export Keys")
description: i18nc("@info", "Export this device's encryption keys.")
icon.name: "document-export"
onClicked: {
root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ExportKeysDialog"), {

View File

@@ -48,7 +48,7 @@ KirigamiSettings.ConfigurationView {
},
KirigamiSettings.ConfigurationModule {
moduleId: "security"
text: i18n("Security")
text: i18nc("@title", "Security & Safety")
icon.name: "preferences-security"
page: () => Qt.createComponent("org.kde.neochat.settings", "NeoChatSecurityPage")
initialProperties: () => {