mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 03:18:07 +00:00
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);
|
||||
}
|
||||
}
|
||||
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
|
||||
QDirIterator itFonts(":/linux/font/", QDirIterator::Subdirectories);
|
||||
while (itFonts.hasNext()) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public:
|
|||
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
|
||||
Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged)
|
||||
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)
|
||||
|
||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||
|
|
|
|||
|
|
@ -25,6 +25,36 @@
|
|||
|
||||
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) {
|
||||
auto sharedPointer = QSharedPointer<ChatMessageCore>(new ChatMessageCore(chatmessage), &QObject::deleteLater);
|
||||
sharedPointer->setSelf(sharedPointer);
|
||||
|
|
@ -64,6 +94,34 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
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() {
|
||||
|
|
@ -84,7 +142,51 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
|
|||
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
|
||||
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(
|
||||
&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 {
|
||||
return mMessageState;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@
|
|||
|
||||
#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 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 isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
|
||||
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:
|
||||
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
|
||||
|
|
@ -76,6 +95,16 @@ public:
|
|||
bool isRead() const;
|
||||
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;
|
||||
void setMessageState(LinphoneEnums::ChatMessageState state);
|
||||
|
||||
|
|
@ -89,11 +118,15 @@ signals:
|
|||
void isReadChanged(bool read);
|
||||
void isRemoteMessageChanged(bool isRemote);
|
||||
void messageStateChanged();
|
||||
void messageReactionChanged();
|
||||
void singletonReactionMapChanged();
|
||||
|
||||
void lDelete();
|
||||
void deleted();
|
||||
void lMarkAsRead();
|
||||
void readChanged();
|
||||
void lSendReaction(const QString &reaction);
|
||||
void lRemoveReaction();
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT QString mText;
|
||||
|
|
@ -105,6 +138,9 @@ private:
|
|||
QString mFromName;
|
||||
QString mPeerName;
|
||||
QString mMessageId;
|
||||
QString mOwnReaction;
|
||||
QList<Reaction> mReactions;
|
||||
QList<QVariant> mReactionsSingletonMap;
|
||||
QDateTime mTimestamp;
|
||||
bool mIsRemoteMessage = false;
|
||||
bool mIsFromChatGroup = false;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ EmojiModel::EmojiModel() {
|
|||
}
|
||||
|
||||
int EmojiModel::count(QString category) {
|
||||
qDebug() << "count of category" << category << 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-ThinItalic.ttf</file>
|
||||
</qresource>
|
||||
<qresource prefix="/emoji">
|
||||
<file>font/EmojiFont.ttf</file>
|
||||
</qresource>
|
||||
<qresource prefix="/linux">
|
||||
<file>font/OpenMoji-color-cbdt.ttf</file>
|
||||
</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 {
|
||||
return mMonitor->getState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,14 @@ public:
|
|||
|
||||
void computeDeliveryStatus();
|
||||
|
||||
void sendReaction(const QString &reaction);
|
||||
|
||||
void removeReaction();
|
||||
|
||||
linphone::ChatMessage::State getState() const;
|
||||
|
||||
QString getOwnReaction() const;
|
||||
|
||||
signals:
|
||||
void messageDeleted();
|
||||
void messageRead();
|
||||
|
|
@ -94,33 +100,34 @@ private:
|
|||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
const std::shared_ptr<linphone::Content> &content);
|
||||
const std::shared_ptr<linphone::Content> &content) override;
|
||||
void onFileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
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,
|
||||
const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset,
|
||||
size_t size);
|
||||
size_t size) override;
|
||||
void onFileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset,
|
||||
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,
|
||||
const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset,
|
||||
size_t total);
|
||||
size_t total) override;
|
||||
void onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
const std::shared_ptr<const linphone::ParticipantImdnState> &state);
|
||||
void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
const std::shared_ptr<const linphone::ParticipantImdnState> &state) override;
|
||||
void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ constexpr int Constants::DefaultFontPointSize;
|
|||
constexpr char Constants::DefaultEmojiFont[];
|
||||
constexpr int Constants::DefaultEmojiFontPointSize;
|
||||
QStringList Constants::getReactionsList() {
|
||||
return {"❤️", "👍", "😂", "😮", "😢"};
|
||||
return {"❤️", "👍", "😂", "😮", "😢", "😠"};
|
||||
}
|
||||
constexpr char Constants::AppDomain[];
|
||||
constexpr size_t Constants::MaxLogsCollectionSize;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public:
|
|||
static constexpr char DefaultEmojiFont[] = "Apple Color Emoji";
|
||||
#else
|
||||
static constexpr char DefaultEmojiFont[] = "Noto Color Emoji";
|
||||
|
||||
#endif
|
||||
static constexpr int DefaultEmojiFontPointSize = 10;
|
||||
static QStringList getReactionsList();
|
||||
|
|
|
|||
|
|
@ -1841,7 +1841,7 @@ QString Utils::encodeTextToQmlRichFormat(const QString &text, const QVariantMap
|
|||
if (lastWasUrl && formattedText.last().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) {
|
||||
|
|
@ -1870,7 +1870,18 @@ QString Utils::encodeEmojiToQmlRichFormat(const QString &body) {
|
|||
return fmtBody;
|
||||
}
|
||||
|
||||
bool Utils::codepointIsEmoji(uint code) {
|
||||
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) ||
|
||||
(code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f;
|
||||
QString Utils::getFilename(QUrl url) {
|
||||
return url.fileName();
|
||||
}
|
||||
|
||||
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,
|
||||
const QVariantMap &options = QVariantMap());
|
||||
Q_INVOKABLE static QString encodeEmojiToQmlRichFormat(const QString &body);
|
||||
|
||||
Q_INVOKABLE static QString getFilename(QUrl url);
|
||||
static bool codepointIsEmoji(uint code);
|
||||
|
||||
// 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 pressedImageColor: style?.image?.pressed || Qt.darker(contentImageColor, 1.1)
|
||||
property bool asynchronous: false
|
||||
property var textFormat: Text.AutoText
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
hoverEnabled: enabled
|
||||
activeFocusOnTab: true
|
||||
|
|
@ -98,6 +99,7 @@ Control.Button {
|
|||
width: textMetrics.advanceWidth
|
||||
wrapMode: Text.WrapAnywhere
|
||||
text: mainItem.text
|
||||
textFormat: mainItem.textFormat
|
||||
maximumLineCount: 1
|
||||
color: mainItem.checkable && mainItem.checked
|
||||
? mainItem.checkedColor || mainItem.pressedColor
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import QtQuick.Controls.Basic as Control
|
|||
import Linphone
|
||||
import UtilsCpp
|
||||
import SettingsCpp
|
||||
import ConstantsCpp
|
||||
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
|
||||
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
|
||||
|
||||
|
|
@ -16,13 +17,15 @@ Control.Control {
|
|||
property string imgUrl
|
||||
|
||||
property ChatMessageGui chatMessage
|
||||
property string ownReaction: chatMessage? chatMessage.core.ownReaction : ""
|
||||
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
|
||||
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
|
||||
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
|
||||
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
|
||||
property bool linkHovered: false
|
||||
property real maxWidth: parent?.width || Math.round(300 * DefaultStyle.dp)
|
||||
|
||||
signal messageDeletionRequested()
|
||||
|
||||
|
|
@ -56,138 +59,196 @@ Control.Control {
|
|||
|
||||
Avatar {
|
||||
id: avatar
|
||||
visible: mainItem.isFromChatGroup
|
||||
opacity: mainItem.isRemoteMessage && mainItem.isFirstMessage ? 1 : 0
|
||||
Layout.preferredWidth: 26 * DefaultStyle.dp
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: mainItem.isRemoteMessage ? 26 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 26 * DefaultStyle.dp
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
||||
}
|
||||
Control.Control {
|
||||
id: chatBubble
|
||||
Item {
|
||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||
spacing: Math.round(2 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(12 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(12 * DefaultStyle.dp)
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Control.Control {
|
||||
id: chatBubble
|
||||
spacing: Math.round(2 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||
leftPadding: 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.
|
||||
id: defaultMouseArea
|
||||
visible: invitationLoader.status !== Loader.Ready // Add other bubbles here that could control the mouse themselves, then add in bubble a signal onMouseEvent
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: (mouse) => mainItem.handleDefaultMouseEvent(mouse)
|
||||
cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
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
|
||||
visible: invitationLoader.status !== Loader.Ready // Add other bubbles here that could control the mouse themselves, then add in bubble a signal onMouseEvent
|
||||
anchors.fill: parent
|
||||
color: mainItem.backgroundColor
|
||||
radius: Math.round(16 * DefaultStyle.dp)
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: (mouse) => mainItem.handleDefaultMouseEvent(mouse)
|
||||
cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && mainItem.isRemoteMessage
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: mainItem.backgroundColor
|
||||
radius: Math.round(16 * DefaultStyle.dp)
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && mainItem.isRemoteMessage
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
Image {
|
||||
visible: mainItem.imgUrl != undefined
|
||||
id: contentimage
|
||||
}
|
||||
Text { // Uses default mouse area for link hovering.
|
||||
id: textElement
|
||||
visible: mainItem.richFormatText !== ""
|
||||
text: mainItem.richFormatText
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: modelData.core.isRemoteMessage ? Text.AlignLeft : Text.AlignRight
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onLinkActivated: {
|
||||
if (link.startsWith('sip'))
|
||||
UtilsCpp.createCall(link)
|
||||
else
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
onHoveredLinkChanged: {
|
||||
mainItem.linkHovered = hoveredLink !== ""
|
||||
}
|
||||
}
|
||||
|
||||
// Meeting invitation bubble
|
||||
/////////////////////////////
|
||||
Loader {
|
||||
id: invitationLoader
|
||||
active: modelData.core.conferenceInfo !== null
|
||||
sourceComponent: invitationComponent
|
||||
}
|
||||
Component {
|
||||
id: invitationComponent
|
||||
ChatMessageInvitationBubble {
|
||||
conferenceInfoGui: modelData.core.conferenceInfo
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Text {
|
||||
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false)
|
||||
color: DefaultStyle.main2_500main
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
Image {
|
||||
visible: mainItem.imgUrl != undefined
|
||||
id: contentimage
|
||||
}
|
||||
Text { // Uses default mouse area for link hovering.
|
||||
id: textElement
|
||||
visible: mainItem.richFormatText !== ""
|
||||
text: mainItem.richFormatText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: DefaultStyle.main2_700
|
||||
textFormat: Text.RichText
|
||||
font {
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onLinkActivated: {
|
||||
if (link.startsWith('sip'))
|
||||
UtilsCpp.createCall(link)
|
||||
else
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
onHoveredLinkChanged: {
|
||||
mainItem.linkHovered = hoveredLink !== ""
|
||||
}
|
||||
}
|
||||
EffectImage {
|
||||
visible: !mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 14 * DefaultStyle.dp
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
|
||||
? AppIcons.envelope
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
|
||||
? AppIcons.check
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
|
||||
? AppIcons.warningCircle
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
|
||||
? AppIcons.checks
|
||||
: ""
|
||||
|
||||
// Meeting invitation bubble
|
||||
/////////////////////////////
|
||||
Loader {
|
||||
id: invitationLoader
|
||||
active: mainItem.chatMessage.core.conferenceInfo !== null
|
||||
sourceComponent: invitationComponent
|
||||
}
|
||||
Component {
|
||||
id: invitationComponent
|
||||
ChatMessageInvitationBubble {
|
||||
conferenceInfoGui: mainItem.chatMessage.core.conferenceInfo
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
Text {
|
||||
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false)
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
}
|
||||
}
|
||||
EffectImage {
|
||||
visible: !mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 14 * DefaultStyle.dp
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
|
||||
? AppIcons.envelope
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
|
||||
? AppIcons.check
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
|
||||
? AppIcons.warningCircle
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
|
||||
? AppIcons.checks
|
||||
: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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.rightMargin: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
|
@ -208,7 +269,7 @@ Control.Control {
|
|||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
onClicked: {
|
||||
var success = UtilsCpp.copyToClipboard(modelData.core.text)
|
||||
var success = UtilsCpp.copyToClipboard(mainItem.chatMessage.core.text)
|
||||
//: Copied
|
||||
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
||||
//: "to clipboard"
|
||||
|
|
@ -229,12 +290,55 @@ Control.Control {
|
|||
}
|
||||
style: ButtonStyle.hoveredBackgroundRed
|
||||
}
|
||||
// Rectangle {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.preferredHeight: Math.round(1 * DefaultStyle.dp)
|
||||
// color: DefaultStyle.main2_200
|
||||
// }
|
||||
}
|
||||
}
|
||||
BigButton {
|
||||
PopupButton {
|
||||
id: emojiButton
|
||||
style: ButtonStyle.noBackground
|
||||
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}
|
||||
|
|
|
|||
|
|
@ -109,8 +109,10 @@ ListView {
|
|||
|
||||
delegate: ChatMessage {
|
||||
chatMessage: modelData
|
||||
property real maxWidth: Math.round(mainItem.width * (3/4))
|
||||
onVisibleChanged: if (!modelData.core.isRead) modelData.core.lMarkAsRead()
|
||||
maxWidth: Math.round(mainItem.width * (3/4))
|
||||
onVisibleChanged: {
|
||||
if (visible && !modelData.core.isRead) modelData.core.lMarkAsRead()
|
||||
}
|
||||
width: mainItem.width
|
||||
property var previousIndex: index - 1
|
||||
property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Linphone
|
||||
import UtilsCpp
|
||||
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
||||
|
||||
// import EmojiModel
|
||||
|
||||
|
|
@ -42,7 +44,9 @@ ColumnLayout {
|
|||
property var searchModel: ListModel {}
|
||||
property bool searchMode: false
|
||||
property int skinColor: -1
|
||||
signal emojiClicked()
|
||||
|
||||
signal emojiClicked(string emoji)
|
||||
|
||||
function changeSkinColor(index) {
|
||||
if (index !== skinColors.current) {
|
||||
skinColors.itemAt(skinColors.current + 1).scale = 0.6
|
||||
|
|
@ -152,13 +156,14 @@ ColumnLayout {
|
|||
ListView {
|
||||
id: list
|
||||
width: mainItem.width
|
||||
height: mainItem.height - categoriesRow.height
|
||||
height: Math.round(250 * DefaultStyle.dp)
|
||||
Layout.fillHeight: true
|
||||
model: mainItem.categories
|
||||
spacing: Math.round(30 * DefaultStyle.dp)
|
||||
topMargin: Math.round(7 * DefaultStyle.dp)
|
||||
bottomMargin: Math.round(7 * DefaultStyle.dp)
|
||||
leftMargin: Math.round(12 * DefaultStyle.dp)
|
||||
// clip: true
|
||||
clip: true
|
||||
delegate: GridLayout {
|
||||
id: grid
|
||||
property string category: mainItem.searchMode ? 'Search Result' : modelData
|
||||
|
|
@ -178,13 +183,11 @@ ColumnLayout {
|
|||
Layout.bottomMargin: Math.round(8 * DefaultStyle.dp)
|
||||
}
|
||||
Repeater {
|
||||
onCountChanged: console.log("emoji list count :", count)
|
||||
model: mainItem.searchMode ? mainItem.searchModel : mainItem.model.count(grid.category)
|
||||
delegate: Rectangle {
|
||||
property alias es: emojiSvg
|
||||
Layout.preferredWidth: Math.round(40 * DefaultStyle.dp)
|
||||
Layout.preferredHeight: Math.round(40 * DefaultStyle.dp)
|
||||
RectangleTest{anchors.fill: parent}
|
||||
radius: Math.round(40 * DefaultStyle.dp)
|
||||
color: mouseArea.containsMouse ? '#e6e6e6' : '#ffffff'
|
||||
Image {
|
||||
|
|
@ -199,10 +202,11 @@ ColumnLayout {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
property string imageUrl: emojiSvg.source
|
||||
onClicked: {
|
||||
var tag = "<img src = '%1' width = '20' height = '20' align = 'top'>"
|
||||
if (mainItem.editor) mainItem.editor.insert(mainItem.editor.cursorPosition, tag.arg(emojiSvg.source))
|
||||
mainItem.emojiClicked(tag.arg(emojiSvg.source))
|
||||
var emojiInFont = Utils.codepointFromFilename(UtilsCpp.getFilename(emojiSvg.source))
|
||||
if (mainItem.editor) mainItem.editor.insert(mainItem.editor.cursorPosition, emojiInFont)
|
||||
mainItem.emojiClicked(emojiInFont)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ TextEdit {
|
|||
property real placeholderWeight: Typography.p1.weight
|
||||
property color placeholderTextColor: color
|
||||
property alias background: background.data
|
||||
property bool hoverEnabled: false
|
||||
property bool hoverEnabled: true
|
||||
property bool hovered: mouseArea.hoverEnabled && mouseArea.containsMouse
|
||||
topPadding: Math.round(5 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(5 * DefaultStyle.dp)
|
||||
|
|
@ -36,10 +36,10 @@ TextEdit {
|
|||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
enabled: mainItem.hoverEnabled
|
||||
hoverEnabled: mainItem.hoverEnabled
|
||||
// onPressed: mainItem.forceActiveFocus()
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: mainItem.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
cursorShape: mainItem.hovered ? Qt.IBeamCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
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.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||
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 {
|
||||
id: scrollbar
|
||||
|
|
@ -130,12 +162,10 @@ RowLayout {
|
|||
RowLayout {
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
BigButton {
|
||||
id: emojiPickerButton
|
||||
style: ButtonStyle.noBackground
|
||||
checkable: true
|
||||
icon.source: AppIcons.smiley
|
||||
onCheckedChanged: {
|
||||
console.log("TODO : emoji")
|
||||
}
|
||||
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
|
|
@ -191,11 +221,9 @@ RowLayout {
|
|||
|
||||
TextArea {
|
||||
id: sendingTextArea
|
||||
width: parent.width
|
||||
width: sendingAreaFlickable.width
|
||||
height: sendingAreaFlickable.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: TextEdit.AutoText
|
||||
//: Say something… : placeholder text for sending message text area
|
||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||
placeholderTextColor: DefaultStyle.main2_400
|
||||
|
|
@ -205,9 +233,9 @@ RowLayout {
|
|||
weight: Typography.p1.weight
|
||||
}
|
||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
property string previousText
|
||||
Component.onCompleted: previousText = text
|
||||
displayAsRichText: true
|
||||
onTextChanged: {
|
||||
if (previousText === "" && text !== "") {
|
||||
mainItem.chat.core.lCompose()
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ FocusScope {
|
|||
Layout.preferredWidth: Math.round(275 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(8 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(8 * DefaultStyle.dp)
|
||||
hoverEnabled: true
|
||||
//: "Ajouter une description"
|
||||
placeholderText: qsTr("meeting_schedule_description_hint")
|
||||
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.
|
||||
property string emojiFont: "OpenMoji Color"
|
||||
property string flagFont: "OpenMoji Color"
|
||||
property string emojiFont: "Noto Color Emoji"
|
||||
property string flagFont: "Noto Color Emoji"
|
||||
property string defaultFont: "Noto Sans"
|
||||
|
||||
property color numericPadPressedButtonColor: "#EEF7F8"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue