Compare commits

...

14 Commits

Author SHA1 Message Date
Joshua Goins
49e4fd1b00 Overhaul video player 2025-05-20 18:08:23 -04:00
James Graham
a1ca768711 Use the forget function to leave a room everywhere
Use the forget function to leave a room everywhere this is both for consistency and to reduce dependencies. This way no dependency on RoomManager is required to leave a room and since in all cases they have an object they can just call the function.
2025-05-20 17:35:34 +01:00
l10n daemon script
f0d2c19393 GIT_SILENT Sync po/docbooks with svn 2025-05-20 01:39:59 +00:00
Carl Schwan
67db05a0c3 Adapt API to ConvergentContextMenu change
Use close instead of using internal menuItem
2025-05-19 18:59:45 +02:00
Ritchie Frodomar
05e932b884 Move typing indicators closer to messages
This merge request addresses a minor papercut in NeoChat's layout when using a screen magnifier.

If you are zoomed in on a conversation, typing indicators are generally invisible. This merge request moves them to align with actual messages, making them more visible to screen magnifier users like myself. That way, you can do the text chat equivalent of waiting for someone else to finish talking before you ramble on.

### Before
![Screenshot of NeoChat in a test conversation. A user is typing. The typing pane is very far off to the left, not visible when zoomed in.](https://cdn.acidiclight.dev/images/2025/04/03/31d836b18875.png)

### After
![Screenshot of the typing indicator in a conversation. The typing pane now aligns with the chatbar and messages.](https://cdn.acidiclight.dev/images/2025/04/03/870ace40ce86.png)
2025-05-19 12:07:02 -04:00
l10n daemon script
763713cd32 GIT_SILENT Sync po/docbooks with svn 2025-05-19 01:42:11 +00:00
Joshua Goins
2687448212 Add header to UserMenu for mobile which has the user info
To give a little bit more context, and it also matches the message
context menu which does the same.
2025-05-18 20:00:21 -04:00
Joshua Goins
d059195e92 Add user menu that is opened via right-clicking/long-tapped on avatar
We can un-clutter our message context menu, which we had to share
with user actions. (Even though we only had one so far.) I added one new
user-specific action which allows you to quickly mention the user in
chat. Otherwise you would've had to copy their username or use the
completion menu.

It's convergent on mobile, it still has the hover indicator and it also
is available through the AuthorComponent.

BUG: 486252
2025-05-18 20:00:20 -04:00
James Graham
6ef7acc8e5 Remove any dependencies on App from the spaces module.
Remove any dependencies on App from the spaces module. This requires moving some dialogs either to spaces, or libneochat if they're used more generically.
2025-05-18 14:38:57 +01:00
l10n daemon script
2cb89807ef GIT_SILENT Sync po/docbooks with svn 2025-05-18 01:40:43 +00:00
Joshua Goins
b5fcad3db0 Fix multiple Global menu issues
See individual commits. When backporting, I'll create a separate patchset just for the we-broke-something-bugs without the strings.
2025-05-17 21:21:38 -04:00
Joshua Goins
d3fd441c88 Improve the "Create a Room/Space" and "Select Existing Room" dialog
First of all, these are now all separate dialogs instead of shoving all
of these functions (which are only marginally related) into one single
dialog. We also convert these to in-app Kirigami Dialogs, which look a
bit nicer. I also touched up the UX in some places, such as adding
descriptions which were previously available to translators. I also hid
some not oft used options like setting a topic, which almost nobody does
before creating a room/space.
2025-05-17 21:09:43 -04:00
Joshua Goins
e9568b50fc If the message body is empty, say so
Normally if a malformed event is empty it will just be empty space - but
that looks buggy. Instead, we can add a message saying "This event does
not have any content."

BUG: 494093
2025-05-17 21:09:25 -04:00
James Graham
e7040a518a Create a new module for the room info drawer QML.
Create a new module for the room info drawer QML. This also requires moving some QML to LibNeoChat common with other modules. Finally all QML in roominfo is modifed to not depend on app.
2025-05-17 14:27:38 +01:00
101 changed files with 28565 additions and 22243 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ endif()
add_subdirectory(libneochat)
add_subdirectory(login)
add_subdirectory(rooms)
add_subdirectory(roominfo)
add_subdirectory(timeline)
add_subdirectory(spaces)
add_subdirectory(chatbar)

View File

@@ -8,8 +8,6 @@ add_library(neochat STATIC
controller.h
roommanager.cpp
roommanager.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
notificationsmanager.cpp
@@ -58,10 +56,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AccountMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
@@ -69,7 +65,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
@@ -79,26 +74,14 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/EmojiSas.qml
qml/VerificationCanceled.qml
qml/MessageSourceSheet.qml
qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/RemoveChildDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
@@ -118,12 +101,14 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AvatarNotification.qml
qml/ReasonDialog.qml
qml/NewPollDialog.qml
qml/UserMenu.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.roominfo
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
@@ -189,7 +174,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline

View File

@@ -32,6 +32,7 @@
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -230,6 +231,7 @@ void Controller::initConnection(NeoChatConnection *connection)
m_notificationsManager.handleNotifications(connection);
});
connect(this, &Controller::globalUrlPreviewDefaultChanged, connection, &NeoChatConnection::globalUrlPreviewEnabledChanged);
connect(connection, &NeoChatConnection::roomAboutToBeLeft, &RoomManager::instance(), &RoomManager::roomLeft);
Q_EMIT connectionAdded(connection);
}

View File

@@ -276,7 +276,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
if (inAnyOfOurRooms) {
doPostInviteNotification(room);
} else {
room->leaveRoom();
room->forget();
}
}
});
@@ -330,14 +330,14 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
if (!room) {
return;
}
RoomManager::instance().leaveRoom(room);
room->forget();
notification->close();
});
connect(rejectAndIgnoreAction, &KNotificationAction::activated, this, [room, notification]() {
if (!room) {
return;
}
RoomManager::instance().leaveRoom(room);
room->forget();
room->connection()->addToIgnoredUsers(room->invitingUserId());
notification->close();
});

