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
This commit is contained in:
Nethius 2024-12-09 09:32:49 +03:00 committed by GitHub
parent 9688a8e52d
commit 2db99715b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 285 additions and 160 deletions

View file

@ -379,6 +379,13 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
auto errorCode = checkErrors(sslErrors, reply); auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater(); reply->deleteLater();
if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains("services")) {
return ErrorCode::ApiServicesMissingError;
}
}
return errorCode; return errorCode;
} }

View file

@ -109,6 +109,7 @@ namespace amnezia
ApiConfigSslError = 1104, ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105, ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106, ApiConfigDecryptionError = 1106,
ApiServicesMissingError = 1107,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,

View file

@ -63,7 +63,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break; case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); 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;
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break; case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;

View file

@ -55,7 +55,7 @@ void ConnectionController::openConnection()
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway(); emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by end_date event"; qDebug() << "attempt to update api config by expires_at event";
if (configVersion == ApiConfigSources::Telegram) { if (configVersion == ApiConfigSources::Telegram) {
emit updateApiConfigFromTelegram(); emit updateApiConfigFromTelegram();
} else { } else {

View file

@ -27,6 +27,9 @@ namespace
constexpr char storeEndpoint[] = "store_endpoint"; constexpr char storeEndpoint[] = "store_endpoint";
constexpr char isAvailable[] = "is_available"; constexpr char isAvailable[] = "is_available";
constexpr char subscription[] = "subscription";
constexpr char endDate[] = "end_date";
} }
namespace serviceType namespace serviceType
@ -51,23 +54,23 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount())) if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant(); return QVariant();
QJsonObject service = m_services.at(index.row()).toObject(); auto apiServiceData = m_services.at(index.row());
QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject(); auto serviceType = apiServiceData.type;
auto serviceType = service.value(configKey::serviceType).toString(); auto isServiceAvailable = apiServiceData.isServiceAvailable;
switch (role) { switch (role) {
case NameRole: { case NameRole: {
return serviceInfo.value(configKey::name).toString(); return apiServiceData.serviceInfo.name;
} }
case CardDescriptionRole: { case CardDescriptionRole: {
auto speed = serviceInfo.value(configKey::speed).toString(); auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. " return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s") "Works for any sites. Speed up to %1 MBit/s")
.arg(speed); .arg(speed);
} else if (serviceType == serviceType::amneziaFree){ } else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. "); QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) { if (isServiceAvailable) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>"); description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
} }
return description; return description;
@ -83,25 +86,24 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case IsServiceAvailableRole: { case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) { if (serviceType == serviceType::amneziaFree) {
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) { if (isServiceAvailable) {
return false; return false;
} }
} }
return true; return true;
} }
case SpeedRole: { case SpeedRole: {
auto speed = serviceInfo.value(configKey::speed).toString(); return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed);
return tr("%1 MBit/s").arg(speed);
} }
case WorkPeriodRole: { case TimeLimitRole: {
auto timelimit = serviceInfo.value(configKey::timelimit).toString(); auto timeLimit = apiServiceData.serviceInfo.timeLimit;
if (timelimit == "0") { if (timeLimit == "0") {
return ""; return "";
} }
return tr("%1 days").arg(timelimit); return tr("%1 days").arg(timeLimit);
} }
case RegionRole: { case RegionRole: {
return serviceInfo.value(configKey::region).toString(); return apiServiceData.serviceInfo.region;
} }
case FeaturesRole: { case FeaturesRole: {
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
@ -113,12 +115,15 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
} }
case PriceRole: { case PriceRole: {
auto price = serviceInfo.value(configKey::price).toString(); auto price = apiServiceData.serviceInfo.price;
if (price == "free") { if (price == "free") {
return tr("Free"); return tr("Free");
} }
return tr("%1 $/month").arg(price); return tr("%1 $/month").arg(price);
} }
case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
}
} }
return QVariant(); return QVariant();
@ -128,15 +133,18 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
{ {
beginResetModel(); beginResetModel();
m_countryCode = data.value(configKey::userCountryCode).toString(); m_services.clear();
m_services = data.value(configKey::services).toArray();
if (m_services.isEmpty()) {
QJsonObject service;
service.insert(configKey::serviceInfo, data.value(configKey::serviceInfo));
service.insert(configKey::serviceType, data.value(configKey::serviceType));
m_services.push_back(service); m_countryCode = data.value(configKey::userCountryCode).toString();
auto services = data.value(configKey::services).toArray();
if (services.isEmpty()) {
m_services.push_back(getApiServicesData(data));
m_selectedServiceIndex = 0; m_selectedServiceIndex = 0;
} else {
for (const auto &service : services) {
m_services.push_back(getApiServicesData(service.toObject()));
}
} }
endResetModel(); endResetModel();
@ -149,32 +157,32 @@ void ApiServicesModel::setServiceIndex(const int index)
QJsonObject ApiServicesModel::getSelectedServiceInfo() QJsonObject ApiServicesModel::getSelectedServiceInfo()
{ {
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject(); auto service = m_services.at(m_selectedServiceIndex);
return service.value(configKey::serviceInfo).toObject(); return service.serviceInfo.object;
} }
QString ApiServicesModel::getSelectedServiceType() QString ApiServicesModel::getSelectedServiceType()
{ {
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject(); auto service = m_services.at(m_selectedServiceIndex);
return service.value(configKey::serviceType).toString(); return service.type;
} }
QString ApiServicesModel::getSelectedServiceProtocol() QString ApiServicesModel::getSelectedServiceProtocol()
{ {
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject(); auto service = m_services.at(m_selectedServiceIndex);
return service.value(configKey::serviceProtocol).toString(); return service.protocol;
} }
QString ApiServicesModel::getSelectedServiceName() QString ApiServicesModel::getSelectedServiceName()
{ {
auto modelIndex = index(m_selectedServiceIndex, 0); auto service = m_services.at(m_selectedServiceIndex);
return data(modelIndex, ApiServicesModel::Roles::NameRole).toString(); return service.serviceInfo.name;
} }
QJsonArray ApiServicesModel::getSelectedServiceCountries() QJsonArray ApiServicesModel::getSelectedServiceCountries()
{ {
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject(); auto service = m_services.at(m_selectedServiceIndex);
return service.value(configKey::availableCountries).toArray(); return service.availableCountries;
} }
QString ApiServicesModel::getCountryCode() QString ApiServicesModel::getCountryCode()
@ -184,8 +192,8 @@ QString ApiServicesModel::getCountryCode()
QString ApiServicesModel::getStoreEndpoint() QString ApiServicesModel::getStoreEndpoint()
{ {
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject(); auto service = m_services.at(m_selectedServiceIndex);
return service.value(configKey::storeEndpoint).toString(); return service.storeEndpoint;
} }
QVariant ApiServicesModel::getSelectedServiceData(const QString roleString) QVariant ApiServicesModel::getSelectedServiceData(const QString roleString)
@ -209,10 +217,46 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
roles[ServiceDescriptionRole] = "serviceDescription"; roles[ServiceDescriptionRole] = "serviceDescription";
roles[IsServiceAvailableRole] = "isServiceAvailable"; roles[IsServiceAvailableRole] = "isServiceAvailable";
roles[SpeedRole] = "speed"; roles[SpeedRole] = "speed";
roles[WorkPeriodRole] = "workPeriod"; roles[TimeLimitRole] = "timeLimit";
roles[RegionRole] = "region"; roles[RegionRole] = "region";
roles[FeaturesRole] = "features"; roles[FeaturesRole] = "features";
roles[PriceRole] = "price"; roles[PriceRole] = "price";
roles[EndDateRole] = "endDate";
return roles; return roles;
} }
ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data)
{
auto serviceInfo = data.value(configKey::serviceInfo).toObject();
auto serviceType = data.value(configKey::serviceType).toString();
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
auto availableCountries = data.value(configKey::availableCountries).toArray();
auto subscriptionObject = data.value(configKey::subscription).toObject();
ApiServicesData serviceData;
serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString();
serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString();
serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString();
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
serviceData.type = serviceType;
serviceData.protocol = serviceProtocol;
serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();
if (serviceInfo.value(configKey::isAvailable).isBool()) {
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
} else {
serviceData.isServiceAvailable = true;
}
serviceData.serviceInfo.object = serviceInfo;
serviceData.availableCountries = availableCountries;
serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString();
return serviceData;
}

View file

@ -3,6 +3,7 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject>
class ApiServicesModel : public QAbstractListModel class ApiServicesModel : public QAbstractListModel
{ {
@ -15,10 +16,11 @@ public:
ServiceDescriptionRole, ServiceDescriptionRole,
IsServiceAvailableRole, IsServiceAvailableRole,
SpeedRole, SpeedRole,
WorkPeriodRole, TimeLimitRole,
RegionRole, RegionRole,
FeaturesRole, FeaturesRole,
PriceRole PriceRole,
EndDateRole
}; };
explicit ApiServicesModel(QObject *parent = nullptr); explicit ApiServicesModel(QObject *parent = nullptr);
@ -48,8 +50,40 @@ protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
struct ServiceInfo
{
QString name;
QString speed;
QString timeLimit;
QString region;
QString price;
QJsonObject object;
};
struct Subscription
{
QString endDate;
};
struct ApiServicesData
{
bool isServiceAvailable;
QString type;
QString protocol;
QString storeEndpoint;
ServiceInfo serviceInfo;
Subscription subscription;
QJsonArray availableCountries;
};
ApiServicesData getApiServicesData(const QJsonObject &data);
QString m_countryCode; QString m_countryCode;
QJsonArray m_services; QVector<ApiServicesData> m_services;
int m_selectedServiceIndex; int m_selectedServiceIndex;
}; };

View file

@ -22,7 +22,7 @@ namespace
constexpr char serviceProtocol[] = "service_protocol"; constexpr char serviceProtocol[] = "service_protocol";
constexpr char publicKeyInfo[] = "public_key"; constexpr char publicKeyInfo[] = "public_key";
constexpr char endDate[] = "end_date"; constexpr char expiresAt[] = "expires_at";
} }
} }
@ -39,6 +39,9 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
emit ServersModel::defaultServerNameChanged(); emit ServersModel::defaultServerNameChanged();
updateDefaultServerContainersModel(); updateDefaultServerContainersModel();
}); });
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
} }
int ServersModel::rowCount(const QModelIndex &parent) const int ServersModel::rowCount(const QModelIndex &parent) const
@ -79,6 +82,12 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int
return true; return true;
} }
bool ServersModel::setData(const int index, const QVariant &value, int role)
{
QModelIndex modelIndex = this->index(index);
return setData(modelIndex, value, role);
}
QVariant ServersModel::data(const QModelIndex &index, int role) const QVariant ServersModel::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) { if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) {
@ -679,6 +688,18 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
return {}; return {};
} }
bool ServersModel::setProcessedServerData(const QString &roleString, const QVariant &value)
{
const auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return setData(m_processedServerIndex, value, it.key());
}
}
return false;
}
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
{ {
auto server = m_servers.at(m_defaultServerIndex).toObject(); auto server = m_servers.at(m_defaultServerIndex).toObject();
@ -718,9 +739,9 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject(); auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject();
const QString endDate = publicKeyInfo.value(configKey::endDate).toString(); const QString expiresAt = publicKeyInfo.value(configKey::expiresAt).toString();
if (endDate.isEmpty()) { if (expiresAt.isEmpty()) {
publicKeyInfo.insert(configKey::endDate, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate)); publicKeyInfo.insert(configKey::expiresAt, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo); apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo);
serverConfig.insert(configKey::apiConfig, apiConfig); serverConfig.insert(configKey::apiConfig, apiConfig);
editServer(serverConfig, serverIndex); editServer(serverConfig, serverIndex);
@ -728,8 +749,8 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
return false; return false;
} }
auto endDateDateTime = QDateTime::fromString(endDate, Qt::ISODate).toUTC(); auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC();
if (endDateDateTime < QDateTime::currentDateTimeUtc()) { if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) {
return true; return true;
} }
return false; return false;

