emoji picker
emoji picker for adding in chat message / sending reaction
This commit is contained in:
parent
01a1fdd04b
commit
481f9500c6
22 changed files with 577 additions and 160 deletions
|
|
@ -714,6 +714,14 @@ void App::initFonts() {
|
||||||
allFamilies << QFontDatabase::applicationFontFamilies(id);
|
allFamilies << QFontDatabase::applicationFontFamilies(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
QDirIterator itEmojis(":/emoji/font/", QDirIterator::Subdirectories);
|
||||||
|
while (itEmojis.hasNext()) {
|
||||||
|
QString ttf = itEmojis.next();
|
||||||
|
if (itEmojis.fileInfo().isFile()) {
|
||||||
|
auto id = QFontDatabase::addApplicationFont(ttf);
|
||||||
|
allFamilies << QFontDatabase::applicationFontFamilies(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
QDirIterator itFonts(":/linux/font/", QDirIterator::Subdirectories);
|
QDirIterator itFonts(":/linux/font/", QDirIterator::Subdirectories);
|
||||||
while (itFonts.hasNext()) {
|
while (itFonts.hasNext()) {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public:
|
||||||
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
|
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
|
||||||
Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged)
|
Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged)
|
||||||
Q_PROPERTY(bool isGroupChat READ isGroupChat CONSTANT)
|
Q_PROPERTY(bool isGroupChat READ isGroupChat CONSTANT)
|
||||||
Q_PROPERTY(bool isEncrypted MEMBER mIsEncrypted)
|
Q_PROPERTY(bool isEncrypted READ isEncrypted CONSTANT)
|
||||||
Q_PROPERTY(bool isReadOnly READ getIsReadOnly WRITE setIsReadOnly NOTIFY readOnlyChanged)
|
Q_PROPERTY(bool isReadOnly READ getIsReadOnly WRITE setIsReadOnly NOTIFY readOnlyChanged)
|
||||||
|
|
||||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,36 @@
|
||||||
|
|
||||||
DEFINE_ABSTRACT_OBJECT(ChatMessageCore)
|
DEFINE_ABSTRACT_OBJECT(ChatMessageCore)
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
Reaction Reaction::operator=(Reaction r) {
|
||||||
|
mAddress = r.mAddress;
|
||||||
|
mBody = r.mBody;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
bool Reaction::operator==(const Reaction &r) const {
|
||||||
|
return r.mBody == mBody && r.mAddress == mAddress;
|
||||||
|
}
|
||||||
|
bool Reaction::operator!=(Reaction r) {
|
||||||
|
return r.mBody != mBody || r.mAddress != mAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reaction Reaction::createMessageReactionVariant(const QString &body, const QString &address) {
|
||||||
|
Reaction r;
|
||||||
|
r.mBody = body;
|
||||||
|
r.mAddress = address;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant createReactionSingletonVariant(const QString &body, int count = 1) {
|
||||||
|
QVariantMap map;
|
||||||
|
map.insert("body", body);
|
||||||
|
map.insert("count", count);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
QSharedPointer<ChatMessageCore> ChatMessageCore::create(const std::shared_ptr<linphone::ChatMessage> &chatmessage) {
|
QSharedPointer<ChatMessageCore> ChatMessageCore::create(const std::shared_ptr<linphone::ChatMessage> &chatmessage) {
|
||||||
auto sharedPointer = QSharedPointer<ChatMessageCore>(new ChatMessageCore(chatmessage), &QObject::deleteLater);
|
auto sharedPointer = QSharedPointer<ChatMessageCore>(new ChatMessageCore(chatmessage), &QObject::deleteLater);
|
||||||
sharedPointer->setSelf(sharedPointer);
|
sharedPointer->setSelf(sharedPointer);
|
||||||
|
|
@ -64,6 +94,34 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
||||||
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
|
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto reac = chatmessage->getOwnReaction();
|
||||||
|
mOwnReaction = reac ? Utils::coreStringToAppString(reac->getBody()) : QString();
|
||||||
|
for (auto &reaction : chatmessage->getReactions()) {
|
||||||
|
if (reaction) {
|
||||||
|
auto fromAddr = reaction->getFromAddress()->clone();
|
||||||
|
fromAddr->clean();
|
||||||
|
auto reac =
|
||||||
|
Reaction::createMessageReactionVariant(Utils::coreStringToAppString(reaction->getBody()),
|
||||||
|
Utils::coreStringToAppString(fromAddr->asStringUriOnly()));
|
||||||
|
mReactions.append(reac);
|
||||||
|
|
||||||
|
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(),
|
||||||
|
[body = reac.mBody](QVariant data) {
|
||||||
|
auto dataBody = data.toMap()["body"].toString();
|
||||||
|
return body == dataBody;
|
||||||
|
});
|
||||||
|
if (it == mReactionsSingletonMap.end())
|
||||||
|
mReactionsSingletonMap.push_back(createReactionSingletonVariant(reac.mBody, 1));
|
||||||
|
else {
|
||||||
|
auto map = it->toMap();
|
||||||
|
auto count = map["count"].toInt();
|
||||||
|
++count;
|
||||||
|
map.remove("count");
|
||||||
|
map.insert("count", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connect(this, &ChatMessageCore::messageReactionChanged, this, &ChatMessageCore::resetReactionsSingleton);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatMessageCore::~ChatMessageCore() {
|
ChatMessageCore::~ChatMessageCore() {
|
||||||
|
|
@ -84,7 +142,51 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
|
||||||
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
|
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
|
||||||
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
|
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
|
||||||
});
|
});
|
||||||
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() { setIsRead(true); });
|
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() {
|
||||||
|
mChatMessageModelConnection->invokeToCore([this] { setIsRead(true); });
|
||||||
|
});
|
||||||
|
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lSendReaction, [this](const QString &reaction) {
|
||||||
|
mChatMessageModelConnection->invokeToModel([this, reaction] { mChatMessageModel->sendReaction(reaction); });
|
||||||
|
});
|
||||||
|
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lRemoveReaction, [this]() {
|
||||||
|
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->removeReaction(); });
|
||||||
|
});
|
||||||
|
mChatMessageModelConnection->makeConnectToModel(
|
||||||
|
&ChatMessageModel::newMessageReaction,
|
||||||
|
[this](const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
|
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) {
|
||||||
|
auto ownReac = message->getOwnReaction();
|
||||||
|
auto own = ownReac ? Utils::coreStringToAppString(message->getOwnReaction()->getBody()) : QString();
|
||||||
|
// We must reset all the reactions each time cause reactionRemoved is not emitted
|
||||||
|
// when someone change its current reaction
|
||||||
|
QList<Reaction> reactions;
|
||||||
|
for (auto &reaction : message->getReactions()) {
|
||||||
|
if (reaction) {
|
||||||
|
auto fromAddr = reaction->getFromAddress()->clone();
|
||||||
|
fromAddr->clean();
|
||||||
|
reactions.append(Reaction::createMessageReactionVariant(
|
||||||
|
Utils::coreStringToAppString(reaction->getBody()),
|
||||||
|
Utils::coreStringToAppString(fromAddr->asStringUriOnly())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mChatMessageModelConnection->invokeToCore([this, own, reactions] {
|
||||||
|
setOwnReaction(own);
|
||||||
|
setReactions(reactions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mChatMessageModelConnection->makeConnectToModel(
|
||||||
|
&ChatMessageModel::reactionRemoved, [this](const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
|
const std::shared_ptr<const linphone::Address> &address) {
|
||||||
|
auto reac = message->getOwnReaction();
|
||||||
|
auto own = reac ? Utils::coreStringToAppString(message->getOwnReaction()->getBody()) : QString();
|
||||||
|
auto addr = address->clone();
|
||||||
|
addr->clean();
|
||||||
|
QString addressString = Utils::coreStringToAppString(addr->asStringUriOnly());
|
||||||
|
mChatMessageModelConnection->invokeToCore([this, own, addressString] {
|
||||||
|
removeReaction(addressString);
|
||||||
|
setOwnReaction(own);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
mChatMessageModelConnection->makeConnectToModel(
|
mChatMessageModelConnection->makeConnectToModel(
|
||||||
&ChatMessageModel::msgStateChanged,
|
&ChatMessageModel::msgStateChanged,
|
||||||
|
|
@ -159,6 +261,94 @@ void ChatMessageCore::setIsRead(bool read) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChatMessageCore::getOwnReaction() const {
|
||||||
|
return mOwnReaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::setOwnReaction(const QString &reaction) {
|
||||||
|
if (mOwnReaction != reaction) {
|
||||||
|
mOwnReaction = reaction;
|
||||||
|
emit messageReactionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Reaction> ChatMessageCore::getReactions() const {
|
||||||
|
return mReactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> ChatMessageCore::getReactionsSingleton() const {
|
||||||
|
return mReactionsSingletonMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::setReactions(const QList<Reaction> &reactions) {
|
||||||
|
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
||||||
|
mReactions = reactions;
|
||||||
|
emit messageReactionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::resetReactionsSingleton() {
|
||||||
|
mReactionsSingletonMap.clear();
|
||||||
|
for (auto &reac : mReactions) {
|
||||||
|
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(),
|
||||||
|
[body = reac.mBody](QVariant data) {
|
||||||
|
auto dataBody = data.toMap()["body"].toString();
|
||||||
|
return body == dataBody;
|
||||||
|
});
|
||||||
|
if (it == mReactionsSingletonMap.end())
|
||||||
|
mReactionsSingletonMap.push_back(createReactionSingletonVariant(reac.mBody, 1));
|
||||||
|
else {
|
||||||
|
auto map = it->toMap();
|
||||||
|
auto count = map["count"].toInt();
|
||||||
|
++count;
|
||||||
|
map.remove("count");
|
||||||
|
map.insert("count", count);
|
||||||
|
mReactionsSingletonMap.erase(it);
|
||||||
|
mReactionsSingletonMap.push_back(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit singletonReactionMapChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::removeReaction(const Reaction &reaction) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto &r : mReactions) {
|
||||||
|
if (reaction == r) {
|
||||||
|
mReactions.removeAt(i);
|
||||||
|
emit messageReactionChanged();
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::removeOneReactionFromSingletonMap(const QString &body) {
|
||||||
|
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(), [body](QVariant data) {
|
||||||
|
auto dataBody = data.toMap()["body"].toString();
|
||||||
|
return body == dataBody;
|
||||||
|
});
|
||||||
|
if (it != mReactionsSingletonMap.end()) {
|
||||||
|
auto map = it->toMap();
|
||||||
|
auto count = map["count"].toInt();
|
||||||
|
if (count <= 1) mReactionsSingletonMap.erase(it);
|
||||||
|
else {
|
||||||
|
--count;
|
||||||
|
map.remove("count");
|
||||||
|
map.insert("count", count);
|
||||||
|
}
|
||||||
|
emit messageReactionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageCore::removeReaction(const QString &address) {
|
||||||
|
int n = mReactions.removeIf([address, this](Reaction r) {
|
||||||
|
if (r.mAddress == address) {
|
||||||
|
removeOneReactionFromSingletonMap(r.mBody);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (n > 0) emit messageReactionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
LinphoneEnums::ChatMessageState ChatMessageCore::getMessageState() const {
|
LinphoneEnums::ChatMessageState ChatMessageCore::getMessageState() const {
|
||||||
return mMessageState;
|
return mMessageState;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,22 @@
|
||||||
|
|
||||||
#include <linphone++/linphone.hh>
|
#include <linphone++/linphone.hh>
|
||||||
|
|
||||||
|
struct Reaction {
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
Q_PROPERTY(QString body MEMBER mBody)
|
||||||
|
Q_PROPERTY(QString address MEMBER mAddress)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString mBody;
|
||||||
|
QString mAddress;
|
||||||
|
|
||||||
|
Reaction operator=(Reaction r);
|
||||||
|
bool operator==(const Reaction &r) const;
|
||||||
|
bool operator!=(Reaction r);
|
||||||
|
static Reaction createMessageReactionVariant(const QString &body, const QString &address);
|
||||||
|
};
|
||||||
|
|
||||||
class ChatCore;
|
class ChatCore;
|
||||||
|
|
||||||
class ChatMessageCore : public QObject, public AbstractObject {
|
class ChatMessageCore : public QObject, public AbstractObject {
|
||||||
|
|
@ -50,6 +66,9 @@ class ChatMessageCore : public QObject, public AbstractObject {
|
||||||
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
|
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
|
||||||
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
|
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
|
||||||
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
|
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
|
||||||
|
Q_PROPERTY(QString ownReaction READ getOwnReaction WRITE setOwnReaction NOTIFY messageReactionChanged)
|
||||||
|
Q_PROPERTY(QList<Reaction> reactions READ getReactions WRITE setReactions NOTIFY messageReactionChanged)
|
||||||
|
Q_PROPERTY(QList<QVariant> reactionsSingleton READ getReactionsSingleton NOTIFY singletonReactionMapChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
|
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
|
||||||
|
|
@ -76,6 +95,16 @@ public:
|
||||||
bool isRead() const;
|
bool isRead() const;
|
||||||
void setIsRead(bool read);
|
void setIsRead(bool read);
|
||||||
|
|
||||||
|
QString getOwnReaction() const;
|
||||||
|
void setOwnReaction(const QString &reaction);
|
||||||
|
QList<Reaction> getReactions() const;
|
||||||
|
QList<QVariant> getReactionsSingleton() const;
|
||||||
|
void removeOneReactionFromSingletonMap(const QString &body);
|
||||||
|
void resetReactionsSingleton();
|
||||||
|
void setReactions(const QList<Reaction> &reactions);
|
||||||
|
void removeReaction(const Reaction &reaction);
|
||||||
|
void removeReaction(const QString &address);
|
||||||
|
|
||||||
LinphoneEnums::ChatMessageState getMessageState() const;
|
LinphoneEnums::ChatMessageState getMessageState() const;
|
||||||
void setMessageState(LinphoneEnums::ChatMessageState state);
|
void setMessageState(LinphoneEnums::ChatMessageState state);
|
||||||
|
|
||||||
|
|
@ -89,11 +118,15 @@ signals:
|
||||||
void isReadChanged(bool read);
|
void isReadChanged(bool read);
|
||||||
void isRemoteMessageChanged(bool isRemote);
|
void isRemoteMessageChanged(bool isRemote);
|
||||||
void messageStateChanged();
|
void messageStateChanged();
|
||||||
|
void messageReactionChanged();
|
||||||
|
void singletonReactionMapChanged();
|
||||||
|
|
||||||
void lDelete();
|
void lDelete();
|
||||||
void deleted();
|
void deleted();
|
||||||
void lMarkAsRead();
|
void lMarkAsRead();
|
||||||
void readChanged();
|
void readChanged();
|
||||||
|
void lSendReaction(const QString &reaction);
|
||||||
|
void lRemoveReaction();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DECLARE_ABSTRACT_OBJECT QString mText;
|
DECLARE_ABSTRACT_OBJECT QString mText;
|
||||||
|
|
@ -105,6 +138,9 @@ private:
|
||||||
QString mFromName;
|
QString mFromName;
|
||||||
QString mPeerName;
|
QString mPeerName;
|
||||||
QString mMessageId;
|
QString mMessageId;
|
||||||
|
QString mOwnReaction;
|
||||||
|
QList<Reaction> mReactions;
|
||||||
|
QList<QVariant> mReactionsSingletonMap;
|
||||||
QDateTime mTimestamp;
|
QDateTime mTimestamp;
|
||||||
bool mIsRemoteMessage = false;
|
bool mIsRemoteMessage = false;
|
||||||
bool mIsFromChatGroup = false;
|
bool mIsFromChatGroup = false;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ EmojiModel::EmojiModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int EmojiModel::count(QString category) {
|
int EmojiModel::count(QString category) {
|
||||||
qDebug() << "count of category" << category << emojies[category].size();
|
|
||||||
return emojies[category].size();
|
return emojies[category].size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
Linphone/data/font/EmojiFont.ttf
Normal file
BIN
Linphone/data/font/EmojiFont.ttf
Normal file
Binary file not shown.
|
|
@ -19,6 +19,9 @@
|
||||||
<file>font/Noto_Sans/NotoSans-Thin.ttf</file>
|
<file>font/Noto_Sans/NotoSans-Thin.ttf</file>
|
||||||
<file>font/Noto_Sans/NotoSans-ThinItalic.ttf</file>
|
<file>font/Noto_Sans/NotoSans-ThinItalic.ttf</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
<qresource prefix="/emoji">
|
||||||
|
<file>font/EmojiFont.ttf</file>
|
||||||
|
</qresource>
|
||||||
<qresource prefix="/linux">
|
<qresource prefix="/linux">
|
||||||
<file>font/OpenMoji-color-cbdt.ttf</file>
|
<file>font/OpenMoji-color-cbdt.ttf</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,20 @@ void ChatMessageModel::deleteMessageFromChatRoom() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatMessageModel::sendReaction(const QString &reaction) {
|
||||||
|
auto linReaction = mMonitor->createReaction(Utils::appStringToCoreString(reaction));
|
||||||
|
linReaction->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatMessageModel::removeReaction() {
|
||||||
|
sendReaction(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatMessageModel::getOwnReaction() const {
|
||||||
|
auto reaction = mMonitor->getOwnReaction();
|
||||||
|
return reaction ? Utils::coreStringToAppString(reaction->getBody()) : QString();
|
||||||
|
}
|
||||||
|
|
||||||
linphone::ChatMessage::State ChatMessageModel::getState() const {
|
linphone::ChatMessage::State ChatMessageModel::getState() const {
|
||||||
return mMonitor->getState();
|
return mMonitor->getState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,14 @@ public:
|
||||||
|
|
||||||
void computeDeliveryStatus();
|
void computeDeliveryStatus();
|
||||||
|
|
||||||
|
void sendReaction(const QString &reaction);
|
||||||
|
|
||||||
|
void removeReaction();
|
||||||
|
|
||||||
linphone::ChatMessage::State getState() const;
|
linphone::ChatMessage::State getState() const;
|
||||||
|
|
||||||
|
QString getOwnReaction() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void messageDeleted();
|
void messageDeleted();
|
||||||
void messageRead();
|
void messageRead();
|
||||||
|
|
@ -94,33 +100,34 @@ private:
|
||||||
|
|
||||||
DECLARE_ABSTRACT_OBJECT
|
DECLARE_ABSTRACT_OBJECT
|
||||||
|
|
||||||
void onMsgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state);
|
void onMsgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
|
linphone::ChatMessage::State state) override;
|
||||||
void onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction);
|
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) override;
|
||||||
void onReactionRemoved(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onReactionRemoved(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<const linphone::Address> &address);
|
const std::shared_ptr<const linphone::Address> &address) override;
|
||||||
void onFileTransferTerminated(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onFileTransferTerminated(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<linphone::Content> &content);
|
const std::shared_ptr<linphone::Content> &content) override;
|
||||||
void onFileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onFileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<linphone::Content> &content,
|
const std::shared_ptr<linphone::Content> &content,
|
||||||
const std::shared_ptr<const linphone::Buffer> &buffer);
|
const std::shared_ptr<const linphone::Buffer> &buffer) override;
|
||||||
std::shared_ptr<linphone::Buffer> onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
|
std::shared_ptr<linphone::Buffer> onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<linphone::Content> &content,
|
const std::shared_ptr<linphone::Content> &content,
|
||||||
size_t offset,
|
size_t offset,
|
||||||
size_t size);
|
size_t size) override;
|
||||||
void onFileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onFileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<linphone::Content> &content,
|
const std::shared_ptr<linphone::Content> &content,
|
||||||
size_t offset,
|
size_t offset,
|
||||||
size_t size,
|
size_t size,
|
||||||
const std::shared_ptr<linphone::Buffer> &buffer);
|
const std::shared_ptr<linphone::Buffer> &buffer) override;
|
||||||
void onFileTransferProgressIndication(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onFileTransferProgressIndication(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<linphone::Content> &content,
|
const std::shared_ptr<linphone::Content> &content,
|
||||||
size_t offset,
|
size_t offset,
|
||||||
size_t total);
|
size_t total) override;
|
||||||
void onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
|
void onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||||
const std::shared_ptr<const linphone::ParticipantImdnState> &state);
|
const std::shared_ptr<const linphone::ParticipantImdnState> &state) override;
|
||||||
void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message);
|
void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||||
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message);
|
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ constexpr int Constants::DefaultFontPointSize;
|
||||||
constexpr char Constants::DefaultEmojiFont[];
|
constexpr char Constants::DefaultEmojiFont[];
|
||||||
constexpr int Constants::DefaultEmojiFontPointSize;
|
constexpr int Constants::DefaultEmojiFontPointSize;
|
||||||
QStringList Constants::getReactionsList() {
|
QStringList Constants::getReactionsList() {
|
||||||
return {"❤️", "👍", "😂", "😮", "😢"};
|
return {"❤️", "👍", "😂", "😮", "😢", "😠"};
|
||||||
}
|
}
|
||||||
constexpr char Constants::AppDomain[];
|
constexpr char Constants::AppDomain[];
|
||||||
constexpr size_t Constants::MaxLogsCollectionSize;
|
constexpr size_t Constants::MaxLogsCollectionSize;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ public:
|
||||||
static constexpr char DefaultEmojiFont[] = "Apple Color Emoji";
|
static constexpr char DefaultEmojiFont[] = "Apple Color Emoji";
|
||||||
#else
|
#else
|
||||||
static constexpr char DefaultEmojiFont[] = "Noto Color Emoji";
|
static constexpr char DefaultEmojiFont[] = "Noto Color Emoji";
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
static constexpr int DefaultEmojiFontPointSize = 10;
|
static constexpr int DefaultEmojiFontPointSize = 10;
|
||||||
static QStringList getReactionsList();
|
static QStringList getReactionsList();
|
||||||
|
|
|
||||||
|
|
@ -1841,7 +1841,7 @@ QString Utils::encodeTextToQmlRichFormat(const QString &text, const QVariantMap
|
||||||
if (lastWasUrl && formattedText.last().back() != ' ') {
|
if (lastWasUrl && formattedText.last().back() != ' ') {
|
||||||
formattedText.push_back(" ");
|
formattedText.push_back(" ");
|
||||||
}
|
}
|
||||||
return "<p style=\"white-space:pre-wrap;\">" + formattedText.join("") + "</p>";
|
return "<p style=\"white-space:pre-wrap;\">" + formattedText.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Utils::encodeEmojiToQmlRichFormat(const QString &body) {
|
QString Utils::encodeEmojiToQmlRichFormat(const QString &body) {
|
||||||
|
|
@ -1870,7 +1870,18 @@ QString Utils::encodeEmojiToQmlRichFormat(const QString &body) {
|
||||||
return fmtBody;
|
return fmtBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::codepointIsEmoji(uint code) {
|
QString Utils::getFilename(QUrl url) {
|
||||||
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) ||
|
return url.fileName();
|
||||||
(code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f;
|
}
|
||||||
|
|
||||||
|
bool Utils::codepointIsEmoji(uint code) {
|
||||||
|
return ((code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
|
||||||
|
(code >= 0x1F300 && code <= 0x1F5FF) || // Misc Symbols and Pictographs
|
||||||
|
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport & Map
|
||||||
|
(code >= 0x1F700 && code <= 0x1F77F) || // Alchemical Symbols
|
||||||
|
(code >= 0x1F900 && code <= 0x1F9FF) || // Supplemental Symbols & Pictographs
|
||||||
|
(code >= 0x1FA70 && code <= 0x1FAFF) || // Symbols and Pictographs Extended-A
|
||||||
|
(code >= 0x2600 && code <= 0x26FF) || // Miscellaneous Symbols
|
||||||
|
(code >= 0x2700 && code <= 0x27BF) // Dingbats
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,8 @@ public:
|
||||||
Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text,
|
Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text,
|
||||||
const QVariantMap &options = QVariantMap());
|
const QVariantMap &options = QVariantMap());
|
||||||
Q_INVOKABLE static QString encodeEmojiToQmlRichFormat(const QString &body);
|
Q_INVOKABLE static QString encodeEmojiToQmlRichFormat(const QString &body);
|
||||||
|
|
||||||
|
Q_INVOKABLE static QString getFilename(QUrl url);
|
||||||
static bool codepointIsEmoji(uint code);
|
static bool codepointIsEmoji(uint code);
|
||||||
|
|
||||||
// QDir findDirectoryByName(QString startPath, QString name);
|
// QDir findDirectoryByName(QString startPath, QString name);
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ Control.Button {
|
||||||
property var checkedImageColor: style?.image?.checked || Qt.darker(contentImageColor, 1.1)
|
property var checkedImageColor: style?.image?.checked || Qt.darker(contentImageColor, 1.1)
|
||||||
property var pressedImageColor: style?.image?.pressed || Qt.darker(contentImageColor, 1.1)
|
property var pressedImageColor: style?.image?.pressed || Qt.darker(contentImageColor, 1.1)
|
||||||
property bool asynchronous: false
|
property bool asynchronous: false
|
||||||
|
property var textFormat: Text.AutoText
|
||||||
spacing: Math.round(5 * DefaultStyle.dp)
|
spacing: Math.round(5 * DefaultStyle.dp)
|
||||||
hoverEnabled: enabled
|
hoverEnabled: enabled
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
@ -98,6 +99,7 @@ Control.Button {
|
||||||
width: textMetrics.advanceWidth
|
width: textMetrics.advanceWidth
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
text: mainItem.text
|
text: mainItem.text
|
||||||
|
textFormat: mainItem.textFormat
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
color: mainItem.checkable && mainItem.checked
|
color: mainItem.checkable && mainItem.checked
|
||||||
? mainItem.checkedColor || mainItem.pressedColor
|
? mainItem.checkedColor || mainItem.pressedColor
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import QtQuick.Controls.Basic as Control
|
||||||
import Linphone
|
import Linphone
|
||||||
import UtilsCpp
|
import UtilsCpp
|
||||||
import SettingsCpp
|
import SettingsCpp
|
||||||
|
import ConstantsCpp
|
||||||
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
|
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
|
||||||
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
|
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
|
||||||
|
|
||||||
|
|
@ -16,13 +17,15 @@ Control.Control {
|
||||||
property string imgUrl
|
property string imgUrl
|
||||||
|
|
||||||
property ChatMessageGui chatMessage
|
property ChatMessageGui chatMessage
|
||||||
|
property string ownReaction: chatMessage? chatMessage.core.ownReaction : ""
|
||||||
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
|
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
|
||||||
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
|
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
|
||||||
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
|
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
|
||||||
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
|
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
|
||||||
property string richFormatText: modelData.core.hasTextContent ? UtilsCpp.encodeTextToQmlRichFormat(modelData.core.utf8Text) : ""
|
property string richFormatText: chatMessage.core.hasTextContent ? UtilsCpp.encodeTextToQmlRichFormat(chatMessage.core.utf8Text) : ""
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
property bool linkHovered: false
|
property bool linkHovered: false
|
||||||
|
property real maxWidth: parent?.width || Math.round(300 * DefaultStyle.dp)
|
||||||
|
|
||||||
signal messageDeletionRequested()
|
signal messageDeletionRequested()
|
||||||
|
|
||||||
|
|
@ -56,24 +59,26 @@ Control.Control {
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
visible: mainItem.isFromChatGroup
|
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage
|
||||||
opacity: mainItem.isRemoteMessage && mainItem.isFirstMessage ? 1 : 0
|
Layout.preferredWidth: mainItem.isRemoteMessage ? 26 * DefaultStyle.dp : 0
|
||||||
Layout.preferredWidth: 26 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: 26 * DefaultStyle.dp
|
Layout.preferredHeight: 26 * DefaultStyle.dp
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||||
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
||||||
}
|
}
|
||||||
Control.Control {
|
Item {
|
||||||
id: chatBubble
|
|
||||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||||
Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
Layout.preferredHeight: childrenRect.height
|
||||||
|
Layout.preferredWidth: childrenRect.width
|
||||||
|
Control.Control {
|
||||||
|
id: chatBubble
|
||||||
spacing: Math.round(2 * DefaultStyle.dp)
|
spacing: Math.round(2 * DefaultStyle.dp)
|
||||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||||
leftPadding: Math.round(12 * DefaultStyle.dp)
|
leftPadding: Math.round(12 * DefaultStyle.dp)
|
||||||
rightPadding: Math.round(12 * DefaultStyle.dp)
|
rightPadding: Math.round(12 * DefaultStyle.dp)
|
||||||
|
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||||
|
|
||||||
MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
|
MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
|
||||||
id: defaultMouseArea
|
id: defaultMouseArea
|
||||||
|
|
@ -118,12 +123,12 @@ Control.Control {
|
||||||
id: textElement
|
id: textElement
|
||||||
visible: mainItem.richFormatText !== ""
|
visible: mainItem.richFormatText !== ""
|
||||||
text: mainItem.richFormatText
|
text: mainItem.richFormatText
|
||||||
textFormat: Text.RichText
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
horizontalAlignment: modelData.core.isRemoteMessage ? Text.AlignLeft : Text.AlignRight
|
horizontalAlignment: Text.AlignLeft
|
||||||
color: DefaultStyle.main2_700
|
color: DefaultStyle.main2_700
|
||||||
|
textFormat: Text.RichText
|
||||||
font {
|
font {
|
||||||
pixelSize: Typography.p1.pixelSize
|
pixelSize: Typography.p1.pixelSize
|
||||||
weight: Typography.p1.weight
|
weight: Typography.p1.weight
|
||||||
|
|
@ -143,13 +148,13 @@ Control.Control {
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
Loader {
|
Loader {
|
||||||
id: invitationLoader
|
id: invitationLoader
|
||||||
active: modelData.core.conferenceInfo !== null
|
active: mainItem.chatMessage.core.conferenceInfo !== null
|
||||||
sourceComponent: invitationComponent
|
sourceComponent: invitationComponent
|
||||||
}
|
}
|
||||||
Component {
|
Component {
|
||||||
id: invitationComponent
|
id: invitationComponent
|
||||||
ChatMessageInvitationBubble {
|
ChatMessageInvitationBubble {
|
||||||
conferenceInfoGui: modelData.core.conferenceInfo
|
conferenceInfoGui: mainItem.chatMessage.core.conferenceInfo
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
|
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
|
||||||
|
|
@ -158,9 +163,9 @@ Control.Control {
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||||
Text {
|
Text {
|
||||||
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false)
|
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false)
|
||||||
color: DefaultStyle.main2_500main
|
color: DefaultStyle.main2_500main
|
||||||
font {
|
font {
|
||||||
pixelSize: Typography.p3.pixelSize
|
pixelSize: Typography.p3.pixelSize
|
||||||
|
|
@ -185,9 +190,65 @@ Control.Control {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Button {
|
||||||
|
id: reactionsButton
|
||||||
|
visible: reactionList.count > 0
|
||||||
|
anchors.top: chatBubble.bottom
|
||||||
|
Binding {
|
||||||
|
target: reactionsButton
|
||||||
|
when: !mainItem.isRemoteMessage
|
||||||
|
property: "anchors.left"
|
||||||
|
value: chatBubble.left
|
||||||
|
}
|
||||||
|
Binding {
|
||||||
|
target: reactionsButton
|
||||||
|
when: mainItem.isRemoteMessage
|
||||||
|
property: "anchors.right"
|
||||||
|
value: chatBubble.right
|
||||||
|
}
|
||||||
|
anchors.topMargin: Math.round(-6 * DefaultStyle.dp)
|
||||||
|
topPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
|
bottomPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
|
leftPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
|
rightPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
|
background: Rectangle {
|
||||||
|
color: DefaultStyle.grey_100
|
||||||
|
border.color: DefaultStyle.grey_0
|
||||||
|
border.width: Math.round(2 * DefaultStyle.dp)
|
||||||
|
radius: Math.round(20 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Math.round(6 * DefaultStyle.dp)
|
||||||
|
Repeater {
|
||||||
|
id: reactionList
|
||||||
|
model: mainItem.chatMessage ? mainItem.chatMessage.core.reactionsSingleton : []
|
||||||
|
delegate: RowLayout {
|
||||||
|
spacing: Math.round(3 * DefaultStyle.dp)
|
||||||
|
Text {
|
||||||
|
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData.body)
|
||||||
|
textFormat: Text.RichText
|
||||||
|
font {
|
||||||
|
pixelSize: Math.round(15 * DefaultStyle.dp)
|
||||||
|
weight: Math.round(400 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
visible: modelData.count > 1
|
||||||
|
text: modelData.count
|
||||||
|
verticalAlignment: Text.AlignBottom
|
||||||
|
font {
|
||||||
|
pixelSize: Typography.p4.pixelSize
|
||||||
|
weight: Typography.p4.weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: actionsLayout
|
id: actionsLayout
|
||||||
visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered
|
visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered || emojiButton.popup.opened
|
||||||
Layout.leftMargin: Math.round(8 * DefaultStyle.dp)
|
Layout.leftMargin: Math.round(8 * DefaultStyle.dp)
|
||||||
Layout.rightMargin: Math.round(8 * DefaultStyle.dp)
|
Layout.rightMargin: Math.round(8 * DefaultStyle.dp)
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
@ -208,7 +269,7 @@ Control.Control {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var success = UtilsCpp.copyToClipboard(modelData.core.text)
|
var success = UtilsCpp.copyToClipboard(mainItem.chatMessage.core.text)
|
||||||
//: Copied
|
//: Copied
|
||||||
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
||||||
//: "to clipboard"
|
//: "to clipboard"
|
||||||
|
|
@ -229,12 +290,55 @@ Control.Control {
|
||||||
}
|
}
|
||||||
style: ButtonStyle.hoveredBackgroundRed
|
style: ButtonStyle.hoveredBackgroundRed
|
||||||
}
|
}
|
||||||
|
// Rectangle {
|
||||||
|
// Layout.fillWidth: true
|
||||||
|
// Layout.preferredHeight: Math.round(1 * DefaultStyle.dp)
|
||||||
|
// color: DefaultStyle.main2_200
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BigButton {
|
PopupButton {
|
||||||
id: emojiButton
|
id: emojiButton
|
||||||
style: ButtonStyle.noBackground
|
style: ButtonStyle.noBackground
|
||||||
icon.source: AppIcons.smiley
|
icon.source: AppIcons.smiley
|
||||||
|
popup.contentItem: RowLayout {
|
||||||
|
Repeater {
|
||||||
|
model: ConstantsCpp.reactionsList
|
||||||
|
delegate: Button {
|
||||||
|
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData)
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: DefaultStyle.grey_200
|
||||||
|
radius: parent.width * 4
|
||||||
|
visible: mainItem.ownReaction === modelData
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
if(modelData) {
|
||||||
|
if (mainItem.ownReaction === modelData) mainItem.chatMessage.core.lRemoveReaction()
|
||||||
|
else mainItem.chatMessage.core.lSendReaction(modelData)
|
||||||
|
}
|
||||||
|
emojiButton.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PopupButton {
|
||||||
|
id: emojiPickerButton
|
||||||
|
icon.source: AppIcons.plusCircle
|
||||||
|
popup.width: Math.round(393 * DefaultStyle.dp)
|
||||||
|
popup.height: Math.round(291 * DefaultStyle.dp)
|
||||||
|
popup.contentItem: EmojiPicker {
|
||||||
|
id: emojiPicker
|
||||||
|
onEmojiClicked: (emoji) => {
|
||||||
|
if (mainItem.chatMessage) {
|
||||||
|
if (mainItem.ownReaction === emoji) mainItem.chatMessage.core.lRemoveReaction()
|
||||||
|
else mainItem.chatMessage.core.lSendReaction(emoji)
|
||||||
|
}
|
||||||
|
emojiPickerButton.close()
|
||||||
|
emojiButton.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item{Layout.fillWidth: true}
|
Item{Layout.fillWidth: true}
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,10 @@ ListView {
|
||||||
|
|
||||||
delegate: ChatMessage {
|
delegate: ChatMessage {
|
||||||
chatMessage: modelData
|
chatMessage: modelData
|
||||||
property real maxWidth: Math.round(mainItem.width * (3/4))
|
maxWidth: Math.round(mainItem.width * (3/4))
|
||||||
onVisibleChanged: if (!modelData.core.isRead) modelData.core.lMarkAsRead()
|
onVisibleChanged: {
|
||||||
|
if (visible && !modelData.core.isRead) modelData.core.lMarkAsRead()
|
||||||
|
}
|
||||||
width: mainItem.width
|
width: mainItem.width
|
||||||
property var previousIndex: index - 1
|
property var previousIndex: index - 1
|
||||||
property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress
|
property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Linphone
|
import Linphone
|
||||||
|
import UtilsCpp
|
||||||
|
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
||||||
|
|
||||||
// import EmojiModel
|
// import EmojiModel
|
||||||
|
|
||||||
|
|
@ -42,7 +44,9 @@ ColumnLayout {
|
||||||
property var searchModel: ListModel {}
|
property var searchModel: ListModel {}
|
||||||
property bool searchMode: false
|
property bool searchMode: false
|
||||||
property int skinColor: -1
|
property int skinColor: -1
|
||||||
signal emojiClicked()
|
|
||||||
|
signal emojiClicked(string emoji)
|
||||||
|
|
||||||
function changeSkinColor(index) {
|
function changeSkinColor(index) {
|
||||||
if (index !== skinColors.current) {
|
if (index !== skinColors.current) {
|
||||||
skinColors.itemAt(skinColors.current + 1).scale = 0.6
|
skinColors.itemAt(skinColors.current + 1).scale = 0.6
|
||||||
|
|
@ -152,13 +156,14 @@ ColumnLayout {
|
||||||
ListView {
|
ListView {
|
||||||
id: list
|
id: list
|
||||||
width: mainItem.width
|
width: mainItem.width
|
||||||
height: mainItem.height - categoriesRow.height
|
height: Math.round(250 * DefaultStyle.dp)
|
||||||
|
Layout.fillHeight: true
|
||||||
model: mainItem.categories
|
model: mainItem.categories
|
||||||
spacing: Math.round(30 * DefaultStyle.dp)
|
spacing: Math.round(30 * DefaultStyle.dp)
|
||||||
topMargin: Math.round(7 * DefaultStyle.dp)
|
topMargin: Math.round(7 * DefaultStyle.dp)
|
||||||
bottomMargin: Math.round(7 * DefaultStyle.dp)
|
bottomMargin: Math.round(7 * DefaultStyle.dp)
|
||||||
leftMargin: Math.round(12 * DefaultStyle.dp)
|
leftMargin: Math.round(12 * DefaultStyle.dp)
|
||||||
// clip: true
|
clip: true
|
||||||
delegate: GridLayout {
|
delegate: GridLayout {
|
||||||
id: grid
|
id: grid
|
||||||
property string category: mainItem.searchMode ? 'Search Result' : modelData
|
property string category: mainItem.searchMode ? 'Search Result' : modelData
|
||||||
|
|
@ -178,13 +183,11 @@ ColumnLayout {
|
||||||
Layout.bottomMargin: Math.round(8 * DefaultStyle.dp)
|
Layout.bottomMargin: Math.round(8 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
Repeater {
|
Repeater {
|
||||||
onCountChanged: console.log("emoji list count :", count)
|
|
||||||
model: mainItem.searchMode ? mainItem.searchModel : mainItem.model.count(grid.category)
|
model: mainItem.searchMode ? mainItem.searchModel : mainItem.model.count(grid.category)
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
property alias es: emojiSvg
|
property alias es: emojiSvg
|
||||||
Layout.preferredWidth: Math.round(40 * DefaultStyle.dp)
|
Layout.preferredWidth: Math.round(40 * DefaultStyle.dp)
|
||||||
Layout.preferredHeight: Math.round(40 * DefaultStyle.dp)
|
Layout.preferredHeight: Math.round(40 * DefaultStyle.dp)
|
||||||
RectangleTest{anchors.fill: parent}
|
|
||||||
radius: Math.round(40 * DefaultStyle.dp)
|
radius: Math.round(40 * DefaultStyle.dp)
|
||||||
color: mouseArea.containsMouse ? '#e6e6e6' : '#ffffff'
|
color: mouseArea.containsMouse ? '#e6e6e6' : '#ffffff'
|
||||||
Image {
|
Image {
|
||||||
|
|
@ -199,10 +202,11 @@ ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
property string imageUrl: emojiSvg.source
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var tag = "<img src = '%1' width = '20' height = '20' align = 'top'>"
|
var emojiInFont = Utils.codepointFromFilename(UtilsCpp.getFilename(emojiSvg.source))
|
||||||
if (mainItem.editor) mainItem.editor.insert(mainItem.editor.cursorPosition, tag.arg(emojiSvg.source))
|
if (mainItem.editor) mainItem.editor.insert(mainItem.editor.cursorPosition, emojiInFont)
|
||||||
mainItem.emojiClicked(tag.arg(emojiSvg.source))
|
mainItem.emojiClicked(emojiInFont)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ TextEdit {
|
||||||
property real placeholderWeight: Typography.p1.weight
|
property real placeholderWeight: Typography.p1.weight
|
||||||
property color placeholderTextColor: color
|
property color placeholderTextColor: color
|
||||||
property alias background: background.data
|
property alias background: background.data
|
||||||
property bool hoverEnabled: false
|
property bool hoverEnabled: true
|
||||||
property bool hovered: mouseArea.hoverEnabled && mouseArea.containsMouse
|
property bool hovered: mouseArea.hoverEnabled && mouseArea.containsMouse
|
||||||
topPadding: Math.round(5 * DefaultStyle.dp)
|
topPadding: Math.round(5 * DefaultStyle.dp)
|
||||||
bottomPadding: Math.round(5 * DefaultStyle.dp)
|
bottomPadding: Math.round(5 * DefaultStyle.dp)
|
||||||
|
|
@ -36,10 +36,10 @@ TextEdit {
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
enabled: mainItem.hoverEnabled
|
||||||
hoverEnabled: mainItem.hoverEnabled
|
hoverEnabled: mainItem.hoverEnabled
|
||||||
// onPressed: mainItem.forceActiveFocus()
|
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
cursorShape: mainItem.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: mainItem.hovered ? Qt.IBeamCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
|
||||||
|
|
@ -819,4 +819,11 @@ function updatePosition(scrollItem, list){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform svg file to unicode emoji
|
||||||
|
function codepointFromFilename(filename) {
|
||||||
|
let baseName = filename.split('.')[0];
|
||||||
|
let parts = baseName.replace(/_/g, '-').split('-');
|
||||||
|
let codePoints = parts.map(hex => parseInt(hex, 16));
|
||||||
|
var unicode = String.fromCodePoint(...codePoints);
|
||||||
|
return unicode;
|
||||||
|
}
|
||||||
|
|
@ -91,6 +91,38 @@ RowLayout {
|
||||||
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
||||||
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||||
Control.ScrollBar.vertical: scrollbar
|
Control.ScrollBar.vertical: scrollbar
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: emojiPickerPopup
|
||||||
|
y: Math.round(chatMessagesListView.y + chatMessagesListView.height - height - 8*DefaultStyle.dp)
|
||||||
|
x: Math.round(chatMessagesListView.x + 8*DefaultStyle.dp)
|
||||||
|
width: Math.round(393 * DefaultStyle.dp)
|
||||||
|
height: Math.round(291 * DefaultStyle.dp)
|
||||||
|
visible: emojiPickerButton.checked
|
||||||
|
closePolicy: Popup.CloseOnPressOutside
|
||||||
|
onClosed: emojiPickerButton.checked = false
|
||||||
|
background: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Rectangle {
|
||||||
|
id: buttonBackground
|
||||||
|
anchors.fill: parent
|
||||||
|
color: DefaultStyle.grey_0
|
||||||
|
radius: Math.round(20 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
MultiEffect {
|
||||||
|
anchors.fill: buttonBackground
|
||||||
|
source: buttonBackground
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowColor: DefaultStyle.grey_1000
|
||||||
|
shadowBlur: 0.1
|
||||||
|
shadowOpacity: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentItem: EmojiPicker {
|
||||||
|
id: emojiPicker
|
||||||
|
editor: sendingTextArea
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ScrollBar {
|
ScrollBar {
|
||||||
id: scrollbar
|
id: scrollbar
|
||||||
|
|
@ -130,12 +162,10 @@ RowLayout {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Math.round(16 * DefaultStyle.dp)
|
spacing: Math.round(16 * DefaultStyle.dp)
|
||||||
BigButton {
|
BigButton {
|
||||||
|
id: emojiPickerButton
|
||||||
style: ButtonStyle.noBackground
|
style: ButtonStyle.noBackground
|
||||||
checkable: true
|
checkable: true
|
||||||
icon.source: AppIcons.smiley
|
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||||
onCheckedChanged: {
|
|
||||||
console.log("TODO : emoji")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BigButton {
|
BigButton {
|
||||||
style: ButtonStyle.noBackground
|
style: ButtonStyle.noBackground
|
||||||
|
|
@ -191,11 +221,9 @@ RowLayout {
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
id: sendingTextArea
|
id: sendingTextArea
|
||||||
width: parent.width
|
width: sendingAreaFlickable.width
|
||||||
height: sendingAreaFlickable.height
|
height: sendingAreaFlickable.height
|
||||||
anchors.left: parent.left
|
textFormat: TextEdit.AutoText
|
||||||
anchors.right: parent.right
|
|
||||||
wrapMode: TextEdit.WordWrap
|
|
||||||
//: Say something… : placeholder text for sending message text area
|
//: Say something… : placeholder text for sending message text area
|
||||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||||
placeholderTextColor: DefaultStyle.main2_400
|
placeholderTextColor: DefaultStyle.main2_400
|
||||||
|
|
@ -205,9 +233,9 @@ RowLayout {
|
||||||
weight: Typography.p1.weight
|
weight: Typography.p1.weight
|
||||||
}
|
}
|
||||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||||
|
wrapMode: TextEdit.WordWrap
|
||||||
property string previousText
|
property string previousText
|
||||||
Component.onCompleted: previousText = text
|
Component.onCompleted: previousText = text
|
||||||
displayAsRichText: true
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (previousText === "" && text !== "") {
|
if (previousText === "" && text !== "") {
|
||||||
mainItem.chat.core.lCompose()
|
mainItem.chat.core.lCompose()
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,6 @@ FocusScope {
|
||||||
Layout.preferredWidth: Math.round(275 * DefaultStyle.dp)
|
Layout.preferredWidth: Math.round(275 * DefaultStyle.dp)
|
||||||
leftPadding: Math.round(8 * DefaultStyle.dp)
|
leftPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
rightPadding: Math.round(8 * DefaultStyle.dp)
|
rightPadding: Math.round(8 * DefaultStyle.dp)
|
||||||
hoverEnabled: true
|
|
||||||
//: "Ajouter une description"
|
//: "Ajouter une description"
|
||||||
placeholderText: qsTr("meeting_schedule_description_hint")
|
placeholderText: qsTr("meeting_schedule_description_hint")
|
||||||
placeholderTextColor: DefaultStyle.main2_600
|
placeholderTextColor: DefaultStyle.main2_600
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ QtObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: Qt 6.8.1 (current version) and previous versions, Qt only support COLRv0 fonts. Don't try to use v1.
|
// Warning: Qt 6.8.1 (current version) and previous versions, Qt only support COLRv0 fonts. Don't try to use v1.
|
||||||
property string emojiFont: "OpenMoji Color"
|
property string emojiFont: "Noto Color Emoji"
|
||||||
property string flagFont: "OpenMoji Color"
|
property string flagFont: "Noto Color Emoji"
|
||||||
property string defaultFont: "Noto Sans"
|
property string defaultFont: "Noto Sans"
|
||||||
|
|
||||||
property color numericPadPressedButtonColor: "#EEF7F8"
|
property color numericPadPressedButtonColor: "#EEF7F8"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue