diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index f60e2d497..3d6cb3352 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -192,7 +192,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &ssl } qDebug() << "something went wrong"; - return amnezia::ErrorCode::InternalError; + return amnezia::ErrorCode::ApiConfigDownloadError; } bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject) diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 20d094dda..4f5262cde 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -82,7 +82,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break; case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break; case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break; - case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email has already been used for trial activation"); break; + case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email address has already been used to activate a trial"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 2bfbf79f1..e813b40f1 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -52,18 +52,18 @@ ApiAccountInfoModel - - + + Active Активна - - <p><a style="color: #EB5757;">Inactive</a> + + Inactive Не активна - + %1 out of %2 %1 из %2 @@ -71,23 +71,51 @@ ApiConfigsController - - + + %1 installed successfully. %1 успешно установлен. - + Subscription restored successfully. Подписка успешно восстановлена. - + + %1/mo + IAP: price per month in plan subtitle + %1/мес + + + + from %1 per month + IAP: card footer minimum monthly price from StoreKit + от %1 в месяц + + + + + This subscription has already been added + Эта подписка уже добавлена + + + + %1 has been added to the app + %1 добавлено в приложение + + + + This email address has already been used to activate a trial. If you like the service, you can upgrade to Premium + Этот адрес электронной почты уже использовался для активации пробного периода. Если вам понравился сервис, вы можете оформить подписку Premium + + + API config reloaded Конфигурация API перезагружена - + Successfully changed the country of connection to %1 Страна подключения изменена на %1 @@ -182,29 +210,24 @@ <p><a style="color: #EB5757;">Недоступно в вашем регионе. Если у вас включен VPN, отключите его, вернитесь на предыдущий экран и попробуйте снова.</a> - %1 MBit/s - %1 Мбит/с + %1 Мбит/с - %1 days - %1 дней + %1 дней - Free - Бесплатно + Бесплатно - %1 $ - %1 $ + %1 $ - %1 $/month - %1 $/месяц + %1 $/месяц @@ -241,45 +264,45 @@ ConnectionController - + Connecting... Подключение... - + Connected Подключено - + Preparing... Подготовка... - + Settings updated successfully, reconnnection... Настройки успешно обновлены, переподключение... - + Settings updated successfully Настройки успешно обновлены - + Reconnecting... Переподключение... - - - + + + Connect Подключиться - + Disconnecting... Отключение... @@ -1697,17 +1720,32 @@ Thank you for staying with us! PageSettingsApiAvailableCountries - + + Subscription expired + Подписка закончилась + + + + Subscription expiring soon + Подписка скоро закончится + + + + Renew subscription + Продлить подписку + + + Location for connection Страны для подключения - + Unable change server location while trying to make an active connection Невозможно изменить локацию во время попытки соединения - + Unable change server location while there is an active connection Невозможно изменить локацию во время активного соединения @@ -1939,12 +1977,12 @@ Thank you for staying with us! PageSettingsApiServerInfo - + Configurations have been updated for some countries. Download and install the updated configuration files Сетевые адреса одного или нескольких серверов были обновлены. Пожалуйста, удалите старые конфигурацию и загрузите новые файлы - + Manage configuration files Управление файлами конфигурации @@ -1964,106 +2002,122 @@ Thank you for staying with us! Активные соединения - + + Subscription expired + Подписка закончилась + + + + Subscription expiring soon + Подписка скоро закончится + + + + + Renew subscription + Продлить подписку + + + Use VLESS protocol Использовать протокол VLESS - + Cannot change protocol during active connection Невозможно изменить протокол во время активного соединения - + Subscription Key Ключ для подключения - + Configuration Files Файлы конфигурации - + Active Devices Активные устройства - + Manage currently connected devices Управление подключенными устройствами - + Support Поддержка - + How to connect on another device Как подключить другие устройства - + Reload API config Перезагрузить конфигурацию API - + Reload API config? Перезагрузить конфигурацию API? - - - + + + Continue Продолжить - - - + + + Cancel Отменить - + Cannot reload API config during active connection Невозможно перзагрузить API конфигурацию при активном соединении - + Unlink this device Отвязать это устройство - + Are you sure you want to unlink this device? Вы уверены, что хотите отвязать это устройство? - + This will unlink the device from your subscription. You can reconnect it anytime by pressing "Reload API config" in subscription settings on device. Это отключит устройство от вашей подписки. Вы можете повторно подключить его в любое время, нажав "Перезагрузить конфигурацию API" в настройках подписки на устройстве. - + Cannot unlink device during active connection Невозможно отвязать устройство во время активного соединения - + Remove from application Удалить из приложения - + Remove from application? Удалить из приложения? - + Cannot remove server during active connection Невозможно удалить сервер во время активного соединения @@ -3111,51 +3165,83 @@ Thank you for staying with us! - PageSetupWizardApiServiceInfo + PageSetupWizardApiFreeInfo - + + Free features + Возможности Free + + + + Continue + Продолжить + + + + PageSetupWizardApiPremiumInfo + + + Recommended + Рекомендуется + + + + Premium features + Возможности Premium + + + Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings. Списание с Apple ID при подтверждении. Продление автоматическое, если автопродление не отключено минимум за 24 часа до окончания периода. Управление в настройках Apple ID. - + + Continue + Продолжить + + + + Subscribe — %1 for %2 + Подписаться — %1 за %2 + + + + PageSetupWizardApiServiceInfo + + Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings. + Списание с Apple ID при подтверждении. Продление автоматическое, если автопродление не отключено минимум за 24 часа до окончания периода. Управление в настройках Apple ID. + + Subscribe Now - Подписаться сейчас + Подписаться сейчас - By continuing, you agree to the <a href="%1" style="color: #FBB26A;">Terms of Use</a> and <a href="%2" style="color: #FBB26A;">Privacy Policy</a> - Продолжая, вы соглашаетесь с <a href="%1" style="color: #FBB26A;">Условиями использования</a> и <a href="%2" style="color: #FBB26A;">Политикой конфиденциальности</a> + Продолжая, вы соглашаетесь с <a href="%1" style="color: #FBB26A;">Условиями использования</a> и <a href="%2" style="color: #FBB26A;">Политикой конфиденциальности</a> - For the region - Для региона + Для региона - Price - Цена + Цена - Work period - Период работы + Период работы - Speed - Скорость + Скорость - Features - Особенности + Особенности - Connect - Подключиться + Подключиться @@ -3170,11 +3256,50 @@ Thank you for staying with us! Choose a VPN service that suits your needs. Выберите VPN-сервис, который подходит именно вам. + + + Recommended + Рекомендуется + + + + PageSetupWizardApiTrialEmail + + + Create an account + Создайте учётную запись + + + + To manage your subscription + Для управления подпиской + + + + + Email + Электронная почта + + + + We will create an account for your trial subscription and send important subscription updates to this email address + Мы создадим учётную запись для вашей пробной подписки и будем отправлять на этот адрес электронной почты важные уведомления о подписке + + + + Continue + Продолжить + + + + Enter a valid email address + Введите корректный адрес электронной почты + PageSetupWizardConfigSource - + File with connection settings Файл с настройками подключения @@ -3249,71 +3374,80 @@ Thank you for staying with us! Другие варианты подключения - + + Recommended + Рекомендуется + + + Site Amnezia Сайт Amnezia - + + The easiest way to connect to the VPN + Самый простой способ подключиться к VPN + + + Restore purchases Восстановить покупки - + VPN by Amnezia VPN от Amnezia - Connect to classic paid and free VPN services from Amnezia - Подключайтесь к классическим платным и бесплатным VPN-сервисам от Amnezia + Подключайтесь к классическим платным и бесплатным VPN-сервисам от Amnezia - + Self-hosted VPN Self-hosted VPN - + Configure Amnezia VPN on your own server Настроить VPN на собственном сервере - + Restore from backup Восстановить из резервной копии - - - - - + + + + + - + Open backup file Открыть резервную копию - + Backup files (*.backup) Файлы резервных копий (*.backup) - + Open config file Открыть файл с конфигурацией - + QR code QR-код - + I have nothing У меня ничего нет @@ -3321,17 +3455,17 @@ Thank you for staying with us! PageSetupWizardCredentials - + Server IP address [:port] IP-адрес[:порт] сервера - + Continue Продолжить - + Enter the address in the format 255.255.255.255:88 Введите адрес в формате 255.255.255.255:88 @@ -3341,48 +3475,54 @@ Thank you for staying with us! Настроить ваш сервер - + 255.255.255.255:22 255.255.255.255:22 - + SSH Username Имя пользователя SSH - + + Password or SSH private key Пароль или закрытый ключ SSH - + + SSH key requirements: supported key types are ED25519 and RSA in PEM format. Paste the private key, including the BEGIN/END lines. If your key doesn’t work, generate a compatible one + Требования к SSH-ключу: поддерживаются ключи ED25519 и RSA в формате PEM. Вставьте закрытый ключ целиком, включая строки BEGIN/END. Если ваш ключ не подходит, создайте совместимый ключ + + + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим лицам - + How to run your VPN server Как создать VPN на собственном сервере - + Where to get connection data, step-by-step instructions for buying a VPS Где взять данные для подключения, пошаговые инструкции по покупке VPS - + Ip address cannot be empty Поле с IP-адресом не может быть пустым - + Login cannot be empty Поле с логином не может быть пустым - + Password/private key cannot be empty Поле с паролем/закрытым ключом не может быть пустым @@ -3516,7 +3656,7 @@ Thank you for staying with us! PageSetupWizardStart - + Let's get started Приступим @@ -4326,7 +4466,22 @@ Thank you for staying with us! Не удалось обработать покупку - + + No active subscription found + Активная подписка не найдена + + + + No purchased subscriptions found. Please purchase a subscription first + Платные подписки не найдены. Сначала оформите подписку + + + + This email address has already been used to activate a trial + Этот адрес электронной почты уже использовался для активации пробного периода + + + ErrorCode: %1. Код ошибки: %1. @@ -4426,37 +4581,37 @@ Thank you for staying with us! Превышен лимит разрешенных конфигураций для одной подписки - + QFile error: The file could not be opened Ошибка QFile: не удалось открыть файл - + QFile error: An error occurred when reading from the file Ошибка QFile: произошла ошибка при чтении из файла - + QFile error: The file could not be accessed Ошибка QFile: не удалось получить доступ к файлу - + QFile error: An unspecified error occurred Ошибка QFile: произошла неизвестная ошибка - + QFile error: A fatal error occurred Ошибка QFile: произошла фатальная ошибка - + QFile error: The operation was aborted Ошибка QFile: операция была прервана - + Internal error Внутренняя ошибка @@ -4985,7 +5140,17 @@ FileZilla или другие SFTP-клиенты, а также смонтир ServersListView - + + Subscription expired. Please renew + Подписка закончилась. Пожалуйста, продлите её + + + + Subscription expiring soon + Подписка скоро закончится + + + Unable change server while there is an active connection Невозможно изменить сервер во время активного соединения @@ -5007,12 +5172,17 @@ FileZilla или другие SFTP-клиенты, а также смонтир SettingsController - + + Can't open file: %1 + Невозможно открыть файл: %1 + + + All settings have been reset to default values Все настройки сброшены до значений по умолчанию - + Backup file is corrupted Файл резервной копии поврежден @@ -5065,6 +5235,29 @@ FileZilla или другие SFTP-клиенты, а также смонтир Экспорт завершен + + SubscriptionExpiredDrawer + + + Amnezia Premium subscription has expired + Подписка Amnezia Premium закончилась + + + + Renew to continue using VPN + Продлите подписку, чтобы продолжить использовать VPN + + + + Renew + Продлить + + + + Support + Поддержка + + SystemTrayNotificationHandler @@ -5098,6 +5291,14 @@ FileZilla или другие SFTP-клиенты, а также смонтир Закрыть + + TermsAndPrivacyText + + + By continuing, you agree to the <a href="%1" style="color: %3;">Terms of Use</a> and <a href="%2" style="color: %3;">Privacy Policy</a> + Продолжая, вы соглашаетесь с <a href="%1" style="color: %3;">Условиями использования</a> и <a href="%2" style="color: %3;">Политикой конфиденциальности</a> + + TextFieldWithHeaderType @@ -5173,12 +5374,12 @@ FileZilla или другие SFTP-клиенты, а также смонтир main2 - + Private key passphrase Парольная фраза для закрытого ключа - + Save Сохранить diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index bda3db971..d55a74dbd 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -661,7 +661,7 @@ bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProduct int duplicateServerIndex = -1; errorCode = importServiceFromBilling(responseBody, isTestPurchase, duplicateServerIndex); if (errorCode == ErrorCode::ApiConfigAlreadyAdded) { - emit installServerFromApiFinished(tr("This subscription is already in the app."), duplicateServerIndex); + emit installServerFromApiFinished(tr("This subscription has already been added"), duplicateServerIndex); return true; } if (errorCode != ErrorCode::NoError) { @@ -669,7 +669,7 @@ bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProduct return false; } emit installServerFromApiFinished( - tr("%1 was added to the app.").arg(m_apiServicesModel->getSelectedServiceName())); + tr("%1 has been added to the app").arg(m_apiServicesModel->getSelectedServiceName())); return true; #else Q_UNUSED(storeProductId); @@ -799,7 +799,7 @@ bool ApiConfigsController::restoreServiceFromAppStore() if (!hasInstalledConfig) { if (duplicateConfigAlreadyPresent) { - emit installServerFromApiFinished(tr("This subscription is already in the app."), duplicateServerIndex); + emit installServerFromApiFinished(tr("This subscription has already been added"), duplicateServerIndex); return true; } @@ -894,7 +894,7 @@ bool ApiConfigsController::importTrialFromGateway(const QString &email) ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError) { if (errorCode == ErrorCode::ApiTrialAlreadyUsedError) { - emit trialEmailError(tr("This email has already been used for trial activation. If you like the service, you can buy Premium.")); + emit trialEmailError(tr("This email address has already been used to activate a trial. If you like the service, you can upgrade to Premium")); return false; } emit errorOccurred(errorCode); diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 4e37a98c6..81112bb0f 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -32,8 +32,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const return tr("Active"); } - return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("

Inactive") - : tr("

Active"); + return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) + ? QStringLiteral("

%1").arg(tr("Inactive")) + : QStringLiteral("

%1").arg(tr("Active")); } case EndDateRole: { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { diff --git a/client/ui/models/api/apiBenefitsModel.cpp b/client/ui/models/api/apiBenefitsModel.cpp index 42b79c9bb..2e6455947 100644 --- a/client/ui/models/api/apiBenefitsModel.cpp +++ b/client/ui/models/api/apiBenefitsModel.cpp @@ -12,7 +12,7 @@ namespace configKey constexpr char title[] = "title"; constexpr char body[] = "body"; constexpr char icon[] = "icon"; - constexpr char accent[] = "accent"; + constexpr char link[] = "link"; } QString gatewayIconKeyToUrl(const QString &iconKey) @@ -62,8 +62,8 @@ QVariant ApiBenefitsModel::data(const QModelIndex &index, int role) const return item.title; case BodyRole: return item.body; - case AccentRole: - return item.accent; + case LinkRole: + return item.link; default: return {}; } @@ -75,7 +75,7 @@ QHash ApiBenefitsModel::roleNames() const { IconRole, "icon" }, { TitleRole, "title" }, { BodyRole, "body" }, - { AccentRole, "accent" }, + { LinkRole, "link" }, }; } @@ -90,7 +90,11 @@ void ApiBenefitsModel::updateModel(const QJsonArray &benefits) const QJsonObject benefitObject = benefitValue.toObject(); QString title = benefitObject.value(configKey::title).toString(); QString body = benefitObject.value(configKey::body).toString(); + const bool isLink = benefitObject.value(configKey::link).toBool(); const QString iconKey = benefitObject.value(configKey::icon).toString(); + if (isLink) { + body = body.trimmed(); + } if (title.isEmpty() && body.isEmpty()) { continue; } @@ -98,7 +102,7 @@ void ApiBenefitsModel::updateModel(const QJsonArray &benefits) item.icon = gatewayIconKeyToUrl(iconKey); item.title = std::move(title); item.body = std::move(body); - item.accent = benefitObject.value(configKey::accent).toBool(); + item.link = isLink; m_serviceBenefits.append(std::move(item)); } endResetModel(); diff --git a/client/ui/models/api/apiBenefitsModel.h b/client/ui/models/api/apiBenefitsModel.h index c6b8465f2..6a09e08e2 100644 --- a/client/ui/models/api/apiBenefitsModel.h +++ b/client/ui/models/api/apiBenefitsModel.h @@ -15,7 +15,7 @@ public: IconRole = Qt::UserRole + 1, TitleRole, BodyRole, - AccentRole + LinkRole }; Q_ENUM(Roles) @@ -34,7 +34,7 @@ private: QString icon; QString title; QString body; - bool accent = false; + bool link = false; }; QVector m_serviceBenefits; diff --git a/client/ui/qml/Components/BenefitRow.qml b/client/ui/qml/Components/BenefitRow.qml index 07b547f12..2870a7128 100644 --- a/client/ui/qml/Components/BenefitRow.qml +++ b/client/ui/qml/Components/BenefitRow.qml @@ -11,7 +11,11 @@ RowLayout { property string iconSource: "" property string titleText: "" property string bodyText: "" - property bool accent: false + property bool link: false + + readonly property string bodyLineText: root.link && root.bodyText.length > 0 ? "@" + root.bodyText : root.bodyText + + readonly property bool bodyClickable: root.link && root.bodyText.length > 0 spacing: 12 @@ -43,22 +47,17 @@ RowLayout { LabelTextType { id: bodyLabel width: parent.width - text: root.bodyText - color: root.accent ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray + text: root.bodyLineText + color: root.link ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray font.pixelSize: 14 wrapMode: Text.Wrap } MouseArea { anchors.fill: bodyLabel - visible: root.accent && root.bodyText.length > 0 + visible: root.bodyClickable cursorShape: Qt.PointingHandCursor - onClicked: { - var t = root.bodyText.trim() - if (t.startsWith("@")) { - Qt.openUrlExternally("https://t.me/" + t.substring(1)) - } - } + onClicked: Qt.openUrlExternally("https://t.me/" + root.bodyText) } } } diff --git a/client/ui/qml/Components/BenefitsPanel.qml b/client/ui/qml/Components/BenefitsPanel.qml index bb1d3a23e..c3469e4ca 100644 --- a/client/ui/qml/Components/BenefitsPanel.qml +++ b/client/ui/qml/Components/BenefitsPanel.qml @@ -33,7 +33,7 @@ Rectangle { iconSource: model.icon titleText: model.title bodyText: model.body - accent: !!model.accent + link: !!model.link } } } diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 54ef0c911..0cc5b0913 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -68,7 +68,7 @@ ListViewType { text: name descriptionText: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon) - ? (isSubscriptionExpired ? qsTr("Subscription expired. Please renew.") : qsTr("Subscription expiring soon.")) + ? (isSubscriptionExpired ? qsTr("Subscription expired. Please renew") : qsTr("Subscription expiring soon")) : serverDescription descriptionColor: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon) ? (isSubscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot) diff --git a/client/ui/qml/Components/SubscriptionExpiredDrawer.qml b/client/ui/qml/Components/SubscriptionExpiredDrawer.qml index 0540dbfa7..230bc7db1 100644 --- a/client/ui/qml/Components/SubscriptionExpiredDrawer.qml +++ b/client/ui/qml/Components/SubscriptionExpiredDrawer.qml @@ -57,7 +57,7 @@ DrawerType2 { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: qsTr("Renew your subscription to continue using VPN") + text: qsTr("Renew to continue using VPN") horizontalAlignment: Text.AlignLeft } diff --git a/client/ui/qml/Components/TermsAndPrivacyText.qml b/client/ui/qml/Components/TermsAndPrivacyText.qml index 5eb4a142a..1fad3da64 100644 --- a/client/ui/qml/Components/TermsAndPrivacyText.qml +++ b/client/ui/qml/Components/TermsAndPrivacyText.qml @@ -17,6 +17,8 @@ ParagraphTextType { textFormat: Text.RichText color: AmneziaStyle.color.mutedGray font.pixelSize: 12 + lineHeight: 1.35 + lineHeightMode: Text.ProportionalHeight text: qsTr("By continuing, you agree to the Terms of Use and Privacy Policy") .arg(root.termsUrl) diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml index 52cd31e0f..827a3950c 100644 --- a/client/ui/qml/Controls2/CardWithIconsType.qml +++ b/client/ui/qml/Controls2/CardWithIconsType.qml @@ -28,20 +28,20 @@ Button { property string leftImageSource - property real textOpacity: 1.0 - property alias focusItem: rightImage hoverEnabled: true clip: false + readonly property real cardTextOpacity: !enabled ? 1.0 : pressed ? 0.7 : hovered ? 0.8 : 1.0 + background: Rectangle { id: backgroundRect anchors.fill: parent radius: 16 - color: defaultColor + color: root.hovered && root.enabled ? root.hoveredColor : root.defaultColor Behavior on color { PropertyAnimation { duration: 200 } @@ -51,6 +51,7 @@ Button { contentItem: Item { id: contentRoot + z: 1 anchors.left: parent.left anchors.right: parent.right @@ -129,7 +130,7 @@ Button { Layout.topMargin: contentRoot.badgeVisible ? 0 : 16 Layout.bottomMargin: root.bodyText !== "" ? 0 : 16 - opacity: root.textOpacity + opacity: root.cardTextOpacity } CaptionTextType { @@ -138,13 +139,16 @@ Button { color: root.bodyTextColor textFormat: Text.RichText + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 Layout.bottomMargin: root.footerText !== "" ? 0 : 8 - opacity: root.textOpacity + opacity: root.cardTextOpacity } ButtonTextType { @@ -159,7 +163,7 @@ Button { Layout.topMargin: 16 Layout.bottomMargin: 16 - opacity: root.textOpacity + opacity: root.cardTextOpacity } } @@ -184,7 +188,7 @@ Button { anchors.fill: parent radius: 12 - color: "transparent" + color: root.pressed ? rightImage.pressedColor : root.hovered ? rightImage.hoveredColor : rightImage.defaultColor Behavior on color { PropertyAnimation { duration: 200 } @@ -198,41 +202,4 @@ Button { } } } - - MouseArea { - anchors.fill: parent - - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - enabled: root.enabled - - onEntered: { - backgroundRect.color = root.hoveredColor - - if (rightImageSource) { - rightImageBackground.color = rightImage.hoveredColor - } - root.textOpacity = 0.8 - } - - onExited: { - backgroundRect.color = root.defaultColor - - if (rightImageSource) { - rightImageBackground.color = rightImage.defaultColor - } - root.textOpacity = 1 - } - - onPressedChanged: { - if (rightImageSource) { - rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } - root.textOpacity = 0.7 - } - - onClicked: { - root.clicked() - } - } } diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index 81f44edbc..c55c04b1e 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -2,8 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import SortFilterProxyModel 0.2 - import PageEnum 1.0 import Style 1.0 @@ -52,6 +50,8 @@ PageType { width: listView.width TextFieldWithHeaderType { + id: gatewayEndpointField + Layout.fillWidth: true Layout.topMargin: 16 Layout.rightMargin: 16 @@ -64,13 +64,25 @@ PageType { clickedFunc: function() { SettingsController.resetGatewayEndpoint() + gatewayEndpointField.textField.text = SettingsController.gatewayEndpoint } + } - textField.onEditingFinished: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - if (textField.text !== SettingsController.gatewayEndpoint) { - SettingsController.gatewayEndpoint = textField.text + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Save") + + clickedFunc: function() { + var trimmed = gatewayEndpointField.textField.text.replace(/^\s+|\s+$/g, '') + gatewayEndpointField.textField.text = trimmed + if (trimmed !== SettingsController.gatewayEndpoint) { + SettingsController.gatewayEndpoint = trimmed } + PageController.showNotificationMessage(qsTr("Settings saved")) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml b/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml index a23eff4bc..a37d0c37e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml @@ -99,7 +99,7 @@ PageType { wrapMode: Text.WordWrap color: AmneziaStyle.color.mutedGray font.pixelSize: 12 - text: qsTr("We will create an account for your trial subscription and send important subscription updates to this email.") + text: qsTr("We will create an account for your trial subscription and send important subscription updates to this email address") } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 061ef65a8..970587846 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -278,7 +278,7 @@ PageType { id: amneziaVpn property string title: qsTr("VPN by Amnezia") - property string description: qsTr("The easiest way to connect to VPN") + property string description: qsTr("The easiest way to connect to the VPN") property string imageSource: "qrc:/images/controls/amnezia.svg" property bool featuredAmneziaConnection: true property bool isVisible: true diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 8b67754d7..f98afe28f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -94,7 +94,7 @@ PageType { visible: title === qsTr("Password or SSH private key") backGroundColor: AmneziaStyle.color.translucentWhite iconPath: "qrc:/images/controls/alert-circle.svg" - textString: qsTr("SSH key requirements: supported ED25519 or RSA in PEM. Paste the private key including BEGIN/END lines. If your key doesn’t work, generate a compatible one.") + textString: qsTr("SSH key requirements: supported key types are ED25519 and RSA in PEM format. Paste the private key, including the BEGIN/END lines. If your key doesn’t work, generate a compatible one") } }