This is the start of a significant refactoring of everything related to sending messages, which is roughly:
- the chatbox
- action handling
- message sending on the c++ side
- autocompletion of users/rooms/emojis/commands/things i forgot
Notable changes so far include:
- ChatBox is now a ColumnLayout. As part of this, i removed the height animations for now. <del>as far as i can tell, they were broken anyway.</del> I'll readd them later
- Actions were refactored to live outside of the message sending function and are now each an object; it's mostly a wrapper around a function that is executed when the action is invoked
- Everything that used to live in ChatBoxHelper is now in NeoChatRoom; that means that the exact input status (text, message being replied to, message being edited, attachment) is now saved between room switching).
- To edit/reply an event, set `NeoChatRoom::chatBox{edit,reply}Id` to the desired event id, `NeoChatRoom::chatBox{reply,edit}{User,Message}` will then be updated automatically
- Attachments behave equivalently with `NeoChatRoom::chatBoxAttachmentPath`
- Error message reporting from ActionsHandler has been fixed (same fix as in !517) and moved to NeoChatRoom
Broken at the moment:
- [x] Any kind of autocompletion
- [x] Mentions
- [x] Fancy effects
- [x] sed-style edits
- [x] last-user-message edits and replies
- [x] Some of the actions, probably
- [x] Replies from notifications
- [x] Lots of keyboard shortcuts
- [x] Custom emojis
- [x] ChatBox height animations
TODO:
- [x] User / room mentions based on QTextCursors instead of the hack we currently use
- [x] Refactor autocompletion stuff
- [x] ???
- [x] Profit
269 lines
9.4 KiB
C++
269 lines
9.4 KiB
C++
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <room.h>
|
|
|
|
#include <QObject>
|
|
#include <QTextCursor>
|
|
|
|
#include <qcoro/task.h>
|
|
|
|
class NeoChatUser;
|
|
|
|
class PushNotificationState : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
enum State {
|
|
Unknown,
|
|
Default,
|
|
Mute,
|
|
MentionKeyword,
|
|
All,
|
|
};
|
|
Q_ENUM(State);
|
|
};
|
|
|
|
struct Mention {
|
|
QTextCursor cursor;
|
|
QString text;
|
|
int start = 0;
|
|
int position = 0;
|
|
QString id;
|
|
};
|
|
|
|
class NeoChatRoom : public Quotient::Room
|
|
{
|
|
|
|
|
|
Q_OBJECT
|
|
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
|
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
|
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
|
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
|
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
|
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
|
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
|
Q_PROPERTY(QString joinRule READ joinRule CONSTANT)
|
|
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
|
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
|
pushNotificationStateChanged)
|
|
|
|
// Due to problems with QTextDocument, unlike the other properties here, chatBoxText is *not* used to store the text when switching rooms
|
|
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
|
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
|
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
|
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
|
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
|
|
Q_PROPERTY(NeoChatUser *chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
|
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
|
|
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
|
|
|
public:
|
|
enum MessageType {
|
|
Positive,
|
|
Info,
|
|
Error,
|
|
};
|
|
Q_ENUM(MessageType);
|
|
|
|
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
|
|
|
[[nodiscard]] QVariantList getUsersTyping() const;
|
|
|
|
/// Get the interesting last event.
|
|
///
|
|
/// This function respect the showLeaveJoinEvent setting and discard
|
|
/// other not interesting events. This function can return an empty pointer
|
|
/// when the room is empty of RoomMessageEvent.
|
|
[[nodiscard]] const Quotient::RoomMessageEvent *lastEvent(bool ignoreStateEvent = false) const;
|
|
|
|
/// Convenient way to get the last event but in a string format.
|
|
///
|
|
/// \see lastEvent
|
|
/// \see lastEventIsSpoiler
|
|
[[nodiscard]] QString lastEventToString() const;
|
|
|
|
/// Convenient way to check if the last event looks like it has spoilers.
|
|
///
|
|
/// \see lastEvent
|
|
/// \see lastEventToString
|
|
[[nodiscard]] bool lastEventIsSpoiler() const;
|
|
|
|
/// Convenient way to get the QDateTime of the last event.
|
|
///
|
|
/// \see lastEvent
|
|
[[nodiscard]] QDateTime lastActiveTime();
|
|
|
|
/// Get subtitle text for room
|
|
///
|
|
/// Fetches last event and removes markdown formatting
|
|
/// \see lastEventToString
|
|
[[nodiscard]] QString subtitleText();
|
|
|
|
[[nodiscard]] bool isSpace();
|
|
|
|
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
|
|
|
[[nodiscard]] QString joinRule() const;
|
|
|
|
[[nodiscard]] bool hasFileUploading() const
|
|
{
|
|
return m_hasFileUploading;
|
|
}
|
|
void setHasFileUploading(bool value)
|
|
{
|
|
if (value == m_hasFileUploading) {
|
|
return;
|
|
}
|
|
m_hasFileUploading = value;
|
|
Q_EMIT hasFileUploadingChanged();
|
|
}
|
|
|
|
[[nodiscard]] int fileUploadingProgress() const
|
|
{
|
|
return m_fileUploadingProgress;
|
|
}
|
|
void setFileUploadingProgress(int value)
|
|
{
|
|
if (m_fileUploadingProgress == value) {
|
|
return;
|
|
}
|
|
m_fileUploadingProgress = value;
|
|
Q_EMIT fileUploadingProgressChanged();
|
|
}
|
|
|
|
[[nodiscard]] bool readMarkerLoaded() const;
|
|
|
|
Q_INVOKABLE [[nodiscard]] int savedTopVisibleIndex() const;
|
|
Q_INVOKABLE [[nodiscard]] int savedBottomVisibleIndex() const;
|
|
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
|
|
|
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
|
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
|
|
|
Q_INVOKABLE QUrl urlToMxcUrl(const QUrl &mxcUrl);
|
|
|
|
[[nodiscard]] QString avatarMediaId() const;
|
|
|
|
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
|
|
|
|
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
|
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
|
|
|
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
|
|
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;
|
|
|
|
bool isInvite() const;
|
|
|
|
Q_INVOKABLE QString htmlSafeName() const;
|
|
Q_INVOKABLE QString htmlSafeDisplayName() const;
|
|
Q_INVOKABLE void clearInvitationNotification();
|
|
Q_INVOKABLE void reportEvent(const QString &eventId, const QString &reason);
|
|
|
|
Q_INVOKABLE void setPushNotificationState(PushNotificationState::State state);
|
|
|
|
QString chatBoxText() const;
|
|
void setChatBoxText(const QString &text);
|
|
|
|
QString chatBoxReplyId() const;
|
|
void setChatBoxReplyId(const QString &replyId);
|
|
|
|
NeoChatUser *chatBoxReplyUser() const;
|
|
QString chatBoxReplyMessage() const;
|
|
|
|
QString chatBoxEditId() const;
|
|
void setChatBoxEditId(const QString &editId);
|
|
|
|
NeoChatUser *chatBoxEditUser() const;
|
|
QString chatBoxEditMessage() const;
|
|
|
|
QString chatBoxAttachmentPath() const;
|
|
void setChatBoxAttachmentPath(const QString &attachmentPath);
|
|
|
|
QVector<Mention> *mentions();
|
|
|
|
QString savedText() const;
|
|
void setSavedText(const QString &savedText);
|
|
|
|
#ifndef QUOTIENT_07
|
|
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
|
{
|
|
return safeMemberName(userId).toHtmlEscaped();
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
QSet<const Quotient::RoomEvent *> highlights;
|
|
|
|
bool m_hasFileUploading = false;
|
|
int m_fileUploadingProgress = 0;
|
|
|
|
PushNotificationState::State m_currentPushNotificationState = PushNotificationState::State::Unknown;
|
|
bool m_pushNotificationStateUpdating = false;
|
|
|
|
void checkForHighlights(const Quotient::TimelineItem &ti);
|
|
|
|
void onAddNewTimelineEvents(timeline_iter_t from) override;
|
|
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
|
void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override;
|
|
|
|
QCoro::Task<void> doDeleteMessagesByUser(const QString &user);
|
|
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString());
|
|
|
|
QString m_chatBoxText;
|
|
QString m_chatBoxReplyId;
|
|
QString m_chatBoxEditId;
|
|
QString m_chatBoxAttachmentPath;
|
|
QVector<Mention> m_mentions;
|
|
QString m_savedText;
|
|
|
|
private Q_SLOTS:
|
|
void countChanged();
|
|
void updatePushNotificationState(QString type);
|
|
|
|
Q_SIGNALS:
|
|
void cachedInputChanged();
|
|
void busyChanged();
|
|
void hasFileUploadingChanged();
|
|
void fileUploadingProgressChanged();
|
|
void backgroundChanged();
|
|
void readMarkerLoadedChanged();
|
|
void lastActiveTimeChanged();
|
|
void isInviteChanged();
|
|
void displayNameChanged();
|
|
void pushNotificationStateChanged(PushNotificationState::State state);
|
|
void showMessage(MessageType messageType, const QString &message);
|
|
void chatBoxTextChanged();
|
|
void chatBoxReplyIdChanged();
|
|
void chatBoxEditIdChanged();
|
|
void chatBoxAttachmentPathChanged();
|
|
|
|
public Q_SLOTS:
|
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
|
void acceptInvitation();
|
|
void forget();
|
|
void sendTypingNotification(bool isTyping);
|
|
|
|
/// @param rawText The text as it was typed.
|
|
/// @param cleanedText The text with link to the users.
|
|
void postMessage(const QString &rawText,
|
|
const QString &cleanedText,
|
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
|
const QString &replyEventId = QString(),
|
|
const QString &relateToEventId = QString());
|
|
void postHtmlMessage(const QString &text,
|
|
const QString &html,
|
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
|
const QString &replyEventId = QString(),
|
|
const QString &relateToEventId = QString());
|
|
void changeAvatar(const QUrl &localFile);
|
|
void addLocalAlias(const QString &alias);
|
|
void removeLocalAlias(const QString &alias);
|
|
void toggleReaction(const QString &eventId, const QString &reaction);
|
|
void deleteMessagesByUser(const QString &user);
|
|
};
|