mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35f5101fa8 | |||
| 63c336d96e | |||
| 09a67572fb | |||
| 4b4b81b395 | |||
| e027c504ae | |||
| 669a95d975 | |||
| a96df5d518 | |||
| c5c81735a0 | |||
| 470ce0f9c8 |
@@ -9,6 +9,7 @@ deploy/build_32/*
|
|||||||
deploy/build_64/*
|
deploy/build_64/*
|
||||||
winbuild*.bat
|
winbuild*.bat
|
||||||
.cache/
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
|
|||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
set(AMNEZIAVPN_VERSION 4.8.9.0)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.8.3
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2089)
|
set(APP_ANDROID_VERSION_CODE 2090)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
#include "core/installedAppsImageProvider.h"
|
#include "core/installedAppsImageProvider.h"
|
||||||
@@ -100,6 +101,9 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||||
|
|
||||||
|
m_newsModel.reset(new NewsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initControllers()
|
void CoreController::initControllers()
|
||||||
@@ -151,6 +155,10 @@ void CoreController::initControllers()
|
|||||||
|
|
||||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
|
|
||||||
|
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||||
|
m_apiNewsController->fetchNews();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
@@ -299,13 +307,10 @@ void CoreController::setQmlRoot()
|
|||||||
|
|
||||||
void CoreController::initApiCountryModelUpdateHandler()
|
void CoreController::initApiCountryModelUpdateHandler()
|
||||||
{
|
{
|
||||||
// TODO
|
|
||||||
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||||
});
|
});
|
||||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
|
||||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initContainerModelUpdateHandler()
|
void CoreController::initContainerModelUpdateHandler()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||||
|
#include "ui/controllers/api/apiNewsController.h"
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/controllers/allowedDnsController.h"
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
|
#include "ui/models/newsmodel.h"
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include "ui/notificationhandler.h"
|
#include "ui/notificationhandler.h"
|
||||||
@@ -113,6 +115,7 @@ private:
|
|||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
|
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
@@ -120,6 +123,7 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_4_34)">
|
||||||
|
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_4_34">
|
||||||
|
<rect width="74" height="74" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 982 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Основа газеты -->
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||||
|
<!-- Линии текста -->
|
||||||
|
<line x1="7" y1="8" x2="17" y2="8"/>
|
||||||
|
<line x1="7" y1="12" x2="17" y2="12"/>
|
||||||
|
<line x1="7" y1="16" x2="13" y2="16"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 410 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 188 B |
@@ -35,6 +35,9 @@
|
|||||||
<file>images/controls/mail.svg</file>
|
<file>images/controls/mail.svg</file>
|
||||||
<file>images/controls/map-pin.svg</file>
|
<file>images/controls/map-pin.svg</file>
|
||||||
<file>images/controls/more-vertical.svg</file>
|
<file>images/controls/more-vertical.svg</file>
|
||||||
|
<file>images/controls/news.svg</file>
|
||||||
|
<file>images/controls/news-unread.svg</file>
|
||||||
|
<file>images/controls/unread-dot.svg</file>
|
||||||
<file>images/controls/plus.svg</file>
|
<file>images/controls/plus.svg</file>
|
||||||
<file>images/controls/qr-code.svg</file>
|
<file>images/controls/qr-code.svg</file>
|
||||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
<file>images/controls/server.svg</file>
|
<file>images/controls/server.svg</file>
|
||||||
<file>images/controls/settings-2.svg</file>
|
<file>images/controls/settings-2.svg</file>
|
||||||
<file>images/controls/settings.svg</file>
|
<file>images/controls/settings.svg</file>
|
||||||
|
<file>images/controls/settings-news.svg</file>
|
||||||
<file>images/controls/share-2.svg</file>
|
<file>images/controls/share-2.svg</file>
|
||||||
<file>images/controls/split-tunneling.svg</file>
|
<file>images/controls/split-tunneling.svg</file>
|
||||||
<file>images/controls/tag.svg</file>
|
<file>images/controls/tag.svg</file>
|
||||||
@@ -212,6 +216,8 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsNewsNotifications.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||||
|
|||||||
+11
-1
@@ -14,7 +14,7 @@ namespace
|
|||||||
const char cloudFlareNs1[] = "1.1.1.1";
|
const char cloudFlareNs1[] = "1.1.1.1";
|
||||||
const char cloudFlareNs2[] = "1.0.0.1";
|
const char cloudFlareNs2[] = "1.0.0.1";
|
||||||
|
|
||||||
constexpr char gatewayEndpoint[] = "http://gw.amnezia.org:80/";
|
constexpr char gatewayEndpoint[] = "http://192.168.0.222:80/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this)
|
Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this)
|
||||||
@@ -578,3 +578,13 @@ void Settings::setAllowedDnsServers(const QStringList &servers)
|
|||||||
{
|
{
|
||||||
setValue("Conf/allowedDnsServers", servers);
|
setValue("Conf/allowedDnsServers", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList Settings::readNewsIds() const
|
||||||
|
{
|
||||||
|
return value("News/readIds").toStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setReadNewsIds(const QStringList &ids)
|
||||||
|
{
|
||||||
|
setValue("News/readIds", ids);
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,6 +236,9 @@ public:
|
|||||||
QStringList allowedDnsServers() const;
|
QStringList allowedDnsServers() const;
|
||||||
void setAllowedDnsServers(const QStringList &servers);
|
void setAllowedDnsServers(const QStringList &servers);
|
||||||
|
|
||||||
|
QStringList readNewsIds() const;
|
||||||
|
void setReadNewsIds(const QStringList &ids);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void saveLogsChanged(bool enabled);
|
void saveLogsChanged(bool enabled);
|
||||||
void screenshotsEnabledChanged(bool enabled);
|
void screenshotsEnabledChanged(bool enabled);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -248,10 +248,10 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
|||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
serverCountryCode,
|
serverCountryCode,
|
||||||
apiConfigObject.value(configKey::serviceType).toString(),
|
apiConfigObject.value(configKey::serviceType).toString(),
|
||||||
m_apiServicesModel->getSelectedServiceProtocol(),
|
configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(),
|
||||||
serverConfigObject.value(configKey::authData).toObject() };
|
serverConfigObject.value(configKey::authData).toObject() };
|
||||||
|
|
||||||
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
QString protocol = gatewayRequestData.serviceProtocol;
|
||||||
ProtocolData protocolData = generateProtocolData(protocol);
|
ProtocolData protocolData = generateProtocolData(protocol);
|
||||||
|
|
||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||||
@@ -283,7 +283,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
|||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
serverCountryCode,
|
serverCountryCode,
|
||||||
apiConfigObject.value(configKey::serviceType).toString(),
|
apiConfigObject.value(configKey::serviceType).toString(),
|
||||||
m_apiServicesModel->getSelectedServiceProtocol(),
|
configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(),
|
||||||
serverConfigObject.value(configKey::authData).toObject() };
|
serverConfigObject.value(configKey::authData).toObject() };
|
||||||
|
|
||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include "apiNewsController.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel,
|
||||||
|
const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent), m_newsModel(newsModel), m_settings(settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiNewsController::fetchNews()
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
||||||
|
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
||||||
|
QByteArray responseBody;
|
||||||
|
QJsonObject payload;
|
||||||
|
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||||
|
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody);
|
||||||
|
qDebug() << "fetchNews" << errorCode;
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||||
|
QJsonArray newsArray;
|
||||||
|
if (doc.isArray()) {
|
||||||
|
newsArray = doc.array();
|
||||||
|
} else if (doc.isObject()) {
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (obj.value("news").isArray()) {
|
||||||
|
newsArray = obj.value("news").toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_newsModel->updateModel(newsArray);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef APINEWSCONTROLLER_H
|
||||||
|
#define APINEWSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <memory>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
#include "ui/models/newsmodel.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
|
||||||
|
class ApiNewsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel,
|
||||||
|
const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE void fetchNews();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APINEWSCONTROLLER_H
|
||||||
@@ -26,6 +26,8 @@ namespace PageLoader
|
|||||||
PageSettingsConnection,
|
PageSettingsConnection,
|
||||||
PageSettingsDns,
|
PageSettingsDns,
|
||||||
PageSettingsApplication,
|
PageSettingsApplication,
|
||||||
|
PageSettingsNewsNotifications,
|
||||||
|
PageSettingsNewsDetail,
|
||||||
PageSettingsBackup,
|
PageSettingsBackup,
|
||||||
PageSettingsAbout,
|
PageSettingsAbout,
|
||||||
PageSettingsLogging,
|
PageSettingsLogging,
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#include "ui/models/newsmodel.h"
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
NewsModel::NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
, m_settings(settings)
|
||||||
|
{
|
||||||
|
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.toString(Qt::ISODate);
|
||||||
|
case IsReadRole:
|
||||||
|
return item.read;
|
||||||
|
case IsProcessedRole:
|
||||||
|
return index.row() == m_processedIndex;
|
||||||
|
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";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::markAsRead(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_items.size())
|
||||||
|
return;
|
||||||
|
if (!m_items[index].read) {
|
||||||
|
m_items[index].read = true;
|
||||||
|
m_readIds.insert(m_items[index].id);
|
||||||
|
saveReadIds();
|
||||||
|
QModelIndex idx = createIndex(index, 0);
|
||||||
|
emit dataChanged(idx, idx, {IsReadRole});
|
||||||
|
emit hasUnreadChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::updateModel(const QJsonArray &serverItems)
|
||||||
|
{
|
||||||
|
QSet<QString> existingIds;
|
||||||
|
for (const NewsItem &item : m_items) {
|
||||||
|
existingIds.insert(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<NewsItem> newItems;
|
||||||
|
for (const QJsonValue &value : serverItems) {
|
||||||
|
if (!value.isObject()) continue;
|
||||||
|
const QJsonObject obj = value.toObject();
|
||||||
|
QString id = obj.value("id").toString();
|
||||||
|
|
||||||
|
if (!existingIds.contains(id)) {
|
||||||
|
NewsItem item;
|
||||||
|
item.id = id;
|
||||||
|
item.title = obj.value("title").toString();
|
||||||
|
item.content = obj.value("content").toString();
|
||||||
|
item.timestamp = QDateTime::fromString(obj.value("timestamp").toString(), Qt::ISODate);
|
||||||
|
item.read = m_readIds.contains(id);
|
||||||
|
newItems.append(item);
|
||||||
|
existingIds.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newItems.isEmpty()) {
|
||||||
|
beginResetModel();
|
||||||
|
m_items.append(newItems);
|
||||||
|
// Sort descending by timestamp (newest first)
|
||||||
|
std::sort(m_items.begin(), m_items.end(), [](const NewsItem &a, const NewsItem &b) {
|
||||||
|
return a.timestamp > b.timestamp;
|
||||||
|
});
|
||||||
|
endResetModel();
|
||||||
|
emit hasUnreadChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NewsModel::hasUnread() const
|
||||||
|
{
|
||||||
|
for (const NewsItem &item : m_items) {
|
||||||
|
if (!item.read)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::loadReadIds()
|
||||||
|
{
|
||||||
|
QStringList ids = m_settings->readNewsIds();
|
||||||
|
m_readIds = QSet<QString>(ids.begin(), ids.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsModel::saveReadIds() const
|
||||||
|
{
|
||||||
|
m_settings->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end()));
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef NEWSMODEL_H
|
||||||
|
#define NEWSMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QSet>
|
||||||
|
#include <memory>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
struct NewsItem {
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
QString content;
|
||||||
|
QDateTime timestamp;
|
||||||
|
bool read;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NewsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
IdRole = Qt::UserRole + 1,
|
||||||
|
TitleRole,
|
||||||
|
ContentRole,
|
||||||
|
TimestampRole,
|
||||||
|
IsReadRole,
|
||||||
|
IsProcessedRole
|
||||||
|
};
|
||||||
|
explicit NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
|
Q_INVOKABLE void markAsRead(int index);
|
||||||
|
|
||||||
|
Q_PROPERTY(int processedIndex READ processedIndex WRITE setProcessedIndex NOTIFY processedIndexChanged)
|
||||||
|
Q_PROPERTY(bool hasUnread READ hasUnread NOTIFY hasUnreadChanged)
|
||||||
|
int processedIndex() const;
|
||||||
|
void setProcessedIndex(int index);
|
||||||
|
|
||||||
|
void updateModel(const QJsonArray &items);
|
||||||
|
bool hasUnread() const;
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void processedIndexChanged(int index);
|
||||||
|
void hasUnreadChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVector<NewsItem> m_items;
|
||||||
|
int m_processedIndex = -1;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSet<QString> m_readIds;
|
||||||
|
void loadReadIds();
|
||||||
|
void saveReadIds() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NEWSMODEL_H
|
||||||
@@ -85,6 +85,21 @@ PageType {
|
|||||||
|
|
||||||
DividerType {}
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: news
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("News & Notifications")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
leftImageSource: NewsModel.hasUnread ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: backup
|
id: backup
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
property var newsItem
|
||||||
|
|
||||||
|
SortFilterProxyModel {
|
||||||
|
id: proxyNews
|
||||||
|
sourceModel: NewsModel
|
||||||
|
filters: [ ValueFilter { roleName: "isProcessed"; value: true } ]
|
||||||
|
Component.onCompleted: root.newsItem = proxyNews.get(0)
|
||||||
|
}
|
||||||
|
Connections {
|
||||||
|
target: NewsModel
|
||||||
|
function onProcessedIndexChanged() {
|
||||||
|
root.newsItem = proxyNews.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
FlickableType {
|
||||||
|
id: fl
|
||||||
|
anchors.top: backButton.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
contentHeight: content.height
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
headerText: newsItem.title
|
||||||
|
}
|
||||||
|
|
||||||
|
ParagraphTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
text: newsItem.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: header
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("News & Notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: newsList
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: header.bottom
|
||||||
|
anchors.topMargin: 16
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
property bool isFocusable: true
|
||||||
|
|
||||||
|
model: NewsModel
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
reuseItems: true
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
implicitWidth: newsList.width
|
||||||
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
leftImageSource: read ? "" : "qrc:/images/controls/unread-dot.svg"
|
||||||
|
isSmallLeftImage: !read
|
||||||
|
text: title
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
NewsModel.markAsRead(index)
|
||||||
|
NewsModel.processedIndex = index
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsNewsDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -367,7 +367,13 @@ PageType {
|
|||||||
objectName: "settingsTabButton"
|
objectName: "settingsTabButton"
|
||||||
|
|
||||||
isSelected: tabBar.currentIndex === 2
|
isSelected: tabBar.currentIndex === 2
|
||||||
image: "qrc:/images/controls/settings.svg"
|
image: NewsModel.hasUnread ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg"
|
||||||
|
Binding {
|
||||||
|
target: settingsTabButton
|
||||||
|
property: "defaultColor"
|
||||||
|
value: "transparent"
|
||||||
|
when: NewsModel.hasUnread
|
||||||
|
}
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
|
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
|
||||||
tabBar.currentIndex = 2
|
tabBar.currentIndex = 2
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
|||||||
IpcClient::Interface()->resetIpStack();
|
IpcClient::Interface()->resetIpStack();
|
||||||
IpcClient::Interface()->flushDns();
|
IpcClient::Interface()->flushDns();
|
||||||
|
|
||||||
if (!m_vpnConfiguration.value(config_key::configVersion).toInt() && container != DockerContainer::Awg
|
if (container != DockerContainer::Awg && container != DockerContainer::WireGuard) {
|
||||||
&& container != DockerContainer::WireGuard) {
|
|
||||||
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
||||||
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT service)
|
set(PROJECT service)
|
||||||
project(${PROJECT})
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION})
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN-service)
|
set(PROJECT AmneziaVPN-service)
|
||||||
project(${PROJECT})
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION})
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|||||||
Reference in New Issue
Block a user