View File

@@ -28,7 +28,7 @@ Kirigami.PromptDialog {
text: i18nc("@action:button", "Leave Room")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "arrow-left-symbolic"
onClicked: RoomManager.leaveRoom(root.room)
onClicked: root.room.forget();
}
}
}

View File

@@ -8,6 +8,8 @@ import QtPositioning
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat
ApplicationWindow {
id: root

View File

@@ -15,7 +15,7 @@ import org.kde.neochat.settings
Labs.MenuBar {
id: root
property NeoChatConnection connection
required property NeoChatConnection connection
Labs.Menu {
title: i18nc("menu", "NeoChat")
@@ -38,25 +38,31 @@ Labs.MenuBar {
title: i18nc("menu", "File")
Labs.MenuItem {
text: i18nc("menu", "Find your friends")
icon.name: "list-add-user"
text: i18nc("@action:inmenu", "Find your Friends")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
onTriggered: pushReplaceLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
icon.name: "system-users-symbolic"
text: i18nc("@action:inmenu", "Create a Room…")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
shortcut: StandardKey.New
onTriggered: {
const dialog = createRoomDialog.createObject(root.overlay);
dialog.open();
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}
}
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu", "Explore Rooms")
onTriggered: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
@@ -77,7 +83,8 @@ Labs.MenuBar {
title: i18nc("menu", "View")
Labs.MenuItem {
text: i18nc("menu item that opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Open Quick Switcher")
icon.name: "search-symbolic"
text: i18nc("@action:inmenu opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Search Rooms")
onTriggered: quickSwitcher.open()
}
}
@@ -85,6 +92,7 @@ Labs.MenuBar {
title: i18nc("menu", "Window")
Labs.MenuItem {
icon.name: "view-fullscreen-symbolic"
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
}
@@ -93,14 +101,12 @@ Labs.MenuBar {
title: i18nc("menu", "Help")
Labs.MenuItem {
text: i18nc("menu", "About Matrix")
onTriggered: UrlHelper.openUrl("https://matrix.org/docs/chat_basics/matrix-for-im/")
}
Labs.MenuItem {
icon.name: "help-about-symbolic"
text: i18nc("menu", "About NeoChat")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
}
Labs.MenuItem {
icon.name: "kde-symbolic"
text: i18nc("menu", "About KDE")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
}

View File

@@ -108,7 +108,7 @@ ColumnLayout {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action:button Reject this invite", "Reject Invite")
onClicked: RoomManager.leaveRoom(root.currentRoom)
onClicked: root.currentRoom.forget()
}
}
@@ -123,7 +123,7 @@ ColumnLayout {
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
root.currentRoom.forget()
root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUserId);
}
}

View File

@@ -80,9 +80,8 @@ Kirigami.ApplicationWindow {
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
sourceComponent: Qt.createComponent("org.kde.neochat", "GlobalMenu")
onActiveChanged: if (active) {
item.connection = root.connection;
sourceComponent: GlobalMenu {
connection: root.connection
}
}
@@ -149,9 +148,13 @@ Kirigami.ApplicationWindow {
}
function openRoomDrawer() {
pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection
const page = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection,
room: RoomManager.currentRoom,
userListModel: RoomManager.userListModel,
mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
});
page.resolveResource.connect((idOrUri, action) => RoomManager.resolveResource(idOrUri, action))
}
contextDrawer: RoomDrawer {
@@ -161,7 +164,18 @@ Kirigami.ApplicationWindow {
// It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width
property bool drawerUserState: NeoChatConfig.autoRoomInfoDrawer
room: RoomManager.currentRoom
connection: root.connection
userListModel: RoomManager.userListModel
mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
onResolveResource: (idOrUri, action) => RoomManager.resolveResource(idOrUri, action)
roomDrawerWidth: NeoChatConfig.roomDrawerWidth
onRoomDrawerWidthChanged: {
NeoChatConfig.roomDrawerWidth = actualWidth;
NeoChatConfig.save();
}
handleClosedIcon.source: "documentinfo-symbolic"
handleClosedToolTip: i18nc("@action:button", "Show Room Information")

View File

@@ -137,7 +137,9 @@ Kirigami.Page {
id: spaceLoader
active: root.currentRoom && root.currentRoom.isSpace
anchors.fill: parent
sourceComponent: SpaceHomePage {}
sourceComponent: SpaceHomePage {
room: root.currentRoom
}
}
Loader {

74
src/app/qml/UserMenu.qml Normal file
View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.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.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.settings
import org.kde.neochat.devtools
KirigamiComponents.ConvergentContextMenu {
id: root
required property Kirigami.ApplicationWindow window
required property var author
headerContentItem: RowLayout {
id: detailRow
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
name: root.author.displayName
source: root.author.avatarUrl
color: root.author.color
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Kirigami.Heading {
level: 1
Layout.fillWidth: true
font.bold: true
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: root.author.displayName
textFormat: Text.PlainText
}
QQC2.Label {
id: idLabel
textFormat: TextEdit.PlainText
text: root.author.id
elide: Qt.ElideRight
}
}
}
QQC2.Action {
text: i18nc("@action:button", "Open Profile")
icon.name: "im-user-symbolic"
onTriggered: RoomManager.resolveResource(root.author.uri)
}
QQC2.Action {
text: i18nc("@action:button", "Mention")
icon.name: "username-copy-symbolic"
onTriggered: {
RoomManager.currentRoom.mainCache.mentionAdded(root.author.id);
}
}
}

View File

@@ -448,29 +448,27 @@ void RoomManager::knockRoom(NeoChatConnection *account, const QString &roomAlias
Qt::SingleShotConnection);
}
void RoomManager::roomLeft(const QString &id)
{
if (id.isEmpty()) {
return;
}
if (m_currentRoom && m_currentRoom->id() == id) {
setCurrentRoom({});
}
if (m_currentSpaceId == id) {
setCurrentSpace({});
}
}
bool RoomManager::visitNonMatrix(const QUrl &url)
{
UrlHelper().openUrl(url);
return true;
}
void RoomManager::leaveRoom(NeoChatRoom *room)
{
if (!room) {
return;
}
if (m_currentRoom && m_currentRoom->id() == room->id()) {
setCurrentRoom({});
}
if (m_currentSpaceId == room->id()) {
setCurrentSpace({});
}
room->forget();
}
ChatDocumentHandler *RoomManager::chatDocumentHandler() const
{
return m_chatDocumentHandler;

View File

@@ -195,11 +195,6 @@ public:
*/
Q_INVOKABLE void loadInitialRoom();
/**
* @brief Leave the room and close it if it is open.
*/
Q_INVOKABLE void leaveRoom(NeoChatRoom *room);
/**
* @brief Knock a room.
*
@@ -208,6 +203,13 @@ public:
*/
void knockRoom(NeoChatConnection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers);
/**
* @brief Cleanup after the given room is left.
*
* This ensures that the current room and space are not set to the left room.
*/
void roomLeft(const QString &id);
/**
* @brief Show a media item maximized.
*

View File

@@ -55,6 +55,19 @@ QQC2.Control {
}
}
Connections {
target: currentRoom.mainCache
function onMentionAdded(mention: string): void {
// add mention text
textField.append(mention + " ");
// move cursor to the end
textField.cursorPosition = textField.text.length;
// move the focus back to the chat bar
textField.forceActiveFocus(Qt.OtherFocusReason);
}
}
/**
* @brief The list of actions in the ChatBar.
*

View File

@@ -41,12 +41,21 @@ target_sources(LibNeoChat PRIVATE
models/locationsmodel.cpp
models/roomlistmodel.cpp
models/stickermodel.cpp
models/userfiltermodel.cpp
models/userlistmodel.cpp
)
ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.libneochat
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/libneochat
QML_FILES
qml/GroupChatDrawerHeader.qml
qml/LocationMapItem.qml
qml/InviteUserPage.qml
qml/ExploreRoomsPage.qml
qml/SearchPage.qml
qml/CreateRoomDialog.qml
qml/CreateSpaceDialog.qml
)
ecm_qt_declare_logging_category(LibNeoChat

View File

@@ -195,6 +195,7 @@ Q_SIGNALS:
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
void threadIdChanged(const QString &oldThreadId, const QString &newThreadId);
void attachmentPathChanged();
void mentionAdded(const QString &mention);
private:
QString m_text = QString();

View File

@@ -29,7 +29,7 @@ QStringList rainbowColors{"#ff2b00"_L1, "#ff5500"_L1, "#ff8000"_L1, "#ffaa00"_L1
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
room->connection()->leaveRoom(room);
room->forget();
} else {
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
@@ -38,13 +38,13 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto leaving = room->connection()->room(text);
auto leaving = dynamic_cast<NeoChatRoom *>(room->connection()->room(text));
if (!leaving) {
leaving = room->connection()->roomByAlias(text);
leaving = dynamic_cast<NeoChatRoom *>(room->connection()->roomByAlias(text));
}
if (leaving) {
Q_EMIT room->showMessage(MessageType::Information, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
room->connection()->leaveRoom(leaving);
leaving->forget();
} else {
Q_EMIT room->showMessage(MessageType::Information, i18nc("Room <roomname> not found", "Room %1 not found.", text));
}

View File

@@ -9,6 +9,7 @@
#include "neochatroom.h"
#include "spacehierarchycache.h"
#include <Quotient/connection.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <qt6keychain/keychain.h>
@@ -386,6 +387,13 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
});
}
Quotient::ForgetRoomJob *NeoChatConnection::forgetRoom(const QString &id)
{
Q_EMIT roomAboutToBeLeft(id);
return Connection::forgetRoom(id);
}
bool NeoChatConnection::directChatExists(Quotient::User *user)
{
return directChats().contains(user);

View File

@@ -150,6 +150,14 @@ public:
*/
Q_INVOKABLE void createSpace(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
/**
* @brief Send /forget to the server and delete room locally.
*
* @note This wraps around the Quotient::Connection::forgetRoom() to allow
* roomAboutToBeLeft() to be emitted.
*/
Quotient::ForgetRoomJob *forgetRoom(const QString &id);
/**
* @brief Whether a direct chat with the user exists.
*/
@@ -224,6 +232,11 @@ Q_SIGNALS:
*/
void errorOccured(const QString &error);
/**
* @brief The given room ID is about to be forgotten.
*/
void roomAboutToBeLeft(const QString &id);
private:
static bool m_globalUrlPreviewDefault;
static PushRuleAction::Action m_defaultAction;

View File

@@ -305,8 +305,9 @@ void NeoChatRoom::forget()
roomIds += predecessor->id();
}
const auto neochatConnection = dynamic_cast<NeoChatConnection *>(connection());
for (const auto &id : roomIds) {
connection()->forgetRoom(id);
neochatConnection->forgetRoom(id);
}
}

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later 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.labs.components as Components
import org.kde.neochat
Kirigami.Dialog {
id: root
property string parentId
required property NeoChatConnection connection
signal newChild(string childName)
title: i18nc("@title", "Create Room")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: [
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:button Create new room", "Create")
enabled: roomNameField.text.length > 0
onTriggered: {
root.connection.createRoom(roomNameField.text, "", root.parentId, false);
root.newChild(roomNameField.text);
root.close();
}
}
]
Component.onCompleted: roomNameField.forceActiveFocus()
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
FormCard.FormRadioDelegate {
id: privateTypeDelegate
text: i18nc("@info:label", "Private")
description: i18nc("@info:description", "This room can only be joined with an invite.")
checked: true
}
FormCard.FormRadioDelegate {
id: publicTypeDelegate
text: i18nc("@info:label", "Public")
description: i18nc("@info:description", "This room can be found and joined by anyone.")
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18nc("@info:label Name of the room", "Name:")
placeholderText: i18nc("@info:placeholder Placeholder for room name", "New Room")
}
FormCard.FormTextFieldDelegate {
id: roomAddressField
label: i18nc("@info:label Address or alias to refer to the room by", "Address:")
placeholderText: i18nc("@info:placeholder Placeholder address for the room", "new-room")
visible: publicTypeDelegate.checked
}
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later 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.labs.components as Components
import org.kde.neochat
Kirigami.Dialog {
id: root
property string parentId
required property NeoChatConnection connection
signal newChild(string childName)
title: i18nc("@title", "Create a Space")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Cancel
Component.onCompleted: roomNameField.forceActiveFocus()
customFooterActions: [
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:button Create new space", "Create")
enabled: roomNameField.text.length > 0
onTriggered: {
root.connection.createSpace(roomNameField.text, "", root.parentId, newOfficialCheck.checked);
root.newChild(roomNameField.text);
root.close();
}
}
]
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18nc("@info:label Name of the space", "Name:")
placeholderText: i18nc("@info:placeholder", "New Space")
}
FormCard.FormDelegateSeparator {
above: roomNameField
below: newOfficialCheck
visible: newOfficialCheck.visible
}
FormCard.FormCheckDelegate {
id: newOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
checked: true
}
}
}