View file

@ -46,6 +46,7 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool setData(const int index, const QVariant &value, int role = Qt::EditRole);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const int index, int role = Qt::DisplayRole) const; QVariant data(const int index, int role = Qt::DisplayRole) const;
@ -115,6 +116,7 @@ public slots:
QVariant getDefaultServerData(const QString roleString); QVariant getDefaultServerData(const QString roleString);
QVariant getProcessedServerData(const QString roleString); QVariant getProcessedServerData(const QString roleString);
bool setProcessedServerData(const QString &roleString, const QVariant &value);
bool isDefaultServerDefaultContainerHasSplitTunneling(); bool isDefaultServerDefaultContainerHasSplitTunneling();
@ -127,6 +129,9 @@ protected:
signals: signals:
void processedServerIndexChanged(const int index); void processedServerIndexChanged(const int index);
// emitted when the processed server index or processed server data is changed
void processedServerChanged();
void defaultServerIndexChanged(const int index); void defaultServerIndexChanged(const int index);
void defaultServerNameChanged(); void defaultServerNameChanged();
void defaultServerDescriptionChanged(); void defaultServerDescriptionChanged();

View file

@ -54,8 +54,14 @@ PageType {
imageSource: "qrc:/images/controls/download.svg" imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex checked: index === ApiCountryModel.currentIndex
checkable: !ConnectionController.isConnected
onClicked: { onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
return
}
if (index !== ApiCountryModel.currentIndex) { if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex var prevIndex = ApiCountryModel.currentIndex

View file

@ -56,12 +56,15 @@ PageType {
} }
LabelWithImageType { LabelWithImageType {
property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable")
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
imageSource: "qrc:/images/controls/history.svg" imageSource: "qrc:/images/controls/history.svg"
leftText: qsTr("Work period") leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period")
rightText: ApiServicesModel.getSelectedServiceData("workPeriod") rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate")
: ApiServicesModel.getSelectedServiceData("workPeriod")
visible: rightText !== "" visible: rightText !== ""
} }

View file

@ -25,6 +25,8 @@ PageType {
property int pageSettingsApiServerInfo: 3 property int pageSettingsApiServerInfo: 3
property int pageSettingsApiLanguageList: 4 property int pageSettingsApiLanguageList: 4
property var processedServer
defaultActiveFocusItem: focusItem defaultActiveFocusItem: focusItem
Connections { Connections {
@ -35,8 +37,18 @@ PageType {
} }
} }
Connections {
target: ServersModel
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
SortFilterProxyModel { SortFilterProxyModel {
id: proxyServersModel id: proxyServersModel
objectName: "proxyServersModel"
sourceModel: ServersModel sourceModel: ServersModel
filters: [ filters: [
ValueFilter { ValueFilter {
@ -44,147 +56,139 @@ PageType {
value: true value: true
} }
] ]
Component.onCompleted: {
root.processedServer = proxyServersModel.get(0)
}
} }
Item { Item {
id: focusItem id: focusItem
KeyNavigation.tab: header //KeyNavigation.tab: header
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 16 spacing: 4
Repeater { BackButtonType {
id: header id: backButton
model: proxyServersModel
activeFocusOnTab: true Layout.topMargin: 20
onFocusChanged: { KeyNavigation.tab: headerContent.actionButton
header.itemAt(0).focusItem.forceActiveFocus()
backButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
root.processedServer.isCountrySelectionAvailable) {
nestedStackView.currentIndex = root.pageSettingsApiLanguageList
} else {
PageController.closePage()
}
}
}
HeaderType {
id: headerContent
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg"
: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name
descriptionText: {
if (root.processedServer.isServerFromGatewayApi) {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
return qsTr("Subscription is valid until ") + ApiServicesModel.getSelectedServiceData("endDate")
} else {
return ApiServicesModel.getSelectedServiceData("serviceDescription")
}
} else if (root.processedServer.isServerFromTelegramApi) {
return root.processedServer.serverDescription
} else if (root.processedServer.hasWriteAccess) {
return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName
} else {
return root.processedServer.hostName
}
} }
delegate: ColumnLayout { KeyNavigation.tab: tabBar
property alias focusItem: backButton actionButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
nestedStackView.currentIndex = root.pageSettingsApiServerInfo
} else {
serverNameEditDrawer.open()
}
}
}
id: content DrawerType2 {
id: serverNameEditDrawer
Layout.topMargin: 20 parent: root
BackButtonType { anchors.fill: parent
id: backButton expandedHeight: root.height * 0.35
KeyNavigation.tab: headerContent.actionButton
backButtonFunction: function() { onClosed: {
if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && if (!GC.isMobile()) {
ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { headerContent.actionButton.forceActiveFocus()
nestedStackView.currentIndex = root.pageSettingsApiLanguageList }
} else { }
PageController.closePage()
} expandedContent: ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 32
anchors.leftMargin: 16
anchors.rightMargin: 16
Connections {
target: serverNameEditDrawer
enabled: !GC.isMobile()
function onOpened() {
serverName.textField.forceActiveFocus()
} }
} }
HeaderType { Item {
id: headerContent id: focusItem1
KeyNavigation.tab: serverName.textField
}
TextFieldWithHeaderType {
id: serverName
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 headerText: qsTr("Server name")
Layout.rightMargin: 16 textFieldText: root.processedServer.name
textField.maximumLength: 30
checkEmptyText: true
actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" : "qrc:/images/controls/edit-3.svg" KeyNavigation.tab: saveButton
headerText: name
descriptionText: {
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
return ApiServicesModel.getSelectedServiceData("serviceDescription")
} else if (ServersModel.getProcessedServerData("isServerFromTelegramApi")) {
return serverDescription
} else if (ServersModel.isProcessedServerHasWriteAccess()) {
return credentialsLogin + " · " + hostName
} else {
return hostName
}
}
KeyNavigation.tab: tabBar
actionButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
nestedStackView.currentIndex = root.pageSettingsApiServerInfo
} else {
serverNameEditDrawer.open()
}
}
} }
DrawerType2 { BasicButtonType {
id: serverNameEditDrawer id: saveButton
parent: root Layout.fillWidth: true
anchors.fill: parent text: qsTr("Save")
expandedHeight: root.height * 0.35 KeyNavigation.tab: focusItem1
onClosed: { clickedFunc: function() {
if (!GC.isMobile()) { if (serverName.textFieldText === "") {
headerContent.actionButton.forceActiveFocus() return
}
}
expandedContent: ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 32
anchors.leftMargin: 16
anchors.rightMargin: 16
Connections {
target: serverNameEditDrawer
enabled: !GC.isMobile()
function onOpened() {
serverName.textField.forceActiveFocus()
}
} }
Item { if (serverName.textFieldText !== root.processedServer.name) {
id: focusItem1 ServersModel.setProcessedServerData("name", serverName.textFieldText);
KeyNavigation.tab: serverName.textField
}
TextFieldWithHeaderType {
id: serverName
Layout.fillWidth: true
headerText: qsTr("Server name")
textFieldText: name
textField.maximumLength: 30
checkEmptyText: true
KeyNavigation.tab: saveButton
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
text: qsTr("Save")
KeyNavigation.tab: focusItem1
clickedFunc: function() {
if (serverName.textFieldText === "") {
return
}
if (serverName.textFieldText !== name) {
name = serverName.textFieldText
}
serverNameEditDrawer.close()
}
} }
serverNameEditDrawer.close()
} }
} }
} }
@ -257,8 +261,7 @@ PageType {
StackLayout { StackLayout {
id: nestedStackView id: nestedStackView
Layout.preferredWidth: root.width Layout.fillWidth: true
Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight
currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ?
(ServersModel.getProcessedServerData("isCountrySelectionAvailable") ? (ServersModel.getProcessedServerData("isCountrySelectionAvailable") ?