mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
* added changelog drawer * Created a scaffold for Linux installation * implement Linux updating * Add debug logs about installer in service * Add client side of installation logic for Windows and MacOS * Add service side of installation logic for Windows * ru readme * Update README_RU.md * Add files via upload * chore: added clang-format config files (#1293) * Update README_RU.md * Update README.md * feature: added subscription expiration date for premium v2 (#1261) * feature: added subscription expiration date for premium v2 * feature: added a check for the presence of the “services” field in the response body of the getServicesList() function * feature: added prohibition to change location when connection is active * bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend * feature/xray user management (#972) * feature: implement client management functionality for Xray --------- Co-authored-by: aiamnezia <ai@amnezia.org> Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com> * Fix formatting * Add some logs * Add logs from installattion shell on Windows * Fix installation for Windows and MacOS * Optimized code * Move installer running to client side for Ubuntu * Move installer launch logic to client side for Windows * Clean service code * Add linux_install script to resources * Add logs for UpdateController * Add draft for MacOS installation * Disable updates checking for Android and iOS * chore: fixed macos update script * chore: remove duplicate lines * chore: post merge fixes * chore: add missing ifdef * decrease version for testing * chore: added changelog text processing depend on OS * add .vscode to .gitignore * Change updater downloading method to retrieving link from the gateway * add Release date file creation to s3 deploy script * Add release date downloading from endpoint * update check refactoring * feat: switch macOS auto-update from DMG to ZIP+PKG installer - Update macOS artifact URL from .dmg to .zip - Rewrite mac_installer.sh to extract ZIP and install PKG via osascript - Increase download timeout to 30s for larger ZIP files * fix: fix Android build * feat: Change get request for updater link to post * refactor: preparing NewsModel for update notifications - Changed `updateModel` to `setNewsList` for better semantic meaning. - Delegate model container updating to private method updateModel - Updated the logic for marking news as read to use item IDs instead of a boolean flag. * feat: Move update notification in news list - Updated `UpdateController` to handle empty release dates in header text. - Added `getVersion` method to `UpdateController` for version retrieval. - Enhanced `NewsModel` to support update notifications with new methods for marking updates as skipped and setting update notifications. - Updated QML pages to display update information and provide actions for updates and skipping them. - Introduced `isUpdate` property in `NewsItem` to differentiate between regular news and updates. * feat: Implement rate limit workaround for gateway requests - Added a delay before contacting the gateway in both `UpdateController` and `ApiNewsController` to prevent rate limit issues caused by simultaneous requests. * refactor: Convert synchronous network requests to asynchronous in UpdateController - Updated `UpdateController` to use asynchronous network requests for fetching gateway URL, version info, changelog, and release date. - Introduced `doGetAsync` method to handle asynchronous GET requests with error handling. - Removed synchronous methods to improve responsiveness and prevent blocking the UI during network operations. - Added a mechanism to prevent multiple concurrent update checks. * chore: Decrease AmneziaVPN version to 4.8.10.0 in CMakeLists.txt for testing * refactor: Improve update check handling to avoid rate limit issues - Updated `CoreController` to initiate update checks after news fetching is complete. - Removed synchronous waiting in `ApiNewsController` to streamline the fetching process. * fix: fixed typo in IsReadRole * fix: fix updater filenames * chore: move updateController to core * refactor: update to mvvm * chore: tiny fix --------- Co-authored-by: aiamnezia <ai@amnezia.org> Co-authored-by: aiamnezia <ai@amnezia.com> Co-authored-by: Pokamest Nikak <pokamest@gmail.com> Co-authored-by: KsZnak <ksu@amnezia.org> Co-authored-by: Cyril Anisimov <cyan84@gmail.com> Co-authored-by: vkamn <vk@amnezia.org>
177 lines
4.5 KiB
C++
177 lines
4.5 KiB
C++
#include "ui/models/newsModel.h"
|
|
#include "core/repositories/secureAppSettingsRepository.h"
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QQmlEngine>
|
|
#include <QStandardPaths>
|
|
#include <algorithm>
|
|
|
|
NewsModel::NewsModel(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
|
: QAbstractListModel(parent), m_appSettingsRepository(appSettingsRepository)
|
|
{
|
|
loadReadIds();
|
|
}
|
|
|
|
int NewsModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent);
|
|
return m_items.size();
|
|
}
|
|
|
|
QVariant NewsModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
|
|
return QVariant();
|
|
|
|
const NewsItem &item = m_items.at(index.row());
|
|
switch (role) {
|
|
case IdRole: return item.id;
|
|
case TitleRole: return item.title;
|
|
case ContentRole: return item.content;
|
|
case TimestampRole: return item.timestamp.toLocalTime().toString(Qt::ISODate);
|
|
case IsReadRole: return m_readIds.contains(item.id);
|
|
case IsProcessedRole: return index.row() == m_processedIndex;
|
|
case IsUpdateRole: return item.isUpdate;
|
|
default: return QVariant();
|
|
}
|
|
}
|
|
|
|
QHash<int, QByteArray> NewsModel::roleNames() const
|
|
{
|
|
QHash<int, QByteArray> roles;
|
|
roles[IdRole] = "id";
|
|
roles[TitleRole] = "title";
|
|
roles[ContentRole] = "content";
|
|
roles[TimestampRole] = "timestamp";
|
|
roles[IsReadRole] = "read";
|
|
roles[IsProcessedRole] = "isProcessed";
|
|
roles[IsUpdateRole] = "isUpdate";
|
|
return roles;
|
|
}
|
|
|
|
void NewsModel::markAsRead(int index)
|
|
{
|
|
if (index < 0 || index >= m_items.size())
|
|
return;
|
|
|
|
const QString &itemId = m_items.at(index).id;
|
|
if (itemId.isEmpty() || m_readIds.contains(itemId))
|
|
return;
|
|
|
|
m_readIds.insert(itemId);
|
|
saveReadIds();
|
|
|
|
QModelIndex idx = createIndex(index, 0);
|
|
emit dataChanged(idx, idx, { IsReadRole });
|
|
emit hasUnreadChanged();
|
|
}
|
|
|
|
void NewsModel::markUpdateAsSkipped()
|
|
{
|
|
if (!m_updateItem.has_value())
|
|
return;
|
|
|
|
const QString updateId = m_updateItem->id;
|
|
if (updateId.isEmpty())
|
|
return;
|
|
|
|
for (int i = 0; i < m_items.size(); ++i) {
|
|
if (m_items.at(i).id == updateId) {
|
|
markAsRead(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int NewsModel::processedIndex() const
|
|
{
|
|
return m_processedIndex;
|
|
}
|
|
|
|
void NewsModel::setProcessedIndex(int index)
|
|
{
|
|
if (index < 0 || index >= m_items.size() || m_processedIndex == index)
|
|
return;
|
|
m_processedIndex = index;
|
|
emit processedIndexChanged(index);
|
|
}
|
|
|
|
void NewsModel::setNewsList(const QJsonArray &serverItems)
|
|
{
|
|
QVector<NewsItem> updatedItems;
|
|
updatedItems.reserve(serverItems.size());
|
|
|
|
for (const QJsonValue &value : serverItems) {
|
|
if (!value.isObject())
|
|
continue;
|
|
|
|
const QJsonObject object = value.toObject();
|
|
|
|
NewsItem item;
|
|
item.id = object.value("id").toString();
|
|
if (item.id.isEmpty())
|
|
continue;
|
|
item.title = object.value("title").toString();
|
|
item.content = object.value("content").toString();
|
|
item.timestamp = QDateTime::fromString(object.value("timestamp").toString(), Qt::ISODate);
|
|
item.isUpdate = false;
|
|
|
|
updatedItems.append(item);
|
|
}
|
|
|
|
m_apiItems = updatedItems;
|
|
updateModel();
|
|
}
|
|
|
|
void NewsModel::setUpdateNotification(const QString &id, const QString &title, const QString &content)
|
|
{
|
|
if (id.isEmpty())
|
|
return;
|
|
|
|
NewsItem updateItem;
|
|
updateItem.id = id;
|
|
updateItem.title = title;
|
|
updateItem.content = content;
|
|
updateItem.timestamp = QDateTime::currentDateTimeUtc();
|
|
updateItem.isUpdate = true;
|
|
|
|
m_updateItem = updateItem;
|
|
updateModel();
|
|
}
|
|
|
|
void NewsModel::updateModel()
|
|
{
|
|
beginResetModel();
|
|
m_items = m_apiItems;
|
|
std::sort(m_items.begin(), m_items.end(), [](const NewsItem &a, const NewsItem &b) { return a.timestamp > b.timestamp; });
|
|
if (m_updateItem.has_value()) {
|
|
m_items.prepend(*m_updateItem);
|
|
}
|
|
endResetModel();
|
|
emit hasUnreadChanged();
|
|
}
|
|
|
|
bool NewsModel::hasUnread() const
|
|
{
|
|
for (const NewsItem &item : m_items) {
|
|
if (!m_readIds.contains(item.id))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NewsModel::loadReadIds()
|
|
{
|
|
QStringList ids = m_appSettingsRepository->getReadNewsIds();
|
|
m_readIds = QSet<QString>(ids.begin(), ids.end());
|
|
}
|
|
|
|
void NewsModel::saveReadIds() const
|
|
{
|
|
m_appSettingsRepository->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end()));
|
|
}
|