File indexing completed on 2024-05-19 16:30:15
0001 // SPDX-FileCopyrightText: 2020 Anthony Fieroni <bvbfan@abv.bg> 0002 // SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 0006 #include "chatlistmodel.h" 0007 0008 #include <QDebug> 0009 #include <QQmlApplicationEngine> 0010 #include <QSqlError> 0011 #include <QSqlQuery> 0012 #include <QStandardPaths> 0013 0014 #include <QCoroFuture> 0015 0016 #include <KLocalizedString> 0017 #include <KPeople/PersonData> 0018 0019 #include "channelhandler.h" 0020 #include "messagemodel.h" 0021 #include "settingsmanager.h" 0022 #include "utils.h" 0023 #include <database.h> 0024 #include <global.h> 0025 0026 ChatListModel::ChatListModel(ChannelHandler &handler, QObject *parent) 0027 : QAbstractListModel(parent) 0028 , m_handler(handler) 0029 , m_mapper(ContactPhoneNumberMapper::instance()) 0030 { 0031 connect(&m_handler, &ChannelHandler::messagesChanged, this, &ChatListModel::fetchChats); 0032 connect(&m_mapper, &ContactPhoneNumberMapper::contactsChanged, this, [this](const QVector<PhoneNumber> &affectedNumbers) { 0033 qDebug() << "New data for" << affectedNumbers; 0034 for (const auto &number : affectedNumbers) { 0035 // Find the Chat object for the phone number 0036 const auto chatIt = std::find_if(m_chats.begin(), m_chats.end(), [&number](const Chat &chat) { 0037 return chat.phoneNumberList.contains(number); 0038 }); 0039 0040 int i = std::distance(m_chats.begin(), chatIt); 0041 const auto row = index(i); 0042 Q_EMIT dataChanged(row, row, {Role::DisplayNameRole}); 0043 } 0044 }); 0045 0046 fetchChats(PhoneNumberList(SL(""))); 0047 0048 Q_EMIT m_handler.interface()->disableNotificationsForNumber(QString()); 0049 } 0050 0051 ChatListModel::~ChatListModel() 0052 { 0053 Q_EMIT m_handler.interface()->disableNotificationsForNumber(QString()); 0054 } 0055 0056 QHash<int, QByteArray> ChatListModel::roleNames() const 0057 { 0058 return { 0059 {Role::DisplayNameRole, BL("displayName")}, 0060 {Role::PhoneNumberListRole, BL("phoneNumberList")}, 0061 {Role::UnreadMessagesRole, BL("unreadMessages")}, 0062 {Role::LastDateTimeRole, BL("lastDateTime")}, 0063 {Role::LastMessageRole, BL("lastMessage")}, 0064 {Role::LastSentByMeRole, BL("lastSentByMe")}, 0065 {Role::LastAttachmentRole, BL("lastAttachment")}, 0066 {Role::LastContactedRole, BL("lastContacted")}, 0067 {Role::IsContactRole, BL("isContact")}, 0068 }; 0069 } 0070 0071 QVariant ChatListModel::data(const QModelIndex &index, int role) const 0072 { 0073 if (!index.isValid() || index.row() < 0 || index.row() >= m_chats.count()) { 0074 return false; 0075 } 0076 0077 PhoneNumberList phoneNumberList = m_chats.at(index.row()).phoneNumberList; 0078 0079 switch (role) { 0080 // All roles that need the personData object 0081 case DisplayNameRole: { 0082 QString names; 0083 for (int i = 0; const auto &number : phoneNumberList) { 0084 if (!names.isEmpty()) { 0085 names.append(SL(", ")); 0086 } 0087 QString name = KPeople::PersonData(m_mapper.uriForNumber(number)).name(); 0088 if (name.isEmpty()) { 0089 name = number.toInternational(); 0090 } else if (phoneNumberList.count() >= 2) { 0091 name = name.split(SL(" ")).first(); 0092 } 0093 0094 if (i > 0 && names.size() + name.size() + 5 > m_characters) { 0095 names.append(i18n("and %1 more", phoneNumberList.count() - i)); 0096 break; 0097 } 0098 0099 names.append(name); 0100 0101 i++; 0102 } 0103 return names; 0104 } 0105 case PhoneNumberListRole: 0106 return QVariant::fromValue(phoneNumberList); 0107 case UnreadMessagesRole: 0108 return m_chats.at(index.row()).unreadMessages; 0109 case LastMessageRole: 0110 return m_chats.at(index.row()).lastMessage; 0111 case LastDateTimeRole: 0112 return m_chats.at(index.row()).lastDateTime; 0113 case LastSentByMeRole: 0114 return m_chats.at(index.row()).lastSentByMe; 0115 case LastAttachmentRole: 0116 return m_chats.at(index.row()).lastAttachment; 0117 case LastContactedRole: { 0118 QDateTime lastContacted = m_chats.at(index.row()).lastDateTime; 0119 if (lastContacted.daysTo(QDateTime::currentDateTime()) == 0) { 0120 QString format = Utils::instance()->isLocale24HourTime() ? SL("hh:mm") : SL("h:mm ap"); 0121 return lastContacted.toString(format); 0122 } else { 0123 return lastContacted.toString(QLocale::system().dateFormat(QLocale::ShortFormat)); 0124 } 0125 } 0126 case IsContactRole: 0127 return phoneNumberList.size() == 1 && !KPeople::PersonData(m_mapper.uriForNumber(phoneNumberList.first())).name().isEmpty(); 0128 } 0129 0130 return {}; 0131 } 0132 0133 int ChatListModel::rowCount(const QModelIndex &parent) const 0134 { 0135 return parent.isValid() ? 0 : m_chats.count(); 0136 } 0137 0138 QPair<Chat *, int> ChatListModel::getChatIndex(const PhoneNumberList &phoneNumberList) 0139 { 0140 auto modelIt = std::find_if(m_chats.begin(), m_chats.end(), [&](const Chat &chat) { 0141 return chat.phoneNumberList.toString() == phoneNumberList.toString(); 0142 }); 0143 0144 if (modelIt != m_chats.cend()) { 0145 const int i = std::distance(m_chats.begin(), modelIt); 0146 return qMakePair(&(*modelIt), i); 0147 } else { 0148 return qMakePair(&(*modelIt), -1); 0149 } 0150 } 0151 0152 void ChatListModel::startChat(const PhoneNumberList &phoneNumberList) 0153 { 0154 if (m_messageModel != nullptr) 0155 m_messageModel->deleteLater(); 0156 0157 m_messageModel = new MessageModel(m_handler, phoneNumberList, this); 0158 chatStarted(m_messageModel); 0159 } 0160 0161 void ChatListModel::markChatAsRead(const PhoneNumberList &phoneNumberList) 0162 { 0163 m_handler.database().markChatAsRead(phoneNumberList); 0164 } 0165 0166 void ChatListModel::fetchChats(const PhoneNumberList &phoneNumberList) 0167 { 0168 if (phoneNumberList.count() == 0) { 0169 fetchChatsInternal(); 0170 } else { 0171 const auto &[chat, idx] = getChatIndex(phoneNumberList); 0172 0173 if (idx == -1) { 0174 // add to chat list 0175 Chat chat; 0176 chat.phoneNumberList = phoneNumberList; 0177 0178 beginInsertRows({}, 0, 0); 0179 m_chats.prepend(chat); 0180 endInsertRows(); 0181 0182 fetchChatDetails(phoneNumberList, false); 0183 } else { 0184 // update existing and sort 0185 fetchChatDetails(phoneNumberList, true); 0186 } 0187 } 0188 } 0189 0190 QCoro::Task<void> ChatListModel::fetchChatsInternal() 0191 { 0192 const auto chats = co_await m_handler.database().chats(PhoneNumberList()); 0193 0194 beginResetModel(); 0195 m_chats = chats; 0196 endResetModel(); 0197 0198 Q_EMIT chatsFetched(); 0199 } 0200 0201 void ChatListModel::fetchChatDetails(const PhoneNumberList &phoneNumberList, const bool sort) 0202 { 0203 [this, phoneNumberList, sort]() -> QCoro::Task<void> { 0204 co_await fetchChatDetailsInternal(phoneNumberList, sort); 0205 }(); 0206 } 0207 0208 QCoro::Task<void> ChatListModel::fetchChatDetailsInternal(const PhoneNumberList &phoneNumberList, const bool sort) 0209 { 0210 const auto &[chat, idx] = getChatIndex(phoneNumberList); 0211 0212 if (idx > -1) { 0213 const QVector<Chat> chats = co_await m_handler.database().chats(phoneNumberList); 0214 0215 chat->phoneNumberList = chats.first().phoneNumberList; 0216 chat->unreadMessages = chats.first().unreadMessages; 0217 chat->lastMessage = chats.first().lastMessage; 0218 chat->lastDateTime = chats.first().lastDateTime; 0219 chat->lastSentByMe = chats.first().lastSentByMe; 0220 chat->lastAttachment = chats.first().lastAttachment; 0221 0222 Q_EMIT dataChanged(index(idx), index(idx)); 0223 } 0224 0225 if (sort) { 0226 beginResetModel(); 0227 // Sort chat list by most recent chat 0228 std::sort(m_chats.begin(), m_chats.end(), [](const Chat &a, const Chat &b) -> bool { 0229 return a.lastDateTime > b.lastDateTime; 0230 }); 0231 endResetModel(); 0232 } 0233 } 0234 0235 void ChatListModel::deleteChat(const PhoneNumberList &phoneNumberList) 0236 { 0237 m_handler.database().deleteChat(phoneNumberList); 0238 0239 const QString folder = QString::number(hash(phoneNumberList.toString())); 0240 QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + SL("/spacebar/attachments/") + folder); 0241 dir.removeRecursively(); 0242 0243 const auto &[chat, idx] = getChatIndex(phoneNumberList); 0244 0245 if (idx > -1) { 0246 beginRemoveRows(QModelIndex(), idx, idx); 0247 m_chats.remove(idx); 0248 endRemoveRows(); 0249 } 0250 } 0251 0252 void ChatListModel::restoreDefaults() 0253 { 0254 SettingsManager::self()->setDefaults(); 0255 } 0256 0257 void ChatListModel::saveSettings() 0258 { 0259 SettingsManager::self()->save(); 0260 Q_EMIT m_handler.interface()->syncSettings(); 0261 } 0262 0263 QString ChatListModel::attachmentsFolder(const PhoneNumberList &phoneNumberList) const 0264 { 0265 const QString folder = QString::number(hash(phoneNumberList.toString())); 0266 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + SL("/spacebar/attachments/") + folder; 0267 } 0268 0269 void ChatListModel::setCharacterLimit(const int &width) 0270 { 0271 // 170 is the pixels taken up by other elements on the list row 0272 m_characters = (width - 170) / 6; 0273 }