View File

@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(RoomInfo STATIC)
ecm_add_qml_module(RoomInfo GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.roominfo
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/roominfo
QML_FILES
RoomDrawer.qml
RoomDrawerPage.qml
RoomInformation.qml
RoomMedia.qml
DirectChatDrawerHeader.qml
LocationsPage.qml
RoomPinnedMessagesPage.qml
RoomSearchPage.qml
)

View File

@@ -8,7 +8,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.libneochat
ColumnLayout {
id: root
@@ -18,6 +18,8 @@ ColumnLayout {
*/
required property NeoChatRoom room
signal resolveResource(string idOrUri, string action)
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 0
@@ -33,7 +35,7 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
onClicked: {
RoomManager.resolveResource(root.room.directChatRemoteMember.uri)
root.resolveResource(root.room.directChatRemoteMember.uri, "")
}
contentItem: KirigamiComponents.Avatar {

View File

@@ -6,12 +6,13 @@ import QtLocation
import QtPositioning
import org.kde.kirigami as Kirigami
import org.kde.neochat
import org.kde.neochat.libneochat
Kirigami.Page {
id: root
required property var room
required property NeoChatRoom room
title: i18nc("Locations on a map", "Locations")

View File

@@ -9,14 +9,19 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.settings
import org.kde.neochat.libneochat
import org.kde.neochat.timeline as Timeline
import org.kde.neochat.settings as Settings
Kirigami.OverlayDrawer {
id: root
readonly property NeoChatRoom room: RoomManager.currentRoom
required property NeoChatRoom room
required property NeoChatConnection connection
required property UserListModel userListModel
required property Timeline.MediaMessageFilterModel mediaMessageFilterModel
signal resolveResource(string idOrUri, string action)
width: actualWidth
interactive: modal
@@ -24,11 +29,12 @@ Kirigami.OverlayDrawer {
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
property int roomDrawerWidth
property int actualWidth: {
if (NeoChatConfig.roomDrawerWidth === -1) {
if (root.roomDrawerWidth === -1) {
return Kirigami.Units.gridUnit * 20;
} else {
return NeoChatConfig.roomDrawerWidth;
return root.roomDrawerWidth;
}
}
@@ -46,8 +52,7 @@ Kirigami.OverlayDrawer {
visible: true
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
onReleased: {
NeoChatConfig.roomDrawerWidth = root.actualWidth;
NeoChatConfig.save();
root.roomDrawerWidth = root.actualWidth;
}
property real _lastX: -1
@@ -56,9 +61,9 @@ Kirigami.OverlayDrawer {
return;
}
if (Qt.application.layoutDirection === Qt.RightToLeft) {
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, NeoChatConfig.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x));
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, root.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x));
} else {
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, NeoChatConfig.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x));
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, root.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x));
}
}
}
@@ -121,7 +126,7 @@ Kirigami.OverlayDrawer {
QQC2.ToolTip.visible: hovered
onClicked: {
RoomSettingsView.openRoomSettings(root.room, RoomSettingsView.Room);
Settings.RoomSettingsView.openRoomSettings(root.room, Settings.RoomSettingsView.Room);
}
}
}
@@ -138,15 +143,17 @@ Kirigami.OverlayDrawer {
id: roomInformation
RoomInformation {
room: root.room
connection: root.connection
userListModel: root.userListModel
onResolveResource: (idOrUri, action) => root.resolveResource(idOrUri, action)
}
}
Component {
id: roomMedia
RoomMedia {
currentRoom: root.room
connection: root.connection
room: root.room
mediaMessageFilterModel: root.mediaMessageFilterModel
}
}

View File

@@ -7,7 +7,8 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.libneochat
import org.kde.neochat.timeline as Timeline
/**
* @brief Page for holding a room drawer component.
@@ -24,8 +25,12 @@ Kirigami.Page {
/**
* @brief The current room that user is viewing.
*/
readonly property NeoChatRoom room: RoomManager.currentRoom
required property NeoChatRoom room
required property NeoChatConnection connection
required property UserListModel userListModel
required property Timeline.MediaMessageFilterModel mediaMessageFilterModel
signal resolveResource(string idOrUri, string action)
title: drawerItemLoader.item ? drawerItemLoader.item.title : ""
@@ -61,15 +66,17 @@ Kirigami.Page {
id: roomInformation
RoomInformation {
room: root.room
connection: root.connection
userListModel: root.userListModel
onResolveResource: (idOrUri, action) => root.resolveResource(idOrUri, action)
}
}
Component {
id: roomMedia
RoomMedia {
currentRoom: root.room
connection: root.connection
room: root.room
mediaMessageFilterModel: root.mediaMessageFilterModel
}
}

View File

@@ -12,7 +12,7 @@ import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.libneochat
/**
* @brief Component for visualising the room information.
@@ -34,13 +34,15 @@ QQC2.ScrollView {
*/
required property NeoChatRoom room
required property NeoChatConnection connection
required property UserListModel userListModel
/**
* @brief The title that should be displayed for this component if available.
*/
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room Information")
signal resolveResource(string idOrUri, string action)
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
@@ -216,7 +218,7 @@ QQC2.ScrollView {
UserFilterModel {
id: userFilterModel
sourceModel: RoomManager.userListModel
sourceModel: root.userListModel
allowEmpty: true
}
@@ -249,7 +251,7 @@ QQC2.ScrollView {
KeyNavigation.backtab: index === 0 ? userList.headerItem.userListSearchField : null
onClicked: {
RoomManager.resolveResource(userDelegate.userId, "mention");
root.resolveResource(userDelegate.userId, "mention");
}
contentItem: RowLayout {
@@ -286,6 +288,8 @@ QQC2.ScrollView {
id: directChatDrawerHeader
DirectChatDrawerHeader {
room: root.room
onResolveResource: (idOrUri, action) => root.resolveResource(idOrUri, action)
}
}

View File

@@ -6,8 +6,8 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.neochat
import org.kde.neochat.timeline
import org.kde.neochat.libneochat
import org.kde.neochat.timeline as Timeline
/**
* @brief Component for visualising the loaded media items in the room.
@@ -31,9 +31,9 @@ QQC2.ScrollView {
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
required property NeoChatRoom room
required property NeoChatConnection connection
required property Timeline.MediaMessageFilterModel mediaMessageFilterModel
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
@@ -42,26 +42,26 @@ QQC2.ScrollView {
clip: true
verticalLayoutDirection: ListView.BottomToTop
model: RoomManager.mediaMessageFilterModel
model: root.mediaMessageFilterModel
delegate: DelegateChooser {
role: "type"
DelegateChoice {
roleValue: MediaMessageFilterModel.Image
delegate: MessageDelegate {
roleValue: Timeline.MediaMessageFilterModel.Image
delegate: Timeline.MessageDelegate {
alwaysFillWidth: true
cardBackground: false
room: root.currentRoom
room: root.room
}
}
DelegateChoice {
roleValue: MediaMessageFilterModel.Video
delegate: MessageDelegate {
roleValue: Timeline.MediaMessageFilterModel.Video
delegate: Timeline.MessageDelegate {
alwaysFillWidth: true
cardBackground: false
room: root.currentRoom
room: root.room
}
}
}

View File

@@ -6,7 +6,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
import org.kde.neochat.libneochat
import org.kde.neochat.timeline
/**

View File

@@ -3,7 +3,7 @@
import QtQuick
import org.kde.neochat
import org.kde.neochat.libneochat
import org.kde.neochat.timeline
/**

View File

@@ -90,29 +90,13 @@ RowLayout {
action: QQC2.Action {
shortcut: StandardKey.New
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root, {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}).open();
}
}
}
QQC2.MenuItem {
text: i18n("Create a Space")
icon.name: "list-add"
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
});
}
}
QQC2.MenuItem {
text: i18n("Scan a QR Code")
icon.name: "view-barcode-qr"

View File

@@ -159,13 +159,9 @@ Kirigami.NavigationTabBar {
text: i18n("Create a Space")
icon.name: "list-add"
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
});
Qt.createComponent('org.kde.neochat', 'CreateSpaceDialog').createObject(root, {
connection: root.connection
}).open();
explorePopup.close();
}
}

View File

@@ -268,13 +268,11 @@ QQC2.Control {
activeFocusOnTab: true
onSelected: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
})
onSelected: {
Qt.createComponent('org.kde.neochat', 'CreateSpaceDialog').createObject(root, {
connection: root.connection
}).open();
}
}
AvatarTabButton {

View File

@@ -72,6 +72,6 @@ KirigamiComponents.ConvergentContextMenu {
QQC2.Action {
text: i18nc("'Space' is a matrix space", "Leave Space")
icon.name: "go-previous"
onTriggered: RoomManager.leaveRoom(room)
onTriggered: root.room.forget()
}
}

View File

@@ -8,6 +8,8 @@ ecm_add_qml_module(Spaces GENERATE_PLUGIN_SOURCE
QML_FILES
SpaceHomePage.qml
SpaceHierarchyDelegate.qml
RemoveChildDialog.qml
SelectExistingRoomDialog.qml
SOURCES
models/spacechildrenmodel.cpp
models/spacechildsortfiltermodel.cpp

View File

@@ -7,7 +7,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.neochat.libneochat
Kirigami.Dialog {
id: root

View File

@@ -9,124 +9,36 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
import org.kde.neochat.libneochat
FormCard.FormCardPage {
Kirigami.Dialog {
id: root
property string parentId: ""
property bool isSpace: false
property bool showChildType: false
property bool showCreateChoice: false
property string parentId
required property NeoChatConnection connection
signal addChild(string childId, bool setChildParent, bool canonical)
signal newChild(string childName)
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
title: i18nc("@title", "Select Existing Room")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
Component.onCompleted: roomNameField.forceActiveFocus()
onAccepted: root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
FormCard.FormHeader {
title: root.isSpace ? i18n("New Space Information") : i18n("New Room Information")
}
FormCard.FormCard {
FormCard.FormComboBoxDelegate {
id: roomTypeCombo
property bool isInitialising: true
Component.onCompleted: pickRoomDelegate.forceActiveFocus()
visible: root.showChildType
text: i18n("Select type")
model: ListModel {
id: roomTypeModel
}
textRole: "text"
valueRole: "isSpace"
Component.onCompleted: {
currentIndex = indexOfValue(root.isSpace);
roomTypeModel.append({
"text": i18n("Room"),
"isSpace": false
});
roomTypeModel.append({
"text": i18n("Space"),
"isSpace": true
});
roomTypeCombo.currentIndex = 0;
roomTypeCombo.isInitialising = false;
}
onCurrentValueChanged: {
if (!isInitialising) {
root.isSpace = currentValue;
}
}
}
FormCard.FormDelegateSeparator {
visible: root.showChildType
}
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18n("Name:")
onAccepted: if (roomNameField.text.length > 0) {
roomTopicField.forceActiveFocus();
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: roomTopicField
label: i18n("Topic:")
onAccepted: ok.clicked()
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
id: newOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
checked: true
}
FormCard.FormDelegateSeparator {
visible: root.parentId.length > 0
}
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
FormCard.FormButtonDelegate {
id: ok
text: root.isSpace ? i18nc("@action:button", "Create Space") : i18nc("@action:button", "Create Room")
enabled: roomNameField.text.length > 0
onClicked: {
if (root.isSpace) {
root.connection.createSpace(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
} else {
root.connection.createRoom(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
}
root.newChild(roomNameField.text);
root.closeDialog();
}
}
}
FormCard.FormHeader {
visible: root.showChildType
title: i18n("Select Existing Room")
}
FormCard.FormCard {
visible: root.showChildType
FormCard.FormButtonDelegate {
id: pickRoomDelegate
visible: !chosenRoomDelegate.visible
text: i18nc("@action:button", "Pick room")
text: i18nc("@action:button", "Pick Room")
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.libneochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
@@ -227,13 +139,15 @@ FormCard.FormCardPage {
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormDelegateSeparator {
below: existingOfficialCheck
}
FormCard.FormCheckDelegate {
id: existingOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
description: enabled ? i18n("You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
description: enabled ? i18nc("@info:description", "You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
checked: enabled
enabled: {
@@ -250,26 +164,17 @@ FormCard.FormCardPage {
}
FormCard.FormDelegateSeparator {
visible: root.parentId.length > 0
above: existingOfficialCheck
below: makeCanonicalCheck
}
FormCard.FormCheckDelegate {
id: makeCanonicalCheck
text: i18nc("@option:check The canonical parent is the default one if a room has multiple parent spaces.", "Make this space the canonical parent")
description: i18nc("@info:description", "The canonical parent is the default one if a room has multiple parent spaces.")
checked: enabled
enabled: existingOfficialCheck.enabled
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Ok")
enabled: chosenRoomDelegate.visible
onClicked: {
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
root.closeDialog();
}
}
}
}

View File

@@ -9,8 +9,7 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
import org.kde.neochat.libneochat as LibNeoChat
import org.kde.neochat.libneochat
Item {
id: root
@@ -202,7 +201,7 @@ Item {
}
}
LibNeoChat.DelegateSizeHelper {
DelegateSizeHelper {
id: sizeHelper
parentItem: root
startBreakpoint: Kirigami.Units.gridUnit * 46

View File

@@ -6,20 +6,47 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.libneochat as LibNeoChat
import org.kde.neochat.settings
import org.kde.neochat.libneochat
import org.kde.neochat.settings as Settings
ColumnLayout {
id: root
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
/**
* @brief The NeoChatRoom the delegate is being displayed in.
*/
required property NeoChatRoom room
anchors.fill: parent
spacing: 0
Component {
id: roomMenuComponent
KirigamiComponents.ConvergentContextMenu {
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:inmenu", "New Room…")
onTriggered: _private.createRoom(root.room.id)
}
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:inmenu", "New Space…")
onTriggered: _private.createSpace(root.room.id)
}
Kirigami.Action {
icon.name: "search-symbolic"
text: i18nc("@action:inmenu", "Existing Room…")
onTriggered: _private.selectExisting(root.room.id)
}
}
}
QQC2.Control {
id: headerItem
Layout.fillWidth: true
@@ -40,32 +67,37 @@ ColumnLayout {
GroupChatDrawerHeader {
id: header
Layout.fillWidth: true
room: root.currentRoom
room: root.room
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
QQC2.Button {
visible: root.currentRoom.canSendState("invite")
visible: root.room.canSendState("invite")
text: i18nc("@button", "Invite user to space")
icon.name: "list-add-user"
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'InviteUserPage'), {
room: root.currentRoom
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.libneochat', 'InviteUserPage'), {
room: root.room
}, {
title: i18nc("@title", "Invite a User")
})
}
QQC2.Button {
visible: root.currentRoom.canSendState("m.space.child")
text: i18nc("@button", "Add new room")
id: addNewButton
visible: root.room.canSendState("m.space.child")
text: i18nc("@button", "Add to Space")
icon.name: "list-add"
onClicked: _private.createRoom(root.currentRoom.id)
onClicked: {
const menu = roomMenuComponent.createObject(addNewButton);
menu.popup();
}
}
QQC2.Button {
text: i18nc("@action:button", "Leave this space")
icon.name: "go-previous"
onClicked: RoomManager.leaveRoom(root.currentRoom)
onClicked: root.room.forget()
}
Item {
Layout.fillWidth: true
@@ -76,7 +108,7 @@ ColumnLayout {
display: QQC2.AbstractButton.IconOnly
text: i18nc("'Space' is a matrix space", "Space Settings")
onClicked: {
RoomSettingsView.openRoomSettings(root.currentRoom, RoomSettingsView.Space);
Settings.RoomSettingsView.openRoomSettings(root.room, Settings.RoomSettingsView.Space);
drawer.close();
}
icon.name: 'settings-configure-symbolic'
@@ -94,7 +126,7 @@ ColumnLayout {
onTextChanged: spaceChildSortFilterModel.filterText = text
}
}
LibNeoChat.DelegateSizeHelper {
DelegateSizeHelper {
id: sizeHelper
parentItem: root
startBreakpoint: Kirigami.Units.gridUnit * 46
@@ -125,7 +157,7 @@ ColumnLayout {
id: spaceChildSortFilterModel
sourceModel: SpaceChildrenModel {
id: spaceChildrenModel
space: root.currentRoom
space: root.room
}
}
@@ -158,27 +190,43 @@ ColumnLayout {
}
QtObject {
id: _private
function createRoom(parentId) {
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
title: i18nc("@title", "Create a Child"),
connection: root.currentRoom.connection,
parentId: parentId,
showChildType: true,
showCreateChoice: true
}, {
title: i18nc("@title", "Create a Child")
});
dialog.addChild.connect((childId, setChildParent, canonical) => {
// We have to get a room object from the connection as we may not
// be adding to the top level parent.
let parent = root.currentRoom.connection.room(parentId);
if (parent) {
parent.addChild(childId, setChildParent, canonical);
}
const dialog = Qt.createComponent('org.kde.neochat.libneochat', 'CreateRoomDialog').createObject(root, {
connection: root.room.connection,
parentId: parentId
});
dialog.newChild.connect(childName => {
spaceChildrenModel.addPendingChild(childName);
});
dialog.open();
}
function createSpace(parentId) {
const dialog = Qt.createComponent('org.kde.neochat.libneochat', 'CreateSpaceDialog').createObject(root, {
connection: root.room.connection,
parentId: parentId,
});
dialog.newChild.connect(childName => {
spaceChildrenModel.addPendingChild(childName);
});
dialog.open();
}
function selectExisting(parentId) {
const dialog = Qt.createComponent('org.kde.neochat.spaces', 'SelectExistingRoomDialog').createObject(root, {
connection: root.room.connection,
parentId: parentId,
});
dialog.addChild.connect((childId, setChildParent, canonical) => {
// We have to get a room object from the connection as we may not
// be adding to the top level parent.
let parent = root.room.connection.room(parentId);
if (parent) {
parent.addChild(childId, setChildParent, canonical);
}
});
dialog.open();
}
}
}

View File

@@ -41,21 +41,42 @@ RowLayout {
implicitHeight: Math.max(nameButton.implicitHeight, timeLabel.implicitHeight)
QQC2.AbstractButton {
QQC2.Label {
id: nameButton
contentItem: QQC2.Label {
text: root.author.disambiguatedName
color: root.author.color
textFormat: Text.PlainText
font.weight: Font.Bold
elide: Text.ElideRight
text: root.author.disambiguatedName
color: root.author.color
textFormat: Text.PlainText
font.weight: Font.Bold
elide: Text.ElideRight
function openUserMenu(): void {
const menu = Qt.createComponent("org.kde.neochat", "UserMenu").createObject(root, {
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow,
author: root.author,
});
menu.popup(root.QQC2.Overlay.overlay);
}
Accessible.name: contentItem.text
onClicked: RoomManager.resolveResource(root.author.uri)
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
// tapping to open profile
TapHandler {
onTapped: RoomManager.resolveResource(root.author.uri)
}
// right-clicking/long-press for context menu
TapHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton
onTapped: nameButton.openUserMenu()
}
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onTapped: nameButton.openUserMenu()
}
}
Item {
Layout.fillWidth: true
@@ -73,16 +94,4 @@ RowLayout {
id: timeHoverHandler
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
acceptedDevices: PointerDevice.TouchScreen
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
gesturePolicy: TapHandler.WithinBounds
onTapped: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
}
}

View File

@@ -142,14 +142,6 @@ KirigamiComponents.ConvergentContextMenu {
}
}
component ShowUserAction: Kirigami.Action {
text: i18nc("@action:inmenu", "Show User")
icon.name: "username-copy"
onTriggered: {
RoomManager.resolveResource(author.id)
}
}
component PinMessageAction: Kirigami.Action {
readonly property bool pinned: currentRoom.isEventPinned(root.eventId)
@@ -222,10 +214,10 @@ KirigamiComponents.ConvergentContextMenu {
dialog.showStickers = false;
dialog.chosen.connect(emoji => {
root.room.toggleReaction(root.eventId, emoji);
root.menuItem.close();
root.close();
});
dialog.closed.connect(() => {
root.menuItem.close();
root.close();
});
dialog.open();
return;

View File

@@ -94,8 +94,6 @@ DelegateContextMenu {
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}
Kirigami.Action {
separator: true
visible: viewSourceAction.visible

View File

@@ -149,6 +149,7 @@ MessageDelegateBase {
TapHandler {
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: _private.showMessageMenu()
}
@@ -159,7 +160,7 @@ MessageDelegateBase {
}
}
avatarComponent: KirigamiComponents.AvatarButton {
avatarComponent: KirigamiComponents.Avatar {
id: avatar
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
implicitHeight: width
@@ -170,7 +171,34 @@ MessageDelegateBase {
asynchronous: true
QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName
onClicked: RoomManager.resolveResource(root.author.uri)
function openUserMenu(): void {
const menu = Qt.createComponent("org.kde.neochat", "UserMenu").createObject(root, {
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow,
author: root.author,
});
console.info(Qt.createComponent("org.kde.neochat", "UserMenu").errorString());
menu.popup(root.QQC2.Overlay.overlay);
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
// tapping to open profile
TapHandler {
onTapped: RoomManager.resolveResource(root.author.uri)
}
// right-clicking/long-press for context menu
TapHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton
onTapped: avatar.openUserMenu()
}
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onTapped: avatar.openUserMenu()
}
}
sectionComponent: SectionDelegate {

View File

@@ -102,7 +102,6 @@ DelegateContextMenu {
}
DelegateContextMenu.PinMessageAction {}
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}
Kirigami.Action {
separator: true
visible: viewSourceAction.visible

View File

@@ -13,6 +13,7 @@ import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.timeline
import org.kde.neochat.libneochat as LibNeoChat
QQC2.ScrollView {
id: root
@@ -261,13 +262,24 @@ QQC2.ScrollView {
}
}
TypingPane {
id: typingPane
LibNeoChat.DelegateSizeHelper {
id: typingPaneSizeHelper
parentItem: typingPaneContainer
startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100
endPercentWidth: NeoChatConfig.compactLayout ? 100 : 85
maxWidth: NeoChatConfig.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
}
RowLayout {
id: typingPaneContainer
visible: root.currentRoom && root.currentRoom.otherMembersTyping.length > 0
labelText: visible ? i18ncp("Message displayed when some users are typing", "%2 is typing", "%2 are typing", root.currentRoom.otherMembersTyping.length, root.currentRoom.otherMembersTyping.map(member => member.displayName).join(", ")) : ""
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: visible ? implicitHeight : 0
height: visible ? typingPane.implicitHeight : 0
z: 2
Behavior on height {
NumberAnimation {
property: "height"
@@ -275,7 +287,15 @@ QQC2.ScrollView {
easing.type: Easing.OutCubic
}
}
z: 2
RowLayout {
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
Layout.maximumWidth: typingPaneSizeHelper.availableWidth
TypingPane {
id: typingPane
labelText: visible ? i18ncp("Message displayed when some users are typing", "%2 is typing", "%2 are typing", root.currentRoom.otherMembersTyping.length, root.currentRoom.otherMembersTyping.map(member => member.displayName).join(", ")) : ""
}
}
}
function goToEvent(eventID) {

View File

@@ -48,24 +48,32 @@ Video {
*/
required property var fileTransferInfo
/**
* @brief Whether the video should be played when downloaded.
*/
required property bool playOnFinished
/**
* @brief Whether the media has been downloaded.
*/
readonly property bool downloaded: root.fileTransferInfo && root.fileTransferInfo.completed
onDownloadedChanged: {
if (downloaded) {
root.autoPlay = root.playOnFinished;
root.source = root.fileTransferInfo.localPath;
}
if (downloaded && playOnFinished) {
playSavedFile();
playOnFinished = false;
console.info("hasAudio:" + root.hasAudio + " hasVideo:" + root.hasVideo);
if (playOnFinished) {
//playSavedFile();
root.Message.contentModel.setPlayOnFinished(false);
}
}
}
/**
* @brief Whether the video should be played when downloaded.
*/
property bool playOnFinished: false
onPaused: console.info("PAUSED")
onPlaying: console.info("PLAYING")
onStopped: console.info("STOPPED")
Layout.preferredWidth: mediaSizeHelper.currentSize.width
Layout.preferredHeight: mediaSizeHelper.currentSize.height
@@ -87,9 +95,13 @@ Video {
target: videoLabel
visible: true
}
PropertyChanges {
/*PropertyChanges {
target: mediaThumbnail
visible: true
}*/
PropertyChanges {
target: infoBackground
visible: true
}
},
State {
@@ -99,6 +111,14 @@ Video {
target: downloadBar
visible: true
}
/*PropertyChanges {
target: mediaThumbnail
visible: true
}*/
PropertyChanges {
target: infoBackground
visible: true
}
},
State {
name: "paused"
@@ -136,10 +156,10 @@ Video {
target: videoControls
stateVisible: true
}
PropertyChanges {
/*PropertyChanges {
target: mediaThumbnail
visible: true
}
}*/
PropertyChanges {
target: videoLabel
visible: true
@@ -188,41 +208,37 @@ Video {
fillMode: Image.PreserveAspectFit
}
QQC2.Label {
id: videoLabel
anchors.centerIn: parent
Rectangle {
id: infoBackground
anchors.fill: parent
color: "black"
opacity: 0.5
visible: false
color: "white"
text: i18n("Video")
font.pixelSize: 16
padding: 8
background: Rectangle {
radius: Kirigami.Units.smallSpacing
color: "black"
opacity: 0.3
}
}
Rectangle {
id: downloadBar
anchors.fill: parent
Kirigami.Icon {
id: videoLabel
anchors.centerIn: parent
visible: false
source: "media-playback-start-symbolic"
width: Kirigami.Units.iconSizes.huge
height: Kirigami.Units.iconSizes.huge
}
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius
Kirigami.LoadingPlaceholder {
id: downloadBar
QQC2.ProgressBar {
anchors.centerIn: parent
anchors.centerIn: parent
width: parent.width * 0.8
from: 0
to: root.fileTransferInfo.total
value: root.fileTransferInfo.progress
}
text: i18nc("@info:placeholder", "Downloading…")
visible: false
determinate: root.fileTransferInfo.progress > 0
progressBar.from: 0
progressBar.to: root.fileTransferInfo.total
progressBar.value: root.fileTransferInfo.progress
}
Rectangle {
@@ -407,7 +423,7 @@ Video {
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
onTapped: if (root.fileTransferInfo.completed) {
if (root.playbackState == MediaPlayer.PlayingState) {
if (root.playbackState === MediaPlayer.PlayingState) {
root.pause();
} else {
MediaManager.startPlayback();
@@ -429,14 +445,18 @@ Video {
if (root.downloaded) {
playSavedFile();
} else {
playOnFinished = true;
//root.Message.contentModel.setPlayOnFinished(true);
Message.room.downloadFile(root.eventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + Message.room.fileNameToDownload(root.eventId));
}
}
function playSavedFile() {
root.stop();
MediaManager.startPlayback();
root.play();
root.state = "playing";
console.info("current state:" + root.state);
}
onStateChanged: console.info("state changed to " + root.state)
onErrorOccurred: (error, errorString) => console.info("ERR: " + errorString)
}

View File

@@ -107,6 +107,7 @@ void MessageContentModel::initializeModel()
});
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
if (m_room != nullptr && eventId == m_eventId) {
setPlayOnFinished(true);
resetContent();
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
}
@@ -377,6 +378,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
}
return QVariant::fromValue<ChatBarCache *>(m_room->editCache());
}
if (role == PlayOnFinishedrole) {
return m_playOnFinished;
}
return {};
}
@@ -416,6 +420,7 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
roles[MessageContentModel::ThreadRootRole] = "threadRoot";
roles[MessageContentModel::LinkPreviewerRole] = "linkPreviewer";
roles[MessageContentModel::ChatBarCacheRole] = "chatBarCache";
roles[MessageContentModel::PlayOnFinishedrole] = "playOnFinished";
return roles;
}
@@ -559,11 +564,19 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::Text: {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
return TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
if (body.trimmed().isEmpty()) {
return TextHandler().textComponents(i18n("<i>This event does not have any content.</i>"),
Qt::TextFormat::RichText,
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
} else {
return TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
}
}
case MessageComponentType::File: {
QList<MessageComponent> components;
@@ -779,4 +792,9 @@ void MessageContentModel::setThreadsEnabled(bool enableThreads)
m_threadsEnabled = enableThreads;
}
void MessageContentModel::setPlayOnFinished(bool value)
{
m_playOnFinished = value;
}
#include "moc_messagecontentmodel.cpp"

Some files were not shown because too many files have changed in this diff Show More