feature/premium v1 migration (#1569)
* feature: premium v1 migration * chore: added stage for macos with new qt version * chore: downgrade qif version * chore: minor ui fixes
This commit is contained in:
parent
a28ed6a977
commit
b457ef9a3f
23 changed files with 829 additions and 14 deletions
82
.github/workflows/deploy.yml
vendored
82
.github/workflows/deploy.yml
vendored
|
@ -20,6 +20,8 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
|
@ -90,6 +92,8 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
|
@ -156,6 +160,8 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
@ -243,7 +249,7 @@ jobs:
|
||||||
|
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS-old:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
@ -255,6 +261,78 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Setup xcode'
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '15.4.0'
|
||||||
|
|
||||||
|
- name: 'Install Qt'
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.QT_VERSION }}
|
||||||
|
host: 'mac'
|
||||||
|
target: 'desktop'
|
||||||
|
arch: 'clang_64'
|
||||||
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
|
dir: ${{ runner.temp }}
|
||||||
|
setup-python: 'true'
|
||||||
|
set-env: 'true'
|
||||||
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||||
|
run: |
|
||||||
|
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||||
|
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||||
|
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||||
|
|
||||||
|
- name: 'Get sources'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
|
- name: 'Setup ccache'
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
|
- name: 'Build project'
|
||||||
|
run: |
|
||||||
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
|
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
||||||
|
bash deploy/build_macos.sh
|
||||||
|
|
||||||
|
- name: 'Upload installer artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_old_installer
|
||||||
|
path: AmneziaVPN.dmg
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload unpacked artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_old_unpacked
|
||||||
|
path: deploy/build/client/AmneziaVPN.app
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
Build-MacOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
QT_VERSION: 6.8.0
|
||||||
|
QIF_VERSION: 4.8.1
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
@ -324,6 +402,8 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
2
.github/workflows/tag-deploy.yml
vendored
2
.github/workflows/tag-deploy.yml
vendored
|
@ -20,6 +20,8 @@ jobs:
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
|
@ -31,6 +31,9 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||||
|
|
||||||
|
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||||
|
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(PACKAGES ${PACKAGES} Widgets)
|
set(PACKAGES ${PACKAGES} Widgets)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -22,12 +22,19 @@ namespace apiDefs
|
||||||
namespace key
|
namespace key
|
||||||
{
|
{
|
||||||
constexpr QLatin1String configVersion("config_version");
|
constexpr QLatin1String configVersion("config_version");
|
||||||
|
constexpr QLatin1String apiEndpoint("api_endpoint");
|
||||||
|
constexpr QLatin1String apiKey("api_key");
|
||||||
|
constexpr QLatin1String description("description");
|
||||||
|
constexpr QLatin1String name("name");
|
||||||
|
constexpr QLatin1String protocol("protocol");
|
||||||
|
|
||||||
constexpr QLatin1String apiConfig("api_config");
|
constexpr QLatin1String apiConfig("api_config");
|
||||||
constexpr QLatin1String stackType("stack_type");
|
constexpr QLatin1String stackType("stack_type");
|
||||||
constexpr QLatin1String serviceType("service_type");
|
constexpr QLatin1String serviceType("service_type");
|
||||||
|
|
||||||
constexpr QLatin1String vpnKey("vpn_key");
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
|
constexpr QLatin1String config("config");
|
||||||
|
constexpr QLatin1String configs("configs");
|
||||||
|
|
||||||
constexpr QLatin1String installationUuid("installation_uuid");
|
constexpr QLatin1String installationUuid("installation_uuid");
|
||||||
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||||
|
@ -51,6 +58,10 @@ namespace apiDefs
|
||||||
constexpr QLatin1String website("website");
|
constexpr QLatin1String website("website");
|
||||||
constexpr QLatin1String websiteName("website_name");
|
constexpr QLatin1String websiteName("website_name");
|
||||||
constexpr QLatin1String telegram("telegram");
|
constexpr QLatin1String telegram("telegram");
|
||||||
|
|
||||||
|
constexpr QLatin1String id("id");
|
||||||
|
constexpr QLatin1String orderId("order_id");
|
||||||
|
constexpr QLatin1String migrationCode("migration_code");
|
||||||
}
|
}
|
||||||
|
|
||||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
|
|
@ -3,6 +3,24 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||||
|
|
||||||
|
QString escapeUnicode(const QString &input)
|
||||||
|
{
|
||||||
|
QString output;
|
||||||
|
for (QChar c : input) {
|
||||||
|
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
|
||||||
|
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
|
||||||
|
} else {
|
||||||
|
output += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
{
|
{
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
|
@ -27,22 +45,28 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
|
||||||
case apiDefs::ConfigSource::Telegram: {
|
case apiDefs::ConfigSource::Telegram: {
|
||||||
};
|
};
|
||||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
constexpr QLatin1String stackPremium("prem");
|
|
||||||
constexpr QLatin1String stackFree("free");
|
|
||||||
|
|
||||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
constexpr QLatin1String serviceFree("amnezia-free");
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
|
|
||||||
|
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||||
|
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||||
|
|
||||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
|
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||||
|
|
||||||
if (serviceType == servicePremium) {
|
if (serviceType == servicePremium) {
|
||||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
} else if (serviceType == serviceFree) {
|
} else if (serviceType == serviceFree) {
|
||||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
} else if (serviceType == serviceExternalPremium) {
|
} else if (serviceType == serviceExternalPremium) {
|
||||||
return apiDefs::ConfigType::ExternalPremium;
|
return apiDefs::ConfigType::ExternalPremium;
|
||||||
|
} else if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||||
|
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -95,3 +119,41 @@ bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
apiDefs::ConfigType::ExternalPremium };
|
apiDefs::ConfigType::ExternalPremium };
|
||||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QPair<QString, QVariant>> orderedFields;
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
|
||||||
|
|
||||||
|
QString vpnKeyStr = "{";
|
||||||
|
for (int i = 0; i < orderedFields.size(); ++i) {
|
||||||
|
const auto &pair = orderedFields[i];
|
||||||
|
if (pair.second.typeId() == QMetaType::Type::QString) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
|
||||||
|
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < orderedFields.size() - 1) {
|
||||||
|
vpnKeyStr += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vpnKeyStr += "}";
|
||||||
|
|
||||||
|
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||||
|
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||||
|
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||||
|
|
||||||
|
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||||
|
|
||||||
|
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ namespace apiUtils
|
||||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||||
|
|
||||||
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // APIUTILS_H
|
#endif // APIUTILS_H
|
||||||
|
|
|
@ -148,6 +148,9 @@ void CoreController::initControllers()
|
||||||
|
|
||||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
|
|
||||||
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
|
@ -220,6 +223,8 @@ void CoreController::initSignalHandlers()
|
||||||
initAutoConnectHandler();
|
initAutoConnectHandler();
|
||||||
initAmneziaDnsToggledHandler();
|
initAmneziaDnsToggledHandler();
|
||||||
initPrepareConfigHandler();
|
initPrepareConfigHandler();
|
||||||
|
initImportPremiumV2VpnKeyHandler();
|
||||||
|
initShowMigrationDrawerHandler();
|
||||||
initStrictKillSwitchHandler();
|
initStrictKillSwitchHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,10 +368,29 @@ void CoreController::initPrepareConfigHandler()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::initImportPremiumV2VpnKeyHandler()
|
||||||
|
{
|
||||||
|
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
|
||||||
|
m_importController->extractConfigFromData(vpnKey);
|
||||||
|
m_importController->importConfig();
|
||||||
|
|
||||||
|
emit m_apiPremV1MigrationController->migrationFinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initShowMigrationDrawerHandler()
|
||||||
|
{
|
||||||
|
QTimer::singleShot(1000, this, [this]() {
|
||||||
|
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
|
||||||
|
m_apiPremV1MigrationController->showMigrationDrawer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::initStrictKillSwitchHandler()
|
void CoreController::initStrictKillSwitchHandler()
|
||||||
{
|
{
|
||||||
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged,
|
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||||
m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged);
|
&VpnConnection::onKillSwitchModeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<PageController> CoreController::pageController() const
|
QSharedPointer<PageController> CoreController::pageController() const
|
||||||
|
|
|
@ -7,6 +7,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/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"
|
||||||
|
@ -82,6 +83,8 @@ private:
|
||||||
void initAutoConnectHandler();
|
void initAutoConnectHandler();
|
||||||
void initAmneziaDnsToggledHandler();
|
void initAmneziaDnsToggledHandler();
|
||||||
void initPrepareConfigHandler();
|
void initPrepareConfigHandler();
|
||||||
|
void initImportPremiumV2VpnKeyHandler();
|
||||||
|
void initShowMigrationDrawerHandler();
|
||||||
void initStrictKillSwitchHandler();
|
void initStrictKillSwitchHandler();
|
||||||
|
|
||||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||||
|
@ -109,6 +112,7 @@ private:
|
||||||
|
|
||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
|
|
|
@ -117,6 +117,7 @@ namespace amnezia
|
||||||
ApiServicesMissingError = 1107,
|
ApiServicesMissingError = 1107,
|
||||||
ApiConfigLimitError = 1108,
|
ApiConfigLimitError = 1108,
|
||||||
ApiNotFoundError = 1109,
|
ApiNotFoundError = 1109,
|
||||||
|
ApiMigrationError = 1110,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|
|
@ -74,6 +74,7 @@ QString errorString(ErrorCode code) {
|
||||||
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
|
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
|
||||||
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
|
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
|
||||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||||
|
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error occurred. Please contact our technical support"); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|
|
@ -236,6 +236,9 @@
|
||||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||||
<file>images/controls/monitor.svg</file>
|
<file>images/controls/monitor.svg</file>
|
||||||
|
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
|
||||||
|
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
|
||||||
|
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|
|
@ -559,6 +559,16 @@ void Settings::disableHomeAdLabel()
|
||||||
setValue("Conf/homeAdLabelVisible", false);
|
setValue("Conf/homeAdLabelVisible", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Settings::isPremV1MigrationReminderActive()
|
||||||
|
{
|
||||||
|
return value("Conf/premV1MigrationReminderActive", true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::disablePremV1MigrationReminder()
|
||||||
|
{
|
||||||
|
setValue("Conf/premV1MigrationReminderActive", false);
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Settings::allowedDnsServers() const
|
QStringList Settings::allowedDnsServers() const
|
||||||
{
|
{
|
||||||
return value("Conf/allowedDnsServers").toStringList();
|
return value("Conf/allowedDnsServers").toStringList();
|
||||||
|
|
|
@ -229,6 +229,9 @@ public:
|
||||||
bool isHomeAdLabelVisible();
|
bool isHomeAdLabelVisible();
|
||||||
void disableHomeAdLabel();
|
void disableHomeAdLabel();
|
||||||
|
|
||||||
|
bool isPremV1MigrationReminderActive();
|
||||||
|
void disablePremV1MigrationReminder();
|
||||||
|
|
||||||
QStringList allowedDnsServers() const;
|
QStringList allowedDnsServers() const;
|
||||||
void setAllowedDnsServers(const QStringList &servers);
|
void setAllowedDnsServers(const QStringList &servers);
|
||||||
|
|
||||||
|
|
127
client/ui/controllers/api/apiPremV1MigrationController.cpp
Normal file
127
client/ui/controllers/api/apiPremV1MigrationController.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
#include "apiPremV1MigrationController.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
|
||||||
|
ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
|
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiPremV1MigrationController::hasConfigsToMigration()
|
||||||
|
{
|
||||||
|
QJsonArray vpnKeys;
|
||||||
|
|
||||||
|
auto serversCount = m_serversModel->getServersCount();
|
||||||
|
for (size_t i = 0; i < serversCount; i++) {
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(i);
|
||||||
|
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject);
|
||||||
|
vpnKeys.append(vpnKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
|
||||||
|
apiPayload["configs"] = vpnKeys;
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
auto migrationsStatus = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
for (const auto &migrationStatus : migrationsStatus) {
|
||||||
|
if (migrationStatus == "not_found") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiPremV1MigrationController::getSubscriptionList(const QString &email)
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
|
||||||
|
apiPayload[apiDefs::key::email] = email;
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
m_email = email;
|
||||||
|
m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
if (m_subscriptionsModel.isEmpty()) {
|
||||||
|
emit noSubscriptionToMigrate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit subscriptionsModelChanged();
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(ErrorCode::ApiMigrationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray ApiPremV1MigrationController::getSubscriptionModel()
|
||||||
|
{
|
||||||
|
return m_subscriptionsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex)
|
||||||
|
{
|
||||||
|
QEventLoop wait;
|
||||||
|
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
|
||||||
|
apiPayload[apiDefs::key::email] = m_email;
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
m_subscriptionIndex = subscriptionIndex;
|
||||||
|
emit otpSuccessfullySent();
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(ErrorCode::ApiMigrationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiPremV1MigrationController::migrate(const QString &migrationCode)
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
|
||||||
|
apiPayload[apiDefs::key::email] = m_email;
|
||||||
|
apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString();
|
||||||
|
apiPayload[apiDefs::key::migrationCode] = migrationCode;
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
auto responseObject = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString();
|
||||||
|
|
||||||
|
emit importPremiumV2VpnKey(premiumV2VpnKey);
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(ErrorCode::ApiMigrationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiPremV1MigrationController::isPremV1MigrationReminderActive()
|
||||||
|
{
|
||||||
|
return m_settings->isPremV1MigrationReminderActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiPremV1MigrationController::disablePremV1MigrationReminder()
|
||||||
|
{
|
||||||
|
m_settings->disablePremV1MigrationReminder();
|
||||||
|
}
|
50
client/ui/controllers/api/apiPremV1MigrationController.h
Normal file
50
client/ui/controllers/api/apiPremV1MigrationController.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#ifndef APIPREMV1MIGRATIONCONTROLLER_H
|
||||||
|
#define APIPREMV1MIGRATIONCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
|
class ApiPremV1MigrationController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged)
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool hasConfigsToMigration();
|
||||||
|
void getSubscriptionList(const QString &email);
|
||||||
|
QJsonArray getSubscriptionModel();
|
||||||
|
void sendMigrationCode(const int subscriptionIndex);
|
||||||
|
void migrate(const QString &migrationCode);
|
||||||
|
|
||||||
|
bool isPremV1MigrationReminderActive();
|
||||||
|
void disablePremV1MigrationReminder();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void subscriptionsModelChanged();
|
||||||
|
|
||||||
|
void otpSuccessfullySent();
|
||||||
|
|
||||||
|
void importPremiumV2VpnKey(const QString &vpnKey);
|
||||||
|
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
void showMigrationDrawer();
|
||||||
|
void migrationFinished();
|
||||||
|
|
||||||
|
void noSubscriptionToMigrate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
|
QJsonArray m_subscriptionsModel;
|
||||||
|
int m_subscriptionIndex;
|
||||||
|
QString m_email;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APIPREMV1MIGRATIONCONTROLLER_H
|
|
@ -348,6 +348,25 @@ void ServersModel::removeServer()
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServersModel::removeServer(const int serverIndex)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_settings->removeServer(serverIndex);
|
||||||
|
m_servers = m_settings->serversArray();
|
||||||
|
|
||||||
|
if (m_settings->defaultServerIndex() == serverIndex) {
|
||||||
|
setDefaultServerIndex(0);
|
||||||
|
} else if (m_settings->defaultServerIndex() > serverIndex) {
|
||||||
|
setDefaultServerIndex(m_settings->defaultServerIndex() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settings->serversCount() == 0) {
|
||||||
|
setDefaultServerIndex(-1);
|
||||||
|
}
|
||||||
|
setProcessedServerIndex(m_defaultServerIndex);
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> ServersModel::roleNames() const
|
QHash<int, QByteArray> ServersModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
|
@ -90,6 +90,7 @@ public slots:
|
||||||
void addServer(const QJsonObject &server);
|
void addServer(const QJsonObject &server);
|
||||||
void editServer(const QJsonObject &server, const int serverIndex);
|
void editServer(const QJsonObject &server, const int serverIndex);
|
||||||
void removeServer();
|
void removeServer();
|
||||||
|
void removeServer(const int serverIndex);
|
||||||
|
|
||||||
QJsonObject getServerConfig(const int serverIndex);
|
QJsonObject getServerConfig(const int serverIndex);
|
||||||
|
|
||||||
|
|
194
client/ui/qml/Components/ApiPremV1MigrationDrawer.qml
Normal file
194
client/ui/qml/Components/ApiPremV1MigrationDrawer.qml
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
expandedHeight: parent.height * 0.9
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiPremV1MigrationController
|
||||||
|
|
||||||
|
function onErrorOccurred(error, goToPageHome) {
|
||||||
|
PageController.showErrorMessage(error)
|
||||||
|
root.closeTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedStateContent: Item {
|
||||||
|
implicitHeight: root.expandedHeight
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
model: 1 // fake model to force the ListView to be created without a model
|
||||||
|
snapMode: ListView.NoSnap
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
Header2Type {
|
||||||
|
id: header
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Switch to the new Amnezia Premium subscription")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
|
ParagraphTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
textFormat: Text.RichText
|
||||||
|
text: {
|
||||||
|
var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ")
|
||||||
|
str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:")
|
||||||
|
str += "<ul style='margin-left: -16px;'>"
|
||||||
|
str += qsTr("<li>9 locations (with more coming soon)</li>")
|
||||||
|
str += qsTr("<li>Easier switching between countries in the app</li>")
|
||||||
|
str += qsTr("<li>Personal dashboard to manage your subscription</li>")
|
||||||
|
str += "</ul>"
|
||||||
|
str += qsTr("Old keys will be deactivated after switching.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: emailLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
borderColor: AmneziaStyle.color.mutedGray
|
||||||
|
headerTextColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
headerText: qsTr("Email")
|
||||||
|
textField.placeholderText: qsTr("mail@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
textField.onFocusChanged: {
|
||||||
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiPremV1MigrationController
|
||||||
|
|
||||||
|
function onNoSubscriptionToMigrate() {
|
||||||
|
emailLabel.errorText = qsTr("No old format subscriptions for a given email")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptionTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
|
||||||
|
color: AmneziaStyle.color.mutedGray
|
||||||
|
|
||||||
|
text: qsTr("Enter the email you used for your current subscription")
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiPremV1SubListDrawer {
|
||||||
|
id: apiPremV1SubListDrawer
|
||||||
|
parent: root
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
OtpCodeDrawer {
|
||||||
|
id: otpCodeDrawer
|
||||||
|
parent: root
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: yesButton
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
|
||||||
|
text: qsTr("Continue")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
ApiPremV1MigrationController.getSubscriptionList(emailLabel.textField.text)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: noButton
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
defaultColor: AmneziaStyle.color.transparent
|
||||||
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
|
disabledColor: AmneziaStyle.color.mutedGray
|
||||||
|
textColor: AmneziaStyle.color.paleGray
|
||||||
|
borderWidth: 1
|
||||||
|
|
||||||
|
text: qsTr("Remind me later")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
root.closeTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.bottomMargin: 32
|
||||||
|
implicitHeight: 32
|
||||||
|
|
||||||
|
defaultColor: "transparent"
|
||||||
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
|
text: qsTr("Don't remind me again")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
var headerText = qsTr("No more reminders? You can always switch to the new format in the server settings")
|
||||||
|
var yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
ApiPremV1MigrationController.disablePremV1MigrationReminder()
|
||||||
|
root.closeTriggered()
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
client/ui/qml/Components/ApiPremV1SubListDrawer.qml
Normal file
89
client/ui/qml/Components/ApiPremV1SubListDrawer.qml
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiPremV1MigrationController
|
||||||
|
|
||||||
|
function onSubscriptionsModelChanged() {
|
||||||
|
if (ApiPremV1MigrationController.subscriptionsModel.length > 1) {
|
||||||
|
root.openTriggered()
|
||||||
|
} else {
|
||||||
|
sendMigrationCode(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMigrationCode(index) {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
ApiPremV1MigrationController.sendMigrationCode(index)
|
||||||
|
root.closeTriggered()
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedHeight: parent.height * 0.9
|
||||||
|
|
||||||
|
expandedStateContent: Item {
|
||||||
|
implicitHeight: root.expandedHeight
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
model: ApiPremV1MigrationController.subscriptionsModel
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
Header2Type {
|
||||||
|
id: header
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Choose Subscription")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
implicitWidth: listView.width
|
||||||
|
implicitHeight: delegateContent.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: delegateContent
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: server
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Order ID: ") + modelData.id
|
||||||
|
|
||||||
|
descriptionText: qsTr("Purchase Date: ") + Qt.formatDateTime(new Date(modelData.created_at), "dd.MM.yyyy hh:mm")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
sendMigrationCode(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
client/ui/qml/Components/OtpCodeDrawer.qml
Normal file
77
client/ui/qml/Components/OtpCodeDrawer.qml
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiPremV1MigrationController
|
||||||
|
|
||||||
|
function onOtpSuccessfullySent() {
|
||||||
|
root.openTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedHeight: parent.height * 0.6
|
||||||
|
|
||||||
|
expandedStateContent: Item {
|
||||||
|
implicitHeight: root.expandedHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Header2Type {
|
||||||
|
id: header
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
|
||||||
|
headerText: qsTr("OTP code was sent to your email")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: otpFiled
|
||||||
|
|
||||||
|
borderColor: AmneziaStyle.color.mutedGray
|
||||||
|
headerTextColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
headerText: qsTr("OTP Code")
|
||||||
|
textField.maximumLength: 30
|
||||||
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: saveButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Continue")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
ApiPremV1MigrationController.migrate(otpFiled.textField.text)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
root.closeTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
@ -39,7 +41,7 @@ DrawerType2 {
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
text: headerText
|
text: root.headerText
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
|
@ -48,7 +50,7 @@ DrawerType2 {
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
text: descriptionText
|
text: root.descriptionText
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
@ -58,11 +60,11 @@ DrawerType2 {
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
text: yesButtonText
|
text: root.yesButtonText
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
if (yesButtonFunction && typeof yesButtonFunction === "function") {
|
if (root.yesButtonFunction && typeof root.yesButtonFunction === "function") {
|
||||||
yesButtonFunction()
|
root.yesButtonFunction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,11 +82,13 @@ DrawerType2 {
|
||||||
textColor: AmneziaStyle.color.paleGray
|
textColor: AmneziaStyle.color.paleGray
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|
||||||
text: noButtonText
|
visible: root.noButtonText !== ""
|
||||||
|
|
||||||
|
text: root.noButtonText
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
if (noButtonFunction && typeof noButtonFunction === "function") {
|
if (root.noButtonFunction && typeof root.noButtonFunction === "function") {
|
||||||
noButtonFunction()
|
root.noButtonFunction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,31 @@ PageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
|
||||||
|
target: ApiPremV1MigrationController
|
||||||
|
|
||||||
|
function onMigrationFinished() {
|
||||||
|
apiPremV1MigrationDrawer.closeTriggered()
|
||||||
|
|
||||||
|
var headerText = qsTr("You've successfully switched to the new Amnezia Premium subscription!")
|
||||||
|
var descriptionText = qsTr("Old keys will no longer work. Please use your new subscription key to connect. \nThank you for staying with us!")
|
||||||
|
var yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = ""
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShowMigrationDrawer() {
|
||||||
|
apiPremV1MigrationDrawer.openTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
objectName: "homeColumnItem"
|
objectName: "homeColumnItem"
|
||||||
|
|
||||||
|
@ -429,4 +454,9 @@ PageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApiPremV1MigrationDrawer {
|
||||||
|
id: apiPremV1MigrationDrawer
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,6 +257,24 @@ PageType {
|
||||||
DividerType {
|
DividerType {
|
||||||
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: labelWithButton6
|
||||||
|
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Switch to the new Amnezia Premium subscription")
|
||||||
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
PageController.goToPageHome()
|
||||||
|
ApiPremV1MigrationController.showMigrationDrawer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {
|
||||||
|
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue