mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
feat: add base amnezia trial support (#2366)
* feat: add base amnezia trial support * feat: add external-trial
This commit is contained in:
@@ -10,8 +10,10 @@ namespace apiDefs
|
|||||||
AmneziaFreeV3,
|
AmneziaFreeV3,
|
||||||
AmneziaPremiumV1,
|
AmneziaPremiumV1,
|
||||||
AmneziaPremiumV2,
|
AmneziaPremiumV2,
|
||||||
|
AmneziaTrialV2,
|
||||||
SelfHosted,
|
SelfHosted,
|
||||||
ExternalPremium
|
ExternalPremium,
|
||||||
|
ExternalTrial
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigSource {
|
enum ConfigSource {
|
||||||
|
|||||||
@@ -58,18 +58,24 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
|
|||||||
};
|
};
|
||||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
|
constexpr QLatin1String serviceTrial("amnezia-trial");
|
||||||
constexpr QLatin1String serviceFree("amnezia-free");
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
|
constexpr QLatin1String serviceExternalTrial("external-trial");
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
if (serviceType == servicePremium) {
|
if (serviceType == servicePremium) {
|
||||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
|
} else if (serviceType == serviceTrial) {
|
||||||
|
return apiDefs::ConfigType::AmneziaTrialV2;
|
||||||
} 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 (serviceType == serviceExternalTrial) {
|
||||||
|
return apiDefs::ConfigType::ExternalTrial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -133,7 +139,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||||
apiDefs::ConfigType::ExternalPremium };
|
apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium,
|
||||||
|
apiDefs::ConfigType::ExternalTrial };
|
||||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +184,9 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
auto configType = apiUtils::getConfigType(serverConfigObject);
|
||||||
|
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2
|
||||||
|
&& configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ bool ApiConfigsController::importService()
|
|||||||
importSerivceFromAppStore();
|
importSerivceFromAppStore();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) {
|
||||||
importServiceFromGateway();
|
importServiceFromGateway();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium;
|
|| m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|
||||||
|
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium
|
||||||
|
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
|
||||||
}
|
}
|
||||||
case HasExpiredWorkerRole: {
|
case HasExpiredWorkerRole: {
|
||||||
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace
|
|||||||
{
|
{
|
||||||
constexpr char amneziaFree[] = "amnezia-free";
|
constexpr char amneziaFree[] = "amnezia-free";
|
||||||
constexpr char amneziaPremium[] = "amnezia-premium";
|
constexpr char amneziaPremium[] = "amnezia-premium";
|
||||||
|
constexpr char amneziaTrial[] = "amnezia-trial";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
auto speed = apiServiceData.serviceInfo.speed;
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium || serviceType == serviceType::amneziaTrial) {
|
||||||
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = apiServiceData.serviceInfo.cardDescription;
|
QString description = apiServiceData.serviceInfo.cardDescription;
|
||||||
@@ -124,8 +125,10 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
case OrderRole: {
|
case OrderRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaTrial) {
|
||||||
return 1;
|
return 1;
|
||||||
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +1,226 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
import PageEnum 1.0
|
import PageEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
import "./"
|
import "./"
|
||||||
import "../Controls2"
|
import "../Controls2"
|
||||||
import "../Controls2/TextTypes"
|
import "../Controls2/TextTypes"
|
||||||
import "../Config"
|
import "../Config"
|
||||||
import "../Components"
|
import "../Components"
|
||||||
|
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
listView.positionViewAtBeginning()
|
listView.positionViewAtBeginning()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButton.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
header: ColumnLayout {
|
header: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
BaseHeaderType {
|
BaseHeaderType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.bottomMargin: 32
|
Layout.bottomMargin: 32
|
||||||
|
|
||||||
headerText: ApiServicesModel.getSelectedServiceData("name")
|
headerText: ApiServicesModel.getSelectedServiceData("name")
|
||||||
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
|
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model: inputFields
|
model: inputFields
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
LabelWithImageType {
|
LabelWithImageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
imageSource: imagePath
|
imageSource: imagePath
|
||||||
leftText: lText
|
leftText: lText
|
||||||
rightText: rText
|
rightText: rText
|
||||||
|
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: ColumnLayout {
|
footer: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
text: {
|
text: {
|
||||||
var text = ApiServicesModel.getSelectedServiceData("features")
|
var text = ApiServicesModel.getSelectedServiceData("features")
|
||||||
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
|
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
color: AmneziaStyle.color.mutedGray
|
color: AmneziaStyle.color.mutedGray
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
|
||||||
text: qsTr("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.")
|
text: qsTr("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.")
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: continueButton
|
id: continueButton
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: 32
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect")
|
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : (ApiServicesModel.getSelectedServiceType() === "amnezia-trial" ? qsTr("Try Trial") : qsTr("Connect"))
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
var result = ApiConfigsController.importService()
|
var result = ApiConfigsController.importService()
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
var endpoint = ApiServicesModel.getStoreEndpoint()
|
var endpoint = ApiServicesModel.getStoreEndpoint()
|
||||||
Qt.openUrlExternally(endpoint)
|
Qt.openUrlExternally(endpoint)
|
||||||
PageController.closePage()
|
PageController.closePage()
|
||||||
PageController.closePage()
|
PageController.closePage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 32
|
Layout.bottomMargin: 32
|
||||||
|
|
||||||
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: AmneziaStyle.color.mutedGray
|
color: AmneziaStyle.color.mutedGray
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
||||||
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
|
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
|
||||||
return qsTr("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>").arg(termsUrl).arg(privacyUrl)
|
return qsTr("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>").arg(termsUrl).arg(privacyUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property list<QtObject> inputFields: [
|
property list<QtObject> inputFields: [
|
||||||
region,
|
region,
|
||||||
price,
|
price,
|
||||||
timeLimit,
|
timeLimit,
|
||||||
speed,
|
speed,
|
||||||
features
|
features
|
||||||
]
|
]
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: region
|
id: region
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
|
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
|
||||||
readonly property string lText: qsTr("For the region")
|
readonly property string lText: qsTr("For the region")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: price
|
id: price
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/tag.svg"
|
readonly property string imagePath: "qrc:/images/controls/tag.svg"
|
||||||
readonly property string lText: qsTr("Price")
|
readonly property string lText: qsTr("Price")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: timeLimit
|
id: timeLimit
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/history.svg"
|
readonly property string imagePath: "qrc:/images/controls/history.svg"
|
||||||
readonly property string lText: qsTr("Work period")
|
readonly property string lText: qsTr("Work period")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
|
||||||
property bool isVisible: rText !== ""
|
property bool isVisible: rText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: speed
|
id: speed
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
|
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
|
||||||
readonly property string lText: qsTr("Speed")
|
readonly property string lText: qsTr("Speed")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: features
|
id: features
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/info.svg"
|
readonly property string imagePath: "qrc:/images/controls/info.svg"
|
||||||
readonly property string lText: qsTr("Features")
|
readonly property string lText: qsTr("Features")
|
||||||
readonly property string rText: ""
|
readonly property string rText: ""
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user