From 8e61d77497e09180d0036d4c73ce5f387eb2604c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 23 Mar 2023 17:49:36 +0300 Subject: [PATCH 001/131] added new controls elements (BasicButtonType, ImageButtonType, LabelWithButtonType, TextFieldWithHeaderType) --- client/fonts/pt-root-ui_vf.ttf | Bin 0 -> 266616 bytes client/images/controls/arrow-right.svg | 4 + client/images/controls/chevron-right.svg | 3 + client/resources.qrc | 8 ++ client/ui/pages.h | 2 +- client/ui/qml/Controls2/BasicButtonType.qml | 57 ++++++++++ client/ui/qml/Controls2/ImageButtonType.qml | 44 ++++++++ .../ui/qml/Controls2/LabelWithButtonType.qml | 43 ++++++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 68 ++++++++++++ client/ui/qml/Pages/PageTest.qml | 104 ++++++++++++++++++ client/ui/qml/Pages/PageVPN.qml | 2 +- 11 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 client/fonts/pt-root-ui_vf.ttf create mode 100644 client/images/controls/arrow-right.svg create mode 100644 client/images/controls/chevron-right.svg create mode 100644 client/ui/qml/Controls2/BasicButtonType.qml create mode 100644 client/ui/qml/Controls2/ImageButtonType.qml create mode 100644 client/ui/qml/Controls2/LabelWithButtonType.qml create mode 100644 client/ui/qml/Controls2/TextFieldWithHeaderType.qml create mode 100644 client/ui/qml/Pages/PageTest.qml diff --git a/client/fonts/pt-root-ui_vf.ttf b/client/fonts/pt-root-ui_vf.ttf new file mode 100644 index 0000000000000000000000000000000000000000..34e6f1f9b241fbfdc7e430a84ccbc394aabe68fa GIT binary patch literal 266616 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 KPo&rZV+Q~OH^k-u literal 0 HcmV?d00001 diff --git a/client/images/controls/arrow-right.svg b/client/images/controls/arrow-right.svg new file mode 100644 index 000000000..69b21b89e --- /dev/null +++ b/client/images/controls/arrow-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/chevron-right.svg b/client/images/controls/chevron-right.svg new file mode 100644 index 000000000..726eabac1 --- /dev/null +++ b/client/images/controls/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index 67937bdaf..7a157e5a7 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -165,5 +165,13 @@ ui/qml/Controls/PopupWithQuestion.qml ui/qml/Pages/PageAdvancedServerSettings.qml ui/qml/Controls/PopupWarning.qml + ui/qml/Controls2/BasicButtonType.qml + ui/qml/Controls2/TextFieldWithHeaderType.qml + ui/qml/Pages/PageTest.qml + fonts/pt-root-ui_vf.ttf + ui/qml/Controls2/LabelWithButtonType.qml + images/controls/arrow-right.svg + images/controls/chevron-right.svg + ui/qml/Controls2/ImageButtonType.qml diff --git a/client/ui/pages.h b/client/ui/pages.h index dfc9a5090..a639d63b5 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml new file mode 100644 index 000000000..7133d2367 --- /dev/null +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Controls + +Button { + id: root + + property string hoveredColor: "#C1C2C5" + property string defaultColor: "#D7D8DB" + property string disabledColor: "#494B50" + property string pressedColor: "#979799" + + property string textColor: "#0E0E11" + + property string borderColor: "#D7D8DB" + property int borderWidth: 0 + + implicitWidth: 328 + implicitHeight: 56 + + hoverEnabled: true + + background: Rectangle { + id: background + anchors.fill: parent + radius: 16 + color: { + if (root.enabled) { + if(root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: borderColor + border.width: borderWidth + } + + MouseArea { + anchors.fill: background + enabled: false + cursorShape: Qt.PointingHandCursor + } + + contentItem: Text { + anchors.fill: background + font.family: "PT Root UI" + font.styleName: "normal" + font.weight: 400 + font.pixelSize: 16 + color: textColor + text: root.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml new file mode 100644 index 000000000..be1bd0e44 --- /dev/null +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: root + + property string image + + property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) + property string defaultColor: Qt.rgba(255, 255, 255, 0) + property string pressedColor: Qt.rgba(255, 255, 255, 0.12) + + property string imageColor: "#878B91" + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: true + + icon.source: image + icon.color: imageColor + + background: Rectangle { + id: background + + anchors.fill: parent + radius: 12 + color: { + if (root.enabled) { + if(root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } + } + } + + MouseArea { + anchors.fill: parent + enabled: false + cursorShape: Qt.PointingHandCursor + } +} diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml new file mode 100644 index 000000000..c49d1e069 --- /dev/null +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property string text + + property var onClickedFunc + property alias buttonImage : button.image + + implicitWidth: 360 + implicitHeight: 72 + + RowLayout { + anchors.fill: parent + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 18 + color: "#d7d8db" + text: root.text + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + ImageButtonType { + id: button + + image: buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + + Layout.alignment: Qt.AlignRight + } + } + +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml new file mode 100644 index 000000000..18f62344a --- /dev/null +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property string headerText + property string textFieldText + property bool textFieldEditable: true + + implicitWidth: 328 + implicitHeight: 74 + + Rectangle { + id: backgroud + anchors.fill: parent + color: "#1c1d21" + radius: 16 + border.color: "#d7d8db" + border.width: textField.focus ? 1 : 0 + } + + ColumnLayout { + anchors.fill: backgroud + + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + TextField { + id: textField + + enabled: root.textFieldEditable + text: root.textFieldText + color: "#d7d8db" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml new file mode 100644 index 000000000..11b3ff1d2 --- /dev/null +++ b/client/ui/qml/Pages/PageTest.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PageEnum 1.0 +import "./" +import "../Controls" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Test + logic: ViewConfigLogic + + Rectangle { + y: 0 + anchors.fill: parent + color: "#0E0E11" + } + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + enabled: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 15 + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + text: qsTr("Forget this server") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + + defaultColor: "#0E0E11" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Forget this server") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 10 + headerText: "Server IP adress [:port]" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 10 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + +// onClickedFunc: function() { +// UiLogic.goToPage(PageEnum.Start) +// } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + +// onClickedFunc: function() { +// UiLogic.goToPage(PageEnum.Start) +// } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } +} diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml index 342fb1293..0e6f5078e 100644 --- a/client/ui/qml/Pages/PageVPN.qml +++ b/client/ui/qml/Pages/PageVPN.qml @@ -39,7 +39,7 @@ PageBase { label.text: qsTr("Donate") onClicked: { - UiLogic.goToPage(PageEnum.About) + UiLogic.goToPage(PageEnum.Test) } } From 167d57408dea255c1e881356192dbd4698181c77 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 6 Apr 2023 16:33:53 +0300 Subject: [PATCH 002/131] added CardType component - added transition for BasicButtonType --- client/resources.qrc | 1 + client/ui/qml/Controls2/BasicButtonType.qml | 4 + client/ui/qml/Controls2/CardType.qml | 118 ++++++++++++++++++++ client/ui/qml/Pages/PageTest.qml | 20 +++- 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 client/ui/qml/Controls2/CardType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 7a157e5a7..9280e2b29 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -173,5 +173,6 @@ images/controls/arrow-right.svg images/controls/chevron-right.svg ui/qml/Controls2/ImageButtonType.qml + ui/qml/Controls2/CardType.qml diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 7133d2367..cbb664e4a 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -35,6 +35,10 @@ Button { } border.color: borderColor border.width: borderWidth + + Behavior on color { + PropertyAnimation { duration: 200 } + } } MouseArea { diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml new file mode 100644 index 000000000..cd7320be8 --- /dev/null +++ b/client/ui/qml/Controls2/CardType.qml @@ -0,0 +1,118 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RadioButton { + id: root + + property string headerText + property string bodyText + property string footerText + + property string hoveredColor: Qt.rgba(255, 255, 255, 0) + property string defaultColor: Qt.rgba(255, 255, 255, 0.05) + property string disabledColor: Qt.rgba(255, 255, 255, 0) + property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) + property string hoveredBorderColor: Qt.rgba(251, 178, 106, 1) + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if(root.checked) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: { + if (root.enabled) { + if(root.checked) { + return pressedBorderColor + } + return hovered ? hoveredBorderColor : defaultColor + } else { + return disabledColor + } + } + border.width: { + if (root.enabled) { + if(root.checked) { + return 1 + } + return hovered ? 1 : 0 + } else { + return 0 + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 16 + + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + height: 30 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + Text { + text: root.bodyText + wrapMode: Text.WordWrap + color: "#878b91" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: root.footerText !== "" ? 0 : 16 + } + + Text { + text: root.footerText + visible: root.footerText !== "" + enabled: root.footerText !== "" + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index 11b3ff1d2..dc3cc69b7 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -46,7 +46,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 10 - defaultColor: "#0E0E11" + defaultColor: "transparent" hoveredColor: Qt.rgba(255, 255, 255, 0.08) pressedColor: Qt.rgba(255, 255, 255, 0.12) disabledColor: "#878B91" @@ -99,6 +99,24 @@ PageBase { height: 1 color: "#2C2D30" } + + CardType { + Layout.fillWidth: true + Layout.topMargin: 10 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + CardType { + Layout.fillWidth: true + Layout.topMargin: 10 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } } } } From c74c5e0c6d137209985baf2ecf735edc70e63171 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 7 Apr 2023 20:50:55 +0300 Subject: [PATCH 003/131] added CheckBoxType - added hover effect to LabelWithButtonType --- client/images/controls/check.svg | 3 + client/resources.qrc | 2 + client/ui/qml/Controls2/CardType.qml | 19 ++--- client/ui/qml/Controls2/CheckBoxType.qml | 77 +++++++++++++++++++ client/ui/qml/Controls2/ImageButtonType.qml | 5 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 33 ++++++++ client/ui/qml/Pages/PageTest.qml | 4 + 7 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 client/images/controls/check.svg create mode 100644 client/ui/qml/Controls2/CheckBoxType.qml diff --git a/client/images/controls/check.svg b/client/images/controls/check.svg new file mode 100644 index 000000000..16b4c0da4 --- /dev/null +++ b/client/images/controls/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index 9280e2b29..bda89bf28 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -174,5 +174,7 @@ images/controls/chevron-right.svg ui/qml/Controls2/ImageButtonType.qml ui/qml/Controls2/CardType.qml + ui/qml/Controls2/CheckBoxType.qml + images/controls/check.svg diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index cd7320be8..31b51f311 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -9,15 +9,16 @@ RadioButton { property string bodyText property string footerText - property string hoveredColor: Qt.rgba(255, 255, 255, 0) - property string defaultColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string defaultColor: Qt.rgba(255, 255, 255, 0) property string disabledColor: Qt.rgba(255, 255, 255, 0) property string pressedColor: Qt.rgba(255, 255, 255, 0.05) property string textColor: "#0E0E11" property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) - property string hoveredBorderColor: Qt.rgba(251, 178, 106, 1) + property string hoveredBorderColor: "transparent" + property string defaultBodredColor: "#FBB26A" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -44,9 +45,9 @@ RadioButton { if(root.checked) { return pressedBorderColor } - return hovered ? hoveredBorderColor : defaultColor + return hovered ? hoveredBorderColor : defaultBodredColor } else { - return disabledColor + return defaultBodredColor } } border.width: { @@ -54,7 +55,7 @@ RadioButton { if(root.checked) { return 1 } - return hovered ? 1 : 0 + return hovered ? 0 : 1 } else { return 0 } @@ -72,7 +73,7 @@ RadioButton { Text { text: root.headerText - color: "#878b91" + color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 font.family: "PT Root UI VF" @@ -87,7 +88,7 @@ RadioButton { Text { text: root.bodyText wrapMode: Text.WordWrap - color: "#878b91" + color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" @@ -103,7 +104,7 @@ RadioButton { text: root.footerText visible: root.footerText !== "" enabled: root.footerText !== "" - color: "#878b91" + color: "#878B91" font.pixelSize: 13 font.weight: 400 font.family: "PT Root UI VF" diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml new file mode 100644 index 000000000..1cd461433 --- /dev/null +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + RowLayout { + id: content + + anchors.fill: parent + + CheckBox { + id: checkBox + + implicitWidth: 56 + implicitHeight: 56 + + indicator: Image { + id: indicator + anchors.verticalCenter: checkBox.verticalCenter + anchors.horizontalCenter: checkBox.horizontalCenter + source: checkBox.checked ? "qrc:/images/controls/check.svg" : "" + } + Rectangle { + anchors.verticalCenter: checkBox.verticalCenter + anchors.horizontalCenter: checkBox.horizontalCenter + width: 24 + height: 24 + color: "transparent" + border.color: "#FBB26A" + border.width: 1 + radius: 4 + } + background: Rectangle { + id: background + color: Qt.rgba(255, 255, 255, 0.05) + radius: 16 + } + } + + ColumnLayout { + Text { + text: "Paragraph" + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 22 + Layout.fillWidth: true + } + + Text { + text: "Caption" + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true + } + } + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + checkBox.checked = !checkBox.checked + } + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index be1bd0e44..b6262906e 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -8,7 +8,7 @@ Button { property string image property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) - property string defaultColor: Qt.rgba(255, 255, 255, 0) + property string defaultColor: "transparent" property string pressedColor: Qt.rgba(255, 255, 255, 0.12) property string imageColor: "#878B91" @@ -34,6 +34,9 @@ Button { return hovered ? hoveredColor : defaultColor } } + Behavior on color { + PropertyAnimation { duration: 200 } + } } MouseArea { diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index c49d1e069..5da4db5f5 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -29,6 +29,7 @@ Item { ImageButtonType { id: button + hoverEnabled: false image: buttonImage onClicked: { if (onClickedFunc && typeof onClickedFunc === "function") { @@ -37,7 +38,39 @@ Item { } Layout.alignment: Qt.AlignRight + + Rectangle { + id: imageBackground + anchors.fill: button + radius: 12 + color: "transparent" + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } } } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: { + imageBackground.color = button.hoveredColor + } + + onExited: { + imageBackground.color = button.defaultColor + } + + onPressedChanged: { + imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } + + onClicked: { + button.clicked() + } + } } diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index dc3cc69b7..9f820389e 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -117,6 +117,10 @@ PageBase { bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" footerText: "футер" } + + CheckBoxType { +// text: qsTr("Auto-negotiate encryption") + } } } } From ec96c1b53437e77d12a48f31f8442dd83e0a20bb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 10 Apr 2023 06:43:36 +0300 Subject: [PATCH 004/131] added hover and pressed effects for CheckBoxType.qml --- client/ui/qml/Controls2/CheckBoxType.qml | 54 ++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 1cd461433..c547a8e42 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -1,10 +1,24 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects Item { id: root + property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string defaultColor: "transparent" + property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + + property string defaultBorderColor: "#D7D8DB" + property string checkedBorderColor: "#FBB26A" + + property string checkedImageColor: "#FBB26A" + property string hoveredImageColor: "#A85809" + property string defaultImageColor: "transparent" + + property string imageSource: "qrc:/images/controls/check.svg" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -23,22 +37,35 @@ Item { id: indicator anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter - source: checkBox.checked ? "qrc:/images/controls/check.svg" : "" + ColorOverlay { + id: imageColor + anchors.fill: indicator + source: indicator + } } + Rectangle { + id: imageBorder + anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter width: 24 height: 24 color: "transparent" - border.color: "#FBB26A" + border.color: checkBox.checked ? checkedBorderColor : defaultBorderColor border.width: 1 radius: 4 } + background: Rectangle { - id: background - color: Qt.rgba(255, 255, 255, 0.05) + id: checkBoxBackground radius: 16 + + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } } } @@ -70,8 +97,27 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + checkBoxBackground.color = hoveredColor + } + + onExited: { + checkBoxBackground.color = defaultColor + } + + onPressedChanged: { + indicator.source = pressed ? imageSource : "" + imageColor.color = pressed ? hoveredImageColor : defaultImageColor + checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor + } + onClicked: { checkBox.checked = !checkBox.checked + indicator.source = checkBox.checked ? imageSource : "" + imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor + imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor } } } From 905a3a30f37b299580dd94fdba818997085303be Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 12 Apr 2023 19:13:41 +0300 Subject: [PATCH 005/131] added some new controls and started layout of pageStart and pageCredentials --- client/amnezia_application.cpp | 2 +- client/images/amneziaBigLogo.png | Bin 0 -> 25211 bytes client/images/amneziaBigLogo.svg | 1 + client/images/controls/arrow-left.svg | 4 + client/resources.qrc | 12 ++ client/ui/pages.h | 2 +- client/ui/qml/Controls2/BodyTextType.qml | 12 ++ client/ui/qml/Controls2/DropDownType.qml | 5 + client/ui/qml/Controls2/FlickableType.qml | 23 +++ client/ui/qml/Controls2/Header2TextType.qml | 10 ++ client/ui/qml/Controls2/HeaderTextType.qml | 40 +++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 4 + client/ui/qml/PageLoader.qml | 57 ++++++ client/ui/qml/Pages/PageTest.qml | 1 - client/ui/qml/Pages2/PageCredentials.qml | 83 +++++++++ client/ui/qml/Pages2/PageStart.qml | 163 ++++++++++++++++++ client/ui/qml/main.qml | 1 - client/ui/qml/main2.qml | 142 +++++++++++++++ 18 files changed, 558 insertions(+), 4 deletions(-) create mode 100644 client/images/amneziaBigLogo.png create mode 100644 client/images/amneziaBigLogo.svg create mode 100644 client/images/controls/arrow-left.svg create mode 100644 client/ui/qml/Controls2/BodyTextType.qml create mode 100644 client/ui/qml/Controls2/DropDownType.qml create mode 100644 client/ui/qml/Controls2/FlickableType.qml create mode 100644 client/ui/qml/Controls2/Header2TextType.qml create mode 100644 client/ui/qml/Controls2/HeaderTextType.qml create mode 100644 client/ui/qml/PageLoader.qml create mode 100644 client/ui/qml/Pages2/PageCredentials.qml create mode 100644 client/ui/qml/Pages2/PageStart.qml create mode 100644 client/ui/qml/main2.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 588854d30..3fb76be20 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -92,7 +92,7 @@ void AmneziaApplication::init() m_engine = new QQmlApplicationEngine; m_uiLogic = new UiLogic(m_settings, m_configurator, m_serverController); - const QUrl url(QStringLiteral("qrc:/ui/qml/main.qml")); + const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) diff --git a/client/images/amneziaBigLogo.png b/client/images/amneziaBigLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..35a45f3b78807c74143c2fd3b430b8e406880448 GIT binary patch literal 25211 zcmbrlg(&&(v52u%$|0z7Iw000P-mE^Pm022lP7*#mvsEGI~ z-7f$@JJwWtE05X@?HPJl$n$V5`uCOnVX@#i;=k?RaE_y3`lE1mZ|_p*aCd0mC@Ovu z!JeB5yPeB>oX&n&%+Jlrxm(OXj^ub;%FE8q{txW)6PA(z9qNW?kq4pOC>sTrwJv*++|ZG36#a3& z=Kc5dB?99!Ag_qBul)Zd@qGVAv9*^Lt$lnMKl+NS7@#Mj! z#}hb84P^k;mHi{z)4e1&GsoMf#M`?BMY-Q!&C7yaH@G^P7NV5@bCQ*VYG2p8pxWNP zWhe#IZcOW(xA#w!2b3#R1VucGV7ot9bTZAmS75D zFi|zRMIMBePdl0v-0tq9wkUh3L7~JS|G^Xcmr#RpGARlSs>;pIxvzA&+dFvJUwK$8 z%E^K~EEPDJ79=N*4#`%m@+jtZi5KAPkw^(~^ZJg#OZ5B?4>n+Y$Q z3a^+!IYBvmm@l|R?*CV`#lq`GR}>5NzOHkwOE5$Q|EtU~{JOyfHGSWQe_z$QwA9o+ zE)+PL6gDLqHpc6rL{OO1NREJSU14QY*P~O9$o81V`iY6z+_B~TRb)zH-g&m_&Nsn} zQghUjK-CR3C=>-%MwH3_wtF;el~& z6S4BQlkHV0jw>^r!#~VS)mJ7^i@wuCTSEt+htfkkS63lK^sB2x*`3)CqN7#wBM7~D zHvJJ16aq)Zv*XMlP?$NCewlxjp1-s6Kje|73&gp#GVUmjepNFEn${Vo3E|hQbTR9U z>oh-tJ0GD~=AGFt{6~-@GZ%gcKO71*tAs$I&d%oM=8(=Kc%?by$e9R^Qqt_KjDu=+ zJ)mf-+5G1G^s8`KTpYa0+1aeKG8-Pl4~JLAnZ0>{SN?A4taAPh^X=?xJX&2{t%-X_ z+P4A#rasDYuikns9&`tx#sxrO_a&4iX#X|;?_TB?YmomjhaT&R-|GHfze1(JME}z{ z33SBxzgE%JwRlR1@t+R#&Wz^O{|}w{aw|^2f*#Mj9I7JD^QkL@h_&kM{V`Ej!0TMb z{~7)Nu=!^*CyWg&m#Ju@LqE)B!e0W&ZRK19aQ{P@j2f1s^4}(nT?nEmp=Il}(ENH+ z;R`(;%pJr|?iwtQM%kYXz2+t6Zvvi}RMd2%!m1%4c8zFa0FLIR_Nk zwA1L04h#7{1O`+_2FmduQh_JPDS+L4BCin(0&;)eE(G8;S#PEi!LnFr219^zKUS?e zbarG4`y%MA5KE*dKyE@RF9D6zh{&ab$Sac_0(j!g|AVB;O%lRjVp3)(P@Yu5?rl4W ze^yLte-*1sUsb$gb{F;8_y9G16a~W4r;ghzDhZ%L7uaW=SY4V$9%VEr)#@5i7#tNP zlt4Ee0P)XPn{CWXj)DP&^h*)j|N65$MQu^8UT1#BuCtPb=A;A={0CgLk@=G13Pgz< zve0J4Vs$&&+#s6h=UZq5x|s)P$TvHd_} zON9;bJajY?b`hZVDEmsoRU;`J_uyp z0RuIy5^r}!Q8UB%8~)v2K_?Llhjj-QCIP^LQuzRp3FSU;o%X z)Q1{tqoyo!COirqrqtThHP9m3^~W+O(G&2lckOEtg>bZ7b)i1Qv}yG(vDk-sFQR18 z$%P_; z0>dE96h~TDPcj(M5)L^v48l=S<1UQ5X7tP7*zW9(yzA1gA*0E3N)Ek^y-e~b+K7dE zZRbx;Y~>QODi_eG^ZE4+O`|~J#0p;5ci}+o&WMbqb33oA`peCsv(AmQc8LKyrmsCf z+uvh?;&{eUG9j%k>d9$M7<;J1qLPx_+x)zUB=PTQ96Mz3+J#*tdP=k0_OGI>Mo+3n z{Z<*W6doT}cp+g3?*cQslH2ywWZo2bb_`YWlb2mu%!E+q({Bh!>MZ6PfUHC=!(Px%?jKF2t*sy zHFCDdLb+GBS0zJc$8-|~|QNnPT0G(TXDBk8!oPIR= zPm&}&FAvca>s`;2Q$e)WJbZ1c-xRx05O^IYquI9J-6}rswG5I{%@FrD1GT*muJx`u z{)65^nA1-Sj0Q)CX20y5sUaaCtH2cz7lct#g(us>tiv6vs^5#NI=H+pnnPO7pg1ijdU60`P3>hai90QTPcOmzA39~XBIKm5Rv`4%Rri+D9zOlXa zM6DNDGb|6a*dOF*EYCs>pNjYFtQZjyBw3QkB9$>rR(+kf6^IDVhE21v&hBt|zG98= zW59DxStaJnu`H;VwqYB3*NHVXY zyg{{nX_s1?%b+p`%hfT{5Tb$Og_VHdnfTm3G9mUBakn?GT_d&ib74}rxJh`_9_q}@ z;)`TSNlBZQW}72qTMW=(?kjNc<@}c9&g1?GTDF4OhgoEEgrPk+oka+~1bS9loo)~A z`H3h)wQD4G+zAF7p{zNeCo9blgP?I)A?S1WkJalE7>-)i20(KtzK3L7Qd}VG}f6-_WaKxG{8T> zfB?yrz4#L!1bqPO)nC6@w^{A$r3J8d)XK5WRzzJd2+Z|Q)O6K(T@UVrNvOLt)n`6J zUSQdxn*7nazjgvcBm#v{Y~NjhT2TAtppG$xyzBK}ML#a$@rD6*nLvr71k)GE;E(XJaZ`g8}#_dd~rS^8%49G`>Rl@MiuS zmifrS49h%JW6I_2=zEVr*Xh$x8C=gpfOc75w@BtDI-ZpP`LTA{$rA6Iai8OcM2v=& z5V{cH{`?O^&MreXslzM1Z6mhTCQe=llk=x^ioP`C06`aR(Ols=ghd&`r`JrI^huP4 zZEodGb&y!nS zEVx2bfoj1zaYb`m z6cy$79U~&>3UOE8e9v26Dq)tf}ueoGdiL5}u?P$4|ETlUqfw|zOmR@mJ<>}^Y7jqJtxWP*0zK8$RXMsH7F z`IxyM-TU$Q?>07WuDkqg_kOkg3bS=u;8#UX3Ry&dvm7>v zgc@LuenN18zq}G>3LdLmDLPvIm(Oc*)zXtNESH3yMhu;-!Trs9&OH+r_TO^jdYB_{F;Ek zY3*9iORMwQQ2e^?0frli$#eRx@Xk+_N&+vk=fx1Z1XL`*JVJY_G5fqwjS@9xdV>Cw zK1-_HVE)z1MpL)3osRousbA!uOc+o&0m~Q9lgz+lWIDS%f{NDIFXXnTdn%DwF@#&S z0RruMvvgcKo7RivC`lYaL4syL9|a0z#+4p*uA186d|v6 zsp7tW45|2>0C#}*syF)j`X2r8siK`Y;6ka%;dwmbQ1M^Q|6-1J3^moXkayGLe}+nV z*y}K4gc4FBLq1qo*5D7~-rDf3>goq!Y>l5@hrifcb{P6E5z*=IQ-gr@XRP~pCVCbI z37T4l-_)o5cF>mBd#Ya?KZ%0`^Io^@;A2m|?d$1|WTyYFOI>g1*kLE>a+NJ%3T`>h zg9FN21sh|#7R{l->VIN!41$YBE7$76mq#?@ zwigtiMS-qr$kTW_K7nlSuq=kxd)r=4?Cp}nL&@OG^Oa|0wue2F?m+;3w?Ordpt}5y zAlsRszN<_#OrC3J&6TCXl^=Dn{^pKhY%W)g(Z(}xDIn&mq#U5LtF^R&g z#xR+Nc3NgYsg8+vwNYUAe=4a+E{7C01{?dI-iNnXu{aBq#;BX_B0dunCVQPlzYj;= z>qM0Z7Lt>?hDh(;ASORKaO5Zrpe%YaWjw-UZxw$%hI<$vs_jQJXmT04xvYYD!=!-? zf)Wg5!1s#%K4cF(%;}+XF>hKrl6zvN$KtfQG!p`nlh`3U_=9W{yCR#`Ohxa$GZ7Ec zQV>qq-1kgId!qvmcxQHQ{HJfE;pJnb57i&8cULYHLnK|0u{g)0N`Sd8EPSZ{I_BhB z`sJ}zk1;pM(KR@jGA2tJ?P_=>vwe(gz!i8@PZ?_SWKj(ns?f7aCt7G6B1x!GMES#H4s}@>eILb^nZW0W;rkWTNgL`MK`sR0J2d@40 zzdiR4fV%@iSjaqU#%&{+TKoJx4{N5nO|Bw67cxy5bm#*>=gdI*&418lB{+-mPDk&W zGBM!;!QiNkIyUlelOzsch<;Y}jH|3@%5TS_scJsu9gF4d;w1ZtbE-RL&6O>pmHNA^=)CHY+M z-oK_mNJ1`I0cfe$70n6>!5~+R&2U)o3p9cgF$NmAX@#jYWtAy5(1PwEsqi7jH_WCw zESTpBq3tf(UPfadZW2K<{DAa?3h=;2l2Nq`)Ydvfd^?z0@eaXD35&;m)3`ffhHK`S zTY>@w6_NJq=--1VpNnDc`IEzhylPvA%nv+WJVmY7jf~SK?K;b z2#ZbcjJzI#dh{;8V-qavqCMxz%r09|*mzZ4G zEJh~rJ08kto0Mts7m`7KSim932R$vrkt-rTC-@bJ?qkSGd)TIdWcGq58i1gM8SOpV zfo&7CArh2o&ICTq3!0ZY^QLHkInQyi2zZ{sXqZC4Q@l91VzEJ@X-Nrev6aP`4Wj>} z%1O%65@v-*l~E=l%eI621kSx1C{>UE*RMhcu2nf*BP-TZ-|SFeVR*R6vV9M?%UU)T zp}BhyqyPgDWfdd=;dR(1bz1Ag19o;y-Up-J=RPad8IgKe%f`H;3$!O*aSYiY$Ya8Y zS7g=tyy8xZflTt>+?REu;l~_ie}!0F0gD8keD7Zq5Z^{!^pm zuD}9P%kpi4yAteaF&-UanR6_Z^cn6qMh^gobgdE04DY#`l}L6B1KIuUrd;{+)Cn?g z(w12Os5nnAqXdJs9+NG29>%f5RbV1H}q;QZYeZ_)lA zL{+_c`p<5OPBIp<2OmQSn2Ep)br0|NOo%&A>)bvyTjFwPHTh$s=TJ~kx)nJG*MalW z)fLGQm-|v2$Wwy|02Nh4Wnicvjnofok71#Nz=v?l;~ZO1`MmqrUo_|~x_Qkz4YJ+w z3J`Qi`|OAEu^RNo{+;a zsbdUicnIDvAA5Hh5bh#CCSLIs$kzZM9LrgKmy_44U8Pg8t|`#xVUe6H#A#Li^%=T|N=*cJ7`YsU zmK=YxmUORis&ZOH;M9-^MY^R$=o&t8uAXu=Q{$PIs2^?D(G z97Jdr5AvyFEI++crWR-+h=)ELDD?;oF|CHq+2qGn{HH)LNJCPhsF_l}eF!0*jvh?7Y(YUw;tP)GcqB z31z0#k}DyOD)zl7TgHC!g<+0V4m6lfUElq7WRJ_ z!Wx+@9*>Wck1eOccqpYu`aC&Gs^jZC zr?F8Ifqi<60#O4Zs+cAgMYWF^Fnbx;YgO7xTQpF+kc-I^)Lm1KSpPdF2A)`^e~)DP zPmu^wS(2)}M=>(E%z(`S1RZLC*$$D_ez}pBhIo-&7t;`pLF>8vV18{QzJEd$t^hK3 ze~Tb+YOFRjmw<)S9j2;5D*s$!2b--&iE^UUjoM$l)#uwKm3&77-eB=2xykg6pd2Y&|)mH-@!-z;)#w^ack6 z`Ad`YTHyKB0Gbi#zK*med+e|uPYC$;wv5kp#vf$D1uB;z{&zRR!J_D<#zI z%|;D=&a`Yj?fSkO3XYZ+5P=ti)%X_a6nc-N+gtTq8SysxA2y1wKv=}`m@-(#4kk(c zvU1&?uMlL_O2ly-xp5yBMxgZh2(lCk5k~eN)7K$V>u>D~h=|;kL+@6>i-^cUVjrQJK>OQ(H`_Ttu|N@ojnTDE+|FIYM>_<)^?Bx? zCu4B*CW9yoNK-i~*k^gGB1?&3OpD`=!OcqIT4rm4wdHDTf3| z(=G_Rg-!n2yvVxA)p$$Z8F+jJYt>F|e3!%GzLrD>WD1j345)HUklWuo(2g2o5x~xZ zoaD5b_w6>peJ^c+_fI`!Qe+LHQ{XzDx0sLuOd4b%+1STA|EwAbCV-P?26CBi%k^$O z0{oNM7APR}P{eE8KymxOg#aV8NZ^Lsxom*zvh@H*2ugx29wI#|vHyM(EC&N6ff29H z44&)r(G0#uBY?F95$3_C8aSW!jjN+}>w*aL;cly?lHPxONU_bBF*K(jlmQRdjg9ZN z_nBM;5E6{2%Ki!|;LGtU-9Oz;-&ChnqXkC1GfbEg%*%>z4!p`As8B*bf(T3CCmPc> zb6kQcI6^@AcUERT7WztZf6orRP6ZgqR-TpK1Xo#g(q=b~koGq*kW8Ts`_@TsH=^@C z8(Y=sh}g}5?6E$XHWkrX5Ca=Xh6mePjy^FH9=0Nf5q9SIeK-Sj$*W~NoMEp z6apB2k?;QL_mAIeVy9I75{aN@;UUxdi@Txi()+D{H_u4S&au|!jA+YgAZFh0MEqx; zldh-=JK;j5Y3i&omQ&acJliFN`%dR()}q19st|M+&-g~!eUXCz9}61k{aPUvT{IBF zN_mB_yk+D5yI#Y3?StWk6&iI{;P~#IKw2fmCjN_6XvfZsfZwdM@o#1r=&r~wi}mL* zB*4JOPrm3)yt$K?VOqkq9Uu4~! zfEvPd#8lqzv>1<8UW~Ab_wVT@!Gd3G$!*RUAd2# z0a_S?UHL_9$yHH>&Zg5*t<&J&&R5O)kD@{Y5}4Fo&t0{JGitwx!)iMn8;qzEzY+5U0+iKWtYzZaI8Aam zUGyD@LH81?YTq<5FvZc2x2rcI@g%y@cgx#A=s!16hAynQZID<-O1AWB5YB< zISwQQ4J>0FxhWGut|lyhzW8fe@5ugS^9r90XX#7J>+pdBJGGab%d(W}=Lsco>4by? zNoxhxcpkVb2`^Oztq-NnsF^iC2fLwnBrQ6rap!Kh=aVwiJ#_x^@&~t>abAS1*n|>6 zQWUNY+%Fc|Lj3+Z;rdt%a@&1QgJQ)^Wx^BV!9>7fR5H&OJ z+I+vn5tX)(2T?<+{hq)19_%51uEKn#dAhf3yTo>Qg~I@C^D{k1SJkpO4w0U{)2k)O zbp%^roA(ZPy1dg~FM6Bvu6Rq#<%x#{Lt6Uwipoboae zc?-N{QOX+dqZmA4{HtvN>$d)>0I=n6E}+chyy&c`iN3&cBCZ8 z#uWl+*jt~P)NQc%2>E^J19hQ|;4S-IXrwxE!)Zy-__Nbx%}l&jPT?QeS`L(yVW4?A z`J9ZEXeoD6DSwfna8A^*?ve9`ug!=iFeHNHbd0YZ^vtnYR&g`h6+Z48tuZ9n3I?WR$iAVqgzNcCi*-U(wruc$eCSs(W%oyfPd{22ird3ZK)+)W+?7Tl% z$N;D9y300v|L*TspW4;E;>y0>CI?90CSQncg!d@Ch{Sw$yI>w=h6dEldw0ccPG$7J zSo}<5Ntm*o=G+cKo|?F3qX){V7=TM7(hf!iX>LKxJ}0OvkyFeTHtZihZuyR5f|NNR z_FQR1(TamfAkuh+lbkcUE!Em4tKg0SfPCBd;@^=fim(xI>zrEn@vK@HuHAx4$6k8q zz7~6z_%c_`rblBb!{+Y-2vJyE2REI2A(m1L4)tNtak94euKwXyZp<zY5$W$QkMED=X5N z$Scl;zd{@;IK%Ca(Z+=d0Xq+#O(ff;*ofOuwkNM%Dic5-_7`#~k*OhbaofGt_hv>S z;gle9wdNf5Lz=kcp$773o-tu1$ zHXEA#Wg87#P~Q|O@_`>-KG1;U*m2doPVni?-KQf#6#A@KqtN@=uBN@>?*+elVhpX! zz5j)6O~S=9|D_=+B5=d8rTw1|;Q?NLFu5#_r@Z|2zNH0#+WDX@uTbQK$W``v0lLia+xm0)I# zd_U?|=KKEiQL1ln{r9P|l-3J`f(ZE0&2??lJ(a^i^|W6LxaUusZ6c2k`zrnWSZp}S zfrRZ2Oab9jo8<-@DxCEOMZxR$eg_Q4;{qN|Qvpy@g|i$wXJ&MkC4PoS77PTsOyN(B z_guT1-_RGGm<{D?3cz`#XkDFQpqB2d4M+oxcyxZW7m>CuXw9G(S26FHZ^^giYks>> zJ_=PdozB*C@oLsa{HwKSWGNnyt#%l;Y0D`7BT+zb@yhY`}#J%+@a{UZBakcn+0QS*xK;CwJm-kRT9ZrP@fQEYl;iH zs!o8*6j;S`D)e@kI?iZ%;DWqdZ+yD7V)}6t|JrZAe3Im$GL0e!r@xa-+HQ83sk9KE zl;<|l@r*VG%;goGLuza8cqh{QitO9uY&c0M!+y8||ID?AcRRlkgtI9)t~x1)1^0Xv zZ>HCj;|oXn z;;-Tt4RZj9t|AvQtv+x>;#G8(7f+x7$@)c&=7Hq#>S6O8&WKVDp@ zQGBK^HB9-3I++Y=Mp9}nyhC<=*@uf;&;yA0F5a;e^fSF~YFA6Hc^nXOc1SvXS1{I8 z9gQ#t)I_$3$D_}MCl-RWreXbk-bF~8Ru_I@FTU$qd=8dodm){@yxJMaCPfl2 ze27^V3f(+Oop6^@we2NcCq&~V4PtV;K0XN8yN@^vv!w8ImwmK3a?<|qID$&Q<}ZoQ zNb6p+!&fjbw#m^s>JC2075wbJ{R)gJpOnan*q@$mb@OHmvGE77Gvqp2~s-%@5{?HDo}OHYFl{7$W+bzRL4<4=mQ5Wxp6lkh$0WHEkL zpQLv?FbVis^tMh*0RA}fY+V@W#6X0Y69M36_MoHw`RaHkK>>u#Dh#6F;)BS`Mu5L= z5IF}LJ7uP`?(xcR^T6whMBD38E13qJL1VAzK=~MM`Mk z!LrQHY0YPY$85O;D!KHHhk%*J)I)tcyK95K6O3Vd-`LSHh<_HHR?*J%I#rG@ToU? z*T)hrJX=6tUiX~lDKT2t$Cr2?4KZU}0Eo{M9rbcv0P6ANG>wiN(3j#7Zchh*sVU7! zCQqR1i$ej@1?a*n<&+Oc2i|GUK&#%`Bh~@vdqF1W%~e=02vD8<8c^9 zM5rv?X$F0U@3m3-thQ6T3Paxt+b!$!%xRnIcT61R%5H{BeQnUdkCTTX6C!iDbOJ)V zmy8$#2j>rqTwS^IoN8!K6hgX+@4DM4<0R<2WanMy=*g*w(eh4T9Zi|?u@6=eeKVKu zcP?QF%-k*v=qm+F7^UYaL#LNLwmNGcLnz&pp?wBCd(Z-4&l*}60>GkVW+-HG%g^yP+Z7;=sn;Ii{G8gps*)~+5kTis;P#n-$jm%3pY zJNzUeK~7bAhj*hs%$W(-Ldr6^(r}hJ8KhvO{-`!$a||RwEfZVu2ch(BlU_?-iJ$GU z!Oua_9%M`>-gkUt2>XK5SZMUMz|R1-Z&dAb-&i3%2Ui8Eo~y$5LWfGT zTT;=_g(%=HMY3v49oV@azdXKKF#B?lj1c2n#04c-r*h_-_wfYT^K_GO_ws9~egHp^ zG$jZRwwut__U0~dmT_9C{HV@=C+-J#uMHLner9I!ykkKs&qitBEL;ra>hB0u>L=@K zr-c`6dW*tjq>7b9-lhlKq?0T6dLIt(UFV=JI10?#Y8Cyv_P{)1a~KB6>nX8RVf{w1m2dh&0MogK7 z)>cHr2tVN(oWp4OTvKro%xB6!N+J#X(pK|G(*YkZ?C4UkDG=GY=5Xad*e&d z8@o!imua$!#T`>Aj*Ea2zhCZT!5_DT+WpwG#zhc9q1VRP%0Zd8Qeo&CJZQFCV+1UmUaG;;Z zf}E|vl>@lW+R~#g3G9-_n_o0B6MXIA)E}^V%m2SIBYb z^~F$XIlWU$u<_!jz`+Ra?&9bFV|4cy!c9SQMg!|p^nx^^2P?f6UnVMfk-ed`cXKmT zos(0Nr?|8P_fX?oIkujW#52f=Gb6JznxU$><=9utOPsVptfJ)+$E5vf)=P(u_h!t^ z!KvhAK_>O&smX(%!e6`hxt@eF|A=4xtZ=VoKTgk2RW}Qc%!mZ-Wwk*u&%lUZqWO=XqyBAMz1FcH z8*wf=9CJy>3SHC}r|k@w#wb&%_S?Cf8>o=GZkU5k>K&8c%oG=<#9KQ;ZsqZjv)k66 zIiiq3u=+_rGmS=uJUI&yEK`1EHT-E7OA*}8`sAlyv9a{x3L>sBV|{uR_mAAOlV%w| znJQnZQJZeGmZm?^bP|M)3wkx=;Dc}fPeE`_kMmZT2Syw7<_RL77&gIhgyhJp5Rik&t&(oFE8!le~?9-OULcy*H50G5%H(0d?Pt}^>b3>6<;=74m#H^KkAM+ z*reepYMl`7$*o9H8+}H9OL)i*0&Tmdm&5EL-?c4|wSS~MihkM%Iysl;5s)dccO;fm zk7N6=-htjSguVCUUMdk=%9d>6N&2h4QNPVkJkeE!u4)q!IOpX7>MtqxZ?#G>3D0Q; zV_yvVFeZ4TrDPT}5bXzZ((n!OBW4eA5XI$U2mn#P;h7_*K2go-i5cTzUWtl9{VHn(m1hnz}7%1k=xh;an zpbbPUL^n6jd?1?h`dIyG^6HOU^b^$%`WKXr7+P0Y47#|s>0dw+eaqwT?3kG9?v6ff z%B)~SU{(DUw2$qrI!L7tCnmRx$~z}3LT_m%O+p?NMg1BdFVwzFlGSL@h|LM?K!iqh z3y{qVzt2a2~XC z8>60{oc@}~;t?30?w5sR!#@43BE*zj?DKBDZUl|mu^34hUPzYJe1_%foZ$Ygih9&$ zcuoQfjUu|+48jD8>vkqKR+d!df{|mMosEgpZ8}Or8#M9>l8_DqU%V+WPkoMrgBW?H z@NL^bjby7@xM8u#70vH!2SN1hEo% zk4;yu^S&$;jLtLIa7E(JuB=8N`$||3$o8&QsH%IK64is zG)Yr-xs5c^w$3u*!a@OM9V^%qr=jV`E(5wLUj&K-ns~hlY?s#&NnNCrdgD5fR{c3M zmv-%oqJ9a4!UZ%|wqlGvr#+|*-+)eyzgVAO?-SiqveEUD9IMay=7@FEzpH1R!#2DQ z2rBzVo5gM=O_PKbGEx-6hI1}h^P;9j;+6Z_axjXn4%iYf^0 z`i3LN90_B}!#y7BVHnGnAO?42>J~9_`$3S!C|hOT>(YGuLCW{yK)MG1B$cm^2=2QI zDv)Lo2G3|fHZOt-haFv-d}|MiyR)kOq8~8WYRd-58qr62+f=40p4=jpUqMpSA z8V4tr=*L4wwmI3CvGZ`MP9~EFeUx&;x4pxv>*&qluwljpIaVgOLjS4@{8Ms}-bDMM z2+pPXc?u%>5J!!`4$5VH^%l5~E*v5IHo~e|{_-_HmOv8OAXRd3;dXBN5y;|mLKj@D zd);kaXGU@?wf{ZzG;*0gHL-?wF|!Nb)(7!indld!<1|#8TVxg8x)dMzyqUoEo|jx* zn9>a5d(9^8GJxYL8vD84NE_rowR0=V>@kyc{*{Qvbmv)61=LSeJR?`)PMjs{7dgUA zMCBs{bsFD^66bbYHhnt3DK+Cv^fW4FtLAXo##I1$91++_oIJJpjDj8Z*^zGyOK~fM zEVy?q>Uo63%9X0+IZ@pX^dp}Q#}a2A^kVJtGlqT|=$lTIdkB-lvrY*PyNG|(sv!T* z79wETTJG`a%PuOD5zn?pBJuIqePL&HX-TPlj2O?i?w>XPX*6Wiw&%Eo^wQTA1=Cye ztFXhK9^|XflUoMoMVI*TqJ{tPO+-N7T|le6REJrmFv^2A{0evTGvXTC$UpPh2`v&i zIe+~OyPtHxY?Rs68%$5kXHq6_?SJ>yG(=LtfVOYqglqQ7E^G}yE=N+sZLaXvBOG;l zj0r4&t8KJ+%dD|U*_V;h1{CgZ`oWW5wIN<|Zxj9zzQHA&*KyH{2gEZBC|bX%bTjZ! zCA^Fvu=O9(&?z%q!wf3_v3n64HP2m87x`QD&o@J~J9DrBlu19bTKmb7?(Apo``DmF4I3ru-Ps<+*OwpDf;?SpLkhF+3+o9p zN`z!fVtV8CpN<_kcD7V^|D^ij&gp#i-FXo&!vkV};+5F+@u7Fp^y5pfZ5zv6Ut`J6 z+Z;Atx75eOosVkRY870buX=YRa__iSn(Uv<2@z6g2li6R^8L}6e4Fy6R-;55y?vZD zvk`Ekya;{zYMQ-?LW%w3bwk@FTU@k!9cATuV24Mjwt=+Uu6)2Z4mzPU38Jf7h@gt0 zE-@dBG5}_Zzg+)Vjz+zv@3*^V{17$!-dABh_m4v0F%3)AlcNAh!gSXBU;U4sj#eS= z1Ao){N>%e!7L*vjR~Yvek^MU{n3?utqJ4GyG72(*dA5D+ZFt{i@5`VLQB0>=r13ra zKh1nqP#jFKF0i<}OK=SmAZTD0mjJ=t11u1n;O@aKSdhit3GNUi$Sx4vA@~9b?p*S} z+_zhG&(rCrnW<`-nXb|953%$!yPb3DAcjf9x+(h{6kU+-GlTB=k=Hi{DD#|ATNraY z9m%A9Zp%bXDP0%aA?Sot@hX8JZUu8cd>xi&Qwk&KcuY+2Gyk=g(e)@Vn`z=cTL_n# zCcOvHJlA_l@WrK?lLzJpNs-8r7X{4}pcoFxd{mcdQL3aW{EqY)gD@RHDL`qAGZb3G zH#W2yI_0Hb;1U;TDe(8>R-ffCeghxhUp%zWL~+mD)GLFbXifpfz^YTB^Q)Xm`Qbm0 z1uUG^u?-UE7nythJ@hc}gY#g5!~pNSyG;(}TcN<)&E-mC)~p%stK0o7G9v%KqxFJ9 z44rN@j`AEQ^HM@)_gvhpCr*kQi2cKHp5nk^Xu{TRqmODVr=2r?V+f%AV<|?&HzxHT zNufZOSl^|}Q1&{;$C7s9T0;&3L^s4Tu7yz8~I}^-D$_8i%7`c;AJe^6uUaG&D z|Deg7T=R#8%a0X{wepW^#Kb5ONy??iegy~ue%>Wpdo@Fq5ZZB$3Sf&jT}DXH3L>Qn zFY2A$5}^sFvT(d}I_0&mUzm{B&7=MGg~F?0Yx?R%>)Y_DEY9_uOp}5fS!yw4Dcsm9=fVBY*z<2?n5X zvo~rRSd*3#t)Ys7Y^q~qye$d2dAnF_BJ3d;IsVh7!4i=tWE$_~X%=P7c5jXICz~YJ zu`jn6yMuR6>jn}K4c+_mF~UNV%eEH&{HX^hL}18>InEd%>AqRRV&(!+{6NwQ3e&BD z;%i$$oX)(xJ^`AcA&Lw^9>f|!L0*oAxjlHp(>CK@@Mg*P>GctPh&}daBaLlSFOP1} z9ax^{q3Ypldeh~DO%;Kn!lY__q*!p)FtvvVdu{tu6WQ7gs(s17mm}<=his@;dF!`E zz#(Wls7v^7MulLZib9YfCnc>>1}m=EEi2*rCAZFE#T1z4?w> z4_~2U_{V!mEs%gt?p2G@5_5S$aRX3cdA$88RX3A@%TPJ;P%<<1M_h}36!>jk*;Pa! zz<+0DiQDu65Fjn{u$uQAzVPju2`2F3uu+;g;Lpffo2{O#Yv}Vv*F9dQQ=}awg!H|~ z%rqc-zbWU-B!Dh5nTX}tD^?yV{gTK%_;L3l>Qunh8<@677?0mBUNhebRv?< zxkrDDmVx0*ZLZtb%C!%-v`j786kmnc{IA1bp=509LCAtE%=e1-qV7?Ye10Y3vaERp z#S67lisk2ySMb90@?6Hewb~1%3td_!hLJ`E-qJX^ZpaO}<3G;Qo&8R7mLcP`K(7f- z>oEsl)vicgBmQ7j$U~VKWY_IWX_}1cu~sXt22Q@GKXyBJ^<@2(ew|9bFU@==ybyC~ zt4sN{nL?R=+S*#=mGMkv7h!yx(!udI<-V_QZOFs^Nnb)0hz<{KKrYd=-KQaEaw-V*~cxuwv!mU&f3T7|ot>=vd{EFgbMdgKcF|(^^ zcUH+7?)%4mRS4ur)!DDU(foS z^-Hc9FuFaG_WH*9878M+!$ST-Xz{l$3cer1G)aC|C0xvv?N3bev(f) z7>_Jt3y&%F5OxXWQhtxjkR1Xx)~KUzdrIx+PTfgER7EeB_4AvoAk`?_O%(zYDhluk ze*P5RLMfLTn$Oo<imZVQL3EP%F z-=-!JB(LBxefs|WJ({I=5AzZH%<3z13}@jWhlXlDh^SQiohr%}c}g3N);pcMmLBfk zlljI5x;vB|2_#4SDn#Hb`g)6r!)$8>V46Kd*Y8s-6cPr&!DC=qWHp{>bCOeDZ41vReCNqyj5iZibi&CW-6B%G z9|Wfmos?7zPjrh`l$tMHL>0A;P{~t~ca|vH>uW#r=`~E|rY+OY`R3kTZ~8_1D5Vsa z@md+3i$LNNpHott2)xafP_rS7G0Ot^rwkhR58l=yfSR@^2rgK-h{P5)w?`9wlxN8&(m)CEk1+U(Ct^F?_9J21{pga2NlzMuu3r2A z<@r#@z0e#>!7TD%3Y^brGFHf+T$xVC>3uYqkzx9S{2N?)m}j^~?q(k_qSG+1{d+Mn zcFUm(^*z;q;4J>qdvR4b8m_msQIazgd2C=kPq=;VT613RnMbO~ZkI5qOe91hv(z@2 zb{iFjS{C`VnDb0mOvO!b@vU#7_|E~#gaj+=vWV?9BT8>}`K68-FLI8*TGUQDDI#g1 zJ(1gAVj6VIJ7N9+b+d*I^8`(C0L|Eh+mmwnu##9V(z|JL)q5^-ypM8L?q$Ex{HgQh zq_`YDmbYz?{G6B`Y^AhN7UE>zpxw>zq$7w-G$Y83!l#KBITbUF!gi;(92cWSTBUBb zphrj`Cv@}`;W2&Hm1q{7|9QI!4$}7)VKxVe)AK?*-gv9vf9`KJlYATSQ!vk=vb{XG{1T<8nBdf&F$n&!$bEMc?JBh+%TTk+ z8`|;fY%J3}pmkK*zmCQ;_{7eyo(RAH2=fx|_~{chHL*n7r_}kce^Kw*PF?;CNYC0e zChi9A!w8y&@X|sR3qKe5eK+1x%O?XOVoj4?FqQTbAm+ViC{Oqx&3j`h7bU0lUiU>P zyz0-$>X;`+LT7i2d%c=QCW@&${<0uy#Lwoo?f=kg)m`Df9AWdvqcsiVr zA36Xis+k90j{XXELJb&b1X=Q7mV=B^;iv|=Lg(Ee4>c-}`mmf)3R$;d_FArdWCIRZ z0_MQ`qXwUDF|m`AFYA_6;3qb7w4--E!(|aHI`?ScnPJHCW>;pMm}=GL1|>LP1T-W6 z;QVWSxGE&)ts3{ly{hd$QjcU|M=&L=^=HT5tm)Yqd3P%crxQM|reO9ahIV9a* z+QVQhDs3~!L1L&AWQI^>9F#bCMqz2S=F(S(E5Yd&mnPY_N0eLBI)yK zy+;xVz>$7vOPL~lIRQRdpFWuw$8HM;1&Tte9Ml}}cAMyO_7?vZR3IPv|j6PC5rmUHqc>v)O5dSZ{AzZ+iZT@r!MZETk?6kSzU|s|s zX0L1Wsn%u9{d2k6+n zYQ}tim--E4TH}Sja6(_!=Jf@3h~Tx2F%f!EY7IIW{3(buaKh?`rkkPmBvNG;R_`BK zU=J03hbKB7ELhLk;O8@)54cN(;V+VvTq(DK=oX8atT`lIGJ-Fa{gY=g|+v z2$MJS(zBC*f|=7(NR}n`bNVl7?57d4esX1uaQ16z0O>qFgV#u$G}&Goz%)%}o+c|* zsE^V;szjTS&a_sLZ%{LMe^%DEM zmbDPF%~B94P5ZzfoMx-s)b~A8ciNPo;#VwL9uL`$JZJUjIzdfOv)$uNwKIIY;cE`y zOv}dRAo(U=Z@uT~W_pQ6v$BT95XaHD`*qJwHB)&S)D?&pTrZi$-oiX&UQp;ovM!kn z;JOT&Y^TAQ56&v-IBsxMLNu0<<6pCAd$WC*6?_-(yjW2_>B->R5zj+}67v%HKH0nf zc4gV$;&~Md8sPct0k$Ns>j(WPZ3`9On^ujV``eQgEDW4U7q(u_brpozIJ)Rc z*Z17MA~`4qmo$L;^0dd|3>_#K!gN(>#F~Fy6EQ@E7k{@&1N2sWFZckm8wgz$I&~r) zg~Kf?8Htvgx$%X+!9}TVSSX?)a-A&$&i_<#YIJX}1Id)4QqosyH3h53l^a32)E1r@`D5a)rNl*GFV(9syhN&TVDN7o>7@VvUyN-+qdv z4cvqY<;F$xpTEjmZ$gcB#OqiG&;)O`j>hND9i`Xn9d%yRjdyG<`G2-*=&*0i$ zzt2-Krkd-1yi4DzGbisDtk2Z*mg{nRiqSmwvb#O3*_qc-!O%6ESt>49;bq@!tUpNm zv%Bs4UVQ9LJrMa$Hstkp%*f8_Kq}sdk-Z-bWnT)_ws6Kba{^RlLwjmW7A!(|#hND1 z+TWSja|9*wQpUByGK6-iT9j7B`Y*@gYacQv$4Yb%5R(Pn7z*=5yq>3I#nDQ0nyqA} z#2~(Jn*-bk$=!0G#h7dr8)^eCgORXnm7?DJ0E${$#to{sT;#GAcEe{*-klbo(H{JJ zW~0{q?jDvGy}_c_L(M6fI?jw2-oKJnoMPCjPofYkRdK}Tus61XE=AfK% z#_~gy&)fV2b8>?N)fHm3lqnv&-5dj+Z?{W@UZ=O0Q{gIO!I}DaTa$nDNkuF?bH!au zPcZ7NypQN1Ho>3^o3HgmO7r)4mh3Ew-(z@~>3>Z*#f!}jEX3_UYVGe^QIHI{ z#Zku!FXnA9299f{T!ZHO(6W9Z8b=R13C6nCCr1Pt70EJf9De0=W*;KKE#YPRYrqS7 zdC`&d-xo!Yo@LP?kw{F|HN62%kQOPD%pRmXNE%Ege&!sOv4|q*-z;hC@B4*%lvSi= zeS|4?(a6i9znrccCKb&AA$xt37iRdez%^LWPXX>_7*`k+OV% zWW8if1j^Jdh2Td?D;zYc78HIy`a8|ua4>Wc{~{(f6-s~dqAP~oeY%s!ETs|E^5ar> zCjiY$_Z@Dntpzyyvy(ZD0omLJj%T*z8kX zsV7qiC>*}I!S~Q2ZeEk@%CJt_z#J0N@wrdD9j`OkpHm@-O`ob;*tBy+Y{`FcxEDQl zu*CH!{Ip4R-j#ori^7AAgB-vBv~G*7OOZo2wcJjxH5^+SjetyzgK}uGXsj!DI&|;# zm{REs^{CzjA`N=;93mTwJNB#bZ;;H7^x>yLL+b={y_QY>YBF*$i8TY+3AnIv-xA-L zlVrP7m&n2ugo&r3<{u@e7;hhW<^3-$A@cJ#H0$P(wV)$|I6PP^pPaL_9@m#>h+;u}#n3e7OsN%h~D!Z`jj6FtnWQI;3 zhHw6ffwi#djTvq0xj#ATGM!36OlfHWpo1=NFTNC{9MAabXU9SmHAjU#d>-mhc4>)rO9Cp{UUk?$0g?=J2;M$zK!{jbGsp zA5N(>s1EavoRa?30!4n^Cv<#`X#3asi>&K@oe<>9@g%;~$nzd0q$4gjg6}(#sp@Qq z+za5HpjA`GbQX@KxMvU~JV6vBhF9zKcwbE{VBy|c48fom8*ogA<+N6)AYfY=(ySdaCy!K$PoWHAjKN)J2TP4ig3T)|5RYXH$CkS9 z7P3vz4Zf91m`&V;sWzCd92(|55yOc=R`PIKGfWHuJ%3$|E9_?Q2p9DP$hIERes6n)aMf)WK#wa zNnN4Nb+iZR1gJ7e1$*zTxcF!5_Zg?Q({!FVGY)V%uQZ2 zT2YC}zHoY=av-$_O?INz>_75@OuutuZ*MJOq_^R>ZAbVKHhr4RI6|&wrV#)2=o#Xe z46&Nb{AVdnIoHA`L>%3vmCe;WjHP#+p!9pL73r>)OIM~Nmmo-S2Hf~7#2AJj6Ul*c z+(^#$;Mn#^!QJgjOu2x;!IkIL?>aBWR-wP!LShWJBu{t8(U*8Lm3dvd9PXt=Fo7; zIycR+f51jvC39iBz%Z>23rp;pLgsx6hqzImmq1v{l@@d+V?_4iAT(z>=mwrAuWx<( zV$HQc(uX+znKy}+FZ8Sc3wU#iz7)mCJPV)eA51@HjS#*O1zqb27a}_Zyx!j|8X#Tl z(R>lU%6EWtw@TU6ke%bL25U%BJ=yuzlzro~gz1yqiU~TJ>^j=!Qv%_|@n=Gka{i4r zE}oSHk-On}a-HJ3!}BJ0Je3wjq;KXS1EXGZCFCzdtsG4CIylTJ69TP{Eb1Hs2%?Gl`4wmu^xQy?!k>if2k&Um#$D|*3xZ|Z@Wf0;2 z#!U-$+Y-7FWkL_u6Q`0?D66aj?i+;))YYoGDh(Q;Q}UcuAivH%R*4*c@x-hD92W=O zz(tRrY@HZA@?qZGSUwSN9j*W5P6*(Ym#mYnF* z%E3V&rAx+-_RS@zqOlW>9uBRV7d%K^32IGC*HxDG9fbR%sB5c1qS_ zHO_uN*B!1<+usKgfXytkfTcz&2af0Y%na#SIGak44v)}yV%#73?`y}-=f9U*T^)V) z_rkt#XTLJ0immF%zNrBY$8`Y#R5Qpv?T0$_q8Fx$F@j0L=?N49oZAgRQrTeZo`aG)_3>J_e9mS zr%1-*#=-F&n+bWyK3OP~3)$%I3de?1_0&fM`{bn1TB_rWbmo$ec`l_d|Aw*<-`!_Q zQGl@@hCrQ2WU5BBgI@0N?NV#Yxn$b;oxRLWhK51pVXB2eVdj%qT1o2Hh?1(OWBdU% zcEgD0XgrCBBYR(KV+qM@0i2hO=l_%br2lV2IuCtGY0g_otg1|nlv&9C0T0#SUjP6A literal 0 HcmV?d00001 diff --git a/client/images/amneziaBigLogo.svg b/client/images/amneziaBigLogo.svg new file mode 100644 index 000000000..c50c77431 --- /dev/null +++ b/client/images/amneziaBigLogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/controls/arrow-left.svg b/client/images/controls/arrow-left.svg new file mode 100644 index 000000000..98c9950b3 --- /dev/null +++ b/client/images/controls/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index bda89bf28..f95ef5850 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -176,5 +176,17 @@ ui/qml/Controls2/CardType.qml ui/qml/Controls2/CheckBoxType.qml images/controls/check.svg + ui/qml/Controls2/DropDownType.qml + ui/qml/Pages2/PageStart.qml + ui/qml/main2.qml + ui/qml/PageLoader.qml + images/amneziaBigLogo.png + images/amneziaBigLogo.svg + ui/qml/Controls2/BodyTextType.qml + ui/qml/Controls2/FlickableType.qml + ui/qml/Controls2/Header2TextType.qml + ui/qml/Pages2/PageCredentials.qml + ui/qml/Controls2/HeaderTextType.qml + images/controls/arrow-left.svg diff --git a/client/ui/pages.h b/client/ui/pages.h index a639d63b5..c33289bb5 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test, Credentials}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/BodyTextType.qml b/client/ui/qml/Controls2/BodyTextType.qml new file mode 100644 index 000000000..9d789385b --- /dev/null +++ b/client/ui/qml/Controls2/BodyTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + text: root.bodyText + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml new file mode 100644 index 000000000..5560aee72 --- /dev/null +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml new file mode 100644 index 000000000..b7c1203f8 --- /dev/null +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -0,0 +1,23 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import "../Config" + +Flickable { + id: fl + + clip: true + width: parent.width + + anchors.bottom: parent.bottom + anchors.left: root.left + anchors.right: root.right + anchors.rightMargin: 1 + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } +} diff --git a/client/ui/qml/Controls2/Header2TextType.qml b/client/ui/qml/Controls2/Header2TextType.qml new file mode 100644 index 000000000..4bbbc0d67 --- /dev/null +++ b/client/ui/qml/Controls2/Header2TextType.qml @@ -0,0 +1,10 @@ +import QtQuick + +Text { + color: "#D7D8DB" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + height: 30 +} diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderTextType.qml new file mode 100644 index 000000000..60fa4e951 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTextType.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + property string buttonImage + property string headerText + property var wrapMode + + ImageButtonType { + id: button + + Layout.leftMargin: -6 + + hoverEnabled: false + image: root.buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + } + + Text { + id: header + + text: root.headerText + + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap + + height: 38 + } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 18f62344a..dd0d29075 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -7,6 +7,7 @@ Item { property string headerText property string textFieldText + property string textFieldPlaceholderText property bool textFieldEditable: true implicitWidth: 328 @@ -45,6 +46,9 @@ Item { enabled: root.textFieldEditable text: root.textFieldText color: "#d7d8db" + + placeholderText: textFieldPlaceholderText + font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml new file mode 100644 index 000000000..5f2fc3c2c --- /dev/null +++ b/client/ui/qml/PageLoader.qml @@ -0,0 +1,57 @@ +import QtQuick + +import Qt.labs.folderlistmodel + +import PageType 1.0 + +Item { + property var pages: ({}) + + signal finished() + + FolderListModel { + id: folderModelPages + folder: "qrc:/ui/qml/Pages2/" + nameFilters: ["*.qml"] + showDirs: false + + onStatusChanged: { + if (status == FolderListModel.Ready) { + for (var i = 0; i < folderModelPages.count; i++) { + createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic); + } + finished() + } + } + + function createPagesObjects(file, type) { + if (file.indexOf("Base") !== -1) { + return; // skip Base Pages + } + + var c = Qt.createComponent("qrc" + file); + + var finishCreation = function(component) { + if (component.status === Component.Ready) { + var obj = component.createObject(root); + if (obj === null) { + console.debug("Error creating object " + component.url); + } else { + obj.visible = false + if (type === PageType.Basic) { + pages[obj.page] = obj + } + } + } else if (component.status === Component.Error) { + console.debug("Error loading component:", component.errorString()); + } + } + + if (c.status === Component.Ready) { + finishCreation(c); + } else { + console.debug("Warning: " + file + " page components are not ready " + c.errorString()); + } + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index 9f820389e..4c571ae04 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -13,7 +13,6 @@ PageBase { logic: ViewConfigLogic Rectangle { - y: 0 anchors.fill: parent color: "#0E0E11" } diff --git a/client/ui/qml/Pages2/PageCredentials.qml b/client/ui/qml/Pages2/PageCredentials.qml new file mode 100644 index 000000000..509ee6a67 --- /dev/null +++ b/client/ui/qml/Pages2/PageCredentials.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Credentials + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderTextType { + Layout.fillWidth: true + Layout.bottomMargin: 16 + Layout.topMargin: 66 + + Layout.preferredWidth: 328 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Подключение к серверу" + wrapMode: Text.WordWrap + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Server IP adress [:port]" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Login to connect via SSH" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Password / Private key" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 40 + + text: qsTr("Настроить сервер простым образом") + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Выбрать протокол для установки") + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml new file mode 100644 index 000000000..6f8f03567 --- /dev/null +++ b/client/ui/qml/Pages2/PageStart.qml @@ -0,0 +1,163 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Start + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 80 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + BodyTextType { + Layout.fillWidth: true + Layout.topMargin: 50 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("У меня есть данные для подключения") + + onClicked: { + drawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("У меня ничего нет") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + } + + Drawer { + id: drawer + + y: 0 + x: 0 + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + } + + modal: true + //interactive: activeFocus + +// onAboutToHide: { +// pageLoader.focus = true +// } +// onAboutToShow: { +// tfSshLog.focus = true +// } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.Credentials) + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + // onClickedFunc: function() { + // UiLogic.goToPage(PageEnum.Start) + // } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } +} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml index b87583845..5194073e0 100644 --- a/client/ui/qml/main.qml +++ b/client/ui/qml/main.qml @@ -85,7 +85,6 @@ Window { } Rectangle { - y: 0 anchors.fill: parent color: "white" } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml new file mode 100644 index 000000000..deb64e896 --- /dev/null +++ b/client/ui/qml/main2.qml @@ -0,0 +1,142 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts + +import PageType 1.0 + +import "Config" + +Window { + id: root + visible: true + width: GC.screenWidth + height: GC.screenHeight + minimumWidth: GC.isDesktop() ? 360 : 0 + minimumHeight: GC.isDesktop() ? 640 : 0 + onClosing: function() { + console.debug("QML onClosing signal") + UiLogic.onCloseWindow() + } + + title: "AmneziaVPN" + + function gotoPage(type, page, reset, slide) { + let p_obj; + if (type === PageType.Basic) p_obj = pageLoader.pages[page] + else if (type === PageType.Proto) p_obj = protocolPages[page] + else if (type === PageType.ShareProto) p_obj = sharePages[page] + else return + + if (pageStackView.depth > 0) { + pageStackView.currentItem.deactivated() + } + + if (slide) { + pageStackView.push(p_obj, {}, StackView.PushTransition) + } else { + pageStackView.push(p_obj, {}, StackView.Immediate) + } + +// if (reset) { +// p_obj.logic.onUpdatePage(); +// } + + p_obj.activated(reset) + } + + function closePage() { + if (pageStackView.depth <= 1) { + return + } + pageStackView.currentItem.deactivated() + pageStackView.pop() + } + + function setStartPage(page, slide) { + if (pageStackView.depth > 0) { + pageStackView.currentItem.deactivated() + } + + pageStackView.clear() + if (slide) { + pageStackView.push(pages[page], {}, StackView.PushTransition) + } else { + pageStackView.push(pages[page], {}, StackView.Immediate) + } + if (page === PageEnum.Start) { + UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty + UiLogic.onUpdatePage(); + } + } + + Rectangle { + anchors.fill: parent + color: "#0E0E11" + } + + StackView { + id: pageStackView + anchors.fill: parent + focus: true + + onCurrentItemChanged: function() { + UiLogic.currentPageValue = currentItem.page + } + + onDepthChanged: function() { + UiLogic.pagesStackDepth = depth + } + + Keys.onPressed: function(event) { + UiLogic.keyPressEvent(event.key) + event.accepted = true + } + } + + Connections { + target: UiLogic + function onGoToPage(page, reset, slide) { + root.gotoPage(PageType.Basic, page, reset, slide) + } + + function onGoToProtocolPage(protocol, reset, slide) { + root.gotoPage(PageType.Proto, protocol, reset, slide) + } + + function onGoToShareProtocolPage(protocol, reset, slide) { + root.gotoPage(PageType.ShareProto, protocol, reset, slide) + } + + function onClosePage() { + root.closePage() + } + + function onSetStartPage(page, slide) { + root.setStartPage(page, slide) + } + + function onShow() { + root.show() + } + + function onHide() { + root.hide() + } + + function onRaise() { + root.show() + root.raise() + root.requestActivate() + } + } + + PageLoader { + id: pageLoader + + onFinished: { + UiLogic.initalizeUiLogic() + } + } + +} From 3d63d6c0f2c64c62b40bc53622b796b851588d2a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Apr 2023 19:31:10 +0300 Subject: [PATCH 006/131] added PageSetupWizardCredentials and PageSetupWizardProtocols - fixed hover and pressed effects for controls --- client/resources.qrc | 4 +- client/ui/pages.h | 4 +- client/ui/qml/Controls2/CardType.qml | 38 ++++--- client/ui/qml/Controls2/CheckBoxType.qml | 4 +- client/ui/qml/Controls2/HeaderTextType.qml | 66 ++++++++---- client/ui/qml/Controls2/ImageButtonType.qml | 4 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 44 ++++++-- .../qml/Controls2/TextFieldWithHeaderType.qml | 8 +- ...als.qml => PageSetupWizardCredentials.qml} | 24 +++-- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 74 +++++++++++++ .../qml/Pages2/PageSetupWizardProtocols.qml | 102 ++++++++++++++++++ client/ui/qml/Pages2/PageStart.qml | 10 +- 12 files changed, 314 insertions(+), 68 deletions(-) rename client/ui/qml/Pages2/{PageCredentials.qml => PageSetupWizardCredentials.qml} (78%) create mode 100644 client/ui/qml/Pages2/PageSetupWizardEasy.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardProtocols.qml diff --git a/client/resources.qrc b/client/resources.qrc index f95ef5850..f14c28ae3 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -185,8 +185,10 @@ ui/qml/Controls2/BodyTextType.qml ui/qml/Controls2/FlickableType.qml ui/qml/Controls2/Header2TextType.qml - ui/qml/Pages2/PageCredentials.qml + ui/qml/Pages2/PageSetupWizardCredentials.qml ui/qml/Controls2/HeaderTextType.qml images/controls/arrow-left.svg + ui/qml/Pages2/PageSetupWizardProtocols.qml + ui/qml/Pages2/PageSetupWizardEasy.qml diff --git a/client/ui/pages.h b/client/ui/pages.h index c33289bb5..4aba79f28 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,9 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test, Credentials}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, + + Test, WizardCredentials, WizardProtocols, WizardEasySetup}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 31b51f311..fa7ee514d 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -9,16 +9,17 @@ RadioButton { property string bodyText property string footerText - property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) - property string defaultColor: Qt.rgba(255, 255, 255, 0) - property string disabledColor: Qt.rgba(255, 255, 255, 0) - property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) + property string selectedColor: Qt.rgba(1, 1, 1, 0) property string textColor: "#0E0E11" - property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) - property string hoveredBorderColor: "transparent" - property string defaultBodredColor: "#FBB26A" + property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -32,30 +33,34 @@ RadioButton { color: { if (root.enabled) { - if(root.checked) { - return pressedColor + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor } - return hovered ? hoveredColor : defaultColor + return defaultColor } else { return disabledColor } } + border.color: { if (root.enabled) { - if(root.checked) { + if (root.pressed) { return pressedBorderColor + } else if (root.checked) { + return selectedBorderColor } - return hovered ? hoveredBorderColor : defaultBodredColor - } else { - return defaultBodredColor } + return defaultBodredColor } + border.width: { if (root.enabled) { if(root.checked) { return 1 } - return hovered ? 0 : 1 + return root.pressed ? 1 : 0 } else { return 0 } @@ -64,6 +69,9 @@ RadioButton { Behavior on color { PropertyAnimation { duration: 200 } } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } ColumnLayout { diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index c547a8e42..a432f7b83 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -6,9 +6,9 @@ import Qt5Compat.GraphicalEffects Item { id: root - property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" - property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderTextType.qml index 60fa4e951..b60ccce48 100644 --- a/client/ui/qml/Controls2/HeaderTextType.qml +++ b/client/ui/qml/Controls2/HeaderTextType.qml @@ -1,40 +1,64 @@ import QtQuick import QtQuick.Layouts -ColumnLayout { +Item { id: root property string buttonImage property string headerText - property var wrapMode + property string descriptionText - ImageButtonType { - id: button + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - Layout.leftMargin: -6 + ColumnLayout { + id: content + anchors.fill: parent - hoverEnabled: false - image: root.buttonImage - onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() + ImageButtonType { + id: backButton + + Layout.leftMargin: -6 + + image: root.buttonImage + imageColor: "#D7D8DB" + onClicked: { + UiLogic.closePage() } } - } - Text { - id: header + Text { + id: header - text: root.headerText + text: root.headerText - color: "#D7D8DB" - font.pixelSize: 36 - font.weight: 700 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 - wrapMode: Text.WordWrap + wrapMode: Text.WordWrap - height: 38 + height: 38 + Layout.fillWidth: true + } + + Text { + id: description + + text: root.descriptionText + + color: "#878B91" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap + + height: 24 + Layout.fillWidth: true + } } } diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index b6262906e..72c783424 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -7,9 +7,9 @@ Button { property string image - property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.08) property string defaultColor: "transparent" - property string pressedColor: Qt.rgba(255, 255, 255, 0.12) + property string pressedColor: Qt.rgba(1, 1, 1, 0.12) property string imageColor: "#878B91" diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 5da4db5f5..60c48569c 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -6,24 +6,48 @@ Item { id: root property string text + property string descriptionText property var onClickedFunc property alias buttonImage : button.image - implicitWidth: 360 - implicitHeight: 72 + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight RowLayout { + id: content anchors.fill: parent - Text { - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 18 - color: "#d7d8db" - text: root.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter + ColumnLayout { + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 18 + color: "#d7d8db" + text: root.text + + Layout.fillWidth: true + height: 22 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + Layout.fillWidth: true + height: 16 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } } ImageButtonType { diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index dd0d29075..350306bb4 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -18,8 +18,12 @@ Item { anchors.fill: parent color: "#1c1d21" radius: 16 - border.color: "#d7d8db" - border.width: textField.focus ? 1 : 0 + border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } ColumnLayout { diff --git a/client/ui/qml/Pages2/PageCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml similarity index 78% rename from client/ui/qml/Pages2/PageCredentials.qml rename to client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 509ee6a67..65c390ab4 100644 --- a/client/ui/qml/Pages2/PageCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.Credentials + page: PageEnum.WizardCredentials FlickableType { id: fl @@ -32,15 +32,11 @@ PageBase { HeaderTextType { Layout.fillWidth: true - Layout.bottomMargin: 16 - Layout.topMargin: 66 - - Layout.preferredWidth: 328 + Layout.topMargin: 20 buttonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" - wrapMode: Text.WordWrap } TextFieldWithHeaderType { @@ -60,23 +56,31 @@ PageBase { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 40 + Layout.topMargin: 24 text: qsTr("Настроить сервер простым образом") + + onClicked: function() { + UiLogic.goToPage(PageEnum.WizardEasySetup) + } } BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 8 + Layout.topMargin: -8 defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" borderWidth: 1 text: qsTr("Выбрать протокол для установки") + + onClicked: function() { + UiLogic.goToPage(PageEnum.WizardProtocols) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml new file mode 100644 index 000000000..809629741 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.WizardEasySetup + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Какой уровень контроля интернета в вашем регионе?" + } + + CardType { + Layout.fillWidth: true + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + } + + CardType { + Layout.fillWidth: true + + checked: true + + headerText: "Средний" + bodyText: "Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются" + } + + CardType { + Layout.fillWidth: true + + headerText: "Низкий" + bodyText: "Хочу просто повысить уровень приватности" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 32 + + text: qsTr("Продолжить") + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml new file mode 100644 index 000000000..b2fb7d575 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -0,0 +1,102 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.WizardProtocols + + SortFilterProxyModel { + id: containersModel + sourceModel: UiLogic.containersModel + filters: [ + ValueFilter { + roleName: "is_installed_role" + value: false + }, + ValueFilter { + roleName: "service_type_role" + value: ProtocolEnum.Vpn + } + ] + } + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + + spacing: 16 + + HeaderTextType { + width: parent.width + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Протокол подключения" + descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + } + + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: containersModel + + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: container + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: name_role + descriptionText: desc_role + buttonImage: "qrc:/images/controls/chevron-right.svg" + + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 6f8f03567..5d303cb81 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -31,7 +31,7 @@ PageBase { source: "qrc:/images/amneziaBigLogo.png" Layout.alignment: Qt.AlignCenter - Layout.topMargin: 80 + Layout.topMargin: 32 Layout.leftMargin: 8 Layout.rightMargin: 8 Layout.preferredWidth: 344 @@ -67,8 +67,8 @@ PageBase { Layout.rightMargin: 16 defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" borderWidth: 1 @@ -123,6 +123,7 @@ PageBase { Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" + wrapMode: Text.WordWrap } LabelWithButtonType { @@ -134,7 +135,8 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.Credentials) + UiLogic.goToPage(PageEnum.WizardCredentials) + drawer.visible = false } } Rectangle { From a9ebf534c691e86536b652359e5bb8ab11f3f0d6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 25 Apr 2023 08:04:20 +0300 Subject: [PATCH 007/131] added DropDown component --- client/images/controls/chevron-down.svg | 3 + client/images/controls/chevron-up.svg | 3 + client/resources.qrc | 2 + client/ui/pages.h | 2 +- client/ui/qml/Controls2/CardType.qml | 1 - client/ui/qml/Controls2/DropDownType.qml | 222 ++++++++++++++++++ client/ui/qml/Controls2/FlickableType.qml | 4 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 35 ++- client/ui/qml/main2.qml | 2 +- 10 files changed, 261 insertions(+), 17 deletions(-) create mode 100644 client/images/controls/chevron-down.svg create mode 100644 client/images/controls/chevron-up.svg diff --git a/client/images/controls/chevron-down.svg b/client/images/controls/chevron-down.svg new file mode 100644 index 000000000..3f4538156 --- /dev/null +++ b/client/images/controls/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/chevron-up.svg b/client/images/controls/chevron-up.svg new file mode 100644 index 000000000..b51628d29 --- /dev/null +++ b/client/images/controls/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index d8cd40c53..3d009107e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -196,5 +196,7 @@ images/controls/arrow-left.svg ui/qml/Pages2/PageSetupWizardProtocols.qml ui/qml/Pages2/PageSetupWizardEasy.qml + images/controls/chevron-down.svg + images/controls/chevron-up.svg diff --git a/client/ui/pages.h b/client/ui/pages.h index 808725d2c..9c5db53c0 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -26,7 +26,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo + AdvancedServerSettings, ClientManagement, ClientInfo, Test, WizardCredentials, WizardProtocols, WizardEasySetup}; Q_ENUM_NS(Page) diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index fa7ee514d..4b94cb1ae 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -111,7 +111,6 @@ RadioButton { Text { text: root.footerText visible: root.footerText !== "" - enabled: root.footerText !== "" color: "#878B91" font.pixelSize: 13 font.weight: 400 diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 5560aee72..ea02ac3c0 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -1,5 +1,227 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Item { + id: root + property string text + property string descriptionText + + property var onClickedFunc + property string buttonImage: "qrc:/images/controls/chevron-down.svg" + + property string defaultColor: "#1C1D21" + + property string borderColor: "#494B50" + + property alias menuModel: menuContent.model + + width: buttonContent.implicitWidth + height: buttonContent.implicitHeight + + Rectangle { + id: buttonBackground + anchors.fill: buttonContent + + radius: 16 + color: defaultColor + border.color: borderColor + + Behavior on border.width { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: buttonContent + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + ColumnLayout { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + Text { + visible: root.descriptionText !== "" + + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + Layout.fillWidth: true + height: 16 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 16 + color: "#d7d8db" + text: root.text + + Layout.fillWidth: true + height: 24 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + + ImageButtonType { + id: button + + Layout.rightMargin: 16 + + hoverEnabled: false + image: buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + + Layout.alignment: Qt.AlignRight + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + buttonBackground.border.width = 1 + } + + onExited: { + buttonBackground.border.width = 0 + } + + onClicked: { + menu.visible = true + } + } + + Drawer { + id: menu + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.8 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + Column { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + spacing: 16 + + Header2TextType { + width: parent.width + + text: "Данные для подключения" + wrapMode: Text.WordWrap + + leftPadding: 16 + rightPadding: 16 + } + + ButtonGroup { + id: radioButtonGroup + } + + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height + + currentIndex: -1 + + clip: true + interactive: false + + delegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: modelData + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + } + } + } + } + } } diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index b7c1203f8..073be058f 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -9,8 +9,8 @@ Flickable { width: parent.width anchors.bottom: parent.bottom - anchors.left: root.left - anchors.right: root.right + anchors.left: parent.left + anchors.right: parent.right anchors.rightMargin: 1 Keys.onUpPressed: scrollBar.decrease() diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 60c48569c..92606940f 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -9,7 +9,7 @@ Item { property string descriptionText property var onClickedFunc - property alias buttonImage : button.image + property alias buttonImage: button.image implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -42,6 +42,8 @@ Item { text: root.descriptionText wrapMode: Text.WordWrap + visible: root.descriptionText !== "" + Layout.fillWidth: true height: 16 diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5d303cb81..f87e0c6e4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -79,18 +79,37 @@ PageBase { // UiLogic.goToPage(PageEnum.Start) // } } + + DropDownType { + Layout.fillWidth: true + + text: "IP, логин и пароль от сервера" + descriptionText: "IP, логин и пароль от сервера" + + menuModel: [ + qsTr("SHA512"), + qsTr("SHA384"), + qsTr("SHA256"), + qsTr("SHA3-512"), + qsTr("SHA3-384"), + qsTr("SHA3-256"), + qsTr("whirlpool"), + qsTr("BLAKE2b512"), + qsTr("BLAKE2s256"), + qsTr("SHA1") + ] + } } Drawer { id: drawer - y: 0 - x: 0 edge: Qt.BottomEdge width: parent.width height: parent.height * 0.4375 clip: true + modal: true background: Rectangle { anchors.fill: parent @@ -99,15 +118,9 @@ PageBase { color: "#1C1D21" } - modal: true - //interactive: activeFocus - -// onAboutToHide: { -// pageLoader.focus = true -// } -// onAboutToShow: { -// tfSshLog.focus = true -// } + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } ColumnLayout { anchors.top: parent.top diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index deb64e896..c7e06eaaf 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -135,7 +135,7 @@ Window { id: pageLoader onFinished: { - UiLogic.initalizeUiLogic() + UiLogic.initializeUiLogic() } } From 904e173037b300274aca344a45952a93e349111f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 26 Apr 2023 08:30:02 +0300 Subject: [PATCH 008/131] added HorizontalRadioButton and VerticalRadioButton components --- client/resources.qrc | 8 +- client/ui/qml/Controls2/CheckBoxType.qml | 8 +- client/ui/qml/Controls2/DropDownType.qml | 2 + .../{HeaderTextType.qml => HeaderType.qml} | 0 .../qml/Controls2/HorizontalRadioButton.qml | 95 +++++++++ .../{ => TextTypes}/BodyTextType.qml | 0 .../{ => TextTypes}/Header2TextType.qml | 0 .../ui/qml/Controls2/VerticalRadioButton.qml | 180 ++++++++++++++++++ .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageStart.qml | 39 ++++ 12 files changed, 330 insertions(+), 8 deletions(-) rename client/ui/qml/Controls2/{HeaderTextType.qml => HeaderType.qml} (100%) create mode 100644 client/ui/qml/Controls2/HorizontalRadioButton.qml rename client/ui/qml/Controls2/{ => TextTypes}/BodyTextType.qml (100%) rename client/ui/qml/Controls2/{ => TextTypes}/Header2TextType.qml (100%) create mode 100644 client/ui/qml/Controls2/VerticalRadioButton.qml diff --git a/client/resources.qrc b/client/resources.qrc index 3d009107e..5fe682a9f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -188,15 +188,17 @@ ui/qml/PageLoader.qml images/amneziaBigLogo.png images/amneziaBigLogo.svg - ui/qml/Controls2/BodyTextType.qml ui/qml/Controls2/FlickableType.qml - ui/qml/Controls2/Header2TextType.qml ui/qml/Pages2/PageSetupWizardCredentials.qml - ui/qml/Controls2/HeaderTextType.qml + ui/qml/Controls2/HeaderType.qml images/controls/arrow-left.svg ui/qml/Pages2/PageSetupWizardProtocols.qml ui/qml/Pages2/PageSetupWizardEasy.qml images/controls/chevron-down.svg images/controls/chevron-up.svg + ui/qml/Controls2/TextTypes/BodyTextType.qml + ui/qml/Controls2/TextTypes/Header2TextType.qml + ui/qml/Controls2/HorizontalRadioButton.qml + ui/qml/Controls2/VerticalRadioButton.qml diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index a432f7b83..92357c523 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -6,6 +6,9 @@ import Qt5Compat.GraphicalEffects Item { id: root + property string text + property string descriptionText + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" property string pressedColor: Qt.rgba(1, 1, 1, 0.05) @@ -71,7 +74,7 @@ Item { ColumnLayout { Text { - text: "Paragraph" + text: root.text color: "#D7D8DB" font.pixelSize: 18 font.weight: 400 @@ -82,7 +85,7 @@ Item { } Text { - text: "Caption" + text: root.descriptionText color: "#878b91" font.pixelSize: 13 font.weight: 400 @@ -94,6 +97,7 @@ Item { } } } + MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index ea02ac3c0..8abb03d25 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderType.qml similarity index 100% rename from client/ui/qml/Controls2/HeaderTextType.qml rename to client/ui/qml/Controls2/HeaderType.qml diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml new file mode 100644 index 000000000..6f00d2105 --- /dev/null +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -0,0 +1,95 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RadioButton { + id: root + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: "#494B50" + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + border.color: { + if (root.enabled) { + if (root.pressed) { + return pressedBorderColor + } else if (root.checked) { + return selectedBorderColor + } + } + return defaultBodredColor + } + + border.width: { + if (root.enabled) { + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 + } else { + return 0 + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 16 + + Text { + text: root.text + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + horizontalAlignment: Qt.AlignHCenter + } + } +} diff --git a/client/ui/qml/Controls2/BodyTextType.qml b/client/ui/qml/Controls2/TextTypes/BodyTextType.qml similarity index 100% rename from client/ui/qml/Controls2/BodyTextType.qml rename to client/ui/qml/Controls2/TextTypes/BodyTextType.qml diff --git a/client/ui/qml/Controls2/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml similarity index 100% rename from client/ui/qml/Controls2/Header2TextType.qml rename to client/ui/qml/Controls2/TextTypes/Header2TextType.qml diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml new file mode 100644 index 000000000..e6c374f79 --- /dev/null +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -0,0 +1,180 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +RadioButton { + id: root + + property string descriptionText + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + property int borderWidth: 0 + + property string defaultCircleBorderColor: "#878B91" + property string selectedCircleBorderColor: "#A85809" + property string pressedCircleBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + + property string defaultInnerCircleColor: "#FBB26A" + + hoverEnabled: true + + indicator: Rectangle { + implicitWidth: 56 + implicitHeight: 56 + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered || contentMouseArea.entered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + width: 24 + height: 24 + radius: 16 + + anchors.centerIn: parent + + color: "transparent" + border.color: { + if (root.enabled) { + if (root.pressed) { + return pressedCircleBorderColor + } else if (root.checked) { + return selectedCircleBorderColor + } + } + return defaultCircleBorderColor + } + + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + width: 12 + height: 12 + radius: 16 + + anchors.centerIn: parent + + color: "transparent" + border.color: defaultInnerCircleColor + border.width: { + if (root.enabled) { + if(root.checked) { + return 6 + } + return root.pressed ? 6 : 0 + } else { + return 0 + } + } + + Behavior on border.width { + PropertyAnimation { duration: 200 } + } + } + + DropShadow { + anchors.fill: innerCircle + horizontalOffset: 0 + verticalOffset: 0 + radius: 12 + samples: 13 + color: "#FBB26A" + source: innerCircle + } + } + } + + contentItem: ColumnLayout { + id: content + anchors.left: indicator.right + anchors.top: parent.top + anchors.leftMargin: 8 + spacing: 16 + + Text { + text: root.text + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + visible: root.descriptionText !== "" + + Layout.fillWidth: true + height: 16 + } + } + + MouseArea { + id: contentMouseArea + + anchors.fill: content + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + +// onEntered: { +// checkBoxBackground.color = hoveredColor +// } + +// onExited: { +// checkBoxBackground.color = defaultColor +// } + +// onPressedChanged: { +// indicator.source = pressed ? imageSource : "" +// imageColor.color = pressed ? hoveredImageColor : defaultImageColor +// checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor +// } + +// onClicked: { +// checkBox.checked = !checkBox.checked +// indicator.source = checkBox.checked ? imageSource : "" +// imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor +// imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor +// } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 65c390ab4..20472de2b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -30,7 +30,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 809629741..b41215920 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -30,7 +30,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index b2fb7d575..a50da64bf 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -49,7 +49,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { width: parent.width buttonImage: "qrc:/images/controls/arrow-left.svg" diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index f87e0c6e4..0cac1b82e 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -8,6 +8,7 @@ import "./" import "../Pages" import "../Controls2" import "../Config" +import "../Controls2/TextTypes" PageBase { id: root @@ -99,6 +100,44 @@ PageBase { qsTr("SHA1") ] } + CheckBoxType { +// text: qsTr("Auto-negotiate encryption") + } + CheckBoxType { + text: qsTr("Auto-negotiate encryption") + descriptionText: "dssaa" + } + + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + color: "#1C1D21" + radius: 16 + RowLayout { + id: buttonGroup + + spacing: 0 + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "ddsasdasd" + } + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "ddsasdasd" + } + } + } + + VerticalRadioButton { + text: "dsasd" + } + VerticalRadioButton { + text: "dsasd" + } } Drawer { From cfc17cf290d89f004874518211d2d0f0b0d6937a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 26 Apr 2023 18:37:56 +0300 Subject: [PATCH 009/131] added mousearea to VerticalRadioButton --- client/ui/qml/Controls2/CheckBoxType.qml | 7 +-- .../ui/qml/Controls2/VerticalRadioButton.qml | 50 +++++++------------ client/ui/qml/Pages2/PageStart.qml | 7 +++ 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 92357c523..98f6c5fe2 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -17,7 +17,7 @@ Item { property string checkedBorderColor: "#FBB26A" property string checkedImageColor: "#FBB26A" - property string hoveredImageColor: "#A85809" + property string pressedImageColor: "#A85809" property string defaultImageColor: "transparent" property string imageSource: "qrc:/images/controls/check.svg" @@ -40,6 +40,7 @@ Item { id: indicator anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter + ColorOverlay { id: imageColor anchors.fill: indicator @@ -112,8 +113,8 @@ Item { } onPressedChanged: { - indicator.source = pressed ? imageSource : "" - imageColor.color = pressed ? hoveredImageColor : defaultImageColor + indicator.source = pressed ? imageSource : checkBox.checked ? imageSource : "" + imageColor.color = pressed ? pressedImageColor : checkBox.checked ? checkedImageColor : defaultImageColor checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index e6c374f79..058cf9ef0 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -26,16 +26,21 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" + implicitWidth: background.implicitWidth + content.implicitWidth + implicitHeight: background.implicitWidth + hoverEnabled: true indicator: Rectangle { + id: background + implicitWidth: 56 implicitHeight: 56 radius: 16 color: { if (root.enabled) { - if (root.hovered || contentMouseArea.entered) { + if (root.hovered) { return hoveredColor } else if (root.checked) { return selectedColor @@ -51,6 +56,8 @@ RadioButton { } Rectangle { + id: outerCircle + width: 24 height: 24 radius: 16 @@ -114,12 +121,14 @@ RadioButton { } } - contentItem: ColumnLayout { + contentItem: Item {} + + ColumnLayout { id: content - anchors.left: indicator.right - anchors.top: parent.top - anchors.leftMargin: 8 - spacing: 16 + anchors.fill: parent + anchors.leftMargin: 8 + background.width + anchors.topMargin: 4 + anchors.bottomMargin: 4 Text { text: root.text @@ -150,31 +159,10 @@ RadioButton { } MouseArea { - id: contentMouseArea - - anchors.fill: content + anchors.fill: root cursorShape: Qt.PointingHandCursor - hoverEnabled: true - -// onEntered: { -// checkBoxBackground.color = hoveredColor -// } - -// onExited: { -// checkBoxBackground.color = defaultColor -// } - -// onPressedChanged: { -// indicator.source = pressed ? imageSource : "" -// imageColor.color = pressed ? hoveredImageColor : defaultImageColor -// checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor -// } - -// onClicked: { -// checkBox.checked = !checkBox.checked -// indicator.source = checkBox.checked ? imageSource : "" -// imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor -// imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor -// } + enabled: false } } + + diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 0cac1b82e..331a6aec1 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -121,6 +121,7 @@ PageBase { id: buttonGroup spacing: 0 + HorizontalRadioButton { implicitWidth: (root.width - 32) / 2 text: "ddsasdasd" @@ -134,9 +135,15 @@ PageBase { VerticalRadioButton { text: "dsasd" + descriptionText: "asd" + checked: true + + Layout.fillWidth: true } VerticalRadioButton { text: "dsasd" + + Layout.fillWidth: true } } From c7acd63ea7a64d21dd8e0a888ef6a6e4d604b318 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 29 Apr 2023 19:09:16 +0300 Subject: [PATCH 010/131] added SwitcherType and TabButtonType - change CheckBoxType root type - added PageTest --- client/resources.qrc | 4 +- client/ui/qml/Controls2/CheckBoxType.qml | 137 ++++----- client/ui/qml/Controls2/HeaderType.qml | 4 + client/ui/qml/Controls2/SwitcherType.qml | 83 +++++ client/ui/qml/Controls2/TabButtonType.qml | 56 ++++ .../ui/qml/Controls2/VerticalRadioButton.qml | 9 +- client/ui/qml/Pages/PageTest.qml | 125 -------- client/ui/qml/Pages2/PageStart.qml | 69 +---- client/ui/qml/Pages2/PageTest.qml | 285 ++++++++++++++++++ 9 files changed, 492 insertions(+), 280 deletions(-) create mode 100644 client/ui/qml/Controls2/SwitcherType.qml create mode 100644 client/ui/qml/Controls2/TabButtonType.qml delete mode 100644 client/ui/qml/Pages/PageTest.qml create mode 100644 client/ui/qml/Pages2/PageTest.qml diff --git a/client/resources.qrc b/client/resources.qrc index 5fe682a9f..1d79af1c9 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -173,7 +173,6 @@ server_scripts/check_user_in_sudo.sh ui/qml/Controls2/BasicButtonType.qml ui/qml/Controls2/TextFieldWithHeaderType.qml - ui/qml/Pages/PageTest.qml fonts/pt-root-ui_vf.ttf ui/qml/Controls2/LabelWithButtonType.qml images/controls/arrow-right.svg @@ -200,5 +199,8 @@ ui/qml/Controls2/TextTypes/Header2TextType.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml + ui/qml/Controls2/SwitcherType.qml + ui/qml/Pages2/PageTest.qml + ui/qml/Controls2/TabButtonType.qml diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 98f6c5fe2..1a22f326b 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -3,10 +3,9 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects -Item { +CheckBox { id: root - property string text property string descriptionText property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) @@ -22,107 +21,87 @@ Item { property string imageSource: "qrc:/images/controls/check.svg" - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight + hoverEnabled: true - RowLayout { - id: content + indicator: Rectangle { + id: checkBoxBackground - anchors.fill: parent + implicitWidth: 56 + implicitHeight: 56 + radius: 16 - CheckBox { - id: checkBox + color: { + if (root.hovered) { + return hoveredColor + } + return defaultColor + } - implicitWidth: 56 - implicitHeight: 56 + Behavior on color { + PropertyAnimation { duration: 200 } + } - indicator: Image { + Rectangle { + id: imageBorder + + anchors.centerIn: parent + width: 24 + height: 24 + color: "transparent" + border.color: root.checked ? checkedBorderColor : defaultBorderColor + border.width: 1 + radius: 4 + + Image { id: indicator - anchors.verticalCenter: checkBox.verticalCenter - anchors.horizontalCenter: checkBox.horizontalCenter + anchors.centerIn: parent + + source: root.pressed ? imageSource : root.checked ? imageSource : "" ColorOverlay { id: imageColor anchors.fill: indicator source: indicator - } - } - Rectangle { - id: imageBorder - - anchors.verticalCenter: checkBox.verticalCenter - anchors.horizontalCenter: checkBox.horizontalCenter - width: 24 - height: 24 - color: "transparent" - border.color: checkBox.checked ? checkedBorderColor : defaultBorderColor - border.width: 1 - radius: 4 - } - - background: Rectangle { - id: checkBoxBackground - radius: 16 - - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } + color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor } } } + } - ColumnLayout { - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + contentItem: ColumnLayout { + anchors.fill: parent + anchors.leftMargin: 8 + checkBoxBackground.width - height: 22 - Layout.fillWidth: true - } + Text { + text: root.text + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" - Text { - text: root.descriptionText - color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 + height: 22 + Layout.fillWidth: true + } - height: 16 - Layout.fillWidth: true - } + Text { + text: root.descriptionText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onEntered: { - checkBoxBackground.color = hoveredColor - } - - onExited: { - checkBoxBackground.color = defaultColor - } - - onPressedChanged: { - indicator.source = pressed ? imageSource : checkBox.checked ? imageSource : "" - imageColor.color = pressed ? pressedImageColor : checkBox.checked ? checkedImageColor : defaultImageColor - checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor - } - - onClicked: { - checkBox.checked = !checkBox.checked - indicator.source = checkBox.checked ? imageSource : "" - imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor - imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor - } + enabled: false } } + + diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index b60ccce48..6e10e75cc 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -22,6 +22,9 @@ Item { image: root.buttonImage imageColor: "#D7D8DB" + + visible: image ? true : false + onClicked: { UiLogic.closePage() } @@ -58,6 +61,7 @@ Item { wrapMode: Text.WordWrap height: 24 + Layout.topMargin: 16 Layout.fillWidth: true } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml new file mode 100644 index 000000000..b593ece84 --- /dev/null +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Switch { + id: root + + property string checkedIndicatorColor: "#412102" + property string defaultIndicatorColor: "transparent" + property string checkedIndicatorBorderColor: "#412102" + property string defaultIndicatorBorderColor: "#494B50" + + property string checkedInnerCircleColor: "#FBB26A" + property string defaultInnerCircleColor: "#D7D8DB" + + property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) + property string defaultIndicatorBackgroundColor: "transparent" + + indicator: Rectangle { + implicitWidth: 52 + implicitHeight: 32 + x: content.width - width + radius: 16 + color: root.checked ? checkedIndicatorColor : defaultIndicatorColor + border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + anchors.verticalCenter: parent.verticalCenter + x: root.checked ? parent.width - width - 4 : 8 + width: root.checked ? 24 : 16 + height: root.checked ? 24 : 16 + radius: 23 + color: root.checked ? checkedInnerCircleColor : defaultInnerCircleColor + + Behavior on x { + PropertyAnimation { duration: 200 } + } + } + + Rectangle { + anchors.centerIn: innerCircle + width: 40 + height: 40 + radius: 23 + color: hovered ? hoveredIndicatorBackgroundColor : defaultIndicatorBackgroundColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: ColumnLayout { + id: content + + Text { + text: root.text + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 22 + Layout.fillWidth: true + Layout.bottomMargin: 16 + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml new file mode 100644 index 000000000..f39edefd2 --- /dev/null +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#412102" + property string defaultColor: "#2C2D30" + property string selectedColor: "#FBB26A" + + property string textColor: "#D7D8DB" + + property bool isSelected: false + + implicitHeight: 48 + + hoverEnabled: true + + background: Rectangle { + id: background + + anchors.fill: parent + color: "transparent" + + Rectangle { + width: parent.width + height: 1 + y: parent.height - height + color: { + if(root.isSelected) { + return selectedColor + } + return hovered ? hoveredColor : defaultColor + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: Text { + anchors.fill: background + height: 24 + + font.family: "PT Root UI" + font.styleName: "normal" + font.weight: 500 + font.pixelSize: 16 + color: textColor + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 058cf9ef0..edcd1c29e 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -26,9 +26,6 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" - implicitWidth: background.implicitWidth + content.implicitWidth - implicitHeight: background.implicitWidth - hoverEnabled: true indicator: Rectangle { @@ -121,14 +118,10 @@ RadioButton { } } - contentItem: Item {} - - ColumnLayout { + contentItem: ColumnLayout { id: content anchors.fill: parent anchors.leftMargin: 8 + background.width - anchors.topMargin: 4 - anchors.bottomMargin: 4 Text { text: root.text diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml deleted file mode 100644 index 4c571ae04..000000000 --- a/client/ui/qml/Pages/PageTest.qml +++ /dev/null @@ -1,125 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Controls2" -import "../Config" - -PageBase { - id: root - page: PageEnum.Test - logic: ViewConfigLogic - - Rectangle { - anchors.fill: parent - color: "#0E0E11" - } - - FlickableType { - id: fl - anchors.top: root.top - anchors.bottom: root.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Forget this server") - -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("Forget this server") - -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - Layout.topMargin: 10 - headerText: "Server IP adress [:port]" - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 10 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - -// onClickedFunc: function() { -// UiLogic.goToPage(PageEnum.Start) -// } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - -// onClickedFunc: function() { -// UiLogic.goToPage(PageEnum.Start) -// } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - - CardType { - Layout.fillWidth: true - Layout.topMargin: 10 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CardType { - Layout.fillWidth: true - Layout.topMargin: 10 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CheckBoxType { -// text: qsTr("Auto-negotiate encryption") - } - } - } -} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 331a6aec1..8d8fbb9d2 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -76,75 +76,10 @@ PageBase { text: qsTr("У меня ничего нет") -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - DropDownType { - Layout.fillWidth: true - - text: "IP, логин и пароль от сервера" - descriptionText: "IP, логин и пароль от сервера" - - menuModel: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - } - CheckBoxType { -// text: qsTr("Auto-negotiate encryption") - } - CheckBoxType { - text: qsTr("Auto-negotiate encryption") - descriptionText: "dssaa" - } - - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight - - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: "#1C1D21" - radius: 16 - RowLayout { - id: buttonGroup - - spacing: 0 - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "ddsasdasd" - } - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "ddsasdasd" - } + onClicked: { + UiLogic.goToPage(PageEnum.Test) } } - - VerticalRadioButton { - text: "dsasd" - descriptionText: "asd" - checked: true - - Layout.fillWidth: true - } - VerticalRadioButton { - text: "dsasd" - - Layout.fillWidth: true - } } Drawer { diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml new file mode 100644 index 000000000..af9ef2863 --- /dev/null +++ b/client/ui/qml/Pages2/PageTest.qml @@ -0,0 +1,285 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageBase { + id: root + page: PageEnum.Test + logic: ViewConfigLogic + + ColumnLayout { + id: content + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + id: header + + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 20 + Layout.bottomMargin: 32 + Layout.fillWidth: true + + buttonImage: "qrc:/images/controls/arrow-left.svg" + headerText: "Server 1" + descriptionText: "root 192.168.111.111" + } + + Item { + Layout.fillWidth: true + + TabBar { + id: tabBar + + anchors { + top: parent.top + right: parent.right + left: parent.left + } + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + id: bb + isSelected: tabBar.currentIndex === 0 + text: qsTr("Протоколы") + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Сервисы") + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Данные") + } + } + + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex + + anchors.top: tabBar.bottom + anchors.topMargin: 16 + + width: parent.width + height: root.height - header.implicitHeight - tabBar.implicitHeight - 100 + + Item { + id: protocolsTab + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: protocolsTabContent.height + + ColumnLayout { + id: protocolsTabContent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + BasicButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + text: qsTr("Forget this server") + } + + BasicButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Forget this server") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + headerText: "Server IP adress [:port]" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 + color: "#2C2D30" + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 + color: "#2C2D30" + } + + CardType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + CardType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + DropDownType { + Layout.fillWidth: true + + text: "IP, логин и пароль от сервера" + descriptionText: "IP, логин и пароль от сервера" + + menuModel: [ + qsTr("SHA512"), + qsTr("SHA384"), + qsTr("SHA256"), + qsTr("SHA3-512"), + qsTr("SHA3-384"), + qsTr("SHA3-256"), + qsTr("whirlpool"), + qsTr("BLAKE2b512"), + qsTr("BLAKE2s256"), + qsTr("SHA1") + ] + } + } + } + } + + Item { + id: servicesTab + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: servicesTabContent.height + + ColumnLayout { + id: servicesTabContent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + CheckBoxType { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + } + CheckBoxType { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + descriptionText: qsTr("Auto-negotiate encryption") + } + + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: buttonGroup + + spacing: 0 + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "UDP" + } + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "TCP" + } + } + } + + VerticalRadioButton { + text: "Раздельное туннелирование" + descriptionText: "Позволяет подключаться к одним сайтам через защищенное соединение, а к другим в обход него" + checked: true + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + + VerticalRadioButton { + text: "Раздельное туннелирование" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + + SwitcherType { + text: "Auto-negotiate encryption" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + } + } + } + } + } + } +} From 68b27451f28dfc1ad634852c1dc53fc15b2b90f5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 1 May 2023 07:29:09 +0300 Subject: [PATCH 011/131] Added scroll bar for DropDownType --- client/ui/qml/Controls2/DropDownType.qml | 143 +++++++++++++---------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 8abb03d25..696aa48a3 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,7 +19,6 @@ Item { property alias menuModel: menuContent.model - width: buttonContent.implicitWidth height: buttonContent.implicitHeight Rectangle { @@ -136,89 +135,105 @@ Item { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } - Column { + Header2TextType { + id: header + width: parent.width + + text: "Данные для подключения" + wrapMode: Text.WordWrap + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } - spacing: 16 + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight - Header2TextType { - width: parent.width + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - text: "Данные для подключения" - wrapMode: Text.WordWrap + spacing: 16 - leftPadding: 16 - rightPadding: 16 - } + ButtonGroup { + id: radioButtonGroup + } - ButtonGroup { - id: radioButtonGroup - } + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height - ListView { - id: menuContent - width: parent.width - height: menuContent.contentItem.height + currentIndex: -1 - currentIndex: -1 + clip: true + interactive: false - clip: true - interactive: false + delegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight - delegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + RadioButton { + id: radioButton - RadioButton { - id: radioButton + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + hoverEnabled: true - hoverEnabled: true + ButtonGroup.group: radioButtonGroup - ButtonGroup.group: radioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" - } - - RowLayout { - id: radioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Text { - id: text - - text: modelData - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" } - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked - width: 24 - height: 24 + RowLayout { + id: radioButtonContent + anchors.fill: parent - Layout.rightMargin: 8 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: modelData + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + root.text = modelData + menu.visible = false } } } From 4f36349630a1ee350c63bb2717c4f75c9973ddf1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 3 May 2023 19:06:16 +0300 Subject: [PATCH 012/131] changed the way to create qml pages, now the page is created when you go to it - added PageSetupWizardConfigSource, PageSetupWizardInstalling, PageSetupWizardProtocolSettings, PageSetupWizardTextKey --- client/amnezia_application.cpp | 6 + client/amnezia_application.h | 2 + client/images/controls/folder-open.svg | 3 + client/images/controls/qr-code.svg | 14 +++ client/images/controls/text-cursor.svg | 5 + client/resources.qrc | 7 ++ client/ui/models/containers_model.cpp | 10 ++ client/ui/models/containers_model.h | 4 + client/ui/pages.h | 4 +- .../ui/pages_logic/ServerContainersLogic.cpp | 4 +- client/ui/qml/Controls2/BasicButtonType.qml | 1 - .../ui/qml/Controls2/LabelWithButtonType.qml | 10 ++ .../qml/Controls2/TextFieldWithHeaderType.qml | 103 ++++++++++------ client/ui/qml/PageLoader.qml | 58 +-------- .../Pages2/PageSetupWizardConfigSource.qml | 114 ++++++++++++++++++ .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 46 +++++++ .../PageSetupWizardProtocolSettings.qml | 96 +++++++++++++++ .../qml/Pages2/PageSetupWizardProtocols.qml | 12 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 74 ++++++++++++ client/ui/qml/Pages2/PageStart.qml | 13 +- client/ui/qml/main2.qml | 56 ++------- client/ui/uilogic.cpp | 8 +- client/ui/uilogic.h | 3 +- 25 files changed, 503 insertions(+), 158 deletions(-) create mode 100644 client/images/controls/folder-open.svg create mode 100644 client/images/controls/qr-code.svg create mode 100644 client/images/controls/text-cursor.svg create mode 100644 client/ui/qml/Pages2/PageSetupWizardConfigSource.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardInstalling.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardTextKey.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 231b94e4a..b4255a0c0 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -99,6 +99,10 @@ void AmneziaApplication::init() }, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); + + m_containersModel.reset(new ContainersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) @@ -180,6 +184,8 @@ void AmneziaApplication::loadFonts() QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); + + QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } void AmneziaApplication::loadTranslator() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 1ac6e772e..abdb1f2a3 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -56,6 +56,8 @@ private: QTranslator* m_translator; QCommandLineParser m_parser; + QScopedPointer m_containersModel; + }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/folder-open.svg b/client/images/controls/folder-open.svg new file mode 100644 index 000000000..f126e5c5a --- /dev/null +++ b/client/images/controls/folder-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/qr-code.svg b/client/images/controls/qr-code.svg new file mode 100644 index 000000000..a8f760fe1 --- /dev/null +++ b/client/images/controls/qr-code.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/images/controls/text-cursor.svg b/client/images/controls/text-cursor.svg new file mode 100644 index 000000000..17876d776 --- /dev/null +++ b/client/images/controls/text-cursor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 1d79af1c9..20eed74db 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -202,5 +202,12 @@ ui/qml/Controls2/SwitcherType.qml ui/qml/Pages2/PageTest.qml ui/qml/Controls2/TabButtonType.qml + ui/qml/Pages2/PageSetupWizardProtocolSettings.qml + ui/qml/Pages2/PageSetupWizardInstalling.qml + ui/qml/Pages2/PageSetupWizardConfigSource.qml + images/controls/folder-open.svg + images/controls/qr-code.svg + images/controls/text-cursor.svg + ui/qml/Pages2/PageSetupWizardTextKey.qml diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 5468452ee..f56613c74 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -56,4 +56,14 @@ void ContainersModel::setSelectedServerIndex(int index) endResetModel(); } +void ContainersModel::setCurrentlyInstalledContainerIndex(int index) +{ +// beginResetModel(); + m_currentlyInstalledContainerIndex = createIndex(index, 0); +// endResetModel(); +} +QString ContainersModel::getCurrentlyInstalledContainerName() +{ + return data(m_currentlyInstalledContainerIndex, NameRole).toString(); +} diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 362068557..9c409fc00 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -27,12 +27,16 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE void setSelectedServerIndex(int index); + Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); + + Q_INVOKABLE QString getCurrentlyInstalledContainerName(); protected: QHash roleNames() const override; private: int m_selectedServerIndex; + QModelIndex m_currentlyInstalledContainerIndex; std::shared_ptr m_settings; }; diff --git a/client/ui/pages.h b/client/ui/pages.h index 9c5db53c0..32ac775dc 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -28,7 +28,9 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, ClientManagement, ClientInfo, - Test, WizardCredentials, WizardProtocols, WizardEasySetup}; + PageStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, + PageSetupWizardTextKey}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index 80bf362c9..2b556dcc4 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -22,8 +22,8 @@ ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): void ServerContainersLogic::onUpdatePage() { - ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); +// ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); +// c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index cbb664e4a..da5d2abfe 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -14,7 +14,6 @@ Button { property string borderColor: "#D7D8DB" property int borderWidth: 0 - implicitWidth: 328 implicitHeight: 56 hoverEnabled: true diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 92606940f..b6d113bb2 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -9,7 +9,9 @@ Item { property string descriptionText property var onClickedFunc + property alias buttonImage: button.image + property string iconImage implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -18,6 +20,13 @@ Item { id: content anchors.fill: parent + Image { + id: icon + source: iconImage + visible: iconImage ? true : false + Layout.rightMargin: visible ? 16 : 0 + } + ColumnLayout { Text { font.family: "PT Root UI" @@ -25,6 +34,7 @@ Item { font.pixelSize: 18 color: "#d7d8db" text: root.text + wrapMode: Text.WordWrap Layout.fillWidth: true height: 22 diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 350306bb4..3458f7411 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -10,7 +10,11 @@ Item { property string textFieldPlaceholderText property bool textFieldEditable: true - implicitWidth: 328 + property string buttonText + property var clickedFunc + + property alias textField: textField + implicitHeight: 74 Rectangle { @@ -26,50 +30,77 @@ Item { } } - ColumnLayout { + RowLayout { anchors.fill: backgroud + ColumnLayout { - Text { - text: root.headerText - color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 - height: 16 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + TextField { + id: textField + + enabled: root.textFieldEditable + text: root.textFieldText + color: "#d7d8db" + + placeholderText: textFieldPlaceholderText + placeholderTextColor: "#494B50" + + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + } } - TextField { - id: textField + BasicButtonType { + visible: root.buttonText !== "" - enabled: root.textFieldEditable - text: root.textFieldText - color: "#d7d8db" + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 0 - placeholderText: textFieldPlaceholderText + text: buttonText - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + Layout.rightMargin: 24 - height: 24 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - topPadding: 0 - rightPadding: 0 - leftPadding: 0 - bottomPadding: 0 - - background: Rectangle { - anchors.fill: parent - color: "#1c1d21" + onClicked: { + if (clickedFunc && typeof clickedFunc === "function") { + clickedFunc() + } } } } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index 5f2fc3c2c..a44fa4b52 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -1,57 +1,7 @@ import QtQuick +import QtQuick.Controls -import Qt.labs.folderlistmodel - -import PageType 1.0 - -Item { - property var pages: ({}) - - signal finished() - - FolderListModel { - id: folderModelPages - folder: "qrc:/ui/qml/Pages2/" - nameFilters: ["*.qml"] - showDirs: false - - onStatusChanged: { - if (status == FolderListModel.Ready) { - for (var i = 0; i < folderModelPages.count; i++) { - createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic); - } - finished() - } - } - - function createPagesObjects(file, type) { - if (file.indexOf("Base") !== -1) { - return; // skip Base Pages - } - - var c = Qt.createComponent("qrc" + file); - - var finishCreation = function(component) { - if (component.status === Component.Ready) { - var obj = component.createObject(root); - if (obj === null) { - console.debug("Error creating object " + component.url); - } else { - obj.visible = false - if (type === PageType.Basic) { - pages[obj.page] = obj - } - } - } else if (component.status === Component.Error) { - console.debug("Error loading component:", component.errorString()); - } - } - - if (c.status === Component.Ready) { - finishCreation(c); - } else { - console.debug("Warning: " + file + " page components are not ready " + c.errorString()); - } - } - } +StackView { + id: stackView + initialItem: "PageStart" } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml new file mode 100644 index 000000000..26f25a15d --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Подключение к серверу" + descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n +Всё в порядке, если код передал друг." + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "Что у вас есть?" + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: "Файл с настройками подключения" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/folder-open.svg" + + onClickedFunc: function() { + onClicked: fileDialog.open() + } + + FileDialog { + id: fileDialog +// currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + + } + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + + //todo ifdef mobile platforms> + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/qr-code.svg" + + onClickedFunc: function() { + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Ключ в виде текста" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/text-cursor.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardTextKey) + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 20472de2b..a6e98fee6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardCredentials + page: PageEnum.PageSetupWizardCredentials FlickableType { id: fl @@ -61,7 +61,7 @@ PageBase { text: qsTr("Настроить сервер простым образом") onClicked: function() { - UiLogic.goToPage(PageEnum.WizardEasySetup) + UiLogic.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -79,7 +79,7 @@ PageBase { text: qsTr("Выбрать протокол для установки") onClicked: function() { - UiLogic.goToPage(PageEnum.WizardProtocols) + UiLogic.goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index b41215920..8c75a1dfa 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardEasySetup + page: PageEnum.PageSetupWizardEasy FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml new file mode 100644 index 000000000..3445b5334 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + //TODO remove later + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Установка" + descriptionText: ContainersModel.getCurrentlyInstalledContainerName() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml new file mode 100644 index 000000000..9b6e788fe --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -0,0 +1,96 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardProtocolSettings + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() + descriptionText: "Эти настройки можно будет изменить позже" + } + + BodyTextType { + Layout.topMargin: 16 + + text: "Network protocol" + } + + //TODO move to separete control + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: buttonGroup + + spacing: 0 + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "UDP" + } + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "TCP" + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Port" + } + } + } + + BasicButtonType { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 32 + + text: qsTr("Установить") + + onClicked: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index a50da64bf..ad4efbc21 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -14,11 +14,11 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardProtocols + page: PageEnum.PageSetupWizardProtocols SortFilterProxyModel { - id: containersModel - sourceModel: UiLogic.containersModel + id: proxyContainersModel + sourceModel: ContainersModel filters: [ ValueFilter { roleName: "is_installed_role" @@ -64,7 +64,7 @@ PageBase { currentIndex: -1 clip: true interactive: false - model: containersModel + model: proxyContainersModel delegate: Item { implicitWidth: containers.width @@ -87,6 +87,10 @@ PageBase { descriptionText: desc_role buttonImage: "qrc:/images/controls/chevron-right.svg" + onClickedFunc: function() { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + UiLogic.goToPage(PageEnum.PageSetupWizardProtocolSettings) + } } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml new file mode 100644 index 000000000..9fddc573d --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Ключ для подключения" + descriptionText: "Строка, которая начинается с vpn://..." + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: "Ключ" + textFieldPlaceholderText: "vpn://" + buttonText: "Вставить" + + clickedFunc: function() { + textField.text = "" + textField.paste() + } + } + } + } + + BasicButtonType { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 32 + + text: qsTr("Подключиться") + + onClicked: function() { +// UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8d8fbb9d2..e04ebbb6c 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -12,7 +12,7 @@ import "../Controls2/TextTypes" PageBase { id: root - page: PageEnum.Start + page: PageEnum.PageStart FlickableType { id: fl @@ -77,7 +77,7 @@ PageBase { text: qsTr("У меня ничего нет") onClicked: { - UiLogic.goToPage(PageEnum.Test) + UiLogic.goToPage(PageEnum.PageTest) } } } @@ -129,7 +129,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.WizardCredentials) + UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) drawer.visible = false } } @@ -144,9 +144,10 @@ PageBase { text: "QR-код, ключ или файл настроек" buttonImage: "qrc:/images/controls/chevron-right.svg" - // onClickedFunc: function() { - // UiLogic.goToPage(PageEnum.Start) - // } + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + drawer.visible = false + } } Rectangle { Layout.fillWidth: true diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index c7e06eaaf..6261e9a70 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -14,6 +14,7 @@ Window { height: GC.screenHeight minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + onClosing: function() { console.debug("QML onClosing signal") UiLogic.onCloseWindow() @@ -21,28 +22,18 @@ Window { title: "AmneziaVPN" - function gotoPage(type, page, reset, slide) { - let p_obj; - if (type === PageType.Basic) p_obj = pageLoader.pages[page] - else if (type === PageType.Proto) p_obj = protocolPages[page] - else if (type === PageType.ShareProto) p_obj = sharePages[page] - else return - + function gotoPage(page, reset, slide) { if (pageStackView.depth > 0) { pageStackView.currentItem.deactivated() } if (slide) { - pageStackView.push(p_obj, {}, StackView.PushTransition) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) } else { - pageStackView.push(p_obj, {}, StackView.Immediate) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) } -// if (reset) { -// p_obj.logic.onUpdatePage(); -// } - - p_obj.activated(reset) + pageStackView.currentItem.activated(reset) } function closePage() { @@ -60,9 +51,9 @@ Window { pageStackView.clear() if (slide) { - pageStackView.push(pages[page], {}, StackView.PushTransition) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) } else { - pageStackView.push(pages[page], {}, StackView.Immediate) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) } if (page === PageEnum.Start) { UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty @@ -79,33 +70,12 @@ Window { id: pageStackView anchors.fill: parent focus: true - - onCurrentItemChanged: function() { - UiLogic.currentPageValue = currentItem.page - } - - onDepthChanged: function() { - UiLogic.pagesStackDepth = depth - } - - Keys.onPressed: function(event) { - UiLogic.keyPressEvent(event.key) - event.accepted = true - } } Connections { target: UiLogic function onGoToPage(page, reset, slide) { - root.gotoPage(PageType.Basic, page, reset, slide) - } - - function onGoToProtocolPage(protocol, reset, slide) { - root.gotoPage(PageType.Proto, protocol, reset, slide) - } - - function onGoToShareProtocolPage(protocol, reset, slide) { - root.gotoPage(PageType.ShareProto, protocol, reset, slide) + root.gotoPage(page, reset, slide) } function onClosePage() { @@ -118,6 +88,7 @@ Window { function onShow() { root.show() + UiLogic.initializeUiLogic() } function onHide() { @@ -130,13 +101,4 @@ Window { root.requestActivate() } } - - PageLoader { - id: pageLoader - - onFinished: { - UiLogic.initializeUiLogic() - } - } - } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index d47c11864..63371420f 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -84,7 +84,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptrdefaultServerIndex(); @@ -619,3 +618,8 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) return false; } +QString UiLogic::pageEnumToString(Page page) { + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString pageName = metaEnum.valueToKey(static_cast(page)); + return "Pages2/" + pageName + ".qml"; +} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 3b7797a88..4b3920666 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -67,7 +67,6 @@ class UiLogic : public QObject AUTO_PROPERTY(int, pagesStackDepth) AUTO_PROPERTY(int, currentPageValue) - READONLY_PROPERTY(QObject *, containersModel) READONLY_PROPERTY(QObject *, protocolsModel) READONLY_PROPERTY(QObject *, clientManagementModel) @@ -124,6 +123,8 @@ public: Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); + Q_INVOKABLE QString pageEnumToString(PageEnumNS::Page page); + void shareTempFile(const QString &suggestedName, QString ext, const QString& data); static QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), From 1c8dbae35913d6eb4b0f55203665f8db63246027 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 6 May 2023 06:52:23 +0300 Subject: [PATCH 013/131] added PageHome, PageSettings, PageShare, PageStart - renamed old PageStart to PageSetupWizardStart - added various text types - moved servers model to "global" scope --- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 3 + client/images/controls/home.svg | 4 + client/images/controls/settings-2.svg | 6 + client/images/controls/share-2.svg | 7 + client/resources.qrc | 16 +- client/ui/models/servers_model.cpp | 35 ++- client/ui/models/servers_model.h | 11 +- client/ui/pages.h | 6 +- client/ui/pages_logic/ServerListLogic.cpp | 4 +- client/ui/qml/Controls2/DropDownType.qml | 61 ++-- client/ui/qml/Controls2/Header2Type.qml | 56 ++++ client/ui/qml/Controls2/HeaderType.qml | 32 +-- .../ui/qml/Controls2/TabImageButtonType.qml | 24 ++ .../Controls2/TextTypes/ButtonTextType.qml | 12 + .../Controls2/TextTypes/Header1TextType.qml | 14 + .../Controls2/TextTypes/Header2TextType.qml | 4 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 13 + ...BodyTextType.qml => ParagraphTextType.qml} | 6 +- client/ui/qml/PageLoader.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 266 ++++++++++++++++++ client/ui/qml/Pages2/PageSettings.qml | 5 + .../Pages2/PageSetupWizardConfigSource.qml | 3 +- .../PageSetupWizardProtocolSettings.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 163 +++++++++++ client/ui/qml/Pages2/PageShare.qml | 5 + client/ui/qml/Pages2/PageStart.qml | 194 ++++--------- client/ui/uilogic.cpp | 4 +- 28 files changed, 735 insertions(+), 226 deletions(-) create mode 100644 client/images/controls/home.svg create mode 100644 client/images/controls/settings-2.svg create mode 100644 client/images/controls/share-2.svg create mode 100644 client/ui/qml/Controls2/Header2Type.qml create mode 100644 client/ui/qml/Controls2/TabImageButtonType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/ButtonTextType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/Header1TextType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/LabelTextType.qml rename client/ui/qml/Controls2/TextTypes/{BodyTextType.qml => ParagraphTextType.qml} (87%) create mode 100644 client/ui/qml/Pages2/PageHome.qml create mode 100644 client/ui/qml/Pages2/PageSettings.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardStart.qml create mode 100644 client/ui/qml/Pages2/PageShare.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index b4255a0c0..2cd616795 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -103,6 +103,9 @@ void AmneziaApplication::init() m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index abdb1f2a3..d15864e1e 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -12,6 +12,8 @@ #include "ui/uilogic.h" #include "configurators/vpn_configurator.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -57,6 +59,7 @@ private: QCommandLineParser m_parser; QScopedPointer m_containersModel; + QScopedPointer m_serversModel; }; diff --git a/client/images/controls/home.svg b/client/images/controls/home.svg new file mode 100644 index 000000000..5ade1d33e --- /dev/null +++ b/client/images/controls/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/settings-2.svg b/client/images/controls/settings-2.svg new file mode 100644 index 000000000..9f1cf9741 --- /dev/null +++ b/client/images/controls/settings-2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/share-2.svg b/client/images/controls/share-2.svg new file mode 100644 index 000000000..b0aa63317 --- /dev/null +++ b/client/images/controls/share-2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 20eed74db..f5817eb0e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -182,7 +182,7 @@ ui/qml/Controls2/CheckBoxType.qml images/controls/check.svg ui/qml/Controls2/DropDownType.qml - ui/qml/Pages2/PageStart.qml + ui/qml/Pages2/PageSetupWizardStart.qml ui/qml/main2.qml ui/qml/PageLoader.qml images/amneziaBigLogo.png @@ -195,7 +195,7 @@ ui/qml/Pages2/PageSetupWizardEasy.qml images/controls/chevron-down.svg images/controls/chevron-up.svg - ui/qml/Controls2/TextTypes/BodyTextType.qml + ui/qml/Controls2/TextTypes/ParagraphTextType.qml ui/qml/Controls2/TextTypes/Header2TextType.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml @@ -209,5 +209,17 @@ images/controls/qr-code.svg images/controls/text-cursor.svg ui/qml/Pages2/PageSetupWizardTextKey.qml + ui/qml/Pages2/PageStart.qml + ui/qml/Controls2/TabImageButtonType.qml + images/controls/home.svg + images/controls/settings-2.svg + images/controls/share-2.svg + ui/qml/Pages2/PageHome.qml + ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageShare.qml + ui/qml/Controls2/TextTypes/Header1TextType.qml + ui/qml/Controls2/TextTypes/LabelTextType.qml + ui/qml/Controls2/TextTypes/ButtonTextType.qml + ui/qml/Controls2/Header2Type.qml diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index c4b0efb32..94d3ced43 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,29 +1,42 @@ #include "servers_model.h" -ServersModel::ServersModel(QObject *parent) : - QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + const QJsonArray &servers = m_settings->serversArray(); + int defaultServer = m_settings->defaultServerIndex(); + QVector serverListContent; + for(int i = 0; i < servers.size(); i++) { + ServerModelContent c; + auto server = servers.at(i).toObject(); + c.desc = server.value(config_key::description).toString(); + c.address = server.value(config_key::hostName).toString(); + if (c.desc.isEmpty()) { + c.desc = c.address; + } + c.isDefault = (i == defaultServer); + serverListContent.push_back(c); + } + setContent(serverListContent); } void ServersModel::clearData() { beginResetModel(); - content.clear(); + m_content.clear(); endResetModel(); } -void ServersModel::setContent(const std::vector &data) +void ServersModel::setContent(const QVector &data) { beginResetModel(); - content = data; + m_content = data; endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(content.size()); + return static_cast(m_content.size()); } QHash ServersModel::roleNames() const { @@ -37,17 +50,17 @@ QHash ServersModel::roleNames() const { QVariant ServersModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(content.size())) { + || index.row() >= static_cast(m_content.size())) { return QVariant(); } if (role == DescRole) { - return content[index.row()].desc; + return m_content[index.row()].desc; } if (role == AddressRole) { - return content[index.row()].address; + return m_content[index.row()].address; } if (role == IsDefaultRole) { - return content[index.row()].isDefault; + return m_content[index.row()].isDefault; } return QVariant(); } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 7f2e3ad1f..e60aea6b8 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -2,8 +2,8 @@ #define SERVERSMODEL_H #include -#include -#include + +#include "settings.h" struct ServerModelContent { QString desc; @@ -15,7 +15,7 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - ServersModel(QObject *parent = nullptr); + ServersModel(std::shared_ptr settings, QObject *parent = nullptr); public: enum SiteRoles { DescRole = Qt::UserRole + 1, @@ -24,7 +24,7 @@ public: }; void clearData(); - void setContent(const std::vector& data); + void setContent(const QVector& data); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -33,7 +33,8 @@ protected: QHash roleNames() const override; private: - std::vector content; + QVector m_content; + std::shared_ptr m_settings; }; #endif // SERVERSMODEL_H diff --git a/client/ui/pages.h b/client/ui/pages.h index 32ac775dc..b2e191c7e 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -28,9 +28,11 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, ClientManagement, ClientInfo, - PageStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey}; + PageSetupWizardTextKey, + + PageStart, PageHome, PageSettings, PageShare}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 79d13c8b4..4a17e2022 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -6,7 +6,7 @@ ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent), - m_serverListModel{new ServersModel(this)} + m_serverListModel{new ServersModel(m_settings, this)} { } @@ -33,7 +33,7 @@ void ServerListLogic::onUpdatePage() { const QJsonArray &servers = m_settings->serversArray(); int defaultServer = m_settings->defaultServerIndex(); - std::vector serverListContent; + QVector serverListContent; for(int i = 0; i < servers.size(); i++) { ServerModelContent c; auto server = servers.at(i).toObject(); diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 696aa48a3..6f5111948 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -12,14 +12,20 @@ Item { property var onClickedFunc property string buttonImage: "qrc:/images/controls/chevron-down.svg" + property string buttonImageColor: "#494B50" + property string defaultColor: "#1C1D21" + property string textColor: "#d7d8db" + property string borderColor: "#494B50" + property int borderWidth: 1 property alias menuModel: menuContent.model - height: buttonContent.implicitHeight + implicitWidth: buttonContent.implicitWidth + implicitHeight: buttonContent.implicitHeight Rectangle { id: buttonBackground @@ -28,6 +34,7 @@ Item { radius: 16 color: defaultColor border.color: borderColor + border.width: borderWidth Behavior on border.width { PropertyAnimation { duration: 200 } @@ -37,72 +44,55 @@ Item { RowLayout { id: buttonContent anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 + + spacing: 0 ColumnLayout { Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Text { + LabelTextType { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: root.descriptionText !== "" - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap - - Layout.fillWidth: true - height: 16 - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter } - Text { - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 16 - color: "#d7d8db" - text: root.text - - Layout.fillWidth: true - height: 24 - - horizontalAlignment: Text.AlignLeft + ButtonTextType { + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + + color: root.textColor + text: root.text } } ImageButtonType { id: button + Layout.leftMargin: 4 Layout.rightMargin: 16 hoverEnabled: false image: buttonImage + imageColor: buttonImageColor onClicked: { if (onClickedFunc && typeof onClickedFunc === "function") { onClickedFunc() } } - - Layout.alignment: Qt.AlignRight } } MouseArea { - anchors.fill: parent + anchors.fill: buttonContent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - buttonBackground.border.width = 1 + buttonBackground.border.width = borderWidth } onExited: { @@ -119,7 +109,7 @@ Item { edge: Qt.BottomEdge width: parent.width - height: parent.height * 0.8 + height: parent.height * 0.9 clip: true modal: true @@ -129,6 +119,9 @@ Item { anchors.bottomMargin: -radius radius: 16 color: "#1C1D21" + + border.color: borderColor + border.width: 1 } Overlay.modal: Rectangle { diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml new file mode 100644 index 000000000..e0173a73b --- /dev/null +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string buttonImage + property string headerText + property string descriptionText + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + ImageButtonType { + id: backButton + + Layout.leftMargin: -6 + + image: root.buttonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + UiLogic.closePage() + } + } + + Header2TextType { + id: header + + Layout.fillWidth: true + + text: root.headerText + } + + ParagraphTextType { + id: description + + Layout.topMargin: 16 + Layout.fillWidth: true + + text: root.descriptionText + + color: "#878B91" + + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 6e10e75cc..407f67f0f 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -30,39 +32,23 @@ Item { } } - Text { + Header1TextType { id: header - text: root.headerText - - color: "#D7D8DB" - font.pixelSize: 36 - font.weight: 700 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 - - wrapMode: Text.WordWrap - - height: 38 Layout.fillWidth: true + + text: root.headerText } - Text { + ParagraphTextType { id: description + Layout.topMargin: 16 + Layout.fillWidth: true + text: root.descriptionText color: "#878B91" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 - - wrapMode: Text.WordWrap - - height: 24 - Layout.topMargin: 16 - Layout.fillWidth: true } } } diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml new file mode 100644 index 000000000..06b77e6ae --- /dev/null +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#412102" + property string defaultColor: "#D7D8DB" + property string selectedColor: "#FBB26A" + + property string image + + property bool isSelected: false + + hoverEnabled: true + + icon.source: image + icon.color: isSelected ? selectedColor : defaultColor + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml new file mode 100644 index 000000000..93a36578a --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 24 + + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 500 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml new file mode 100644 index 000000000..dbc04b6a7 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -0,0 +1,14 @@ +import QtQuick + +Text { + height: 38 + + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap +} + diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index 4bbbc0d67..1400ceb2b 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -1,10 +1,12 @@ import QtQuick Text { + height: 30 + color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 font.family: "PT Root UI VF" - height: 30 + wrapMode: Text.WordWrap } diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml new file mode 100644 index 000000000..386490229 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + height: 16 + + color: "#878B91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/BodyTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml similarity index 87% rename from client/ui/qml/Controls2/TextTypes/BodyTextType.qml rename to client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 9d789385b..269830bc5 100644 --- a/client/ui/qml/Controls2/TextTypes/BodyTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -1,12 +1,12 @@ import QtQuick Text { - text: root.bodyText - wrapMode: Text.WordWrap + height: 24 + color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" - height: 24 + wrapMode: Text.WordWrap } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index a44fa4b52..86f9e5a18 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -3,5 +3,5 @@ import QtQuick.Controls StackView { id: stackView - initialItem: "PageStart" + initialItem: "PageSetupWizardStart" } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml new file mode 100644 index 000000000..ad1b7d38c --- /dev/null +++ b/client/ui/qml/Pages2/PageHome.qml @@ -0,0 +1,266 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageHome + + property string defaultColor: "#1C1D21" + + property string borderColor: "#2C2D30" + + property string currentServerName: menuContent.currentItem.delegateData.desc + property string currentServerDescription: menuContent.currentItem.delegateData.address + + Rectangle { + id: buttonBackground + anchors.fill: buttonContent + anchors.bottomMargin: -radius + + radius: 16 + color: defaultColor + border.color: borderColor + border.width: 1 + + Rectangle { + width: parent.width + height: 1 + y: parent.height - height - parent.radius + + color: borderColor + } + } + + ColumnLayout { + id: buttonContent + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + RowLayout { + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Header1TextType { + text: currentServerName + } + + Image { + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + + source: "qrc:/images/controls/chevron-down.svg" + } + } + + LabelTextType { + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerDescription + } + } + + MouseArea { + anchors.fill: buttonBackground + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onClicked: { + menu.visible = true + } + } + + Drawer { + id: menu + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.90 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + + color: "#1C1D21" + border.color: borderColor + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + id: menuHeader + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + Header1TextType { + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerName + } + + LabelTextType { + Layout.bottomMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerDescription + } + + RowLayout { + + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 8 + + DropDownType { + implicitHeight: 40 + + borderWidth: 0 + buttonImageColor: "#0E0E11" + + defaultColor: "#D7D8DB" + + textColor: "#0E0E11" + text: "testtesttest" + } + + BasicButtonType { + implicitHeight: 40 + + text: "Amnezia DNS" + } + } + + Header2Type { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Серверы" + } + } + + +// Header2TextType { +// id: menuHeader +// width: parent.width + +// text: "Данные для подключения" +// wrapMode: Text.WordWrap + +// anchors.top: parent.top +// anchors.left: parent.left +// anchors.right: parent.right +// anchors.topMargin: 16 +// anchors.leftMargin: 16 +// anchors.rightMargin: 16 +// } + + FlickableType { + anchors.top: menuHeader.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + ButtonGroup { + id: radioButtonGroup + } + + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height + + model: ServersModel + currentIndex: 0 + + clip: true + interactive: false + + delegate: Item { + id: menuContentDelegate + + property variant delegateData: model + + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: desc + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml new file mode 100644 index 000000000..5560aee72 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 26f25a15d..a6ba52bb8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -64,7 +64,6 @@ PageBase { FileDialog { id: fileDialog -// currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] onAccepted: { } @@ -76,7 +75,7 @@ PageBase { color: "#2C2D30" } - //todo ifdef mobile platforms> + //todo ifdef mobile platforms LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 9b6e788fe..17c22b04c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -41,7 +41,7 @@ PageBase { descriptionText: "Эти настройки можно будет изменить позже" } - BodyTextType { + ParagraphTextType { Layout.topMargin: 16 text: "Network protocol" diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml new file mode 100644 index 000000000..0167ed1fe --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -0,0 +1,163 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageBase { + id: root + page: PageEnum.PageSetupWizardStart + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 32 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 50 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("У меня есть данные для подключения") + + onClicked: { + drawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("У меня ничего нет") + + onClicked: { + UiLogic.goToPage(PageEnum.PageTest) + } + } + } + + Drawer { + id: drawer + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: borderColor + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + wrapMode: Text.WordWrap + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) + drawer.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + drawer.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml new file mode 100644 index 000000000..5560aee72 --- /dev/null +++ b/client/ui/qml/Pages2/PageShare.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e04ebbb6c..9e94ed28b 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -7,154 +7,74 @@ import PageEnum 1.0 import "./" import "../Pages" import "../Controls2" -import "../Config" import "../Controls2/TextTypes" +import "../Config" PageBase { id: root page: PageEnum.PageStart - FlickableType { - id: fl - anchors.top: root.top - anchors.bottom: root.bottom - contentHeight: content.height + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex - ColumnLayout { - id: content + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: tabBar.top - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Image { - id: image - source: "qrc:/images/amneziaBigLogo.png" - - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 32 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - Layout.preferredWidth: 344 - Layout.preferredHeight: 279 - } - - BodyTextType { - Layout.fillWidth: true - Layout.topMargin: 50 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("У меня есть данные для подключения") - - onClicked: { - drawer.visible = true - } - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("У меня ничего нет") - - onClicked: { - UiLogic.goToPage(PageEnum.PageTest) - } - } + width: { + console.log(parent.width) + return parent.width + } + height: { + console.log(root.height - tabBar.implicitHeight) + return root.height - tabBar.implicitHeight } - Drawer { - id: drawer + PageHome { + } - edge: Qt.BottomEdge - width: parent.width - height: parent.height * 0.4375 - - clip: true - modal: true - - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter - - text: "Данные для подключения" - wrapMode: Text.WordWrap - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 32 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - } + PageSetupWizardEasy { } } + + TabBar { + id: tabBar + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + topPadding: 8 + bottomPadding: 34 + leftPadding: 96 + rightPadding: 96 + + background: Rectangle { + color: "#1C1D21" + } + + + TabImageButtonType { + isSelected: tabBar.currentIndex === 0 + image: "qrc:/images/controls/home.svg" + } + TabImageButtonType { + isSelected: tabBar.currentIndex === 1 + image: "qrc:/images/controls/share-2.svg" + } + TabImageButtonType { + isSelected: tabBar.currentIndex === 2 + image: "qrc:/images/controls/settings-2.svg" + } + } + + MouseArea { + anchors.fill: tabBar + anchors.leftMargin: 96 + anchors.rightMargin: 96 + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 63371420f..bb2f90b22 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -151,10 +151,10 @@ void UiLogic::initializeUiLogic() if (m_settings->serversCount() > 0) { if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - emit goToPage(Page::Vpn, true, false); + emit goToPage(Page::PageStart, true, false); } else { - emit goToPage(Page::PageStart, true, false); + emit goToPage(Page::PageSetupWizardStart, true, false); } m_selectedServerIndex = m_settings->defaultServerIndex(); From b66f4bf2bee3a31fe1cbb7036a33893127a90686 Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Thu, 11 May 2023 14:50:50 +0800 Subject: [PATCH 014/131] added display of protocols on PageHome --- client/amnezia_application.cpp | 1 - client/images/controls/plus.svg | 4 + client/resources.qrc | 1 + client/ui/models/servers_model.cpp | 50 ++++--- client/ui/models/servers_model.h | 5 +- client/ui/pages_logic/ServerListLogic.cpp | 2 +- client/ui/qml/Controls2/BasicButtonType.qml | 2 +- client/ui/qml/Controls2/DropDownType.qml | 80 +++-------- client/ui/qml/Controls2/Header2Type.qml | 38 +++++- client/ui/qml/Controls2/HeaderType.qml | 42 +++++- .../ui/qml/Controls2/LabelWithButtonType.qml | 4 +- client/ui/qml/Controls2/TabButtonType.qml | 2 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 126 +++++++++++++++--- .../Pages2/PageSetupWizardConfigSource.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 2 +- client/ui/qml/Pages2/PageTest.qml | 2 +- 22 files changed, 237 insertions(+), 138 deletions(-) create mode 100644 client/images/controls/plus.svg diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2cd616795..dea868e44 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -187,7 +187,6 @@ void AmneziaApplication::loadFonts() QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } diff --git a/client/images/controls/plus.svg b/client/images/controls/plus.svg new file mode 100644 index 000000000..96d0b71d5 --- /dev/null +++ b/client/images/controls/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index f5817eb0e..307c992de 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -221,5 +221,6 @@ ui/qml/Controls2/TextTypes/LabelTextType.qml ui/qml/Controls2/TextTypes/ButtonTextType.qml ui/qml/Controls2/Header2Type.qml + images/controls/plus.svg diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 94d3ced43..89287b7f1 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,41 +2,34 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { + refresh(); +} + +void ServersModel::refresh() +{ + beginResetModel(); const QJsonArray &servers = m_settings->serversArray(); int defaultServer = m_settings->defaultServerIndex(); QVector serverListContent; for(int i = 0; i < servers.size(); i++) { - ServerModelContent c; + ServerModelContent content; auto server = servers.at(i).toObject(); - c.desc = server.value(config_key::description).toString(); - c.address = server.value(config_key::hostName).toString(); - if (c.desc.isEmpty()) { - c.desc = c.address; + content.desc = server.value(config_key::description).toString(); + content.address = server.value(config_key::hostName).toString(); + if (content.desc.isEmpty()) { + content.desc = content.address; } - c.isDefault = (i == defaultServer); - serverListContent.push_back(c); + content.isDefault = (i == defaultServer); + serverListContent.push_back(content); } - setContent(serverListContent); -} - -void ServersModel::clearData() -{ - beginResetModel(); - m_content.clear(); - endResetModel(); -} - -void ServersModel::setContent(const QVector &data) -{ - beginResetModel(); - m_content = data; + m_data = serverListContent; endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_content.size()); + return static_cast(m_data.size()); } QHash ServersModel::roleNames() const { @@ -50,19 +43,24 @@ QHash ServersModel::roleNames() const { QVariant ServersModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_content.size())) { + || index.row() >= static_cast(m_data.size())) { return QVariant(); } if (role == DescRole) { - return m_content[index.row()].desc; + return m_data[index.row()].desc; } if (role == AddressRole) { - return m_content[index.row()].address; + return m_data[index.row()].address; } if (role == IsDefaultRole) { - return m_content[index.row()].isDefault; + return m_data[index.row()].isDefault; } return QVariant(); } +void ServersModel::setDefaultServerIndex(int index) +{ + +} + diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index e60aea6b8..c8c32b56d 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -23,8 +23,7 @@ public: IsDefaultRole }; - void clearData(); - void setContent(const QVector& data); + void refresh(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -33,7 +32,7 @@ protected: QHash roleNames() const override; private: - QVector m_content; + QVector m_data; std::shared_ptr m_settings; }; diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 4a17e2022..56a682b85 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -45,5 +45,5 @@ void ServerListLogic::onUpdatePage() c.isDefault = (i == defaultServer); serverListContent.push_back(c); } - qobject_cast(m_serverListModel)->setContent(serverListContent); +// qobject_cast(m_serverListModel)->setContent(serverListContent); } diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index da5d2abfe..266beefe1 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -48,7 +48,7 @@ Button { contentItem: Text { anchors.fill: background - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.weight: 400 font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 6f5111948..ea6ca5fca 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -10,6 +10,9 @@ Item { property string text property string descriptionText + property string headerText + property string headerBackButtonImage + property var onClickedFunc property string buttonImage: "qrc:/images/controls/chevron-down.svg" property string buttonImageColor: "#494B50" @@ -22,7 +25,10 @@ Item { property string borderColor: "#494B50" property int borderWidth: 1 - property alias menuModel: menuContent.model + property Component menuDelegate + property variant menuModel + + property alias menuVisible: menu.visible implicitWidth: buttonContent.implicitWidth implicitHeight: buttonContent.implicitHeight @@ -128,12 +134,13 @@ Item { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } - Header2TextType { + Header2Type { id: header - width: parent.width - text: "Данные для подключения" - wrapMode: Text.WordWrap + headerText: root.headerText + backButtonImage: root.headerBackButtonImage + + width: parent.width anchors.top: parent.top anchors.left: parent.left @@ -170,64 +177,13 @@ Item { clip: true interactive: false - delegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + model: root.menuModel - RadioButton { - id: radioButton - - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight - - hoverEnabled: true - - ButtonGroup.group: radioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" - } - - RowLayout { - id: radioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Text { - id: text - - text: modelData - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - } - - onClicked: { - root.text = modelData - menu.visible = false - } + delegate: Row { + Loader { + id: loader + sourceComponent: root.menuDelegate + property QtObject modelData: model } } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index e0173a73b..5c2f0c9ba 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -6,7 +6,12 @@ import "TextTypes" Item { id: root - property string buttonImage + property string backButtonImage + property string actionButtonImage + + property var backButtonFunction + property var actionButtonFunction + property string headerText property string descriptionText @@ -22,22 +27,41 @@ Item { Layout.leftMargin: -6 - image: root.buttonImage + image: root.backButtonImage imageColor: "#D7D8DB" visible: image ? true : false onClicked: { - UiLogic.closePage() + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } } } - Header2TextType { - id: header + RowLayout { + Header2TextType { + id: header - Layout.fillWidth: true + Layout.fillWidth: true - text: root.headerText + text: root.headerText + } + + ImageButtonType { + id: headerActionButton + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonImage && typeof actionButtonImage === "function") { + actionButtonImage() + } + } + } } ParagraphTextType { diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 407f67f0f..a051d2d87 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -6,7 +6,12 @@ import "TextTypes" Item { id: root - property string buttonImage + property string backButtonImage + property string actionButtonImage + + property var backButtonFunction + property var actionButtonFunction + property string headerText property string descriptionText @@ -22,22 +27,43 @@ Item { Layout.leftMargin: -6 - image: root.buttonImage + image: root.backButtonImage imageColor: "#D7D8DB" visible: image ? true : false onClicked: { - UiLogic.closePage() + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } } } - Header1TextType { - id: header + RowLayout { + Header1TextType { + id: header - Layout.fillWidth: true + Layout.fillWidth: true - text: root.headerText + text: root.headerText + } + + ImageButtonType { + id: headerActionButton + + Layout.alignment: Qt.AlignRight + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonImage && typeof actionButtonImage === "function") { + actionButtonImage() + } + } + } } ParagraphTextType { @@ -49,6 +75,8 @@ Item { text: root.descriptionText color: "#878B91" + + visible: root.descriptionText !== "" } } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index b6d113bb2..d8e195f1d 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -29,7 +29,7 @@ Item { ColumnLayout { Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 18 color: "#d7d8db" @@ -44,7 +44,7 @@ Item { } Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 13 font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index f39edefd2..4699dfcd2 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -43,7 +43,7 @@ TabButton { anchors.fill: background height: 24 - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.weight: 500 font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index edcd1c29e..420051cd5 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -136,7 +136,7 @@ RadioButton { } Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 13 font.letterSpacing: 0.02 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ad1b7d38c..3f81f1681 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -2,7 +2,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ProtocolEnum 1.0 import "./" import "../Pages" @@ -125,11 +128,27 @@ PageBase { } RowLayout { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "service_type_role" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "is_installed_role" + value: true + } + ] + } + DropDownType { + id: protocolsDropDown + implicitHeight: 40 borderWidth: 0 @@ -138,7 +157,83 @@ PageBase { defaultColor: "#D7D8DB" textColor: "#0E0E11" - text: "testtesttest" + headerText: "Протокол подключения" + headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + + menuModel: proxyContainersModel + + menuDelegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + // todo remove dirty hack? + text: { + if (modelData !== null) { + return modelData.name_role + } else + return "" + } + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + protocolsDropDown.text = text.text + protocolsDropDown.menuVisible = false + } + } + + Component.onCompleted: { + if (modelData !== null && modelData.default_role) { + protocolsDropDown.text = modelData.name_role + } + } + } } BasicButtonType { @@ -149,29 +244,17 @@ PageBase { } Header2Type { + Layout.fillWidth: true + Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 + actionButtonImage: "qrc:/images/controls/plus.svg" + headerText: "Серверы" } } - -// Header2TextType { -// id: menuHeader -// width: parent.width - -// text: "Данные для подключения" -// wrapMode: Text.WordWrap - -// anchors.top: parent.top -// anchors.left: parent.left -// anchors.right: parent.right -// anchors.topMargin: 16 -// anchors.leftMargin: 16 -// anchors.rightMargin: 16 -// } - FlickableType { anchors.top: menuHeader.bottom anchors.topMargin: 16 @@ -257,6 +340,13 @@ PageBase { Layout.rightMargin: 8 } } + + onClicked: { + console.log(index) + ContainersModel.setSelectedServerIndex(index) + root.currentServerName = desc + root.currentServerDescription = address + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index a6ba52bb8..5445d1f34 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -36,7 +36,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index a6e98fee6..506c1cde3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -34,7 +34,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 8c75a1dfa..94e22fe3e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -34,7 +34,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Какой уровень контроля интернета в вашем регионе?" } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 3445b5334..a74182578 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -36,7 +36,7 @@ PageBase { Layout.topMargin: 20 //TODO remove later - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка" descriptionText: ContainersModel.getCurrentlyInstalledContainerName() diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 17c22b04c..78856444a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -35,7 +35,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() descriptionText: "Эти настройки можно будет изменить позже" diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index ad4efbc21..501076155 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -51,7 +51,7 @@ PageBase { HeaderType { width: parent.width - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Протокол подключения" descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 9fddc573d..d295f1b97 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -35,7 +35,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Ключ для подключения" descriptionText: "Строка, которая начинается с vpn://..." diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index af9ef2863..2336e46e8 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -29,7 +29,7 @@ PageBase { Layout.bottomMargin: 32 Layout.fillWidth: true - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Server 1" descriptionText: "root 192.168.111.111" } From e3e7503a7c00afeaa4d4b8d8f6d9494bf30440db Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 12 May 2023 11:36:09 +0800 Subject: [PATCH 015/131] added saving the selected server and protocol to the config --- client/ui/models/containers_model.cpp | 37 ++++--- client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 118 ++++++++++++++--------- client/ui/models/servers_model.h | 13 ++- client/ui/qml/Controls2/DropDownType.qml | 6 ++ client/ui/qml/Pages2/PageHome.qml | 82 +++++++++------- 6 files changed, 160 insertions(+), 97 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index f56613c74..c7aa0ec1f 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -1,10 +1,8 @@ #include "containers_model.h" -ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) +ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + setSelectedServerIndex(m_settings->defaultServerIndex()); } int ContainersModel::rowCount(const QModelIndex &parent) const @@ -13,14 +11,19 @@ int ContainersModel::rowCount(const QModelIndex &parent) const return ContainerProps::allContainers().size(); } -QHash ContainersModel::roleNames() const { - QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[DefaultRole] = "default_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; - return roles; +bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + if (role == DefaultRole) { + DockerContainer container = ContainerProps::allContainers().at(index.row()); + m_settings->setDefaultContainer(m_selectedServerIndex, container); + } + + emit dataChanged(index, index); + return true; } QVariant ContainersModel::data(const QModelIndex &index, int role) const @@ -67,3 +70,13 @@ QString ContainersModel::getCurrentlyInstalledContainerName() { return data(m_currentlyInstalledContainerIndex, NameRole).toString(); } + +QHash ContainersModel::roleNames() const { + QHash roles; + roles[NameRole] = "name_role"; + roles[DescRole] = "desc_role"; + roles[DefaultRole] = "default_role"; + roles[ServiceTypeRole] = "service_type_role"; + roles[IsInstalledRole] = "is_installed_role"; + return roles; +} diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 9c409fc00..ba0fea57e 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -25,6 +25,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE void setSelectedServerIndex(int index); Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 89287b7f1..44ed26197 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,34 +2,83 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - refresh(); -} -void ServersModel::refresh() -{ - beginResetModel(); - const QJsonArray &servers = m_settings->serversArray(); - int defaultServer = m_settings->defaultServerIndex(); - QVector serverListContent; - for(int i = 0; i < servers.size(); i++) { - ServerModelContent content; - auto server = servers.at(i).toObject(); - content.desc = server.value(config_key::description).toString(); - content.address = server.value(config_key::hostName).toString(); - if (content.desc.isEmpty()) { - content.desc = content.address; - } - content.isDefault = (i == defaultServer); - serverListContent.push_back(content); - } - m_data = serverListContent; - endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_data.size()); + return static_cast(m_settings->serversCount()); +} + +bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 + || index.row() >= static_cast(m_settings->serversCount())) { + return false; + } +// if (role == DescRole) { +// return m_data[index.row()].desc; +// } +// if (role == AddressRole) { +// return m_data[index.row()].address; +// } +// if (role == IsDefaultRole) { +// return m_data[index.row()].isDefault; +// } +} + +QVariant ServersModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_settings->serversCount())) { + return QVariant(); + } + + const QJsonArray &servers = m_settings->serversArray(); + const QJsonObject server = servers.at(index.row()).toObject(); + + if (role == DescRole) { + auto description = server.value(config_key::description).toString(); + if (description.isEmpty()) { + return server.value(config_key::hostName).toString(); + } + return description; + } + if (role == AddressRole) { + return server.value(config_key::hostName).toString(); + } + if (role == IsDefaultRole) { + return index.row() == m_settings->defaultServerIndex(); + } + return QVariant(); + + +// if (!index.isValid() || index.row() < 0 +// || index.row() >= static_cast(m_data.size())) { +// return QVariant(); +// } +// if (role == DescRole) { +// return m_data[index.row()].desc; +// } +// if (role == AddressRole) { +// return m_data[index.row()].address; +// } +// if (role == IsDefaultRole) { +// return m_data[index.row()].isDefault; +// } +// return QVariant(); +} + +void ServersModel::setDefaultServerIndex(int index) +{ +// beginResetModel(); + m_settings->setDefaultServer(index); + // endResetModel(); +} + +int ServersModel::getDefaultServerIndex() +{ + return m_settings->defaultServerIndex(); } QHash ServersModel::roleNames() const { @@ -39,28 +88,3 @@ QHash ServersModel::roleNames() const { roles[IsDefaultRole] = "is_default"; return roles; } - -QVariant ServersModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_data.size())) { - return QVariant(); - } - if (role == DescRole) { - return m_data[index.row()].desc; - } - if (role == AddressRole) { - return m_data[index.row()].address; - } - if (role == IsDefaultRole) { - return m_data[index.row()].isDefault; - } - return QVariant(); -} - -void ServersModel::setDefaultServerIndex(int index) -{ - -} - - diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index c8c32b56d..cd46846b9 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -15,24 +15,27 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - ServersModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { + enum ServersModelRoles { DescRole = Qt::UserRole + 1, AddressRole, IsDefaultRole }; - void refresh(); + ServersModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +public slots: + void setDefaultServerIndex(int index); + int getDefaultServerIndex(); + protected: QHash roleNames() const override; private: - QVector m_data; std::shared_ptr m_settings; }; diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index ea6ca5fca..64013ba4b 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -17,6 +17,7 @@ Item { property string buttonImage: "qrc:/images/controls/chevron-down.svg" property string buttonImageColor: "#494B50" + property int buttonMaximumWidth property string defaultColor: "#1C1D21" @@ -70,8 +71,13 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + Layout.maximumWidth: buttonMaximumWidth ? buttonMaximumWidth : implicitWidth + color: root.textColor text: root.text + + wrapMode: Text.NoWrap + elide: Text.ElideRight } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 3f81f1681..f4f4d13c3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -21,8 +21,8 @@ PageBase { property string borderColor: "#2C2D30" - property string currentServerName: menuContent.currentItem.delegateData.desc - property string currentServerDescription: menuContent.currentItem.delegateData.address + property string currentServerName: serversMenuContent.currentItem.delegateData.desc + property string currentServerDescription: serversMenuContent.currentItem.delegateData.address Rectangle { id: buttonBackground @@ -108,7 +108,7 @@ PageBase { } ColumnLayout { - id: menuHeader + id: serversMenuHeader anchors.top: parent.top anchors.right: parent.right anchors.left: parent.left @@ -147,12 +147,13 @@ PageBase { } DropDownType { - id: protocolsDropDown + id: containersDropDown implicitHeight: 40 borderWidth: 0 buttonImageColor: "#0E0E11" + buttonMaximumWidth: 150 defaultColor: "#D7D8DB" @@ -162,27 +163,38 @@ PageBase { menuModel: proxyContainersModel + ButtonGroup { + id: containersRadioButtonGroup + } + menuDelegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + implicitWidth: root.width + implicitHeight: containerRadioButton.implicitHeight RadioButton { - id: radioButton + id: containerRadioButton implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + implicitHeight: containerRadioButtonContent.implicitHeight hoverEnabled: true - ButtonGroup.group: radioButtonGroup + ButtonGroup.group: containersRadioButtonGroup + + checked: { + if (modelData !== null) { + return modelData.default_role + } + return false + } indicator: Rectangle { anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" } RowLayout { - id: radioButtonContent + id: containerRadioButtonContent anchors.fill: parent anchors.rightMargin: 16 @@ -191,7 +203,7 @@ PageBase { z: 1 Text { - id: text + id: containerRadioButtonText // todo remove dirty hack? text: { @@ -214,7 +226,7 @@ PageBase { Image { source: "qrc:/images/controls/check.svg" - visible: radioButton.checked + visible: containerRadioButton.checked width: 24 height: 24 @@ -223,14 +235,16 @@ PageBase { } onClicked: { - protocolsDropDown.text = text.text - protocolsDropDown.menuVisible = false + modelData.default_role = true + + containersDropDown.text = containerRadioButtonText.text + containersDropDown.menuVisible = false } } Component.onCompleted: { if (modelData !== null && modelData.default_role) { - protocolsDropDown.text = modelData.name_role + containersDropDown.text = modelData.name_role } } } @@ -256,7 +270,7 @@ PageBase { } FlickableType { - anchors.top: menuHeader.bottom + anchors.top: serversMenuHeader.bottom anchors.topMargin: 16 contentHeight: col.implicitHeight @@ -269,45 +283,46 @@ PageBase { spacing: 16 ButtonGroup { - id: radioButtonGroup + id: serversRadioButtonGroup } ListView { - id: menuContent + id: serversMenuContent width: parent.width - height: menuContent.contentItem.height + height: serversMenuContent.contentItem.height model: ServersModel - currentIndex: 0 + currentIndex: ServersModel.getDefaultServerIndex() clip: true - interactive: false delegate: Item { id: menuContentDelegate property variant delegateData: model - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + implicitWidth: serversMenuContent.width + implicitHeight: serverRadioButton.implicitHeight RadioButton { - id: radioButton + id: serverRadioButton implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + implicitHeight: serverRadioButtonContent.implicitHeight hoverEnabled: true - ButtonGroup.group: radioButtonGroup + checked: index === serversMenuContent.currentIndex + + ButtonGroup.group: serversRadioButtonGroup indicator: Rectangle { anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" } RowLayout { - id: radioButtonContent + id: serverRadioButtonContent anchors.fill: parent anchors.rightMargin: 16 @@ -316,7 +331,7 @@ PageBase { z: 1 Text { - id: text + id: serverRadioButtonText text: desc color: "#D7D8DB" @@ -333,7 +348,7 @@ PageBase { Image { source: "qrc:/images/controls/check.svg" - visible: radioButton.checked + visible: serverRadioButton.checked width: 24 height: 24 @@ -342,10 +357,11 @@ PageBase { } onClicked: { - console.log(index) - ContainersModel.setSelectedServerIndex(index) root.currentServerName = desc root.currentServerDescription = address + + ServersModel.setDefaultServerIndex(index) + ContainersModel.setSelectedServerIndex(index) } } } From 35d4222c7a87f6374fe324514ef0018cc96fe92a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 12 May 2023 23:54:31 +0800 Subject: [PATCH 016/131] added connectionController and ConnectionButton.qml --- client/CMakeLists.txt | 5 ++ client/amnezia_application.cpp | 12 +++- client/amnezia_application.h | 10 ++- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 71 +++++++++++++++++++ client/ui/controllers/connectionController.h | 33 +++++++++ client/ui/models/containers_model.cpp | 48 +++++++------ client/ui/models/containers_model.h | 10 ++- client/ui/models/servers_model.cpp | 27 ++----- client/ui/models/servers_model.h | 1 + client/ui/qml/Components/ConnectButton.qml | 40 +++++++++++ .../Controls2/TextTypes/ButtonTextType.qml | 2 +- .../Controls2/TextTypes/Header1TextType.qml | 2 +- .../Controls2/TextTypes/Header2TextType.qml | 2 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 2 +- .../Controls2/TextTypes/ParagraphTextType.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 25 ++++--- 17 files changed, 233 insertions(+), 60 deletions(-) create mode 100644 client/ui/controllers/connectionController.cpp create mode 100644 client/ui/controllers/connectionController.h create mode 100644 client/ui/qml/Components/ConnectButton.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9b110666d..d86431d5a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -128,17 +128,22 @@ file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configur file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) +file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) +file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) + set(HEADERS ${HEADERS} ${COMMON_FILES_H} ${PAGE_LOGIC_H} ${CONFIGURATORS_H} ${UI_MODELS_H} + ${UI_CONTROLLERS_H} ) set(SOURCES ${SOURCES} ${COMMON_FILES_CPP} ${PAGE_LOGIC_CPP} ${CONFIGURATORS_CPP} ${UI_MODELS_CPP} + ${UI_CONTROLLERS_CPP} ) qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index dea868e44..6fadcabb5 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -5,7 +5,6 @@ #include #include - #include "core/servercontroller.h" #include "logger.h" #include "defines.h" @@ -100,12 +99,23 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); + // m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + connect(m_connectionController.get(), &ConnectionController::connectToVpn, + m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); + connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, + m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + // m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index d15864e1e..322a440ca 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -9,11 +9,13 @@ #include #include "settings.h" +#include "vpnconnection.h" #include "ui/uilogic.h" #include "configurators/vpn_configurator.h" #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" +#include "ui/controllers/connectionController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -58,8 +60,12 @@ private: QTranslator* m_translator; QCommandLineParser m_parser; - QScopedPointer m_containersModel; - QScopedPointer m_serversModel; + QSharedPointer m_containersModel; + QSharedPointer m_serversModel; + + QScopedPointer m_vpnConnection; + + QScopedPointer m_connectionController; }; diff --git a/client/resources.qrc b/client/resources.qrc index 307c992de..1ea7bf38c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -222,5 +222,6 @@ ui/qml/Controls2/TextTypes/ButtonTextType.qml ui/qml/Controls2/Header2Type.qml images/controls/plus.svg + ui/qml/Components/ConnectButton.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp new file mode 100644 index 000000000..41dbed64f --- /dev/null +++ b/client/ui/controllers/connectionController.cpp @@ -0,0 +1,71 @@ +#include "connectionController.h" + +#include + +ConnectionController::ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel) +{ + +} + +bool ConnectionController::onConnectionButtonClicked() +{ + if (!isConnected) { + openVpnConnection(); + } else { + closeVpnConnection(); + } +} + +bool ConnectionController::openVpnConnection() +{ + int serverIndex = m_serversModel->getDefaultServerIndex(); + QModelIndex serverModelIndex = m_serversModel->index(serverIndex); + ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverModelIndex, + ServersModel::ServersModelRoles::CredentialsRole)); + + DockerContainer container = m_containersModel->getDefaultContainer(); + QModelIndex containerModelIndex = m_containersModel->index(container); + const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, + ContainersModel::ContainersModelRoles::ConfigRole)); + + //todo error handling + qApp->processEvents(); + emit connectToVpn(serverIndex, credentials, container, containerConfig); + isConnected = true; + + +// int serverIndex = m_settings->defaultServerIndex(); +// ServerCredentials credentials = m_settings->serverCredentials(serverIndex); +// DockerContainer container = m_settings->defaultContainer(serverIndex); + +// if (m_settings->containers(serverIndex).isEmpty()) { +// set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); +// set_pushButtonConnectChecked(false); +// return; +// } + +// if (container == DockerContainer::None) { +// set_labelErrorText(tr("VPN Protocol not chosen")); +// set_pushButtonConnectChecked(false); +// return; +// } + + +// const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); + +// set_labelErrorText(""); +// set_pushButtonConnectChecked(true); +// set_pushButtonConnectEnabled(false); + +// qApp->processEvents(); + +// emit connectToVpn(serverIndex, credentials, container, containerConfig); +} + +bool ConnectionController::closeVpnConnection() +{ + emit disconnectFromVpn(); +} + diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h new file mode 100644 index 000000000..125e92048 --- /dev/null +++ b/client/ui/controllers/connectionController.h @@ -0,0 +1,33 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class ConnectionController : public QObject +{ + Q_OBJECT + +public: + explicit ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + QObject *parent = nullptr); + +public slots: + bool onConnectionButtonClicked(); + +signals: + void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + void disconnectFromVpn(); + +private: + bool openVpnConnection(); + bool closeVpnConnection(); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + + bool isConnected = false; +}; + +#endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index c7aa0ec1f..db7572e65 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -17,7 +17,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i return false; } - if (role == DefaultRole) { + if (role == IsDefaultRole) { DockerContainer container = ContainerProps::allContainers().at(index.row()); m_settings->setDefaultContainer(m_selectedServerIndex, container); } @@ -33,22 +33,23 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } - DockerContainer c = ContainerProps::allContainers().at(index.row()); - if (role == NameRole) { - return ContainerProps::containerHumanNames().value(c); - } - if (role == DescRole) { - return ContainerProps::containerDescriptions().value(c); - } - if (role == DefaultRole) { - return c == m_settings->defaultContainer(m_selectedServerIndex); - } - if (role == ServiceTypeRole) { - return ContainerProps::containerService(c); - } - if (role == IsInstalledRole) { - return m_settings->containers(m_selectedServerIndex).contains(c); + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: + return ContainerProps::containerHumanNames().value(container); + case DescRole: + return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + return m_settings->containerConfig(m_selectedServerIndex, container); + case ServiceTypeRole: + return ContainerProps::containerService(container); + case IsInstalledRole: + return m_settings->containers(m_selectedServerIndex).contains(container); + case IsDefaultRole: + return container == m_settings->defaultContainer(m_selectedServerIndex); } + return QVariant(); } @@ -71,12 +72,17 @@ QString ContainersModel::getCurrentlyInstalledContainerName() return data(m_currentlyInstalledContainerIndex, NameRole).toString(); } +DockerContainer ContainersModel::getDefaultContainer() +{ + return m_settings->defaultContainer(m_selectedServerIndex); +} + QHash ContainersModel::roleNames() const { QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[DefaultRole] = "default_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; + roles[NameRole] = "name"; + roles[DescRole] = "description"; + roles[ServiceTypeRole] = "serviceType"; + roles[IsInstalledRole] = "isInstalled"; + roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index ba0fea57e..c56511db0 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -15,12 +15,13 @@ class ContainersModel : public QAbstractListModel public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); public: - enum SiteRoles { + enum ContainersModelRoles { NameRole = Qt::UserRole + 1, DescRole, - DefaultRole, ServiceTypeRole, - IsInstalledRole + ConfigRole, + IsInstalledRole, + IsDefaultRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -32,6 +33,9 @@ public: Q_INVOKABLE QString getCurrentlyInstalledContainerName(); +public slots: + DockerContainer getDefaultContainer(); + protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 44ed26197..dccf59b9e 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -37,36 +37,23 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const QJsonArray &servers = m_settings->serversArray(); const QJsonObject server = servers.at(index.row()).toObject(); - if (role == DescRole) { + switch (role) { + case DescRole: { auto description = server.value(config_key::description).toString(); if (description.isEmpty()) { return server.value(config_key::hostName).toString(); } return description; } - if (role == AddressRole) { + case AddressRole: return server.value(config_key::hostName).toString(); - } - if (role == IsDefaultRole) { + case CredentialsRole: + return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case IsDefaultRole: return index.row() == m_settings->defaultServerIndex(); } + return QVariant(); - - -// if (!index.isValid() || index.row() < 0 -// || index.row() >= static_cast(m_data.size())) { -// return QVariant(); -// } -// if (role == DescRole) { -// return m_data[index.row()].desc; -// } -// if (role == AddressRole) { -// return m_data[index.row()].address; -// } -// if (role == IsDefaultRole) { -// return m_data[index.row()].isDefault; -// } -// return QVariant(); } void ServersModel::setDefaultServerIndex(int index) diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index cd46846b9..30c8b5a2c 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -18,6 +18,7 @@ public: enum ServersModelRoles { DescRole = Qt::UserRole + 1, AddressRole, + CredentialsRole, IsDefaultRole }; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml new file mode 100644 index 000000000..5457cf55a --- /dev/null +++ b/client/ui/qml/Components/ConnectButton.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: root + + implicitHeight: 190 + implicitWidth: 190 + + background: Rectangle { + id: background + + radius: parent.width * 0.5 + + color: "transparent" + + border.width: 2 + border.color: "white" + } + + contentItem: Text { + height: 24 + + font.family: "PT Root UI VF" + font.weight: 700 + font.pixelSize: 20 + + color: "#D7D8DB" + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + background.color = "red" + ConnectionController.onConnectionButtonClicked() + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index 93a36578a..d26594d68 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 500 + font.weight: Font.Medium font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml index dbc04b6a7..99addc7bd 100644 --- a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 36 - font.weight: 700 + font.weight: Font.Bold font.family: "PT Root UI VF" font.letterSpacing: -0.03 diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index 1400ceb2b..ed96f6f1c 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 25 - font.weight: 700 + font.weight: Font.Bold font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml index 386490229..a2ffa18b8 100644 --- a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -5,7 +5,7 @@ Text { color: "#878B91" font.pixelSize: 13 - font.weight: 400 + font.weight: Font.Normal font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 269830bc5..74b155abf 100644 --- a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 400 + font.weight: Font.Normal font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index f4f4d13c3..40eba1489 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -12,6 +12,7 @@ import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageBase { id: root @@ -24,6 +25,12 @@ PageBase { property string currentServerName: serversMenuContent.currentItem.delegateData.desc property string currentServerDescription: serversMenuContent.currentItem.delegateData.address + ConnectButton { + anchors.centerIn: parent + + text: "Подключиться" + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -136,11 +143,11 @@ PageBase { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "service_type_role" + roleName: "serviceType" value: ProtocolEnum.Vpn }, ValueFilter { - roleName: "is_installed_role" + roleName: "isInstalled" value: true } ] @@ -153,7 +160,7 @@ PageBase { borderWidth: 0 buttonImageColor: "#0E0E11" - buttonMaximumWidth: 150 + buttonMaximumWidth: 150 //todo make it dynamic defaultColor: "#D7D8DB" @@ -183,7 +190,7 @@ PageBase { checked: { if (modelData !== null) { - return modelData.default_role + return modelData.isDefault } return false } @@ -208,7 +215,7 @@ PageBase { // todo remove dirty hack? text: { if (modelData !== null) { - return modelData.name_role + return modelData.name } else return "" } @@ -235,7 +242,7 @@ PageBase { } onClicked: { - modelData.default_role = true + modelData.isDefault = true containersDropDown.text = containerRadioButtonText.text containersDropDown.menuVisible = false @@ -243,14 +250,16 @@ PageBase { } Component.onCompleted: { - if (modelData !== null && modelData.default_role) { - containersDropDown.text = modelData.name_role + if (modelData !== null && modelData.isDefault) { + containersDropDown.text = modelData.name } } } } BasicButtonType { + id: dnsButton + implicitHeight: 40 text: "Amnezia DNS" From 116fa6777b400261f71aa9126e956febe09d37f2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 14 May 2023 21:11:19 +0800 Subject: [PATCH 017/131] added logic to the connect to vpn button --- client/3rd/qtkeychain | 2 +- client/amnezia_application.cpp | 8 +- client/images/connectionOff.svg | 18 ++++ client/images/connectionOn.svg | 17 ++++ client/images/connectionProgress.svg | 30 +++++++ client/protocols/openvpnovercloakprotocol.cpp | 4 +- client/protocols/openvpnprotocol.cpp | 20 ++--- client/protocols/shadowsocksvpnprotocol.cpp | 4 +- client/protocols/vpnprotocol.cpp | 34 +++---- client/protocols/vpnprotocol.h | 40 +++++++-- client/protocols/wireguardprotocol.cpp | 14 +-- client/resources.qrc | 3 + .../ui/controllers/connectionController.cpp | 10 ++- client/ui/controllers/connectionController.h | 6 +- client/ui/notificationhandler.cpp | 8 +- client/ui/notificationhandler.h | 2 +- client/ui/pages_logic/SitesLogic.cpp | 4 +- client/ui/pages_logic/VpnLogic.cpp | 22 ++--- client/ui/pages_logic/VpnLogic.h | 2 +- client/ui/qml/Components/ConnectButton.qml | 88 +++++++++++++++++-- client/ui/qml/Pages2/PageHome.qml | 2 - client/ui/qml/Pages2/PageStart.qml | 10 +-- client/ui/systemtray_notificationhandler.cpp | 22 ++--- client/ui/systemtray_notificationhandler.h | 4 +- client/ui/uilogic.cpp | 6 +- client/vpnconnection.cpp | 32 +++---- client/vpnconnection.h | 6 +- 27 files changed, 293 insertions(+), 125 deletions(-) create mode 100644 client/images/connectionOff.svg create mode 100644 client/images/connectionOn.svg create mode 100644 client/images/connectionProgress.svg diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain index c6f0b6631..8bbaa6d83 160000 --- a/client/3rd/qtkeychain +++ b/client/3rd/qtkeychain @@ -1 +1 @@ -Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194 +Subproject commit 8bbaa6d8302cf0747d9786ace4dd13c7fb746502 diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 6fadcabb5..a8231fce3 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -109,6 +109,9 @@ void AmneziaApplication::init() m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, + m_connectionController.get(), &ConnectionController::connectionStateChanged); connect(m_connectionController.get(), &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, @@ -156,7 +159,6 @@ void AmneziaApplication::init() void AmneziaApplication::registerTypes() { - qRegisterMetaType("VpnProtocol::VpnConnectionState"); qRegisterMetaType("ServerCredentials"); qRegisterMetaType("DockerContainer"); @@ -164,7 +166,6 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); qRegisterMetaType("Page"); - qRegisterMetaType("ConnectionState"); qRegisterMetaType("PageProtocolLogicBase *"); @@ -181,6 +182,9 @@ void AmneziaApplication::registerTypes() m_protocolProps = new ProtocolProps; qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + + // + Vpn::declareQmlVpnConnectionStateEnum(); } void AmneziaApplication::loadFonts() diff --git a/client/images/connectionOff.svg b/client/images/connectionOff.svg new file mode 100644 index 000000000..27905ff91 --- /dev/null +++ b/client/images/connectionOff.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionOn.svg b/client/images/connectionOn.svg new file mode 100644 index 000000000..ef3176221 --- /dev/null +++ b/client/images/connectionOn.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionProgress.svg b/client/images/connectionProgress.svg new file mode 100644 index 000000000..8c4024c9d --- /dev/null +++ b/client/images/connectionProgress.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 559398951..52bcae4b3 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_errorHandlerConnection = connect(&m_ckProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); stop(); @@ -81,7 +81,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_ckProcess.waitForStarted(); if (m_ckProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 150e84be9..273d7b769 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -45,16 +45,16 @@ void OpenVpnProtocol::stop() // TODO: need refactoring // sendTermSignal() will even return true while server connected ??? - if ((m_connectionState == VpnProtocol::Preparing) || - (m_connectionState == VpnProtocol::Connecting) || - (m_connectionState == VpnProtocol::Connected) || - (m_connectionState == VpnProtocol::Reconnecting)) { + if ((m_connectionState == Vpn::ConnectionState::Preparing) || + (m_connectionState == Vpn::ConnectionState::Connecting) || + (m_connectionState == Vpn::ConnectionState::Connected) || + (m_connectionState == Vpn::ConnectionState::Reconnecting)) { if (!sendTermSignal()) { killOpenVpnProcess(); } m_managementServer.stop(); qApp->processEvents(); - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); } } @@ -175,7 +175,7 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); @@ -208,7 +208,7 @@ ErrorCode OpenVpnProtocol::start() }); connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); m_openVpnProcess->start(); @@ -256,14 +256,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains("CONNECTED,SUCCESS")) { sendByteCount(); stopTimeoutTimer(); - setConnectionState(VpnProtocol::Connected); + setConnectionState(Vpn::ConnectionState::Connected); continue; } else if (line.contains("EXITING,SIGTER")) { //openVpnStateSigTermHandler(); - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); continue; } else if (line.contains("RECONNECTING")) { - setConnectionState(VpnProtocol::Reconnecting); + setConnectionState(Vpn::ConnectionState::Reconnecting); continue; } } diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 82ae08b8a..0ffc2768a 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode ShadowSocksVpnProtocol::start() connect(&m_ssProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed); stop(); @@ -81,7 +81,7 @@ ErrorCode ShadowSocksVpnProtocol::start() m_ssProcess.waitForStarted(); if (m_ssProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index a8f392e90..841d307c7 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -18,7 +18,7 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) : QObject(parent), - m_connectionState(VpnConnectionState::Unknown), + m_connectionState(Vpn::ConnectionState::Unknown), m_rawConfig(configuration), m_timeoutTimer(new QTimer(this)), m_receivedBytes(0), @@ -32,7 +32,7 @@ void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; if (lastError){ - setConnectionState(VpnConnectionState::Error); + setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); } @@ -60,7 +60,7 @@ void VpnProtocol::stopTimeoutTimer() m_timeoutTimer->stop(); } -VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const +Vpn::ConnectionState VpnProtocol::connectionState() const { return m_connectionState; } @@ -76,19 +76,19 @@ void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes) m_sentBytes = sentBytes; } -void VpnProtocol::setConnectionState(VpnProtocol::VpnConnectionState state) +void VpnProtocol::setConnectionState(Vpn::ConnectionState state) { qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state); if (m_connectionState == state) { return; } - if (m_connectionState == VpnConnectionState::Disconnected && state == VpnConnectionState::Disconnecting) { + if (m_connectionState == Vpn::ConnectionState::Disconnected && state == Vpn::ConnectionState::Disconnecting) { return; } m_connectionState = state; - if (m_connectionState == VpnConnectionState::Disconnected) { + if (m_connectionState == Vpn::ConnectionState::Disconnected) { m_receivedBytes = 0; m_sentBytes = 0; } @@ -124,17 +124,17 @@ QString VpnProtocol::routeGateway() const return m_routeGateway; } -QString VpnProtocol::textConnectionState(VpnConnectionState connectionState) +QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState) { switch (connectionState) { - case VpnConnectionState::Unknown: return tr("Unknown"); - case VpnConnectionState::Disconnected: return tr("Disconnected"); - case VpnConnectionState::Preparing: return tr("Preparing"); - case VpnConnectionState::Connecting: return tr("Connecting..."); - case VpnConnectionState::Connected: return tr("Connected"); - case VpnConnectionState::Disconnecting: return tr("Disconnecting..."); - case VpnConnectionState::Reconnecting: return tr("Reconnecting..."); - case VpnConnectionState::Error: return tr("Error"); + case Vpn::ConnectionState::Unknown: return tr("Unknown"); + case Vpn::ConnectionState::Disconnected: return tr("Disconnected"); + case Vpn::ConnectionState::Preparing: return tr("Preparing"); + case Vpn::ConnectionState::Connecting: return tr("Connecting..."); + case Vpn::ConnectionState::Connected: return tr("Connected"); + case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); + case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); + case Vpn::ConnectionState::Error: return tr("Error"); default: ; } @@ -149,10 +149,10 @@ QString VpnProtocol::textConnectionState() const bool VpnProtocol::isConnected() const { - return m_connectionState == VpnConnectionState::Connected; + return m_connectionState == Vpn::ConnectionState::Connected; } bool VpnProtocol::isDisconnected() const { - return m_connectionState == VpnConnectionState::Disconnected; + return m_connectionState == Vpn::ConnectionState::Disconnected; } diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index 4b6876d54..bb71a5de2 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -12,6 +12,33 @@ using namespace amnezia; class QTimer; +//todo change name +namespace Vpn +{ + Q_NAMESPACE + enum ConnectionState { + Unknown, + Disconnected, + Preparing, + Connecting, + Connected, + Disconnecting, + Reconnecting, + Error + }; + Q_ENUM_NS(ConnectionState) + + static void declareQmlVpnConnectionStateEnum() { + qmlRegisterUncreatableMetaObject( + Vpn::staticMetaObject, + "ConnectionState", + 1, 0, + "ConnectionState", + "Error: only enums" + ); + } +} + class VpnProtocol : public QObject { Q_OBJECT @@ -20,10 +47,7 @@ public: explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr); virtual ~VpnProtocol() override = default; - enum VpnConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, Reconnecting, Error}; - Q_ENUM(VpnConnectionState) - - static QString textConnectionState(VpnConnectionState connectionState); + static QString textConnectionState(Vpn::ConnectionState connectionState); virtual ErrorCode prepare() { return ErrorCode::NoError; } @@ -32,7 +56,7 @@ public: virtual ErrorCode start() = 0; virtual void stop() = 0; - VpnConnectionState connectionState() const; + Vpn::ConnectionState connectionState() const; ErrorCode lastError() const; QString textConnectionState() const; void setLastError(ErrorCode lastError); @@ -44,7 +68,7 @@ public: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void timeoutTimerEvent(); void protocolError(amnezia::ErrorCode e); @@ -52,13 +76,13 @@ public slots: virtual void onTimeout(); // todo: remove? void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void setConnectionState(VpnProtocol::VpnConnectionState state); + void setConnectionState(Vpn::ConnectionState state); protected: void startTimeoutTimer(); void stopTimeoutTimer(); - VpnConnectionState m_connectionState; + Vpn::ConnectionState m_connectionState; QString m_routeGateway; QString m_vpnLocalAddress; diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 666bf80d6..4f66f5a1b 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -51,7 +51,7 @@ void WireguardProtocol::stop() connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { @@ -62,12 +62,12 @@ void WireguardProtocol::stop() if (IpcClient::Interface()) { QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); if (result.returnValue()) { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } } else { qCritical() << "IPC client not initialized"; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } #endif @@ -75,7 +75,7 @@ void WireguardProtocol::stop() m_wireguardStopProcess->start(); m_wireguardStopProcess->waitForFinished(10000); - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); #endif } @@ -156,7 +156,7 @@ ErrorCode WireguardProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess(); @@ -179,7 +179,7 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { @@ -187,7 +187,7 @@ ErrorCode WireguardProtocol::start() }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(VpnConnectionState::Connected); + setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { diff --git a/client/resources.qrc b/client/resources.qrc index 1ea7bf38c..f75c8132f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -223,5 +223,8 @@ ui/qml/Controls2/Header2Type.qml images/controls/plus.svg ui/qml/Components/ConnectButton.qml + images/connectionProgress.svg + images/connectionOff.svg + images/connectionOn.svg diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 41dbed64f..ce52a2c82 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -11,13 +11,18 @@ ConnectionController::ConnectionController(const QSharedPointer &s bool ConnectionController::onConnectionButtonClicked() { - if (!isConnected) { + if (!isConnected()) { openVpnConnection(); } else { closeVpnConnection(); } } +bool ConnectionController::isConnected() +{ + return m_isConnected; +} + bool ConnectionController::openVpnConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); @@ -33,7 +38,7 @@ bool ConnectionController::openVpnConnection() //todo error handling qApp->processEvents(); emit connectToVpn(serverIndex, credentials, container, containerConfig); - isConnected = true; + m_isConnected = true; // int serverIndex = m_settings->defaultServerIndex(); @@ -67,5 +72,6 @@ bool ConnectionController::openVpnConnection() bool ConnectionController::closeVpnConnection() { emit disconnectFromVpn(); + m_isConnected = false; } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 125e92048..8282a5824 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -3,6 +3,7 @@ #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" +#include "protocols/vpnprotocol.h" class ConnectionController : public QObject { @@ -16,9 +17,12 @@ public: public slots: bool onConnectionButtonClicked(); + bool isConnected(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); + void connectionStateChanged(Vpn::ConnectionState state); private: bool openVpnConnection(); @@ -27,7 +31,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; - bool isConnected = false; + bool m_isConnected = false; }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index 779569d77..f932eb173 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -52,9 +52,9 @@ NotificationHandler::~NotificationHandler() { s_instance = nullptr; } -void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void NotificationHandler::setConnectionState(Vpn::ConnectionState state) { - if (state != VpnProtocol::Connected && state != VpnProtocol::Disconnected) { + if (state != Vpn::ConnectionState::Connected && state != Vpn::ConnectionState::Disconnected) { return; } @@ -62,14 +62,14 @@ void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState sta QString message; switch (state) { - case VpnProtocol::VpnConnectionState::Connected: + case Vpn::ConnectionState::Connected: m_connected = true; title = tr("AmneziaVPN"); message = tr("VPN Connected"); break; - case VpnProtocol::VpnConnectionState::Disconnected: + case Vpn::ConnectionState::Disconnected: if (m_connected) { m_connected = false; title = tr("AmneziaVPN"); diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index b87e95757..9a2182de3 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -31,7 +31,7 @@ public: void messageClickHandle(); public slots: - virtual void setConnectionState(VpnProtocol::VpnConnectionState state); + virtual void setConnectionState(Vpn::ConnectionState state); signals: void notificationShown(const QString& title, const QString& message); diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index 17357073a..bf926e569 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -116,14 +116,14 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) if (!ok || row < 0 || row >= siteModel->rowCount()) return; sites.append(siteModel->data(row, 0).toString()); - if (uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { ips.append(siteModel->data(row, 1).toString()); } } m_settings->removeVpnSites(mode, sites); - if (uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { uiLogic()->m_vpnConnection->deleteRoutes(ips); uiLogic()->m_vpnConnection->flushDns(); } diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp index d3df1a081..4db542441 100644 --- a/client/ui/pages_logic/VpnLogic.cpp +++ b/client/ui/pages_logic/VpnLogic.cpp @@ -42,7 +42,7 @@ VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): }); } else { - onConnectionStateChanged(VpnProtocol::Disconnected); + onConnectionStateChanged(Vpn::ConnectionState::Disconnected); } } @@ -119,7 +119,7 @@ void VpnLogic::onBytesChanged(quint64 receivedData, quint64 sentData) set_labelSpeedSentText(VpnConnection::bytesPerSecToText(sentData)); } -void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) +void VpnLogic::onConnectionStateChanged(Vpn::ConnectionState state) { qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); if (uiLogic()->m_vpnConnection == NULL) { @@ -134,50 +134,50 @@ void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) set_labelStateText(VpnProtocol::textConnectionState(state)); switch (state) { - case VpnProtocol::Disconnected: + case Vpn::ConnectionState::Disconnected: onBytesChanged(0,0); pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = true; break; - case VpnProtocol::Preparing: + case Vpn::ConnectionState::Preparing: pbConnectChecked = true; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Connecting: + case Vpn::ConnectionState::Connecting: pbConnectChecked = true; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Connected: + case Vpn::ConnectionState::Connected: pbConnectChecked = true; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = false; break; - case VpnProtocol::Disconnecting: + case Vpn::ConnectionState::Disconnecting: pbConnectChecked = false; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Reconnecting: + case Vpn::ConnectionState::Reconnecting: pbConnectChecked = true; pbConnectEnabled = true; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Error: + case Vpn::ConnectionState::Error: pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = true; break; - case VpnProtocol::Unknown: + case Vpn::ConnectionState::Unknown: pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; @@ -241,6 +241,6 @@ void VpnLogic::onConnectWorker(int serverIndex, const ServerCredentials &credent void VpnLogic::onDisconnect() { - onConnectionStateChanged(VpnProtocol::Disconnected); + onConnectionStateChanged(Vpn::ConnectionState::Disconnected); emit disconnectFromVpn(); } diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h index f7b21be26..a0f7763b2 100644 --- a/client/ui/pages_logic/VpnLogic.h +++ b/client/ui/pages_logic/VpnLogic.h @@ -58,7 +58,7 @@ public slots: void onDisconnect(); void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); + void onConnectionStateChanged(Vpn::ConnectionState state); void onVpnProtocolError(amnezia::ErrorCode errorCode); signals: diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 5457cf55a..bce652d83 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -2,21 +2,40 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import ConnectionState 1.0 + Button { id: root - implicitHeight: 190 - implicitWidth: 190 + property var isConnected: ConnectionController.isConnected - background: Rectangle { - id: background + text: "Подключиться" - radius: parent.width * 0.5 +// implicitHeight: 190 +// implicitWidth: 190 - color: "transparent" +// color: "transparent" - border.width: 2 - border.color: "white" + background: Image { + id: border + + source: connectionProccess.running ? "/images/connectionProgress.svg" : + ConnectionController.isConnected() ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + + RotationAnimator { + id: connectionProccess + + target: border + running: false + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } } contentItem: Text { @@ -34,7 +53,58 @@ Button { } onClicked: { - background.color = "red" ConnectionController.onConnectionButtonClicked() + console.log(connectionProccess.from) + } + + Connections { + target: ConnectionController + function onConnectionStateChanged(state) { + switch(state) { + case ConnectionState.Unknown: { + console.log("Unknown") + break + } + case ConnectionState.Disconnected: { + console.log("Disconnected") + connectionProccess.running = false + root.text = "Подключиться" + break + } + case ConnectionState.Preparing: { + console.log("Preparing") + break + } + case ConnectionState.Connecting: { + console.log("Connecting") + connectionProccess.running = true + root.text = "Подключение..." + break + } + case ConnectionState.Connected: { + console.log("Connected") + connectionProccess.running = false + root.text = "Подключено" + break + } + case ConnectionState.Disconnecting: { + console.log("Disconnecting") + connectionProccess.running = true + root.text = "Отключение..." + break + } + case ConnectionState.Reconnecting: { + console.log("Reconnecting") + connectionProccess.running = true + root.text = "Переподключение..." + break + } + case ConnectionState.Error: { + console.log("Error") + connectionProccess.running = false + break + } + } + } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 40eba1489..5dd0aaae7 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -27,8 +27,6 @@ PageBase { ConnectButton { anchors.centerIn: parent - - text: "Подключиться" } Rectangle { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 9e94ed28b..220c6f196 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -23,14 +23,8 @@ PageBase { anchors.left: parent.left anchors.bottom: tabBar.top - width: { - console.log(parent.width) - return parent.width - } - height: { - console.log(root.height - tabBar.implicitHeight) - return root.height - tabBar.implicitHeight - } + width: parent.width + height: root.height - tabBar.implicitHeight PageHome { } diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index e142caf54..25ff9f6cf 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -45,13 +45,13 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : }); m_systemTrayIcon.setContextMenu(&m_menu); - setTrayState(VpnProtocol::Disconnected); + setTrayState(Vpn::ConnectionState::Disconnected); } SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { } -void SystemTrayNotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState state) { setTrayState(state); NotificationHandler::setConnectionState(state); @@ -73,47 +73,47 @@ void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationR #endif } -void SystemTrayNotificationHandler::setTrayState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state) { QString resourcesPath = ":/images/tray/%1"; switch (state) { - case VpnProtocol::Disconnected: + case Vpn::ConnectionState::Disconnected: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Preparing: + case Vpn::ConnectionState::Preparing: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connecting: + case Vpn::ConnectionState::Connecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connected: + case Vpn::ConnectionState::Connected: setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Disconnecting: + case Vpn::ConnectionState::Disconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Reconnecting: + case Vpn::ConnectionState::Reconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Error: + case Vpn::ConnectionState::Error: setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Unknown: + case Vpn::ConnectionState::Unknown: default: m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h index 46563bde7..96134f140 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -17,7 +17,7 @@ public: explicit SystemTrayNotificationHandler(QObject* parent); ~SystemTrayNotificationHandler(); - void setConnectionState(VpnProtocol::VpnConnectionState state) override; + void setConnectionState(Vpn::ConnectionState state) override; protected: virtual void notify(Message type, const QString& title, @@ -26,7 +26,7 @@ protected: private: void showHideWindow(); - void setTrayState(VpnProtocol::VpnConnectionState state); + void setTrayState(Vpn::ConnectionState state); void onTrayActivated(QSystemTrayIcon::ActivationReason reason); void setTrayIcon(const QString &iconPath); diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index bb2f90b22..929d84d6f 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -107,7 +107,7 @@ UiLogic::~UiLogic() emit hide(); #ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) { + if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { m_vpnConnection->disconnectFromVpn(); for (int i = 0; i < 50; i++) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); @@ -131,13 +131,13 @@ void UiLogic::initializeUiLogic() #ifdef Q_OS_ANDROID connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { if (connected) { - pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); + pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); m_vpnConnection->restoreConnection(); } }); if (!AndroidController::instance()->initialize(pageLogic())) { qCritical() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } #endif diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 57d201272..468a6d967 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -50,12 +50,12 @@ void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) emit bytesChanged(receivedBytes, sentBytes); } -void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) +void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP if (IpcClient::Interface()) { - if (state == VpnProtocol::Connected){ + if (state == Vpn::ConnectionState::Connected){ IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); @@ -85,7 +85,7 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta } - else if (state == VpnProtocol::Error) { + else if (state == Vpn::ConnectionState::Error) { IpcClient::Interface()->flushDns(); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { @@ -96,7 +96,7 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta #endif #ifdef Q_OS_IOS - if (state == VpnProtocol::Connected){ + if (state == Vpn::ConnectionState::Connected){ m_isIOSConnected = true; checkIOSStatus(); } @@ -179,7 +179,7 @@ QSharedPointer VpnConnection::vpnProtocol() const void VpnConnection::addRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), ips); } @@ -193,7 +193,7 @@ void VpnConnection::addRoutes(const QStringList &ips) void VpnConnection::deleteRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeDeleteList(vpnProtocol()->vpnGateway(), ips); } @@ -319,14 +319,14 @@ void VpnConnection::connectToVpn(int serverIndex, if (!IpcClient::init(m_IpcClient)) { qWarning() << "Error occurred when init IPC client"; emit serviceIsNotReady(); - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } } #endif m_remoteAddress = credentials.hostName; - emit connectionStateChanged(VpnProtocol::Connecting); + emit connectionStateChanged(Vpn::ConnectionState::Connecting); if (m_vpnProtocol) { disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); @@ -338,14 +338,14 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig); if (e) { - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } #if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } m_vpnProtocol->prepare(); @@ -362,7 +362,7 @@ void VpnConnection::connectToVpn(int serverIndex, // IOSVpnProtocol *iosVpnProtocol = new IOSVpnProtocol(proto, m_vpnConfiguration); if (!iosVpnProtocol->initialize()) { qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } m_vpnProtocol.reset(iosVpnProtocol); @@ -371,12 +371,12 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit VpnProtocol::Error; + if (e) emit Vpn::ConnectionState::Error; } void VpnConnection::createProtocolConnections() { connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, SLOT(onConnectionStateChanged(Vpn::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } @@ -433,7 +433,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); #ifdef Q_OS_ANDROID AndroidController::instance()->stop(); #endif @@ -442,9 +442,9 @@ void VpnConnection::disconnectFromVpn() m_vpnProtocol.data()->stop(); } -VpnProtocol::VpnConnectionState VpnConnection::connectionState() +Vpn::ConnectionState VpnConnection::connectionState() { - if (!m_vpnProtocol) return VpnProtocol::Disconnected; + if (!m_vpnProtocol) return Vpn::ConnectionState::Disconnected; return m_vpnProtocol->connectionState(); } diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 8dea74781..3a3a30473 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -54,7 +54,7 @@ public: bool isConnected() const; bool isDisconnected() const; - VpnProtocol::VpnConnectionState connectionState(); + Vpn::ConnectionState connectionState(); QSharedPointer vpnProtocol() const; void addRoutes(const QStringList &ips); @@ -76,14 +76,14 @@ public slots: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void vpnProtocolError(amnezia::ErrorCode error); void serviceIsNotReady(); protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); + void onConnectionStateChanged(Vpn::ConnectionState state); #ifdef Q_OS_IOS void checkIOSStatus(); From 03a0e2084ae35810821ec9910057f72cc9408d9d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 15 May 2023 13:38:17 +0800 Subject: [PATCH 018/131] added PageLoader and pageController --- client/amnezia_application.cpp | 4 + client/amnezia_application.h | 3 + client/images/controls/download.svg | 5 + client/resources.qrc | 1 + client/ui/controllers/pageController.cpp | 25 +++++ client/ui/controllers/pageController.h | 50 ++++++++++ client/ui/models/servers_model.cpp | 8 +- client/ui/models/servers_model.h | 3 +- client/ui/qml/Components/ConnectButton.qml | 94 +++++++++---------- client/ui/qml/Controls2/DropDownType.qml | 5 + client/ui/qml/Controls2/Header2Type.qml | 2 + client/ui/qml/Controls2/HeaderType.qml | 2 + client/ui/qml/PageLoader.qml | 32 ++++++- client/ui/qml/Pages2/PageHome.qml | 84 +++++++++++++---- .../Pages2/PageSetupWizardConfigSource.qml | 6 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 8 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 4 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 +- .../PageSetupWizardProtocolSettings.qml | 6 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 10 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 6 +- client/ui/qml/Pages2/PageStart.qml | 4 +- client/ui/qml/Pages2/PageTest.qml | 5 +- client/ui/qml/main2.qml | 73 +------------- client/ui/uilogic.cpp | 6 -- client/ui/uilogic.h | 2 - 27 files changed, 265 insertions(+), 193 deletions(-) create mode 100644 client/images/controls/download.svg create mode 100644 client/ui/controllers/pageController.cpp create mode 100644 client/ui/controllers/pageController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index a8231fce3..4b7f31b76 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -118,6 +118,9 @@ void AmneziaApplication::init() m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + m_pageController.reset(new PageController(m_serversModel)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + // m_uiLogic->registerPagesLogic(); @@ -185,6 +188,7 @@ void AmneziaApplication::registerTypes() // Vpn::declareQmlVpnConnectionStateEnum(); + PageLoader::declareQmlPageEnum(); } void AmneziaApplication::loadFonts() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 322a440ca..e21139081 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -13,9 +13,11 @@ #include "ui/uilogic.h" #include "configurators/vpn_configurator.h" + #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" #include "ui/controllers/connectionController.h" +#include "ui/controllers/pageController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -66,6 +68,7 @@ private: QScopedPointer m_vpnConnection; QScopedPointer m_connectionController; + QScopedPointer m_pageController; }; diff --git a/client/images/controls/download.svg b/client/images/controls/download.svg new file mode 100644 index 000000000..1e592887f --- /dev/null +++ b/client/images/controls/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index f75c8132f..77e7d86ff 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -226,5 +226,6 @@ images/connectionProgress.svg images/connectionOff.svg images/connectionOn.svg + images/controls/download.svg diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp new file mode 100644 index 000000000..101207672 --- /dev/null +++ b/client/ui/controllers/pageController.cpp @@ -0,0 +1,25 @@ +#include "pageController.h" + +PageController::PageController(const QSharedPointer &serversModel, + QObject *parent) : QObject(parent), m_serversModel(serversModel) +{ +} + +void PageController::setStartPage() +{ + if (m_serversModel->getServersCount()) { + if (m_serversModel->getDefaultServerIndex() < 0) { + m_serversModel->setDefaultServerIndex(0); + } + emit goToPage(PageLoader::PageEnum::PageStart, false); + } else { + emit goToPage(PageLoader::PageEnum::PageSetupWizardStart, false); + } +} + +QString PageController::getPagePath(PageLoader::PageEnum page) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString pageName = metaEnum.valueToKey(static_cast(page)); + return "Pages2/" + pageName + ".qml"; +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h new file mode 100644 index 000000000..9bfd6bda4 --- /dev/null +++ b/client/ui/controllers/pageController.h @@ -0,0 +1,50 @@ +#ifndef PAGECONTROLLER_H +#define PAGECONTROLLER_H + +#include +#include + +#include "ui/models/servers_model.h" + +namespace PageLoader +{ + Q_NAMESPACE + enum class PageEnum { PageStart = 0, PageHome, PageSettings, PageShare, + + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, + PageSetupWizardTextKey + }; + Q_ENUM_NS(PageEnum) + + static void declareQmlPageEnum() { + qmlRegisterUncreatableMetaObject( + PageLoader::staticMetaObject, + "PageEnum", + 1, 0, + "PageEnum", + "Error: only enums" + ); + } +} + +class PageController : public QObject +{ + Q_OBJECT +public: + explicit PageController(const QSharedPointer &serversModel, + QObject *parent = nullptr); + +public slots: + void setStartPage(); + QString getPagePath(PageLoader::PageEnum page); + +signals: + void goToPage(PageLoader::PageEnum page, bool slide = true); + void closePage(); + +private: + QSharedPointer m_serversModel; +}; + +#endif // PAGECONTROLLER_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index dccf59b9e..8f39e1401 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -56,6 +56,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } +//todo mode to setData? void ServersModel::setDefaultServerIndex(int index) { // beginResetModel(); @@ -63,11 +64,16 @@ void ServersModel::setDefaultServerIndex(int index) // endResetModel(); } -int ServersModel::getDefaultServerIndex() +const int ServersModel::getDefaultServerIndex() { return m_settings->defaultServerIndex(); } +const int ServersModel::getServersCount() +{ + return m_settings->serversCount(); +} + QHash ServersModel::roleNames() const { QHash roles; roles[DescRole] = "desc"; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 30c8b5a2c..9fb5ac1a7 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,7 +31,8 @@ public: public slots: void setDefaultServerIndex(int index); - int getDefaultServerIndex(); + const int getDefaultServerIndex(); + const int getServersCount(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index bce652d83..8bf39f03f 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,15 +7,8 @@ import ConnectionState 1.0 Button { id: root - property var isConnected: ConnectionController.isConnected - text: "Подключиться" -// implicitHeight: 190 -// implicitWidth: 190 - -// color: "transparent" - background: Image { id: border @@ -54,56 +47,55 @@ Button { onClicked: { ConnectionController.onConnectionButtonClicked() - console.log(connectionProccess.from) } Connections { target: ConnectionController function onConnectionStateChanged(state) { switch(state) { - case ConnectionState.Unknown: { - console.log("Unknown") - break - } - case ConnectionState.Disconnected: { - console.log("Disconnected") - connectionProccess.running = false - root.text = "Подключиться" - break - } - case ConnectionState.Preparing: { - console.log("Preparing") - break - } - case ConnectionState.Connecting: { - console.log("Connecting") - connectionProccess.running = true - root.text = "Подключение..." - break - } - case ConnectionState.Connected: { - console.log("Connected") - connectionProccess.running = false - root.text = "Подключено" - break - } - case ConnectionState.Disconnecting: { - console.log("Disconnecting") - connectionProccess.running = true - root.text = "Отключение..." - break - } - case ConnectionState.Reconnecting: { - console.log("Reconnecting") - connectionProccess.running = true - root.text = "Переподключение..." - break - } - case ConnectionState.Error: { - console.log("Error") - connectionProccess.running = false - break - } + case ConnectionState.Unknown: { + console.log("Unknown") + break + } + case ConnectionState.Disconnected: { + console.log("Disconnected") + connectionProccess.running = false + root.text = "Подключиться" + break + } + case ConnectionState.Preparing: { + console.log("Preparing") + break + } + case ConnectionState.Connecting: { + console.log("Connecting") + connectionProccess.running = true + root.text = "Подключение..." + break + } + case ConnectionState.Connected: { + console.log("Connected") + connectionProccess.running = false + root.text = "Подключено" + break + } + case ConnectionState.Disconnecting: { + console.log("Disconnecting") + connectionProccess.running = true + root.text = "Отключение..." + break + } + case ConnectionState.Reconnecting: { + console.log("Reconnecting") + connectionProccess.running = true + root.text = "Переподключение..." + break + } + case ConnectionState.Error: { + console.log("Error") + connectionProccess.running = false + break + } } } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 64013ba4b..eab189f32 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -154,6 +154,10 @@ Item { anchors.topMargin: 16 anchors.leftMargin: 16 anchors.rightMargin: 16 + + backButtonFunction: function() { + root.menuVisible = false + } } FlickableType { @@ -190,6 +194,7 @@ Item { id: loader sourceComponent: root.menuDelegate property QtObject modelData: model + property var delegateIndex: index } } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index 5c2f0c9ba..ef463acd4 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -35,6 +35,8 @@ Item { onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() + } else { + PageController.closePage() } } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index a051d2d87..6c4e78471 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -35,6 +35,8 @@ Item { onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() + } else { + PageController.closePage() } } } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index 86f9e5a18..b3d53a055 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -3,5 +3,35 @@ import QtQuick.Controls StackView { id: stackView - initialItem: "PageSetupWizardStart" + + function gotoPage(page, slide) { + if (slide) { + stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + } else { + stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + } + } + + function closePage() { + if (stackView.depth <= 1) { + return + } + + stackView.pop() + } + + Connections { + target: PageController + function onGoToPage(page, slide) { + stackView.gotoPage(page, slide) + } + + function onClosePage() { + stackView.closePage() + } + } + + Component.onCompleted: { + PageController.setStartPage() + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 5dd0aaae7..8edd6b124 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -8,15 +8,13 @@ import PageEnum 1.0 import ProtocolEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" import "../Components" -PageBase { +Item { id: root - page: PageEnum.PageHome property string defaultColor: "#1C1D21" @@ -143,10 +141,6 @@ PageBase { ValueFilter { roleName: "serviceType" value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isInstalled" - value: true } ] } @@ -196,6 +190,19 @@ PageBase { indicator: Rectangle { anchors.fill: parent color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: { + if (modelData !== null) { + if (modelData.isInstalled) { + return true + } + } + return false } RowLayout { @@ -207,10 +214,33 @@ PageBase { z: 1 + Image { + source: { + if (modelData !== null) { + if (modelData.isInstalled) { + return "qrc:/images/controls/check.svg" + } + } + return "qrc:/images/controls/download.svg" + } + visible: { + if (modelData !== null) { + if (modelData.isInstalled) { + return containerRadioButton.checked + } + } + return true + } + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + Text { id: containerRadioButtonText - // todo remove dirty hack? text: { if (modelData !== null) { return modelData.name @@ -228,22 +258,26 @@ PageBase { Layout.topMargin: 20 Layout.bottomMargin: 20 } - - Image { - source: "qrc:/images/controls/check.svg" - visible: containerRadioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } } onClicked: { - modelData.isDefault = true + if (checked) { + modelData.isDefault = true - containersDropDown.text = containerRadioButtonText.text - containersDropDown.menuVisible = false + containersDropDown.text = containerRadioButtonText.text + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false } } @@ -326,6 +360,10 @@ PageBase { indicator: Rectangle { anchors.fill: parent color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -370,6 +408,12 @@ PageBase { ServersModel.setDefaultServerIndex(index) ContainersModel.setSelectedServerIndex(index) } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 5445d1f34..db2730aca 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -6,14 +6,12 @@ import QtQuick.Dialogs import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl @@ -100,7 +98,7 @@ PageBase { iconImage: "qrc:/images/controls/text-cursor.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardTextKey) + PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 506c1cde3..479e4bfad 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -5,13 +5,11 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardCredentials FlickableType { id: fl @@ -61,7 +59,7 @@ PageBase { text: qsTr("Настроить сервер простым образом") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardEasy) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -79,7 +77,7 @@ PageBase { text: qsTr("Выбрать протокол для установки") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardProtocols) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 94e22fe3e..664f4de7f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -5,13 +5,11 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardEasy FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index a74182578..fa0d5a146 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 78856444a..6c3b64b9c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardProtocolSettings FlickableType { id: fl @@ -90,7 +88,7 @@ PageBase { text: qsTr("Установить") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 501076155..24a86a072 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -8,13 +8,11 @@ import PageEnum 1.0 import ProtocolEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardProtocols SortFilterProxyModel { id: proxyContainersModel @@ -89,7 +87,7 @@ PageBase { onClickedFunc: function() { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - UiLogic.goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 0167ed1fe..5f1a0479b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" import "../Controls2/TextTypes" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardStart FlickableType { id: fl @@ -77,7 +75,7 @@ PageBase { text: qsTr("У меня ничего нет") onClicked: { - UiLogic.goToPage(PageEnum.PageTest) + PageController.goToPage(PageEnum.PageTest) } } } @@ -132,7 +130,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) + PageController.goToPage(PageEnum.PageSetupWizardCredentials) drawer.visible = false } } @@ -148,7 +146,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) drawer.visible = false } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index d295f1b97..f94b9ff48 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl @@ -68,7 +66,7 @@ PageBase { text: qsTr("Подключиться") onClicked: function() { -// UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) +// PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 220c6f196..ade0980df 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageStart StackLayout { id: stackLayout diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index 2336e46e8..53ec99fc9 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -4,15 +4,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" import "../Controls2/TextTypes" -PageBase { +Item { id: root - page: PageEnum.Test - logic: ViewConfigLogic ColumnLayout { id: content diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6261e9a70..b10d749fb 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -22,83 +22,14 @@ Window { title: "AmneziaVPN" - function gotoPage(page, reset, slide) { - if (pageStackView.depth > 0) { - pageStackView.currentItem.deactivated() - } - - if (slide) { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) - } else { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) - } - - pageStackView.currentItem.activated(reset) - } - - function closePage() { - if (pageStackView.depth <= 1) { - return - } - pageStackView.currentItem.deactivated() - pageStackView.pop() - } - - function setStartPage(page, slide) { - if (pageStackView.depth > 0) { - pageStackView.currentItem.deactivated() - } - - pageStackView.clear() - if (slide) { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) - } else { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) - } - if (page === PageEnum.Start) { - UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty - UiLogic.onUpdatePage(); - } - } - Rectangle { anchors.fill: parent color: "#0E0E11" } - StackView { - id: pageStackView + PageLoader { + id: pageLoader anchors.fill: parent focus: true } - - Connections { - target: UiLogic - function onGoToPage(page, reset, slide) { - root.gotoPage(page, reset, slide) - } - - function onClosePage() { - root.closePage() - } - - function onSetStartPage(page, slide) { - root.setStartPage(page, slide) - } - - function onShow() { - root.show() - UiLogic.initializeUiLogic() - } - - function onHide() { - root.hide() - } - - function onRaise() { - root.show() - root.raise() - root.requestActivate() - } - } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 929d84d6f..3ad74645a 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -617,9 +617,3 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) } return false; } - -QString UiLogic::pageEnumToString(Page page) { - QMetaEnum metaEnum = QMetaEnum::fromType(); - QString pageName = metaEnum.valueToKey(static_cast(page)); - return "Pages2/" + pageName + ".qml"; -} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 4b3920666..081b8e5a6 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -123,8 +123,6 @@ public: Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); - Q_INVOKABLE QString pageEnumToString(PageEnumNS::Page page); - void shareTempFile(const QString &suggestedName, QString ext, const QString& data); static QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), From acca85b99a1bf3d0cc71e5508dc1b1edee71dad5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 17 May 2023 23:28:27 +0800 Subject: [PATCH 019/131] added installController with logic for server/container installation --- client/amnezia_application.cpp | 3 + client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 2 +- client/ui/controllers/installController.cpp | 82 ++++++++ client/ui/controllers/installController.h | 40 ++++ client/ui/models/containers_model.cpp | 53 +++-- client/ui/models/containers_model.h | 16 +- client/ui/models/servers_model.cpp | 21 +- client/ui/models/servers_model.h | 9 +- client/ui/qml/Components/ConnectButton.qml | 2 + client/ui/qml/Controls2/DropDownType.qml | 64 +++--- client/ui/qml/Controls2/Header2Type.qml | 4 +- client/ui/qml/Controls2/ProgressBarType.qml | 19 ++ client/ui/qml/Pages2/PageHome.qml | 39 ++-- .../qml/Pages2/PageSetupWizardCredentials.qml | 10 + .../qml/Pages2/PageSetupWizardInstalling.qml | 105 ++++++++-- .../PageSetupWizardProtocolSettings.qml | 194 +++++++++++++----- .../qml/Pages2/PageSetupWizardProtocols.qml | 4 - client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- 19 files changed, 519 insertions(+), 151 deletions(-) create mode 100644 client/ui/controllers/installController.cpp create mode 100644 client/ui/controllers/installController.h create mode 100644 client/ui/qml/Controls2/ProgressBarType.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4b7f31b76..df4a4ec1d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -121,6 +121,9 @@ void AmneziaApplication::init() m_pageController.reset(new PageController(m_serversModel)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + // m_uiLogic->registerPagesLogic(); diff --git a/client/resources.qrc b/client/resources.qrc index 77e7d86ff..a50a23c53 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -227,5 +227,6 @@ images/connectionOff.svg images/connectionOn.svg images/controls/download.svg + ui/qml/Controls2/ProgressBarType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index ce52a2c82..58fef3739 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -33,7 +33,7 @@ bool ConnectionController::openVpnConnection() DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, - ContainersModel::ContainersModelRoles::ConfigRole)); + ContainersModel::Roles::ConfigRole)); //todo error handling qApp->processEvents(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp new file mode 100644 index 000000000..ae7533822 --- /dev/null +++ b/client/ui/controllers/installController.cpp @@ -0,0 +1,82 @@ +#include "installController.h" + +#include + +#include "core/servercontroller.h" + +InstallController::InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ + +} + +ErrorCode InstallController::install(DockerContainer container, int port, TransportProto transportProto) +{ + Proto mainProto = ContainerProps::defaultProtocol(container); + + QJsonObject containerConfig { + { config_key::port, QString::number(port) }, + { config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto) } + }; + QJsonObject config { + { config_key::container, ContainerProps::containerToString(container) }, + { ProtocolProps::protoToString(mainProto), containerConfig } + }; + + if (m_shouldCreateServer) { + return installServer(container, config); + } else { + return installContainer(container, config); + } +} + +ErrorCode InstallController::installServer(DockerContainer container, QJsonObject& config) +{ + //todo check if container already installed + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + if (errorCode == ErrorCode::NoError) { + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.password); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + server.insert(config_key::containers, QJsonArray{ config }); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + m_settings->addServer(server); + m_settings->setDefaultServer(m_settings->serversCount() - 1); + } + + return errorCode; +} + +ErrorCode InstallController::installContainer(DockerContainer container, QJsonObject& config) +{ + //todo check if container already installed + ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); + if (errorCode == ErrorCode::NoError) { + m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); + emit installContainerFinished(); + } + + //todo error processing + return errorCode; +} + +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port) +{ + m_currentlyInstalledServerCredentials = { hostName, userName, password, port }; +} + +void InstallController::setShouldCreateServer(bool shouldCreateServer) +{ + m_shouldCreateServer = shouldCreateServer; +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h new file mode 100644 index 000000000..f9cd9fb0a --- /dev/null +++ b/client/ui/controllers/installController.h @@ -0,0 +1,40 @@ +#ifndef INSTALLCONTROLLER_H +#define INSTALLCONTROLLER_H + +#include + +#include "core/defs.h" +#include "containers/containers_defs.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class InstallController : public QObject +{ + Q_OBJECT +public: + explicit InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + ErrorCode install(DockerContainer container, int port, TransportProto transportProto); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port); + void setShouldCreateServer(bool shouldCreateServer); + +signals: + void installContainerFinished(); +private: + ErrorCode installServer(DockerContainer container, QJsonObject& config); + ErrorCode installContainer(DockerContainer container, QJsonObject& config); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + + ServerCredentials m_currentlyInstalledServerCredentials; + + bool m_shouldCreateServer; +}; + +#endif // INSTALLCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index db7572e65..a814a838a 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -2,7 +2,6 @@ ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - setSelectedServerIndex(m_settings->defaultServerIndex()); } int ContainersModel::rowCount(const QModelIndex &parent) const @@ -17,9 +16,23 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i return false; } - if (role == IsDefaultRole) { - DockerContainer container = ContainerProps::allContainers().at(index.row()); - m_settings->setDefaultContainer(m_selectedServerIndex, container); + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: +// return ContainerProps::containerHumanNames().value(container); + case DescRole: +// return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + case ServiceTypeRole: +// return ContainerProps::containerService(container); + case DockerContainerRole: +// return container; + case IsInstalledRole: +// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); } emit dataChanged(index, index); @@ -41,40 +54,42 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case DescRole: return ContainerProps::containerDescriptions().value(container); case ConfigRole: - return m_settings->containerConfig(m_selectedServerIndex, container); + return m_settings->containerConfig(m_currentlyProcessedServerIndex, container); case ServiceTypeRole: return ContainerProps::containerService(container); + case DockerContainerRole: + return container; case IsInstalledRole: - return m_settings->containers(m_selectedServerIndex).contains(container); + return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsCurrentlyInstalled: + return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: - return container == m_settings->defaultContainer(m_selectedServerIndex); + return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); } return QVariant(); } -void ContainersModel::setSelectedServerIndex(int index) +void ContainersModel::setCurrentlyProcessedServerIndex(int index) { beginResetModel(); - m_selectedServerIndex = index; + m_currentlyProcessedServerIndex = index; endResetModel(); } void ContainersModel::setCurrentlyInstalledContainerIndex(int index) { -// beginResetModel(); - m_currentlyInstalledContainerIndex = createIndex(index, 0); -// endResetModel(); -} - -QString ContainersModel::getCurrentlyInstalledContainerName() -{ - return data(m_currentlyInstalledContainerIndex, NameRole).toString(); + m_currentlyInstalledContainerIndex = index; } DockerContainer ContainersModel::getDefaultContainer() { - return m_settings->defaultContainer(m_selectedServerIndex); + return m_settings->defaultContainer(m_currentlyProcessedServerIndex); +} + +int ContainersModel::getCurrentlyInstalledContainerIndex() +{ + return m_currentlyInstalledContainerIndex; } QHash ContainersModel::roleNames() const { @@ -82,7 +97,9 @@ QHash ContainersModel::roleNames() const { roles[NameRole] = "name"; roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; + roles[DockerContainerRole] = "dockerContainer"; roles[IsInstalledRole] = "isInstalled"; + roles[IsCurrentlyInstalled] = "isCurrentlyInstalled"; roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index c56511db0..150129255 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -15,12 +15,14 @@ class ContainersModel : public QAbstractListModel public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); public: - enum ContainersModelRoles { + enum Roles { NameRole = Qt::UserRole + 1, DescRole, ServiceTypeRole, ConfigRole, + DockerContainerRole, IsInstalledRole, + IsCurrentlyInstalled, IsDefaultRole }; @@ -28,20 +30,20 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); - Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); - - Q_INVOKABLE QString getCurrentlyInstalledContainerName(); public slots: DockerContainer getDefaultContainer(); + void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyInstalledContainerIndex(int index); + int getCurrentlyInstalledContainerIndex(); + protected: QHash roleNames() const override; private: - int m_selectedServerIndex; - QModelIndex m_currentlyInstalledContainerIndex; + int m_currentlyProcessedServerIndex; + int m_currentlyInstalledContainerIndex; std::shared_ptr m_settings; }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 8f39e1401..fe973811e 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -38,14 +38,14 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const QJsonObject server = servers.at(index.row()).toObject(); switch (role) { - case DescRole: { + case NameRole: { auto description = server.value(config_key::description).toString(); if (description.isEmpty()) { return server.value(config_key::hostName).toString(); } return description; } - case AddressRole: + case HostNameRole: return server.value(config_key::hostName).toString(); case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); @@ -74,10 +74,21 @@ const int ServersModel::getServersCount() return m_settings->serversCount(); } +void ServersModel::setCurrentlyProcessedServerIndex(int index) +{ + m_currenlyProcessedServerIndex = index; +} + +ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() +{ + return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); +} + QHash ServersModel::roleNames() const { QHash roles; - roles[DescRole] = "desc"; - roles[AddressRole] = "address"; - roles[IsDefaultRole] = "is_default"; + roles[NameRole] = "name"; + roles[HostNameRole] = "hostName"; + roles[CredentialsRole] = "credentials"; + roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 9fb5ac1a7..08b449761 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -16,8 +16,8 @@ class ServersModel : public QAbstractListModel Q_OBJECT public: enum ServersModelRoles { - DescRole = Qt::UserRole + 1, - AddressRole, + NameRole = Qt::UserRole + 1, + HostNameRole, CredentialsRole, IsDefaultRole }; @@ -34,11 +34,16 @@ public slots: const int getDefaultServerIndex(); const int getServersCount(); + void setCurrentlyProcessedServerIndex(int index); + ServerCredentials getCurrentlyProcessedServerCredentials(); + protected: QHash roleNames() const override; private: std::shared_ptr m_settings; + + int m_currenlyProcessedServerIndex; }; #endif // SERVERSMODEL_H diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 8bf39f03f..5002d5bd7 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -65,6 +65,8 @@ Button { } case ConnectionState.Preparing: { console.log("Preparing") + connectionProccess.running = true + root.text = "Подключение..." break } case ConnectionState.Connecting: { diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index eab189f32..e2a3531f5 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -8,40 +8,38 @@ Item { id: root property string text + property string textColor: "#d7d8db" + property string descriptionText property string headerText property string headerBackButtonImage - property var onClickedFunc - property string buttonImage: "qrc:/images/controls/chevron-down.svg" - property string buttonImageColor: "#494B50" + property var onRootButtonClicked + property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" + property string rootButtonImageColor: "#494B50" + property string rootButtonDefaultColor: "#1C1D21" + property int rootButtonMaximumWidth - property int buttonMaximumWidth - - property string defaultColor: "#1C1D21" - - property string textColor: "#d7d8db" - - property string borderColor: "#494B50" - property int borderWidth: 1 + property string rootButtonBorderColor: "#494B50" + property int rootButtonBorderWidth: 1 property Component menuDelegate property variant menuModel property alias menuVisible: menu.visible - implicitWidth: buttonContent.implicitWidth - implicitHeight: buttonContent.implicitHeight + implicitWidth: rootButtonContent.implicitWidth + implicitHeight: rootButtonContent.implicitHeight Rectangle { - id: buttonBackground - anchors.fill: buttonContent + id: rootButtonBackground + anchors.fill: rootButtonContent radius: 16 - color: defaultColor - border.color: borderColor - border.width: borderWidth + color: rootButtonDefaultColor + border.color: rootButtonBorderColor + border.width: rootButtonBorderWidth Behavior on border.width { PropertyAnimation { duration: 200 } @@ -49,7 +47,7 @@ Item { } RowLayout { - id: buttonContent + id: rootButtonContent anchors.fill: parent spacing: 0 @@ -71,7 +69,7 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - Layout.maximumWidth: buttonMaximumWidth ? buttonMaximumWidth : implicitWidth + Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth color: root.textColor text: root.text @@ -81,38 +79,36 @@ Item { } } + //todo change to image type ImageButtonType { - id: button - Layout.leftMargin: 4 Layout.rightMargin: 16 hoverEnabled: false - image: buttonImage - imageColor: buttonImageColor - onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() - } - } + image: rootButtonImage + imageColor: rootButtonImageColor } } MouseArea { - anchors.fill: buttonContent + anchors.fill: rootButtonContent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - buttonBackground.border.width = borderWidth + rootButtonBackground.border.width = rootButtonBorderWidth } onExited: { - buttonBackground.border.width = 0 + rootButtonBackground.border.width = 0 } onClicked: { - menu.visible = true + if (onRootButtonClicked && typeof onRootButtonClicked === "function") { + onRootButtonClicked() + } else { + menu.visible = true + } } } @@ -132,7 +128,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: "#494B50" border.width: 1 } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index ef463acd4..f985d5237 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -59,8 +59,8 @@ Item { visible: image ? true : false onClicked: { - if (actionButtonImage && typeof actionButtonImage === "function") { - actionButtonImage() + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() } } } diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml new file mode 100644 index 000000000..f2f2370a8 --- /dev/null +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -0,0 +1,19 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ProgressBar { + id: root + + implicitHeight: 4 + + background: Rectangle { + color: "#412102" + } + + contentItem: Rectangle { + width: root.visualPosition * root.width + height: root.height + color: "#FBB26A" + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8edd6b124..a29a40891 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -20,8 +20,10 @@ Item { property string borderColor: "#2C2D30" - property string currentServerName: serversMenuContent.currentItem.delegateData.desc - property string currentServerDescription: serversMenuContent.currentItem.delegateData.address + property string currentServerName: serversMenuContent.currentItem.delegateData.name + property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName + + property string currentContainerName ConnectButton { anchors.centerIn: parent @@ -72,7 +74,7 @@ Item { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerDescription + text: currentContainerName + " | " + currentServerHostName } } @@ -127,7 +129,7 @@ Item { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerDescription + text: currentServerHostName } RowLayout { @@ -150,16 +152,21 @@ Item { implicitHeight: 40 - borderWidth: 0 - buttonImageColor: "#0E0E11" - buttonMaximumWidth: 150 //todo make it dynamic - - defaultColor: "#D7D8DB" + rootButtonBorderWidth: 0 + rootButtonImageColor: "#0E0E11" + rootButtonMaximumWidth: 150 //todo make it dynamic + rootButtonDefaultColor: "#D7D8DB" textColor: "#0E0E11" headerText: "Протокол подключения" headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + onRootButtonClicked: function() { + ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + containersDropDown.menuVisible = true + } + menuModel: proxyContainersModel ButtonGroup { @@ -265,9 +272,11 @@ Item { modelData.isDefault = true containersDropDown.text = containerRadioButtonText.text + root.currentContainerName = containerRadioButtonText.text containersDropDown.menuVisible = false } else { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) + InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false @@ -307,6 +316,10 @@ Item { actionButtonImage: "qrc:/images/controls/plus.svg" headerText: "Серверы" + + actionButtonFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardStart) + } } } @@ -378,7 +391,7 @@ Item { Text { id: serverRadioButtonText - text: desc + text: name color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 @@ -402,11 +415,11 @@ Item { } onClicked: { - root.currentServerName = desc - root.currentServerDescription = address + serversMenuContent.currentIndex = index + root.currentServerName = name + root.currentServerHostName = hostName ServersModel.setDefaultServerIndex(index) - ContainersModel.setSelectedServerIndex(index) } MouseArea { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 479e4bfad..def76b89e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -38,18 +38,25 @@ Item { } TextFieldWithHeaderType { + id: hostname + Layout.fillWidth: true headerText: "Server IP adress [:port]" } TextFieldWithHeaderType { + id: username + Layout.fillWidth: true headerText: "Login to connect via SSH" } TextFieldWithHeaderType { + id: secretData + Layout.fillWidth: true headerText: "Password / Private key" + textField.echoMode: TextInput.Password } BasicButtonType { @@ -77,6 +84,9 @@ Item { text: qsTr("Выбрать протокол для установки") onClicked: function() { + InstallController.setShouldCreateServer(true) + InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index fa0d5a146..c2c761cce 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 import "./" @@ -12,32 +14,111 @@ import "../Config" Item { id: root + property real progressBarValue: 0 + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyInstalled" + value: true + } + ] + } + FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.fill: parent contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 - HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + ListView { + // todo change id naming + id: container + width: parent.width + height: container.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel - //TODO remove later - backButtonImage: "qrc:/images/controls/arrow-left.svg" + delegate: Item { + implicitWidth: container.width + implicitHeight: delegateContent.implicitHeight - headerText: "Установка" - descriptionText: ContainersModel.getCurrentlyInstalledContainerName() + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: "Установка" + descriptionText: name + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + value: progressBarValue + + Timer { + id: timer + + interval: 300 + repeat: true + running: true + onTriggered: { + progressBarValue += 0.001 + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: "Обычно это занимает не больше 5 минут" + } + } + } + } + } + + Timer { + id: closePageTimer + + interval: 1000 + repeat: false + running: false + onTriggered: { + // todo go to root installing page + PageController.goToPage(PageEnum.PageHome) + } + } + + Connections { + target: InstallController + + function onInstallContainerFinished() { + progressBarValue = 1 + closePageTimer.start() } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 6c3b64b9c..9499f5a55 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -2,7 +2,11 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 import "./" import "../Controls2" @@ -12,83 +16,169 @@ import "../Config" Item { id: root + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyInstalled" + value: true + } + ] + } + FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.fill: parent contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 - HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + ListView { + // todo change id naming + id: containers + width: parent.width + height: containers.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel - backButtonImage: "qrc:/images/controls/arrow-left.svg" + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight - headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() - descriptionText: "Эти настройки можно будет изменить позже" - } + ColumnLayout { + id: delegateContent - ParagraphTextType { - Layout.topMargin: 16 + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - text: "Network protocol" - } + HeaderType { + id: header - //TODO move to separete control - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight + Layout.fillWidth: true + Layout.topMargin: 20 - color: "#1C1D21" - radius: 16 + backButtonImage: "qrc:/images/controls/arrow-left.svg" - RowLayout { - id: buttonGroup + headerText: "Установка " + name + descriptionText: "Эти настройки можно будет изменить позже" + } - spacing: 0 + ParagraphTextType { + id: transportProtoHeader - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "UDP" - } + Layout.topMargin: 16 - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "TCP" + text: "Network protocol" + } + + Rectangle { + id: transportProtoBackground + + implicitWidth: transportProtoButtonGroup.implicitWidth + implicitHeight: transportProtoButtonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: transportProtoButtonGroup + + property int currentIndex + spacing: 0 + + HorizontalRadioButton { + checked: transportProtoButtonGroup.currentIndex === 0 + + implicitWidth: (root.width - 32) / 2 + text: "UDP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + transportProtoButtonGroup.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: transportProtoButtonGroup.currentIndex === 1 + + implicitWidth: (root.width - 32) / 2 + text: "TCP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + transportProtoButtonGroup.currentIndex = 1 + } + } + } + + MouseArea { + id: transportProtoButtonMouseArea + + anchors.fill: parent + } + } + + TextFieldWithHeaderType { + id: port + + Layout.fillWidth: true + headerText: "Port" + } + + Rectangle { + // todo make it dynamic + implicitHeight: root.height - port.implicitHeight - + transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - + header.implicitHeight - installButton.implicitHeight - 100 + + color: "transparent" + } + + BasicButtonType { + id: installButton + + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Установить") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + + InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) + } + } + + Component.onCompleted: { + //todo move to protocols model? + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { + port.visible = false + } else { + port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) + } + transportProtoButtonGroup.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + + port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) + transportProtoButtonMouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + } } } } - - TextFieldWithHeaderType { - Layout.fillWidth: true - headerText: "Port" - } - } - } - - BasicButtonType { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - anchors.bottomMargin: 32 - - text: qsTr("Установить") - - onClicked: function() { - PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 24a86a072..11b09edd2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -18,10 +18,6 @@ Item { id: proxyContainersModel sourceModel: ContainersModel filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false - }, ValueFilter { roleName: "service_type_role" value: ProtocolEnum.Vpn diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 5f1a0479b..8293b210f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -96,7 +96,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: "#2C2D30" border.width: 1 } From 04791139494edf5ee9240cb01041afa2d4822068 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 22 May 2023 00:10:51 +0800 Subject: [PATCH 020/131] moved ContainersPageHomeListView and ConnectionTypeSelectionDrawer to separate components --- client/amnezia_application.h | 2 + client/containers/containers_defs.cpp | 30 ++++ client/containers/containers_defs.h | 4 + client/core/defs.h | 4 +- client/core/sshclient.cpp | 8 +- client/resources.qrc | 2 + client/settings.cpp | 4 +- client/ui/controllers/installController.cpp | 16 +- client/ui/controllers/installController.h | 2 +- client/ui/models/containers_model.cpp | 24 ++- client/ui/models/containers_model.h | 16 +- client/ui/pages_logic/StartPageLogic.cpp | 8 +- .../ConnectionTypeSelectionDrawer.qml | 87 +++++++++ .../Components/ContainersPageHomeListView.qml | 119 +++++++++++++ client/ui/qml/Controls2/DropDownType.qml | 30 +--- client/ui/qml/Pages2/PageHome.qml | 168 +++--------------- .../qml/Pages2/PageSetupWizardCredentials.qml | 5 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 109 +++++++++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 1 - .../qml/Pages2/PageSetupWizardProtocols.qml | 11 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 81 +-------- client/ui/uilogic.cpp | 4 +- 23 files changed, 443 insertions(+), 294 deletions(-) create mode 100644 client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml create mode 100644 client/ui/qml/Components/ContainersPageHomeListView.qml diff --git a/client/amnezia_application.h b/client/amnezia_application.h index e21139081..97b16f13f 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -18,6 +18,7 @@ #include "ui/models/containers_model.h" #include "ui/controllers/connectionController.h" #include "ui/controllers/pageController.h" +#include "ui/controllers/installController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -69,6 +70,7 @@ private: QScopedPointer m_connectionController; QScopedPointer m_pageController; + QScopedPointer m_installController; }; diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index cca77e7db..7c7c000b8 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -188,3 +188,33 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) default: return {}; } } + +bool ContainerProps::isEasySetupContainer(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return true; + case DockerContainer::Cloak : return true; + case DockerContainer::ShadowSocks : return true; + default: return false; + } +} + +QString ContainerProps::easySetupHeader(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return tr("Low"); + case DockerContainer::Cloak : return tr("High"); + case DockerContainer::ShadowSocks : return tr("Medium"); + default: return ""; + } +} + +QString ContainerProps::easySetupDescription(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::Cloak : return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::ShadowSocks : return tr("I just want to increase the level of privacy"); + default: return ""; + } +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index ff230c3e9..d4a35d265 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -57,6 +57,10 @@ public: Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + + static bool isEasySetupContainer(amnezia::DockerContainer container); + static QString easySetupHeader(amnezia::DockerContainer container); + static QString easySetupDescription(amnezia::DockerContainer container); }; diff --git a/client/core/defs.h b/client/core/defs.h index 452038a51..3a3ff5653 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -12,10 +12,10 @@ struct ServerCredentials { QString hostName; QString userName; - QString password; + QString secretData; int port = 22; - bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; } + bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; } }; enum ErrorCode diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 795af965d..0367dc1f5 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -55,10 +55,10 @@ namespace libssh { std::string authUsername = credentials.userName.toStdString(); int authResult = SSH_ERROR; - if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) { ssh_key privateKey = nullptr; ssh_key publicKey = nullptr; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey); } @@ -86,7 +86,7 @@ namespace libssh { return errorCode; } } else { - authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); + authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { qDebug() << ssh_get_error(m_session); return fromLibsshErrorCode(ssh_get_error_code(m_session)); @@ -354,7 +354,7 @@ namespace libssh { ssh_key privateKey = nullptr; m_passphraseCallback = passphraseCallback; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { char* key = new char[65535]; diff --git a/client/resources.qrc b/client/resources.qrc index a50a23c53..ff7139a80 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,5 +228,7 @@ images/connectionOn.svg images/controls/download.svg ui/qml/Controls2/ProgressBarType.qml + ui/qml/Components/ConnectionTypeSelectionDrawer.qml + ui/qml/Components/ContainersPageHomeListView.qml diff --git a/client/settings.cpp b/client/settings.cpp index 135e87bab..a978d4086 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -185,7 +185,7 @@ bool Settings::haveAuthData(int serverIndex) const { if (serverIndex < 0) return false; ServerCredentials cred = serverCredentials(serverIndex); - return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty()); + return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.secretData.isEmpty()); } QString Settings::nextAvailableServerName() const @@ -321,7 +321,7 @@ ServerCredentials Settings::serverCredentials(int index) const ServerCredentials credentials; credentials.hostName = s.value(config_key::hostName).toString(); credentials.userName = s.value(config_key::userName).toString(); - credentials.password = s.value(config_key::password).toString(); + credentials.secretData = s.value(config_key::password).toString(); credentials.port = s.value(config_key::port).toInt(); return credentials; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index ae7533822..3e4809e64 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -41,7 +41,7 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec QJsonObject server; server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); - server.insert(config_key::password, m_currentlyInstalledServerCredentials.password); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); @@ -50,8 +50,12 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); + + //todo change to server finished + emit installContainerFinished(); } + //todo error processing return errorCode; } @@ -71,9 +75,15 @@ ErrorCode InstallController::installContainer(DockerContainer container, QJsonOb return errorCode; } -void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port) +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) { - m_currentlyInstalledServerCredentials = { hostName, userName, password, port }; + m_currentlyInstalledServerCredentials.hostName = hostName; + if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { + m_currentlyInstalledServerCredentials.port = m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); + m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); + } + m_currentlyInstalledServerCredentials.userName = userName; + m_currentlyInstalledServerCredentials.secretData = secretData; } void InstallController::setShouldCreateServer(bool shouldCreateServer) diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index f9cd9fb0a..c0d6ad20a 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -19,7 +19,7 @@ public: public slots: ErrorCode install(DockerContainer container, int port, TransportProto transportProto); - void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); signals: diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index a814a838a..6a999011c 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -33,6 +33,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); case IsDefaultRole: m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + emit defaultContainerChanged(); } emit dataChanged(index, index); @@ -59,12 +60,20 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::containerService(container); case DockerContainerRole: return container; + case IsEasySetupContainerRole: + return ContainerProps::isEasySetupContainer(container); + case EasySetupHeaderRole: + return ContainerProps::easySetupHeader(container); + case EasySetupDescriptionRole: + return ContainerProps::easySetupDescription(container); case IsInstalledRole: return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsCurrentlyInstalled: + case IsCurrentlyInstalledRole: return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); + case IsSupportedRole: + return ContainerProps::isSupportedByCurrentPlatform(container); } return QVariant(); @@ -87,6 +96,11 @@ DockerContainer ContainersModel::getDefaultContainer() return m_settings->defaultContainer(m_currentlyProcessedServerIndex); } +QString ContainersModel::getDefaultContainerName() +{ + return ContainerProps::containerHumanNames().value(getDefaultContainer()); +} + int ContainersModel::getCurrentlyInstalledContainerIndex() { return m_currentlyInstalledContainerIndex; @@ -98,8 +112,14 @@ QHash ContainersModel::roleNames() const { roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; + + roles[IsEasySetupContainerRole] = "isEasySetupContainer"; + roles[EasySetupHeaderRole] = "easySetupHeader"; + roles[EasySetupDescriptionRole] = "easySetupDescription"; + roles[IsInstalledRole] = "isInstalled"; - roles[IsCurrentlyInstalled] = "isCurrentlyInstalled"; + roles[IsCurrentlyInstalledRole] = "isCurrentlyInstalled"; roles[IsDefaultRole] = "isDefault"; + roles[IsSupportedRole] = "isSupported"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 150129255..36b5567a7 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -14,16 +14,22 @@ class ContainersModel : public QAbstractListModel Q_OBJECT public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); -public: + enum Roles { NameRole = Qt::UserRole + 1, DescRole, ServiceTypeRole, ConfigRole, DockerContainerRole, + + IsEasySetupContainerRole, + EasySetupHeaderRole, + EasySetupDescriptionRole, + IsInstalledRole, - IsCurrentlyInstalled, - IsDefaultRole + IsCurrentlyInstalledRole, + IsDefaultRole, + IsSupportedRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -31,8 +37,12 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +signals: + void defaultContainerChanged(); + public slots: DockerContainer getDefaultContainer(); + QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(int index); void setCurrentlyInstalledContainerIndex(int index); diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 8d9c33f6e..86d972d1c 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -123,9 +123,9 @@ void StartPageLogic::onPushButtonConnect() key = m_configurator->sshConfigurator->convertOpenSShKey(key); } - serverCredentials.password = key; + serverCredentials.secretData = key; } else { - serverCredentials.password = lineEditPasswordText(); + serverCredentials.secretData = lineEditPasswordText(); } set_pushButtonConnectEnabled(false); @@ -147,7 +147,7 @@ void StartPageLogic::onPushButtonConnect() QString decryptedPrivateKey; errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); if (errorCode == ErrorCode::NoError) { - serverCredentials.password = decryptedPrivateKey; + serverCredentials.secretData = decryptedPrivateKey; } } @@ -220,7 +220,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile) credentials.hostName = profile.value(config_key::hostName).toString(); credentials.port = profile.value(config_key::port).toInt(); credentials.userName = profile.value(config_key::userName).toString(); - credentials.password = profile.value(config_key::password).toString(); + credentials.secretData = profile.value(config_key::password).toString(); if (credentials.isValid() || profile.contains(config_key::containers)) { // check config diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml new file mode 100644 index 000000000..3ae5b2781 --- /dev/null +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +Drawer { + id: root + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: "#2C2D30" + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + wrapMode: Text.WordWrap + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + root.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + root.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } +} diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml new file mode 100644 index 000000000..b3d34b62d --- /dev/null +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -0,0 +1,119 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: menuContent + + property var rootWidth + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + + ButtonGroup { + id: containersRadioButtonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: containerRadioButton.implicitHeight + + RadioButton { + id: containerRadioButton + + implicitWidth: parent.width + implicitHeight: containerRadioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: containersRadioButtonGroup + + checked: isDefault + + indicator: Rectangle { + anchors.fill: parent + color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: isInstalled + + RowLayout { + id: containerRadioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Image { + source: isInstalled ? "qrc:/images/controls/check.svg" : "qrc:/images/controls/download.svg" + visible: isInstalled ? containerRadioButton.checked : true + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + + Text { + id: containerRadioButtonText + + text: name + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + } + + onClicked: { + if (checked) { + isDefault = true + menuContent.currentIndex = index + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + Component.onCompleted: { + if (isDefault) { + root.currentContainerName = name + } + } + } +} + diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index e2a3531f5..2b8986b55 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,8 +24,7 @@ Item { property string rootButtonBorderColor: "#494B50" property int rootButtonBorderWidth: 1 - property Component menuDelegate - property variant menuModel + property Component listView property alias menuVisible: menu.visible @@ -169,30 +168,9 @@ Item { spacing: 16 - ButtonGroup { - id: radioButtonGroup - } - - ListView { - id: menuContent - width: parent.width - height: menuContent.contentItem.height - - currentIndex: -1 - - clip: true - interactive: false - - model: root.menuModel - - delegate: Row { - Loader { - id: loader - sourceComponent: root.menuDelegate - property QtObject modelData: model - property var delegateIndex: index - } - } + Loader { + id: listViewLoader + sourceComponent: root.listView } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a29a40891..a04b17444 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerProps 1.0 import "./" import "../Controls2" @@ -22,21 +23,28 @@ Item { property string currentServerName: serversMenuContent.currentItem.delegateData.name property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName - property string currentContainerName ConnectButton { anchors.centerIn: parent } + Connections { + target: ContainersModel + + function onDefaultContainerChanged() { + root.currentContainerName = ContainersModel.getDefaultContainerName() + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent anchors.bottomMargin: -radius radius: 16 - color: defaultColor - border.color: borderColor + color: root.defaultColor + border.color: root.borderColor border.width: 1 Rectangle { @@ -44,7 +52,7 @@ Item { height: 1 y: parent.height - height - parent.radius - color: borderColor + color: root.borderColor } } @@ -59,7 +67,7 @@ Item { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Header1TextType { - text: currentServerName + text: root.currentServerName } Image { @@ -74,7 +82,7 @@ Item { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentContainerName + " | " + currentServerHostName + text: root.currentContainerName + " | " + root.currentServerHostName } } @@ -104,7 +112,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: root.borderColor border.width: 1 } @@ -122,14 +130,14 @@ Item { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerName + text: root.currentServerName } LabelTextType { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerHostName + text: root.currentServerHostName } RowLayout { @@ -157,6 +165,7 @@ Item { rootButtonMaximumWidth: 150 //todo make it dynamic rootButtonDefaultColor: "#D7D8DB" + text: root.currentContainerName textColor: "#0E0E11" headerText: "Протокол подключения" headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -167,134 +176,11 @@ Item { containersDropDown.menuVisible = true } - menuModel: proxyContainersModel + listView: ContainersPageHomeListView { + rootWidth: root.width - ButtonGroup { - id: containersRadioButtonGroup - } - - menuDelegate: Item { - implicitWidth: root.width - implicitHeight: containerRadioButton.implicitHeight - - RadioButton { - id: containerRadioButton - - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight - - hoverEnabled: true - - ButtonGroup.group: containersRadioButtonGroup - - checked: { - if (modelData !== null) { - return modelData.isDefault - } - return false - } - - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - checkable: { - if (modelData !== null) { - if (modelData.isInstalled) { - return true - } - } - return false - } - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: { - if (modelData !== null) { - if (modelData.isInstalled) { - return "qrc:/images/controls/check.svg" - } - } - return "qrc:/images/controls/download.svg" - } - visible: { - if (modelData !== null) { - if (modelData.isInstalled) { - return containerRadioButton.checked - } - } - return true - } - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: containerRadioButtonText - - text: { - if (modelData !== null) { - return modelData.name - } else - return "" - } - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - } - - onClicked: { - if (checked) { - modelData.isDefault = true - - containersDropDown.text = containerRadioButtonText.text - root.currentContainerName = containerRadioButtonText.text - containersDropDown.menuVisible = false - } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) - InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.menuVisible = false - menu.visible = false - } - } - - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - Component.onCompleted: { - if (modelData !== null && modelData.isDefault) { - containersDropDown.text = modelData.name - } - } + model: proxyContainersModel + currentIndex: ContainersModel.getDefaultContainer() } } @@ -318,9 +204,14 @@ Item { headerText: "Серверы" actionButtonFunction: function() { - PageController.goToPage(PageEnum.PageSetupWizardStart) + menu.visible = false + connectionTypeSelection.visible = true } } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } } FlickableType { @@ -416,10 +307,9 @@ Item { onClicked: { serversMenuContent.currentIndex = index - root.currentServerName = name - root.currentServerHostName = hostName ServersModel.setDefaultServerIndex(index) + ContainersModel.setCurrentlyProcessedServerIndex(index) } MouseArea { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index def76b89e..20c6e735e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -41,7 +41,7 @@ Item { id: hostname Layout.fillWidth: true - headerText: "Server IP adress [:port]" + headerText: "Server IP address [:port]" } TextFieldWithHeaderType { @@ -66,6 +66,9 @@ Item { text: qsTr("Настроить сервер простым образом") onClicked: function() { + InstallController.setShouldCreateServer(true) + InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 664f4de7f..9f53a2400 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -2,7 +2,11 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 import "./" import "../Controls2" @@ -11,13 +15,28 @@ import "../Config" Item { id: root + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isEasySetupContainer" + value: true + } + ] + sorters: RoleSorter { + roleName: "dockerContainer" + sortOrder: Qt.DescendingOrder + } + } + FlickableType { id: fl anchors.top: root.top anchors.bottom: root.bottom contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top @@ -25,47 +44,91 @@ Item { anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 + anchors.topMargin: 20 spacing: 16 HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + implicitWidth: parent.width + anchors.topMargin: 20 backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Какой уровень контроля интернета в вашем регионе?" + headerText: qsTr("What is the level of Internet control in your region?") } - CardType { - Layout.fillWidth: true + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + spacing: 16 - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - } + currentIndex: 1 + clip: true + interactive: false + model: proxyContainersModel - CardType { - Layout.fillWidth: true + property int dockerContainer + property int containerDefaultPort + property int containerDefaultTransportProto - checked: true + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight - headerText: "Средний" - bodyText: "Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются" - } + ColumnLayout { + id: delegateContent - CardType { - Layout.fillWidth: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - headerText: "Низкий" - bodyText: "Хочу просто повысить уровень приватности" + CardType { + id: card + + Layout.fillWidth: true + + headerText: easySetupHeader + bodyText: easySetupDescription + + ButtonGroup.group: buttonGroup + + onClicked: function() { + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + containers.dockerContainer = dockerContainer + containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto) + containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) + } + } + } + + Component.onCompleted: { + if (index === containers.currentIndex) { + card.checked = true + card.clicked() + } + } + } + + ButtonGroup { + id: buttonGroup + } } BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 32 + implicitWidth: parent.width + anchors.topMargin: 24 + anchors.bottomMargin: 32 - text: qsTr("Продолжить") + text: qsTr("Continue") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(containers.dockerContainer, + containers.containerDefaultPort, + containers.containerDefaultTransportProto) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index c2c761cce..b4f44fd91 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -109,7 +109,7 @@ Item { running: false onTriggered: { // todo go to root installing page - PageController.goToPage(PageEnum.PageHome) + PageController.goToPage(PageEnum.PageStart) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 9499f5a55..e79176276 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -157,7 +157,6 @@ Item { onClicked: function() { PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 11b09edd2..f7cd382fb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -19,9 +19,14 @@ Item { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "service_type_role" + roleName: "serviceType" value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true } + ] } @@ -77,8 +82,8 @@ Item { Layout.topMargin: 16 Layout.bottomMargin: 16 - text: name_role - descriptionText: desc_role + text: name + descriptionText: description buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 8293b210f..b7d56ce53 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" Item { id: root @@ -55,7 +56,7 @@ Item { text: qsTr("У меня есть данные для подключения") onClicked: { - drawer.visible = true + connectionTypeSelection.visible = true } } @@ -80,82 +81,8 @@ Item { } } - Drawer { - id: drawer - - edge: Qt.BottomEdge - width: parent.width - height: parent.height * 0.4375 - - clip: true - modal: true - - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#2C2D30" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter - - text: "Данные для подключения" - wrapMode: Text.WordWrap - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 32 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardConfigSource) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - } + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 3ad74645a..1cb43ee86 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -350,7 +350,7 @@ void UiLogic::installServer(QPair &container) QJsonObject server; server.insert(config_key::hostName, m_installCredentials.hostName); server.insert(config_key::userName, m_installCredentials.userName); - server.insert(config_key::password, m_installCredentials.password); + server.insert(config_key::password, m_installCredentials.secretData); server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); @@ -574,7 +574,7 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) if (createNewServer) { server.insert(config_key::hostName, installCredentials.hostName); server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.password); + server.insert(config_key::password, installCredentials.secretData); server.insert(config_key::port, installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); } From ca6b7fbeb260fd1c61cf959eec7d16c806ba228d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 22 May 2023 22:11:20 +0800 Subject: [PATCH 021/131] added importController --- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 2 + client/ui/controllers/importController.cpp | 204 ++++++++++++++++++ client/ui/controllers/importController.h | 37 ++++ .../Pages2/PageSetupWizardConfigSource.qml | 12 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 1 + 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 client/ui/controllers/importController.cpp create mode 100644 client/ui/controllers/importController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index df4a4ec1d..9fdddc865 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -124,6 +124,9 @@ void AmneziaApplication::init() m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + // m_uiLogic->registerPagesLogic(); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 97b16f13f..f4156d159 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/connectionController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/installController.h" +#include "ui/controllers/importController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -71,6 +72,7 @@ private: QScopedPointer m_connectionController; QScopedPointer m_pageController; QScopedPointer m_installController; + QScopedPointer m_importController; }; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp new file mode 100644 index 000000000..ea1e7ad62 --- /dev/null +++ b/client/ui/controllers/importController.cpp @@ -0,0 +1,204 @@ +#include "importController.h" + +#include + +namespace { + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; + + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; + + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) && + (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && + (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } + return ConfigTypes::Amnezia; + } +} + +ImportController::ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ + +} + +bool ImportController::importFromFile(const QUrl &fileUrl) +{ + QFile file(fileUrl.toLocalFile()); + if (file.open(QIODevice::ReadOnly)) { + QByteArray data = file.readAll(); + + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + return importOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + return importWireGuardConfig(data); + } else { + return importAmneziaConfig(data); + } + } + return false; +} + +bool ImportController::import(const QJsonObject &config) +{ + ServerCredentials credentials; + credentials.hostName = config.value(config_key::hostName).toString(); + credentials.port = config.value(config_key::port).toInt(); + credentials.userName = config.value(config_key::userName).toString(); + credentials.secretData = config.value(config_key::password).toString(); + + if (credentials.isValid() || config.contains(config_key::containers)) { + m_settings->addServer(config); + + if (config.value(config_key::containers).toArray().isEmpty()) { + m_settings->setDefaultServer(m_settings->serversCount() - 1); + } + + emit importFinished(); + } else { + qDebug() << "Failed to import profile"; + qDebug().noquote() << QJsonDocument(config).toJson(); + return false; + } + + return true; +} + +bool ImportController::importAmneziaConfig(QString data) +{ + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QJsonObject config; + config = QJsonDocument::fromJson(ba).object(); + if (!config.isEmpty()) { + return import(config); + } + + return false; +} + +//bool ImportController::importConnectionFromQr(const QByteArray &data) +//{ +// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); +// if (!dataObj.isEmpty()) { +// return importConnection(dataObj); +// } + +// QByteArray ba_uncompressed = qUncompress(data); +// if (!ba_uncompressed.isEmpty()) { +// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); +// } + +// return false; +//} + +bool ImportController::importOpenVpnConfig(const QString &data) +{ + QJsonObject openVpnConfig; + openVpnConfig[config_key::config] = data; + + QJsonObject lastConfig; + lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); + lastConfig[config_key::isThirdPartyConfig] = true; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); + containers.insert(config_key::openvpn, QJsonValue(lastConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-openvpn"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + + const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data); + if (dnsMatch.hasNext()) { + config[config_key::dns1] = dnsMatch.next().captured(1); + } + if (dnsMatch.hasNext()) { + config[config_key::dns2] = dnsMatch.next().captured(1); + } + + config[config_key::hostName] = hostName; + + return import(config); +} + +bool ImportController::importWireGuardConfig(const QString &data) +{ + QJsonObject lastConfig; + lastConfig[config_key::config] = data; + + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); + QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); + QString hostName; + QString port; + if (hostNameAndPortMatch.hasMatch()) { + hostName = hostNameAndPortMatch.captured(1); + port = hostNameAndPortMatch.captured(2); + } + + QJsonObject wireguardConfig; + wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); + wireguardConfig[config_key::isThirdPartyConfig] = true; + wireguardConfig[config_key::port] = port; + wireguardConfig[config_key::transport_proto] = "udp"; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); + containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-wireguard"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); + if (dnsMatch.hasMatch()) { + config[config_key::dns1] = dnsMatch.captured(1); + config[config_key::dns2] = dnsMatch.captured(2); + } + + config[config_key::hostName] = hostName; + + return import(config); +} diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h new file mode 100644 index 000000000..7dd548d87 --- /dev/null +++ b/client/ui/controllers/importController.h @@ -0,0 +1,37 @@ +#ifndef IMPORTCONTROLLER_H +#define IMPORTCONTROLLER_H + +#include + +#include "core/defs.h" +#include "containers/containers_defs.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class ImportController : public QObject +{ + Q_OBJECT +public: + explicit ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + bool importFromFile(const QUrl &fileUrl); + +signals: + void importFinished(); +private: + bool import(const QJsonObject &config); + bool importAmneziaConfig(QString data); + bool importOpenVpnConfig(const QString &data); + bool importWireGuardConfig(const QString &data); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + +}; + +#endif // IMPORTCONTROLLER_H diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index db2730aca..5f2bf2f35 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,6 +13,14 @@ import "../Config" Item { id: root + Connections { + target: ImportController + + function onImportFinished() { + + } + } + FlickableType { id: fl anchors.top: root.top @@ -63,7 +71,7 @@ Item { FileDialog { id: fileDialog onAccepted: { - + ImportController.importFromFile(selectedFile) } } } @@ -84,6 +92,7 @@ Item { onClickedFunc: function() { } } + Rectangle { Layout.fillWidth: true height: 1 @@ -101,6 +110,7 @@ Item { PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } + Rectangle { Layout.fillWidth: true height: 1 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 9f53a2400..9555c915c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -124,6 +124,7 @@ Item { text: qsTr("Continue") onClicked: function() { + ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, From e00656d757646e5fa9c7339fde611969e04d8deb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 25 May 2023 15:40:17 +0800 Subject: [PATCH 022/131] added PageSettings and PageSettingsServersList. - replaced PageLoader with PageType with stackView property. - added error handling when installing a server/container --- client/images/controls/settings.svg | 4 + client/resources.qrc | 8 +- client/ui/controllers/installController.cpp | 22 ++-- client/ui/controllers/installController.h | 9 +- client/ui/controllers/pageController.cpp | 8 +- client/ui/controllers/pageController.h | 6 +- client/ui/models/servers_model.cpp | 10 +- client/ui/models/servers_model.h | 6 +- client/ui/pages.h | 21 ++-- .../ConnectionTypeSelectionDrawer.qml | 8 +- .../Components/ContainersPageHomeListView.qml | 2 +- client/ui/qml/Controls2/Header2Type.qml | 2 +- client/ui/qml/Controls2/HeaderType.qml | 6 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 6 +- client/ui/qml/Controls2/PageType.qml | 31 +++++ client/ui/qml/Controls2/PopupType.qml | 61 ++++++++++ .../Controls2/TextTypes/CaptionTextType.qml | 13 ++ client/ui/qml/PageLoader.qml | 37 ------ client/ui/qml/Pages2/PageHome.qml | 35 ++++-- client/ui/qml/Pages2/PageSettings.qml | 68 ++++++++++- .../ui/qml/Pages2/PageSettingsServersList.qml | 111 ++++++++++++++++++ .../Pages2/PageSetupWizardConfigSource.qml | 12 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 11 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 32 ++--- .../PageSetupWizardProtocolSettings.qml | 4 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 36 +++++- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 39 +++++- client/ui/qml/main2.qml | 4 +- 31 files changed, 486 insertions(+), 142 deletions(-) create mode 100644 client/images/controls/settings.svg create mode 100644 client/ui/qml/Controls2/PageType.qml create mode 100644 client/ui/qml/Controls2/PopupType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/CaptionTextType.qml delete mode 100644 client/ui/qml/PageLoader.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServersList.qml diff --git a/client/images/controls/settings.svg b/client/images/controls/settings.svg new file mode 100644 index 000000000..0693fb4e2 --- /dev/null +++ b/client/images/controls/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ff7139a80..ca1674af8 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -184,7 +184,6 @@ ui/qml/Controls2/DropDownType.qml ui/qml/Pages2/PageSetupWizardStart.qml ui/qml/main2.qml - ui/qml/PageLoader.qml images/amneziaBigLogo.png images/amneziaBigLogo.svg ui/qml/Controls2/FlickableType.qml @@ -215,7 +214,7 @@ images/controls/settings-2.svg images/controls/share-2.svg ui/qml/Pages2/PageHome.qml - ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageSettingsServersList.qml ui/qml/Pages2/PageShare.qml ui/qml/Controls2/TextTypes/Header1TextType.qml ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -230,5 +229,10 @@ ui/qml/Controls2/ProgressBarType.qml ui/qml/Components/ConnectionTypeSelectionDrawer.qml ui/qml/Components/ContainersPageHomeListView.qml + ui/qml/Controls2/TextTypes/CaptionTextType.qml + images/controls/settings.svg + ui/qml/Pages2/PageSettings.qml + ui/qml/Controls2/PageType.qml + ui/qml/Controls2/PopupType.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 3e4809e64..31f58a2a4 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -3,6 +3,7 @@ #include #include "core/servercontroller.h" +#include "core/errorstrings.h" InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -12,7 +13,7 @@ InstallController::InstallController(const QSharedPointer &servers } -ErrorCode InstallController::install(DockerContainer container, int port, TransportProto transportProto) +void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); @@ -26,13 +27,13 @@ ErrorCode InstallController::install(DockerContainer container, int port, Transp }; if (m_shouldCreateServer) { - return installServer(container, config); + installServer(container, config); } else { - return installContainer(container, config); + installContainer(container, config); } } -ErrorCode InstallController::installServer(DockerContainer container, QJsonObject& config) +void InstallController::installServer(DockerContainer container, QJsonObject& config) { //todo check if container already installed ServerController serverController(m_settings); @@ -51,15 +52,14 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); - //todo change to server finished - emit installContainerFinished(); + emit installServerFinished(); + return; } - //todo error processing - return errorCode; + emit installationErrorOccurred(errorString(errorCode)); } -ErrorCode InstallController::installContainer(DockerContainer container, QJsonObject& config) +void InstallController::installContainer(DockerContainer container, QJsonObject& config) { //todo check if container already installed ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); @@ -69,10 +69,10 @@ ErrorCode InstallController::installContainer(DockerContainer container, QJsonOb if (errorCode == ErrorCode::NoError) { m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); emit installContainerFinished(); + return; } - //todo error processing - return errorCode; + emit installationErrorOccurred(errorString(errorCode)); } void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index c0d6ad20a..d9e1ad95d 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -18,15 +18,18 @@ public: QObject *parent = nullptr); public slots: - ErrorCode install(DockerContainer container, int port, TransportProto transportProto); + void install(DockerContainer container, int port, TransportProto transportProto); void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); signals: void installContainerFinished(); + void installServerFinished(); + + void installationErrorOccurred(QString errorMessage); private: - ErrorCode installServer(DockerContainer container, QJsonObject& config); - ErrorCode installContainer(DockerContainer container, QJsonObject& config); + void installServer(DockerContainer container, QJsonObject& config); + void installContainer(DockerContainer container, QJsonObject& config); QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 101207672..63c3ba589 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -5,15 +5,15 @@ PageController::PageController(const QSharedPointer &serversModel, { } -void PageController::setStartPage() +QString PageController::getInitialPage() { if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { m_serversModel->setDefaultServerIndex(0); } - emit goToPage(PageLoader::PageEnum::PageStart, false); + return getPagePath(PageLoader::PageEnum::PageStart); } else { - emit goToPage(PageLoader::PageEnum::PageSetupWizardStart, false); + return getPagePath(PageLoader::PageEnum::PageSetupWizardStart); } } @@ -21,5 +21,5 @@ QString PageController::getPagePath(PageLoader::PageEnum page) { QMetaEnum metaEnum = QMetaEnum::fromType(); QString pageName = metaEnum.valueToKey(static_cast(page)); - return "Pages2/" + pageName + ".qml"; + return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 9bfd6bda4..f37edca9b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,12 +36,12 @@ public: QObject *parent = nullptr); public slots: - void setStartPage(); + QString getInitialPage(); QString getPagePath(PageLoader::PageEnum page); signals: - void goToPage(PageLoader::PageEnum page, bool slide = true); - void closePage(); + void goToPageHome(); + void showErrorMessage(QString errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index fe973811e..884009e8c 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -51,6 +51,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(m_settings->serverCredentials(index.row())); case IsDefaultRole: return index.row() == m_settings->defaultServerIndex(); + case IsCurrentlyProcessedRole: + return index.row() == m_currenlyProcessedServerIndex; } return QVariant(); @@ -59,7 +61,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const //todo mode to setData? void ServersModel::setDefaultServerIndex(int index) { -// beginResetModel(); + // beginResetModel(); m_settings->setDefaultServer(index); // endResetModel(); } @@ -84,11 +86,17 @@ ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); } +void ServersModel::addServer() +{ + +} + QHash ServersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; roles[CredentialsRole] = "credentials"; roles[IsDefaultRole] = "isDefault"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 08b449761..ae1ec8d95 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -19,7 +19,8 @@ public: NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, - IsDefaultRole + IsDefaultRole, + IsCurrentlyProcessedRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -32,11 +33,14 @@ public: public slots: void setDefaultServerIndex(int index); const int getDefaultServerIndex(); + const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); ServerCredentials getCurrentlyProcessedServerCredentials(); + void addServer(); + protected: QHash roleNames() const override; diff --git a/client/ui/pages.h b/client/ui/pages.h index b2e191c7e..82c0d4095 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -21,18 +21,19 @@ public: namespace PageEnumNS { Q_NAMESPACE -enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, - Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, - GeneralSettings, AppSettings, NetworkSettings, ServerSettings, - ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo, +enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn, + Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, + GeneralSettings, AppSettings, NetworkSettings, ServerSettings, + ServerContainers, ServersList, ShareConnection, Sites, + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, + AdvancedServerSettings, ClientManagement, ClientInfo, - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey, + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - PageStart, PageHome, PageSettings, PageShare}; + PageSettings, PageSettingsServersList, + + PageStart, PageHome, PageShare}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 3ae5b2781..20029eb07 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -57,8 +57,8 @@ Drawer { text: "IP, логин и пароль от сервера" buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardCredentials) root.visible = false } } @@ -73,8 +73,8 @@ Drawer { text: "QR-код, ключ или файл настроек" buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardConfigSource) root.visible = false } } diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml index b3d34b62d..309b42177 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -96,7 +96,7 @@ ListView { } else { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index f985d5237..ce9d804a9 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -36,7 +36,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - PageController.closePage() + closePage() } } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 6c4e78471..2c0fbd851 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -36,7 +36,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - PageController.closePage() + closePage() } } } @@ -61,8 +61,8 @@ Item { visible: image ? true : false onClicked: { - if (actionButtonImage && typeof actionButtonImage === "function") { - actionButtonImage() + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() } } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index d8e195f1d..2530f5832 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -8,7 +8,7 @@ Item { property string text property string descriptionText - property var onClickedFunc + property var clickedFunction property alias buttonImage: button.image property string iconImage @@ -68,8 +68,8 @@ Item { hoverEnabled: false image: buttonImage onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml new file mode 100644 index 000000000..4eb517537 --- /dev/null +++ b/client/ui/qml/Controls2/PageType.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property StackView stackView: StackView.view + + function goToPage(page, slide = true) { + if (slide) { + root.stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + } else { + root.stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + } + } + + function closePage() { + if (root.stackView.depth <= 1) { + return + } + + root.stackView.pop() + } + + function goToStartPage() { + while (root.stackView.depth > 1) { + root.stackView.pop() + } + } +} diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml new file mode 100644 index 000000000..dd92d5fed --- /dev/null +++ b/client/ui/qml/Controls2/PopupType.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Popup { + id: root + + property string popupErrorMessageText + property bool closeButtonVisible: true + + leftMargin: 25 + rightMargin: 25 + bottomMargin: 70 + + width: parent.width - leftMargin - rightMargin + + anchors.centerIn: parent + modal: true + closePolicy: Popup.CloseOnEscape + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + anchors.fill: parent + + color: Qt.rgba(215/255, 216/255, 219/255, 0.95) + radius: 4 + } + + contentItem: RowLayout { + width: parent.width + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: root.popupErrorMessageText + } + + BasicButtonType { + visible: closeButtonVisible + + defaultColor: Qt.rgba(215/255, 216/255, 219/255, 0.95) + hoveredColor: "#C1C2C5" + pressedColor: "#AEB0B7" + disabledColor: "#494B50" + + textColor: "#0E0E11" + borderWidth: 0 + + text: "Close" + onClicked: { + root.close() + } + } + } +} diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml new file mode 100644 index 000000000..15cc96c10 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + height: 16 + + color: "#0E0E11" + font.pixelSize: 13 + font.weight: Font.Normal + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml deleted file mode 100644 index b3d53a055..000000000 --- a/client/ui/qml/PageLoader.qml +++ /dev/null @@ -1,37 +0,0 @@ -import QtQuick -import QtQuick.Controls - -StackView { - id: stackView - - function gotoPage(page, slide) { - if (slide) { - stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) - } else { - stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) - } - } - - function closePage() { - if (stackView.depth <= 1) { - return - } - - stackView.pop() - } - - Connections { - target: PageController - function onGoToPage(page, slide) { - stackView.gotoPage(page, slide) - } - - function onClosePage() { - stackView.closePage() - } - } - - Component.onCompleted: { - PageController.setStartPage() - } -} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a04b17444..d5455849a 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -14,7 +14,7 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" -Item { +PageType { id: root property string defaultColor: "#1C1D21" @@ -37,6 +37,21 @@ Item { } } + Connections { + target: InstallController + + function onInstallContainerFinished() { + goToStartPage() + menu.visible = true + containersDropDown.menuVisible = true + } + + function onInstallServerFinished() { + goToStartPage() + menu.visible = true + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -279,6 +294,15 @@ Item { z: 1 + Image { + source: "qrc:/images/controls/check.svg" + visible: serverRadioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + Text { id: serverRadioButtonText @@ -295,13 +319,10 @@ Item { Layout.bottomMargin: 20 } - Image { - source: "qrc:/images/controls/check.svg" - visible: serverRadioButton.checked - width: 24 - height: 24 + ImageButtonType { + image: "qrc:/images/controls/settings.svg" - Layout.rightMargin: 8 +// onClicked: } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 5560aee72..9d5b74427 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -1,5 +1,71 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts -Item { +import SortFilterProxyModel 0.2 +import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + id: fl + anchors.fill: parent + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Repeater { + model: proxyServersModel + + delegate: HeaderType { + id: header + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/plus.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: name + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + closePage() + } + } + } + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml new file mode 100644 index 000000000..79e24f75b --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -0,0 +1,111 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + HeaderType { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/plus.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Серверы" + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + PageController.goToPageHome() + } + } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + ListView { + id: servers + width: parent.width + height: servers.contentItem.height + + model: ServersModel + + clip: true + + delegate: Item { + implicitWidth: servers.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 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: name + descriptionText: hostName + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ServersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettings) + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 5f2bf2f35..53a408896 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -10,7 +10,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root Connections { @@ -44,7 +44,7 @@ Item { backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Подключение к серверу" + headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n Всё в порядке, если код передал друг." } @@ -64,7 +64,7 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/folder-open.svg" - onClickedFunc: function() { + clickedFunction: function() { onClicked: fileDialog.open() } @@ -89,7 +89,7 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/qr-code.svg" - onClickedFunc: function() { + clickedFunction: function() { } } @@ -106,8 +106,8 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/text-cursor.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardTextKey) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 20c6e735e..95463c3e8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -8,7 +8,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root FlickableType { @@ -69,7 +69,7 @@ Item { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - PageController.goToPage(PageEnum.PageSetupWizardEasy) + goToPage(PageEnum.PageSetupWizardEasy) } } @@ -90,7 +90,7 @@ Item { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - PageController.goToPage(PageEnum.PageSetupWizardProtocols) + goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 9555c915c..8707c19b3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -12,7 +12,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -34,7 +34,7 @@ Item { id: fl anchors.top: root.top anchors.bottom: root.bottom - contentHeight: content.height + contentHeight: content.implicitHeight + buttonContinue.anchors.bottomMargin Column { id: content @@ -49,8 +49,9 @@ Item { spacing: 16 HeaderType { + id: header + implicitWidth: parent.width - anchors.topMargin: 20 backButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -117,6 +118,8 @@ Item { } BasicButtonType { + id: buttonContinue + implicitWidth: parent.width anchors.topMargin: 24 anchors.bottomMargin: 32 @@ -125,7 +128,7 @@ Item { onClicked: function() { ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) - PageController.goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index b4f44fd91..fd8aac6b1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -11,11 +11,20 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root property real progressBarValue: 0 + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + closePage() + PageController.showErrorMessage(errorMessage) + } + } + SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel @@ -100,26 +109,5 @@ Item { } } } - - Timer { - id: closePageTimer - - interval: 1000 - repeat: false - running: false - onTriggered: { - // todo go to root installing page - PageController.goToPage(PageEnum.PageStart) - } - } - - Connections { - target: InstallController - - function onInstallContainerFinished() { - progressBarValue = 1 - closePageTimer.start() - } - } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index e79176276..fa3ac8383 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -13,7 +13,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -156,7 +156,7 @@ Item { text: qsTr("Установить") onClicked: function() { - PageController.goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f7cd382fb..bffe09cb2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -11,7 +11,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -86,9 +86,9 @@ Item { descriptionText: description buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { + clickedFunction: function() { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index b7d56ce53..4d0f4c5c5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -10,9 +10,27 @@ import "../Config" import "../Controls2/TextTypes" import "../Components" -Item { +PageType { id: root + Connections { + target: PageController + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.popupErrorMessageText = errorMessage + popupErrorMessage.open() + } + } + + Connections { + target: InstallController + + function onInstallServerFinished() { + goToStartPage() +// goToPage(PageEnum.PageStart) + } + } + FlickableType { id: fl anchors.top: root.top @@ -44,7 +62,7 @@ Item { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." } BasicButtonType { @@ -76,7 +94,7 @@ Item { text: qsTr("У меня ничего нет") onClicked: { - PageController.goToPage(PageEnum.PageTest) + goToPage(PageEnum.PageTest) } } } @@ -85,4 +103,16 @@ Item { id: connectionTypeSelection } } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index f94b9ff48..43dbda0e5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -9,7 +9,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root FlickableType { @@ -66,7 +66,7 @@ Item { text: qsTr("Подключиться") onClicked: function() { -// PageController.goToPage(PageEnum.PageSetupWizardInstalling) +// goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index ade0980df..19ea3bc44 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -9,9 +9,22 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root + Connections { + target: PageController + + function onGoToPageHome() { + tabBar.currentIndex = 0 + } + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.popupErrorMessageText = errorMessage + popupErrorMessage.open() + } + } + StackLayout { id: stackLayout currentIndex: tabBar.currentIndex @@ -24,10 +37,18 @@ Item { width: parent.width height: root.height - tabBar.implicitHeight - PageHome { + StackView { + id: homeStackView + initialItem: "PageHome.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) } - PageSetupWizardEasy { + Item { + + } + + StackView { + id: settingsStackView + initialItem: "PageSettingsServersList.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) } } @@ -69,4 +90,16 @@ Item { cursorShape: Qt.PointingHandCursor enabled: false } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index b10d749fb..5ca1b3458 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -27,9 +27,9 @@ Window { color: "#0E0E11" } - PageLoader { - id: pageLoader + StackView { anchors.fill: parent focus: true + initialItem: PageController.getInitialPage() } } From 1e180489a40b89265c7789edbfb3813985d201e0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 27 May 2023 22:46:41 +0800 Subject: [PATCH 023/131] added display of vpn containers and services on the settings page - added PageSettingsData and implementation of 'remove all containers' button --- client/images/controls/edit-3.svg | 4 + client/resources.qrc | 5 + client/ui/models/containers_model.cpp | 18 +++ client/ui/models/containers_model.h | 2 + .../ConnectionTypeSelectionDrawer.qml | 22 ++-- .../Components/ContainersPageHomeListView.qml | 1 - .../PageSettingsContainersListView.qml | 107 ++++++++++++++++++ client/ui/qml/Controls2/DividerType.qml | 8 ++ .../ui/qml/Controls2/LabelWithButtonType.qml | 58 +++++++--- .../Controls2/TextTypes/ListItemTitleType.qml | 12 ++ client/ui/qml/Pages2/PageHome.qml | 19 +--- client/ui/qml/Pages2/PageSettings.qml | 95 ++++++++++++++-- client/ui/qml/Pages2/PageSettingsData.qml | 67 +++++++++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 13 +-- .../Pages2/PageSetupWizardConfigSource.qml | 27 ++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 8 ++ .../qml/Pages2/PageSetupWizardProtocols.qml | 10 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 16 ++- 19 files changed, 393 insertions(+), 102 deletions(-) create mode 100644 client/images/controls/edit-3.svg create mode 100644 client/ui/qml/Components/PageSettingsContainersListView.qml create mode 100644 client/ui/qml/Controls2/DividerType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml create mode 100644 client/ui/qml/Pages2/PageSettingsData.qml diff --git a/client/images/controls/edit-3.svg b/client/images/controls/edit-3.svg new file mode 100644 index 000000000..4e1dc0713 --- /dev/null +++ b/client/images/controls/edit-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ca1674af8..ca02df7d4 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -234,5 +234,10 @@ ui/qml/Pages2/PageSettings.qml ui/qml/Controls2/PageType.qml ui/qml/Controls2/PopupType.qml + images/controls/edit-3.svg + ui/qml/Pages2/PageSettingsData.qml + ui/qml/Components/PageSettingsContainersListView.qml + ui/qml/Controls2/TextTypes/ListItemTitleType.qml + ui/qml/Controls2/DividerType.qml diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 6a999011c..4dc7010e6 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -1,5 +1,7 @@ #include "containers_model.h" +#include "core/servercontroller.h" + ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { } @@ -106,6 +108,22 @@ int ContainersModel::getCurrentlyInstalledContainerIndex() return m_currentlyInstalledContainerIndex; } +void ContainersModel::removeAllContainers() +{ + + ServerController serverController(m_settings); + auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + + if (errorCode == ErrorCode::NoError) { + beginResetModel(); + m_settings->setContainers(m_currentlyProcessedServerIndex, {}); + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); + endResetModel(); + } + + //todo process errors +} + QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 36b5567a7..642b66802 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -48,6 +48,8 @@ public slots: void setCurrentlyInstalledContainerIndex(int index); int getCurrentlyInstalledContainerIndex(); + void removeAllContainers(); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 20029eb07..2e6b3f2ee 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -37,12 +37,11 @@ Drawer { anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - Header2TextType { Layout.fillWidth: true Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" @@ -52,7 +51,7 @@ Drawer { LabelWithButtonType { id: ip Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 16 text: "IP, логин и пароль от сервера" buttonImage: "qrc:/images/controls/chevron-right.svg" @@ -62,11 +61,9 @@ Drawer { root.visible = false } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true @@ -78,10 +75,7 @@ Drawer { root.visible = false } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} } } diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml index 309b42177..f8b5101eb 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -116,4 +116,3 @@ ListView { } } } - diff --git a/client/ui/qml/Components/PageSettingsContainersListView.qml b/client/ui/qml/Components/PageSettingsContainersListView.qml new file mode 100644 index 000000000..2a59b8b29 --- /dev/null +++ b/client/ui/qml/Components/PageSettingsContainersListView.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: root + + height: root.contentItem.height + + clip: true + + ButtonGroup { + id: containersRadioButtonGroup + } + + delegate: Item { + implicitWidth: parent.width + implicitHeight: containerRadioButton.implicitHeight + + RadioButton { + id: containerRadioButton + + implicitWidth: parent.width + implicitHeight: containerRadioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: containersRadioButtonGroup + + checked: isDefault + + indicator: Rectangle { + anchors.fill: parent + color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: isInstalled + + RowLayout { + id: containerRadioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + ColumnLayout { + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + ListItemTitleType { + Layout.fillWidth: true + + text: name + } + + CaptionTextType { + Layout.fillWidth: true + + text: description + color: "#878B91" + } + } + + Image { + source: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + if (isInstalled) { +// isDefault = true +// root.currentIndex = index + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + } +} diff --git a/client/ui/qml/Controls2/DividerType.qml b/client/ui/qml/Controls2/DividerType.qml new file mode 100644 index 000000000..6341807a5 --- /dev/null +++ b/client/ui/qml/Controls2/DividerType.qml @@ -0,0 +1,8 @@ +import QtQuick +import QtQuick.Layouts + +Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" +} diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 2530f5832..e4e711a6b 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -13,12 +15,16 @@ Item { property alias buttonImage: button.image property string iconImage + property string textColor: "#d7d8db" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight RowLayout { id: content anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 Image { id: icon @@ -28,34 +34,28 @@ Item { } ColumnLayout { - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 18 - color: "#d7d8db" + ListItemTitleType { text: root.text - wrapMode: Text.WordWrap + color: textColor Layout.fillWidth: true - height: 22 + Layout.topMargin: 16 + Layout.bottomMargin: description.visible ? 0 : 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 + CaptionTextType { + id: description + color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap visible: root.descriptionText !== "" Layout.fillWidth: true - height: 16 + Layout.bottomMargin: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -88,21 +88,45 @@ Item { } } } + + Rectangle { + id: background + anchors.fill: root + color: "transparent" + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - imageBackground.color = button.hoveredColor + if (buttonImage) { + imageBackground.color = button.hoveredColor + } else { + background.color = button.hoveredColor + } } onExited: { - imageBackground.color = button.defaultColor + if (buttonImage) { + imageBackground.color = button.defaultColor + } else { + background.color = button.defaultColor + } } onPressedChanged: { - imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + if (buttonImage) { + imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } else { + background.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } } onClicked: { diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml new file mode 100644 index 000000000..23069db20 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 21.6 + + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: Font.Normal + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d5455849a..1fba3bff8 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -37,21 +37,6 @@ PageType { } } - Connections { - target: InstallController - - function onInstallContainerFinished() { - goToStartPage() - menu.visible = true - containersDropDown.menuVisible = true - } - - function onInstallServerFinished() { - goToStartPage() - menu.visible = true - } - } - Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -166,6 +151,10 @@ PageType { ValueFilter { roleName: "serviceType" value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true } ] } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 9d5b74427..57c252b45 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import ContainerProps 1.0 import ProtocolProps 1.0 @@ -12,6 +13,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -27,35 +29,28 @@ PageType { ] } - FlickableType { - id: fl - anchors.fill: parent - contentHeight: content.height - ColumnLayout { id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + anchors.fill: parent spacing: 16 Repeater { + id: header model: proxyServersModel delegate: HeaderType { - id: header - Layout.fillWidth: true Layout.topMargin: 20 Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" + actionButtonImage: "qrc:/images/controls/edit-3.svg" backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: name + descriptionText: hostName actionButtonFunction: function() { connectionTypeSelection.visible = true @@ -66,6 +61,82 @@ PageType { } } } + + TabBar { + id: tabBar + + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + isSelected: tabBar.currentIndex === 0 + text: qsTr("Protocols") + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Services") + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Data") + } + } + + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex + + Layout.fillWidth: true + height: root.height + + StackView { + id: protocolsStackView + initialItem: PageSettingsContainersListView { + model: SortFilterProxyModel { + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + } + } + + StackView { + id: servicesStackView + initialItem: PageSettingsContainersListView { + model: SortFilterProxyModel { + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Other + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + } + } + + StackView { + id: dataStackView + initialItem: PageSettingsData { + + } + } + } } - } +// } } diff --git a/client/ui/qml/Pages2/PageSettingsData.qml b/client/ui/qml/Pages2/PageSettingsData.qml new file mode 100644 index 000000000..74293aa55 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsData.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +PageType { + id: root + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Clear Amnezia cache" + descriptionText: "May be needed when changing other settings" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Remove server from application" + textColor: "#EB5757" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Clear server from Amnezia software" + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeAllContainers() + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 79e24f75b..2344b7e8c 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -57,11 +57,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 - ListView { id: servers width: parent.width @@ -85,8 +80,6 @@ PageType { LabelWithButtonType { id: server Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 text: name descriptionText: hostName @@ -98,11 +91,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 53a408896..25b88f761 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -33,14 +33,12 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - spacing: 16 HeaderType { Layout.fillWidth: true Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 backButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -52,6 +50,8 @@ PageType { Header2TextType { Layout.fillWidth: true Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 text: "Что у вас есть?" } @@ -75,11 +75,8 @@ PageType { } } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} //todo ifdef mobile platforms LabelWithButtonType { @@ -93,11 +90,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} LabelWithButtonType { Layout.fillWidth: true @@ -111,11 +104,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index fd8aac6b1..006743d94 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -23,6 +23,14 @@ PageType { closePage() PageController.showErrorMessage(errorMessage) } + + function onInstallContainerFinished() { + goToStartPage() + } + + function onInstallServerFinished() { + goToStartPage() + } } SortFilterProxyModel { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index bffe09cb2..cdacaf040 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -75,12 +75,12 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + anchors.rightMargin: -16 + anchors.leftMargin: -16 LabelWithButtonType { id: container Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 text: name descriptionText: description @@ -92,11 +92,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 4d0f4c5c5..804e145cd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -26,8 +26,9 @@ PageType { target: InstallController function onInstallServerFinished() { + //todo add smt like changeStartPage goToStartPage() -// goToPage(PageEnum.PageStart) + goToPage(PageEnum.PageStart) } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 19ea3bc44..c50d4591c 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -38,8 +38,9 @@ PageType { height: root.height - tabBar.implicitHeight StackView { - id: homeStackView - initialItem: "PageHome.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) + initialItem: PageHome { + id: pageHome + } } Item { @@ -47,8 +48,9 @@ PageType { } StackView { - id: settingsStackView - initialItem: "PageSettingsServersList.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) + initialItem: PageSettingsServersList { + id: pageSettingsServersList + } } } @@ -72,6 +74,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" + onClicked: { + pageSettingsServersList.goToStartPage() + } } TabImageButtonType { isSelected: tabBar.currentIndex === 1 @@ -80,6 +85,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" + onClicked: { + pageHome.goToStartPage() + } } } From de0cd976debb0691e69b77e1f0cc62b4e17d12de Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 1 Jun 2023 11:25:33 +0800 Subject: [PATCH 024/131] added page transition effects - added functionality for buttons on PageSettingsServerData page --- client/amnezia_application.cpp | 2 +- client/images/controls/amnezia.svg | 3 + client/images/controls/app.svg | 6 + client/images/controls/radio.svg | 7 + client/images/controls/save.svg | 5 + client/images/controls/server.svg | 6 + client/resources.qrc | 18 +- .../ui/controllers/connectionController.cpp | 1 - client/ui/controllers/installController.cpp | 5 +- client/ui/controllers/pageController.cpp | 3 +- client/ui/controllers/pageController.h | 7 +- client/ui/models/containers_model.cpp | 11 +- client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 51 ++++-- client/ui/models/servers_model.h | 5 +- client/ui/pages.h | 9 +- client/ui/qml/Components/ConnectButton.qml | 14 +- .../ConnectionTypeSelectionDrawer.qml | 6 +- ...istView.qml => HomeContainersListView.qml} | 59 +----- ...iew.qml => SettingsContainersListView.qml} | 3 +- client/ui/qml/Controls2/DrawerType.qml | 20 +++ client/ui/qml/Controls2/DropDownType.qml | 6 +- client/ui/qml/Controls2/PageType.qml | 5 +- client/ui/qml/Controls2/StackViewType.qml | 61 +++++++ client/ui/qml/Controls2/SwitcherType.qml | 1 + .../ui/qml/Controls2/VerticalRadioButton.qml | 61 +++++-- client/ui/qml/Pages2/PageHome.qml | 121 ++++++------- client/ui/qml/Pages2/PageSettings.qml | 169 +++++++----------- ...ngsData.qml => PageSettingsServerData.qml} | 13 ++ .../ui/qml/Pages2/PageSettingsServerInfo.qml | 134 ++++++++++++++ .../Pages2/PageSettingsServerProtocols.qml | 38 ++++ .../qml/Pages2/PageSettingsServerServices.qml | 38 ++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 7 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 16 ++ client/ui/qml/Pages2/PageSetupWizardStart.qml | 10 -- client/ui/qml/Pages2/PageStart.qml | 31 ++-- client/ui/qml/main2.qml | 24 ++- client/ui/uilogic.cpp | 14 +- 38 files changed, 656 insertions(+), 335 deletions(-) create mode 100644 client/images/controls/amnezia.svg create mode 100644 client/images/controls/app.svg create mode 100644 client/images/controls/radio.svg create mode 100644 client/images/controls/save.svg create mode 100644 client/images/controls/server.svg rename client/ui/qml/Components/{ContainersPageHomeListView.qml => HomeContainersListView.qml} (52%) rename client/ui/qml/Components/{PageSettingsContainersListView.qml => SettingsContainersListView.qml} (97%) create mode 100644 client/ui/qml/Controls2/DrawerType.qml create mode 100644 client/ui/qml/Controls2/StackViewType.qml rename client/ui/qml/Pages2/{PageSettingsData.qml => PageSettingsServerData.qml} (67%) create mode 100644 client/ui/qml/Pages2/PageSettingsServerInfo.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerProtocols.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerServices.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 9fdddc865..c8bdcf3bb 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -179,7 +179,7 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("PageProtocolLogicBase *"); - declareQmlPageEnum(); +// declareQmlPageEnum(); declareQmlProtocolEnum(); declareQmlContainerEnum(); diff --git a/client/images/controls/amnezia.svg b/client/images/controls/amnezia.svg new file mode 100644 index 000000000..0a6017dda --- /dev/null +++ b/client/images/controls/amnezia.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/app.svg b/client/images/controls/app.svg new file mode 100644 index 000000000..87775cd10 --- /dev/null +++ b/client/images/controls/app.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/radio.svg b/client/images/controls/radio.svg new file mode 100644 index 000000000..277318149 --- /dev/null +++ b/client/images/controls/radio.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/images/controls/save.svg b/client/images/controls/save.svg new file mode 100644 index 000000000..442ff72c4 --- /dev/null +++ b/client/images/controls/save.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/server.svg b/client/images/controls/server.svg new file mode 100644 index 000000000..52aad6566 --- /dev/null +++ b/client/images/controls/server.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ca02df7d4..db53165c2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,16 +228,26 @@ images/controls/download.svg ui/qml/Controls2/ProgressBarType.qml ui/qml/Components/ConnectionTypeSelectionDrawer.qml - ui/qml/Components/ContainersPageHomeListView.qml + ui/qml/Components/HomeContainersListView.qml ui/qml/Controls2/TextTypes/CaptionTextType.qml images/controls/settings.svg - ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageSettingsServerInfo.qml ui/qml/Controls2/PageType.qml ui/qml/Controls2/PopupType.qml images/controls/edit-3.svg - ui/qml/Pages2/PageSettingsData.qml - ui/qml/Components/PageSettingsContainersListView.qml + ui/qml/Pages2/PageSettingsServerData.qml + ui/qml/Components/SettingsContainersListView.qml ui/qml/Controls2/TextTypes/ListItemTitleType.qml ui/qml/Controls2/DividerType.qml + ui/qml/Controls2/DrawerType.qml + ui/qml/Controls2/StackViewType.qml + ui/qml/Pages2/PageSettings.qml + images/controls/amnezia.svg + images/controls/app.svg + images/controls/radio.svg + images/controls/save.svg + images/controls/server.svg + ui/qml/Pages2/PageSettingsServerProtocols.qml + ui/qml/Pages2/PageSettingsServerServices.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 58fef3739..617fd5ee1 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -74,4 +74,3 @@ bool ConnectionController::closeVpnConnection() emit disconnectFromVpn(); m_isConnected = false; } - diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 31f58a2a4..15a24c31c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -49,8 +49,9 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co server.insert(config_key::containers, QJsonArray{ config }); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); + m_serversModel->addServer(server); + auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); + m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); emit installServerFinished(); return; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 63c3ba589..e49177a55 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -9,7 +9,8 @@ QString PageController::getInitialPage() { if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { - m_serversModel->setDefaultServerIndex(0); + auto defaultServerIndex = m_serversModel->index(0); + m_serversModel->setData(defaultServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); } return getPagePath(PageLoader::PageEnum::PageStart); } else { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f37edca9b..1d041deb1 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,7 +9,10 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageSettings, PageShare, + enum class PageEnum { PageStart = 0, PageHome, PageShare, + + PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, + PageSettingsServerProtocols, PageSettingsServerServices, PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, @@ -41,6 +44,8 @@ public slots: signals: void goToPageHome(); + void restorePageHomeState(bool isContainerInstalled = false); + void replaceStartPage(); void showErrorMessage(QString errorMessage); private: diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 4dc7010e6..b240878d9 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -86,6 +86,7 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) beginResetModel(); m_currentlyProcessedServerIndex = index; endResetModel(); + emit defaultContainerChanged(); } void ContainersModel::setCurrentlyInstalledContainerIndex(int index) @@ -115,7 +116,7 @@ void ContainersModel::removeAllContainers() auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); if (errorCode == ErrorCode::NoError) { - beginResetModel(); + beginResetModel(); m_settings->setContainers(m_currentlyProcessedServerIndex, {}); m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); endResetModel(); @@ -124,6 +125,14 @@ void ContainersModel::removeAllContainers() //todo process errors } +void ContainersModel::clearCachedProfiles() +{ + const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); + for (DockerContainer container : containers.keys()) { + m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container); + } +} + QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 642b66802..3ce7bd6b9 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -49,6 +49,7 @@ public slots: int getCurrentlyInstalledContainerIndex(); void removeAllContainers(); + void clearCachedProfiles(); protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 884009e8c..d05bb6959 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -17,15 +17,14 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int || index.row() >= static_cast(m_settings->serversCount())) { return false; } -// if (role == DescRole) { -// return m_data[index.row()].desc; -// } -// if (role == AddressRole) { -// return m_data[index.row()].address; -// } -// if (role == IsDefaultRole) { -// return m_data[index.row()].isDefault; -// } + + switch (role) { + case IsDefaultRole: m_settings->setDefaultServer(index.row()); + default: return true; + } + + emit dataChanged(index, index); + return true; } QVariant ServersModel::data(const QModelIndex &index, int role) const @@ -58,14 +57,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } -//todo mode to setData? -void ServersModel::setDefaultServerIndex(int index) -{ - // beginResetModel(); - m_settings->setDefaultServer(index); - // endResetModel(); -} - const int ServersModel::getDefaultServerIndex() { return m_settings->defaultServerIndex(); @@ -81,14 +72,38 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) m_currenlyProcessedServerIndex = index; } +bool ServersModel::isDefaultServerCurrentlyProcessed() +{ + return m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex; +} + ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() { return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); } -void ServersModel::addServer() +void ServersModel::addServer(const QJsonObject &server) { + beginResetModel(); + m_settings->addServer(server); + endResetModel(); +} +void ServersModel::removeServer() +{ + beginResetModel(); + m_settings->removeServer(m_currenlyProcessedServerIndex); + + if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { + m_settings->setDefaultServer(0); + } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { + m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); + } + + if (m_settings->serversCount() == 0) { + m_settings->setDefaultServer(-1); + } + endResetModel(); } QHash ServersModel::roleNames() const { diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index ae1ec8d95..54ac5ef49 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,15 +31,16 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void setDefaultServerIndex(int index); const int getDefaultServerIndex(); + bool isDefaultServerCurrentlyProcessed(); const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); ServerCredentials getCurrentlyProcessedServerCredentials(); - void addServer(); + void addServer(const QJsonObject &server); + void removeServer(); protected: QHash roleNames() const override; diff --git a/client/ui/pages.h b/client/ui/pages.h index 82c0d4095..f3d045b22 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -26,14 +26,7 @@ enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo, - - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - - PageSettings, PageSettingsServersList, - - PageStart, PageHome, PageShare}; + AdvancedServerSettings, ClientManagement, ClientInfo}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 5002d5bd7..73accef4f 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,7 +7,7 @@ import ConnectionState 1.0 Button { id: root - text: "Подключиться" + text: qsTr("Connect") background: Image { id: border @@ -60,37 +60,37 @@ Button { case ConnectionState.Disconnected: { console.log("Disconnected") connectionProccess.running = false - root.text = "Подключиться" + root.text = qsTr("Connect") break } case ConnectionState.Preparing: { console.log("Preparing") connectionProccess.running = true - root.text = "Подключение..." + root.text = qsTr("Connection...") break } case ConnectionState.Connecting: { console.log("Connecting") connectionProccess.running = true - root.text = "Подключение..." + root.text = qsTr("Connection...") break } case ConnectionState.Connected: { console.log("Connected") connectionProccess.running = false - root.text = "Подключено" + root.text = qsTr("Connected") break } case ConnectionState.Disconnecting: { console.log("Disconnecting") connectionProccess.running = true - root.text = "Отключение..." + root.text = qsTr("Disconnection...") break } case ConnectionState.Reconnecting: { console.log("Reconnecting") connectionProccess.running = true - root.text = "Переподключение..." + root.text = qsTr("Reconnection...") break } case ConnectionState.Error: { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 2e6b3f2ee..d80d5e5af 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -8,16 +8,12 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Drawer { +DrawerType { id: root - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.4375 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/HomeContainersListView.qml similarity index 52% rename from client/ui/qml/Components/ContainersPageHomeListView.qml rename to client/ui/qml/Components/HomeContainersListView.qml index f8b5101eb..c3e98fbba 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -29,64 +29,23 @@ ListView { implicitWidth: rootWidth implicitHeight: containerRadioButton.implicitHeight - RadioButton { + VerticalRadioButton { id: containerRadioButton - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - hoverEnabled: true + text: name + descriptionText: description ButtonGroup.group: containersRadioButtonGroup - checked: isDefault - - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + imageSource: "qrc:/images/controls/download.svg" + showImage: !isInstalled checkable: isInstalled - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: isInstalled ? "qrc:/images/controls/check.svg" : "qrc:/images/controls/download.svg" - visible: isInstalled ? containerRadioButton.checked : true - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: containerRadioButtonText - - text: name - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - } + checked: isDefault onClicked: { if (checked) { diff --git a/client/ui/qml/Components/PageSettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml similarity index 97% rename from client/ui/qml/Components/PageSettingsContainersListView.qml rename to client/ui/qml/Components/SettingsContainersListView.qml index 2a59b8b29..5175fd49d 100644 --- a/client/ui/qml/Components/PageSettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -14,6 +14,7 @@ import "../Controls2/TextTypes" ListView { id: root + width: parent.width height: root.contentItem.height clip: true @@ -23,7 +24,7 @@ ListView { } delegate: Item { - implicitWidth: parent.width + implicitWidth: root.width implicitHeight: containerRadioButton.implicitHeight RadioButton { diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml new file mode 100644 index 000000000..bede3700d --- /dev/null +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Controls + +Drawer { + edge: Qt.BottomEdge + + clip: true + modal: true + + enter: Transition { + SmoothedAnimation { + velocity: 4 + } + } + exit: Transition { + SmoothedAnimation { + velocity: 4 + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 2b8986b55..5ac50fa7b 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -111,16 +111,12 @@ Item { } } - Drawer { + DrawerType { id: menu - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.9 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 4eb517537..046201b7e 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -8,10 +8,11 @@ Item { property StackView stackView: StackView.view function goToPage(page, slide = true) { + var pagePath = PageController.getPagePath(page) if (slide) { - root.stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) } else { - root.stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } } diff --git a/client/ui/qml/Controls2/StackViewType.qml b/client/ui/qml/Controls2/StackViewType.qml new file mode 100644 index 000000000..e2646e454 --- /dev/null +++ b/client/ui/qml/Controls2/StackViewType.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls + +StackView { + id: root + + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + replaceEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + replaceExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } +} + diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b593ece84..e4df04faf 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -59,6 +59,7 @@ Switch { } } + contentItem: ColumnLayout { contentItem: ColumnLayout { id: content diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 420051cd5..50af3c79c 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import "TextTypes" + RadioButton { id: root @@ -13,7 +15,8 @@ RadioButton { property string disabledColor: Qt.rgba(1, 1, 1, 0) property string selectedColor: Qt.rgba(1, 1, 1, 0) - property string textColor: "#0E0E11" + property string textColor: "#D7D8DB" + property string selectedTextColor: "#FBB26A" property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) property string selectedBorderColor: "#FBB26A" @@ -26,11 +29,16 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" + property string imageSource + property bool showImage + hoverEnabled: true indicator: Rectangle { id: background + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 implicitHeight: 56 radius: 16 @@ -52,6 +60,16 @@ RadioButton { PropertyAnimation { duration: 200 } } + Image { + source: imageSource + visible: showImage + + anchors.centerIn: parent + + width: 24 + height: 24 + } + Rectangle { id: outerCircle @@ -59,6 +77,8 @@ RadioButton { height: 24 radius: 16 + visible: !showImage + anchors.centerIn: parent color: "transparent" @@ -120,34 +140,41 @@ RadioButton { contentItem: ColumnLayout { id: content - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right anchors.leftMargin: 8 + background.width - Text { - text: root.text - wrapMode: Text.WordWrap - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + spacing: 4 + + ListItemTitleType { + text: root.text + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } - height: 24 Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: description.visible ? 0 : 16 + + Behavior on color { + PropertyAnimation { duration: 200 } + } } - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 + CaptionTextType { + id: description + color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap visible: root.descriptionText !== "" Layout.fillWidth: true - height: 16 + Layout.bottomMargin: 16 } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 1fba3bff8..0ecbafcc3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -37,6 +37,17 @@ PageType { } } + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + menu.visible = true + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -96,16 +107,12 @@ PageType { } } - Drawer { + DrawerType { id: menu - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.90 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius @@ -180,7 +187,7 @@ PageType { containersDropDown.menuVisible = true } - listView: ContainersPageHomeListView { + listView: HomeContainersListView { rootWidth: root.width model: proxyContainersModel @@ -251,81 +258,63 @@ PageType { property variant delegateData: model implicitWidth: serversMenuContent.width - implicitHeight: serverRadioButton.implicitHeight + implicitHeight: serverRadioButtonContent.implicitHeight - RadioButton { - id: serverRadioButton + ColumnLayout { + id: serverRadioButtonContent + anchors.fill: parent - implicitWidth: parent.width - implicitHeight: serverRadioButtonContent.implicitHeight + anchors.rightMargin: 16 + anchors.leftMargin: 16 - hoverEnabled: true - - checked: index === serversMenuContent.currentIndex - - ButtonGroup.group: serversRadioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + spacing: 0 RowLayout { - id: serverRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: "qrc:/images/controls/check.svg" - visible: serverRadioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: serverRadioButtonText - - text: name - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 + VerticalRadioButton { + id: serverRadioButton Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 + + text: name + descriptionText: "description" + + checked: index === serversMenuContent.currentIndex + + ButtonGroup.group: serversRadioButtonGroup + + onClicked: { + serversMenuContent.currentIndex = index + + isDefault = true + ContainersModel.setCurrentlyProcessedServerIndex(index) + } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } ImageButtonType { image: "qrc:/images/controls/settings.svg" -// onClicked: + implicitWidth: 56 + implicitHeight: 56 + + z: 1 + + onClicked: function() { + ServersModel.setCurrentlyProcessedServerIndex(index) + ContainersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettingsServerInfo) + menu.visible = false + } } } - onClicked: { - serversMenuContent.currentIndex = index - - ServersModel.setDefaultServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) - } - - MouseArea { - anchors.fill: serverRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false + DividerType { + Layout.fillWidth: true } } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 57c252b45..d3b87c191 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -1,142 +1,109 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts - -import SortFilterProxyModel 0.2 +import QtQuick.Dialogs import PageEnum 1.0 -import ProtocolEnum 1.0 -import ContainerProps 1.0 -import ProtocolProps 1.0 import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -import "../Components" PageType { id: root - SortFilterProxyModel { - id: proxyServersModel - sourceModel: ServersModel - filters: [ - ValueFilter { - roleName: "isCurrentlyProcessed" - value: true - } - ] - } + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height ColumnLayout { id: content - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - spacing: 16 + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - Repeater { - id: header - model: proxyServersModel + headerText: qsTr("Settings") + } - delegate: HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 - actionButtonImage: "qrc:/images/controls/edit-3.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" + text: qsTr("Servers") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/server.svg" - headerText: name - descriptionText: hostName - - actionButtonFunction: function() { - connectionTypeSelection.visible = true - } - - backButtonFunction: function() { - closePage() - } + clickedFunction: function() { + goToPage(PageEnum.PageSettingsServersList) } } - TabBar { - id: tabBar + DividerType {} + LabelWithButtonType { Layout.fillWidth: true - background: Rectangle { - color: "transparent" - } + text: qsTr("Connection") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/radio.svg" - TabButtonType { - isSelected: tabBar.currentIndex === 0 - text: qsTr("Protocols") - } - TabButtonType { - isSelected: tabBar.currentIndex === 1 - text: qsTr("Services") - } - TabButtonType { - isSelected: tabBar.currentIndex === 2 - text: qsTr("Data") + clickedFunction: function() { } } - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex + DividerType {} + LabelWithButtonType { Layout.fillWidth: true - height: root.height - StackView { - id: protocolsStackView - initialItem: PageSettingsContainersListView { - model: SortFilterProxyModel { - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - } - } + text: qsTr("Application") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/app.svg" - StackView { - id: servicesStackView - initialItem: PageSettingsContainersListView { - model: SortFilterProxyModel { - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Other - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - } - } - - StackView { - id: dataStackView - initialItem: PageSettingsData { - - } + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) } } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Backup") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/save.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("About AmneziaVPN") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) + } + } + + DividerType {} } -// } + } } diff --git a/client/ui/qml/Pages2/PageSettingsData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml similarity index 67% rename from client/ui/qml/Pages2/PageSettingsData.qml rename to client/ui/qml/Pages2/PageSettingsServerData.qml index 74293aa55..3ab762993 100644 --- a/client/ui/qml/Pages2/PageSettingsData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -33,6 +33,7 @@ PageType { descriptionText: "May be needed when changing other settings" clickedFunction: function() { + ContainersModel.clearCachedProfiles() } } @@ -45,6 +46,15 @@ PageType { textColor: "#EB5757" clickedFunction: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { + ConnectionController.closeVpnConnection() + } + ServersModel.removeServer() + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + } } } @@ -57,6 +67,9 @@ PageType { textColor: "#EB5757" clickedFunction: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { + ConnectionController.closeVpnConnection() + } ContainersModel.removeAllContainers() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml new file mode 100644 index 000000000..29a0c3c1a --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + ColumnLayout { + id: content + + anchors.fill: parent + + spacing: 16 + + Repeater { + id: header + model: proxyServersModel + + delegate: HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: name + descriptionText: hostName + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + closePage() + } + } + } + + TabBar { + id: tabBar + + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + isSelected: tabBar.currentIndex === 0 + text: qsTr("Protocols") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerProtocols) +// } + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Services") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerServices) +// } + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Data") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerData) +// } + } + } + + StackLayout { + Layout.preferredWidth: root.width + Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + + currentIndex: tabBar.currentIndex + + PageSettingsServerProtocols { + stackView: root.stackView + } + PageSettingsServerServices { + stackView: root.stackView + } + PageSettingsServerData { + stackView: root.stackView + } + } + +// StackViewType { +// id: tabBarStackView + +// Layout.preferredWidth: root.width +// Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + +// function goToTabBarPage(page) { +// var pagePath = PageController.getPagePath(page) +// while (tabBarStackView.depth > 1) { +// tabBarStackView.pop() +// } +// tabBarStackView.replace(pagePath, { "objectName" : pagePath }) +// } + +// Component.onCompleted: { +// var pagePath = PageController.getPagePath(PageEnum.PageSettingsServerProtocols) +// tabBarStackView.push(pagePath, { "objectName" : pagePath }) +// } +// } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml new file mode 100644 index 000000000..e93aa5280 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: containersProxyModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + + SettingsContainersListView { + model: containersProxyModel + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml new file mode 100644 index 000000000..7351f5857 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: containersProxyModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Other + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + + SettingsContainersListView { + model: containersProxyModel + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 2344b7e8c..63a40cc0c 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -36,10 +36,6 @@ PageType { actionButtonFunction: function() { connectionTypeSelection.visible = true } - - backButtonFunction: function() { - PageController.goToPageHome() - } } ConnectionTypeSelectionDrawer { @@ -87,7 +83,8 @@ PageType { clickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(index) - goToPage(PageEnum.PageSettings) + ContainersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 006743d94..cf7b1d28c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -26,10 +26,26 @@ PageType { function onInstallContainerFinished() { goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState(true) + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + goToPage(PageEnum.PageSettingsServerInfo, false) + } else { + goToPage(PageEnum.PageHome) + } } function onInstallServerFinished() { goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + } else { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + stackView.replace(pagePath, { "objectName" : pagePath }) + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 804e145cd..6358f00dd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -22,16 +22,6 @@ PageType { } } - Connections { - target: InstallController - - function onInstallServerFinished() { - //todo add smt like changeStartPage - goToStartPage() - goToPage(PageEnum.PageStart) - } - } - FlickableType { id: fl anchors.top: root.top diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index c50d4591c..445770370 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -17,6 +17,7 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 + tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) } function onShowErrorMessage(errorMessage) { @@ -25,9 +26,8 @@ PageType { } } - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex + StackViewType { + id: tabBarStackView anchors.top: parent.top anchors.right: parent.right @@ -37,20 +37,15 @@ PageType { width: parent.width height: root.height - tabBar.implicitHeight - StackView { - initialItem: PageHome { - id: pageHome - } + function goToTabBarPage(page) { + var pagePath = PageController.getPagePath(page) + tabBarStackView.clear(StackView.PopTransition) + tabBarStackView.replace(pagePath, { "objectName" : pagePath }) } - Item { - - } - - StackView { - initialItem: PageSettingsServersList { - id: pageSettingsServersList - } + Component.onCompleted: { + var pagePath = PageController.getPagePath(PageEnum.PageHome) + tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -70,12 +65,12 @@ PageType { color: "#1C1D21" } - TabImageButtonType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - pageSettingsServersList.goToStartPage() + tabBarStackView.goToTabBarPage(PageEnum.PageHome) + ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) } } TabImageButtonType { @@ -86,7 +81,7 @@ PageType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" onClicked: { - pageHome.goToStartPage() + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 5ca1b3458..bda1c709b 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -4,8 +4,10 @@ import QtQuick.Controls import QtQuick.Layouts import PageType 1.0 +import PageEnum 1.0 import "Config" +import "Controls2" Window { id: root @@ -27,9 +29,27 @@ Window { color: "#0E0E11" } - StackView { + StackViewType { + id: rootStackView + anchors.fill: parent focus: true - initialItem: PageController.getInitialPage() + + Component.onCompleted: { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + rootStackView.push(pagePath, { "objectName" : pagePath }) + } + } + + Connections { + target: PageController + + function onReplaceStartPage() { + var pagePath = PageController.getInitialPage() + while (rootStackView.depth > 1) { + rootStackView.pop() + } + rootStackView.replace(pagePath, { "objectName" : pagePath }) + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 1cb43ee86..a0cac1cd3 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -149,13 +149,13 @@ void UiLogic::initializeUiLogic() connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); - if (m_settings->serversCount() > 0) { - if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - emit goToPage(Page::PageStart, true, false); - } - else { - emit goToPage(Page::PageSetupWizardStart, true, false); - } +// if (m_settings->serversCount() > 0) { +// if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); +// emit goToPage(Page::PageStart, true, false); +// } +// else { +// emit goToPage(Page::PageSetupWizardStart, true, false); +// } m_selectedServerIndex = m_settings->defaultServerIndex(); From 420c33d3baa322b57e3b19f43083d730d4cc639c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 15:49:10 +0800 Subject: [PATCH 025/131] added PageSetupWizardViewConfig - added a popup with a question when deleting containers/servers - added import from code and import error handling --- client/core/defs.h | 5 +- client/core/errorstrings.cpp | 2 + client/images/controls/file-cog-2.svg | 11 ++ client/resources.qrc | 5 + .../ui/controllers/connectionController.cpp | 53 ++---- client/ui/controllers/connectionController.h | 14 +- client/ui/controllers/importController.cpp | 75 +++++---- client/ui/controllers/importController.h | 17 +- client/ui/controllers/pageController.h | 4 +- client/ui/qml/Components/ConnectButton.qml | 8 +- .../ConnectionTypeSelectionDrawer.qml | 14 -- .../qml/Components/HomeContainersListView.qml | 60 ++++--- client/ui/qml/Components/QuestionDrawer.qml | 77 +++++++++ client/ui/qml/Controls2/BackButtonType.qml | 55 +++++++ client/ui/qml/Controls2/DrawerType.qml | 14 ++ client/ui/qml/Controls2/DropDownType.qml | 39 ++--- client/ui/qml/Controls2/Header2Type.qml | 22 --- client/ui/qml/Controls2/HeaderType.qml | 22 --- client/ui/qml/Controls2/ProgressBarType.qml | 10 +- client/ui/qml/Controls2/SwitcherType.qml | 1 - .../qml/Controls2/TextFieldWithHeaderType.qml | 3 +- client/ui/qml/Pages2/PageDeinstalling.qml | 91 +++++++++++ client/ui/qml/Pages2/PageHome.qml | 14 -- client/ui/qml/Pages2/PageSettings.qml | 3 - .../ui/qml/Pages2/PageSettingsServerData.qml | 65 ++++++-- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 58 ++----- .../ui/qml/Pages2/PageSettingsServersList.qml | 18 ++- .../Pages2/PageSetupWizardConfigSource.qml | 18 +-- .../qml/Pages2/PageSetupWizardCredentials.qml | 16 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 25 +-- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 +- .../PageSetupWizardProtocolSettings.qml | 7 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 5 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 22 +-- .../qml/Pages2/PageSetupWizardViewConfig.qml | 152 ++++++++++++++++++ client/ui/qml/Pages2/PageStart.qml | 2 + client/ui/qml/main2.qml | 2 +- 37 files changed, 701 insertions(+), 312 deletions(-) create mode 100644 client/images/controls/file-cog-2.svg create mode 100644 client/ui/qml/Components/QuestionDrawer.qml create mode 100644 client/ui/qml/Controls2/BackButtonType.qml create mode 100644 client/ui/qml/Pages2/PageDeinstalling.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardViewConfig.qml diff --git a/client/core/defs.h b/client/core/defs.h index 3a3ff5653..61c45e4e3 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -68,7 +68,10 @@ enum ErrorCode OpenSslFailed, OpenVpnExecutableCrashed, ShadowSocksExecutableCrashed, - CloakExecutableCrashed + CloakExecutableCrashed, + + // import and install errors + ImportInvalidConfigError }; } // namespace amnezia diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 17b40b092..dd298c76f 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -57,6 +57,8 @@ QString errorString(ErrorCode code){ case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter"); case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses"); + case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentiaks for connecting to the server"); + case(InternalError): default: return QObject::tr("Internal error"); diff --git a/client/images/controls/file-cog-2.svg b/client/images/controls/file-cog-2.svg new file mode 100644 index 000000000..20815319c --- /dev/null +++ b/client/images/controls/file-cog-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index db53165c2..185c4469c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -249,5 +249,10 @@ images/controls/server.svg ui/qml/Pages2/PageSettingsServerProtocols.qml ui/qml/Pages2/PageSettingsServerServices.qml + ui/qml/Pages2/PageSetupWizardViewConfig.qml + images/controls/file-cog-2.svg + ui/qml/Components/QuestionDrawer.qml + ui/qml/Pages2/PageDeinstalling.qml + ui/qml/Controls2/BackButtonType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 617fd5ee1..c4717012a 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -9,21 +9,7 @@ ConnectionController::ConnectionController(const QSharedPointer &s } -bool ConnectionController::onConnectionButtonClicked() -{ - if (!isConnected()) { - openVpnConnection(); - } else { - closeVpnConnection(); - } -} - -bool ConnectionController::isConnected() -{ - return m_isConnected; -} - -bool ConnectionController::openVpnConnection() +void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); QModelIndex serverModelIndex = m_serversModel->index(serverIndex); @@ -35,16 +21,6 @@ bool ConnectionController::openVpnConnection() const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); - //todo error handling - qApp->processEvents(); - emit connectToVpn(serverIndex, credentials, container, containerConfig); - m_isConnected = true; - - -// int serverIndex = m_settings->defaultServerIndex(); -// ServerCredentials credentials = m_settings->serverCredentials(serverIndex); -// DockerContainer container = m_settings->defaultContainer(serverIndex); - // if (m_settings->containers(serverIndex).isEmpty()) { // set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); // set_pushButtonConnectChecked(false); @@ -57,20 +33,23 @@ bool ConnectionController::openVpnConnection() // return; // } - -// const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - -// set_labelErrorText(""); -// set_pushButtonConnectChecked(true); -// set_pushButtonConnectEnabled(false); - -// qApp->processEvents(); - -// emit connectToVpn(serverIndex, credentials, container, containerConfig); + //todo error handling + qApp->processEvents(); + emit connectToVpn(serverIndex, credentials, container, containerConfig); } -bool ConnectionController::closeVpnConnection() +void ConnectionController::closeConnection() { emit disconnectFromVpn(); - m_isConnected = false; +} + +bool ConnectionController::isConnected() +{ + return m_isConnected; +} + +void ConnectionController::setIsConnected(bool isConnected) +{ + m_isConnected = isConnected; + emit isConnectedChanged(); } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 8282a5824..b1b0ff98b 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -10,24 +10,26 @@ class ConnectionController : public QObject Q_OBJECT public: + Q_PROPERTY(bool isConnected READ isConnected WRITE setIsConnected NOTIFY isConnectedChanged) + explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, QObject *parent = nullptr); -public slots: - bool onConnectionButtonClicked(); - bool isConnected(); + void setIsConnected(bool isConnected); + +public slots: + void openConnection(); + void closeConnection(); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(Vpn::ConnectionState state); + void isConnectedChanged(); private: - bool openVpnConnection(); - bool closeVpnConnection(); - QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index ea1e7ad62..48e811e09 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -1,6 +1,9 @@ #include "importController.h" #include +#include + +#include "core/errorstrings.h" namespace { enum class ConfigTypes { @@ -39,50 +42,68 @@ ImportController::ImportController(const QSharedPointer &serversMo } -bool ImportController::importFromFile(const QUrl &fileUrl) +void ImportController::extractConfigFromFile(const QUrl &fileUrl) { QFile file(fileUrl.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); + QString data = file.readAll(); auto configFormat = checkConfigFormat(data); if (configFormat == ConfigTypes::OpenVpn) { - return importOpenVpnConfig(data); + m_config = extractOpenVpnConfig(data); } else if (configFormat == ConfigTypes::WireGuard) { - return importWireGuardConfig(data); + m_config = extractWireGuardConfig(data); } else { - return importAmneziaConfig(data); + m_config = extractAmneziaConfig(data); } + + m_configFileName = QFileInfo(file.fileName()).fileName(); } - return false; } -bool ImportController::import(const QJsonObject &config) +void ImportController::extractConfigFromCode(QString code) +{ + m_config = extractAmneziaConfig(code); +} + +QString ImportController::getConfig() +{ + return QJsonDocument(m_config).toJson(QJsonDocument::Indented); +} + +QString ImportController::getConfigFileName() +{ + return m_configFileName; +} + +void ImportController::importConfig() { ServerCredentials credentials; - credentials.hostName = config.value(config_key::hostName).toString(); - credentials.port = config.value(config_key::port).toInt(); - credentials.userName = config.value(config_key::userName).toString(); - credentials.secretData = config.value(config_key::password).toString(); + credentials.hostName = m_config.value(config_key::hostName).toString(); + credentials.port = m_config.value(config_key::port).toInt(); + credentials.userName = m_config.value(config_key::userName).toString(); + credentials.secretData = m_config.value(config_key::password).toString(); - if (credentials.isValid() || config.contains(config_key::containers)) { - m_settings->addServer(config); + if (credentials.isValid() || m_config.contains(config_key::containers)) { + m_serversModel->addServer(m_config); - if (config.value(config_key::containers).toArray().isEmpty()) { - m_settings->setDefaultServer(m_settings->serversCount() - 1); + if (!m_config.value(config_key::containers).toArray().isEmpty()) { + auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); + m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); } emit importFinished(); } else { qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(config).toJson(); - return false; + qDebug().noquote() << QJsonDocument(m_config).toJson(); + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); } - return true; + m_config = {}; + m_configFileName.clear(); } -bool ImportController::importAmneziaConfig(QString data) +QJsonObject ImportController::extractAmneziaConfig(QString &data) { data.replace("vpn://", ""); QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); @@ -92,13 +113,7 @@ bool ImportController::importAmneziaConfig(QString data) ba = ba_uncompressed; } - QJsonObject config; - config = QJsonDocument::fromJson(ba).object(); - if (!config.isEmpty()) { - return import(config); - } - - return false; + return QJsonDocument::fromJson(ba).object(); } //bool ImportController::importConnectionFromQr(const QByteArray &data) @@ -116,7 +131,7 @@ bool ImportController::importAmneziaConfig(QString data) // return false; //} -bool ImportController::importOpenVpnConfig(const QString &data) +QJsonObject ImportController::extractOpenVpnConfig(const QString &data) { QJsonObject openVpnConfig; openVpnConfig[config_key::config] = data; @@ -156,10 +171,10 @@ bool ImportController::importOpenVpnConfig(const QString &data) config[config_key::hostName] = hostName; - return import(config); + return config; } -bool ImportController::importWireGuardConfig(const QString &data) +QJsonObject ImportController::extractWireGuardConfig(const QString &data) { QJsonObject lastConfig; lastConfig[config_key::config] = data; @@ -200,5 +215,5 @@ bool ImportController::importWireGuardConfig(const QString &data) config[config_key::hostName] = hostName; - return import(config); + return config; } diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 7dd548d87..114bb5310 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -18,20 +18,27 @@ public: QObject *parent = nullptr); public slots: - bool importFromFile(const QUrl &fileUrl); + void importConfig(); + void extractConfigFromFile(const QUrl &fileUrl); + void extractConfigFromCode(QString code); + QString getConfig(); + QString getConfigFileName(); signals: void importFinished(); + void importErrorOccurred(QString errorMessage); private: - bool import(const QJsonObject &config); - bool importAmneziaConfig(QString data); - bool importOpenVpnConfig(const QString &data); - bool importWireGuardConfig(const QString &data); + QJsonObject extractAmneziaConfig(QString &data); + QJsonObject extractOpenVpnConfig(const QString &data); + QJsonObject extractWireGuardConfig(const QString &data); QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; + QJsonObject m_config; + QString m_configFileName; + }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1d041deb1..22ee0bb3c 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,14 +9,14 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageShare, + enum class PageEnum { PageStart = 0, PageHome, PageShare, PageDeinstalling, PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, PageSettingsServerProtocols, PageSettingsServerServices, PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey + PageSetupWizardTextKey, PageSetupWizardViewConfig }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 73accef4f..06c148f17 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -13,7 +13,7 @@ Button { id: border source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected() ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" RotationAnimator { id: connectionProccess @@ -46,7 +46,7 @@ Button { } onClicked: { - ConnectionController.onConnectionButtonClicked() + ConnectionController.isConnected ? ConnectionController.closeConnection() : ConnectionController.openConnection() } Connections { @@ -61,6 +61,7 @@ Button { console.log("Disconnected") connectionProccess.running = false root.text = qsTr("Connect") + ConnectionController.isConnected = false break } case ConnectionState.Preparing: { @@ -78,7 +79,8 @@ Button { case ConnectionState.Connected: { console.log("Connected") connectionProccess.running = false - root.text = qsTr("Connected") + root.text = qsTr("Disconnect") + ConnectionController.isConnected = true break } case ConnectionState.Disconnecting: { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index d80d5e5af..3c1ecd5bf 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -14,20 +14,6 @@ DrawerType { width: parent.width height: parent.height * 0.4375 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#2C2D30" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - ColumnLayout { anchors.top: parent.top anchors.left: parent.left diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index c3e98fbba..0d42b8e99 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -27,44 +27,54 @@ ListView { delegate: Item { implicitWidth: rootWidth - implicitHeight: containerRadioButton.implicitHeight + implicitHeight: content.implicitHeight - VerticalRadioButton { - id: containerRadioButton + ColumnLayout { + id: content anchors.fill: parent anchors.rightMargin: 16 anchors.leftMargin: 16 - text: name - descriptionText: description + VerticalRadioButton { + id: containerRadioButton - ButtonGroup.group: containersRadioButtonGroup + Layout.fillWidth: true - imageSource: "qrc:/images/controls/download.svg" - showImage: !isInstalled + text: name + descriptionText: description - checkable: isInstalled - checked: isDefault + ButtonGroup.group: containersRadioButtonGroup - onClicked: { - if (checked) { - isDefault = true - menuContent.currentIndex = index - containersDropDown.menuVisible = false - } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.menuVisible = false - menu.visible = false + imageSource: "qrc:/images/controls/download.svg" + showImage: !isInstalled + + checkable: isInstalled + checked: isDefault + + onClicked: { + if (checked) { + isDefault = true + menuContent.currentIndex = index + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false } } - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false + DividerType { + Layout.fillWidth: true } } diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml new file mode 100644 index 000000000..a79f91403 --- /dev/null +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + property string headerText + property string descriptionText + property string yesButtonText + property string noButtonText + + property var yesButtonFunction + property var noButtonFunction + + width: parent.width + height: parent.height * 0.5 + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 8 + + Header2TextType { + Layout.fillWidth: true + + text: headerText + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: descriptionText + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: yesButtonText + + onClicked: { + if (yesButtonFunction && typeof yesButtonFunction === "function") { + yesButtonFunction() + } + } + } + + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: noButtonText + + onClicked: { + if (noButtonFunction && typeof noButtonFunction === "function") { + noButtonFunction() + } + } + } + } +} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml new file mode 100644 index 000000000..bec318238 --- /dev/null +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +Item { + id: root + + property string backButtonImage: "qrc:/images/controls/arrow-left.svg" + property var backButtonFunction + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + RowLayout { + id: content + + anchors.fill: parent + + ImageButtonType { + image: backButtonImage + imageColor: "#D7D8DB" + + onClicked: { + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } else { + closePage() + } + } + } + + Rectangle { + id: background + Layout.fillWidth: true + + color: "transparent" + + ShaderEffectSource { + id: effectSource + + sourceItem: background + anchors.fill: background + sourceRect: Qt.rect(x,y, width, height) + } + + FastBlur { + id: blur + anchors.fill: effectSource + + source: effectSource + radius: 100 + } + } + } +} diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index bede3700d..e3c9d5881 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -17,4 +17,18 @@ Drawer { velocity: 4 } } + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: "#2C2D30" + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 5ac50fa7b..700d9981b 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -117,28 +117,9 @@ Item { width: parent.width height: parent.height * 0.9 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#494B50" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - Header2Type { + ColumnLayout { id: header - headerText: root.headerText - backButtonImage: root.headerBackButtonImage - - width: parent.width - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -146,8 +127,11 @@ Item { anchors.leftMargin: 16 anchors.rightMargin: 16 - backButtonFunction: function() { - root.menuVisible = false + BackButtonType { + backButtonImage: root.headerBackButtonImage + backButtonFunction: function() { + root.menuVisible = false + } } } @@ -164,6 +148,17 @@ Item { spacing: 16 + Header2Type { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + headerText: root.headerText + + width: parent.width + } + Loader { id: listViewLoader sourceComponent: root.listView diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index ce9d804a9..9433f52a6 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -6,10 +6,7 @@ import "TextTypes" Item { id: root - property string backButtonImage property string actionButtonImage - - property var backButtonFunction property var actionButtonFunction property string headerText @@ -22,25 +19,6 @@ Item { id: content anchors.fill: parent - ImageButtonType { - id: backButton - - Layout.leftMargin: -6 - - image: root.backButtonImage - imageColor: "#D7D8DB" - - visible: image ? true : false - - onClicked: { - if (backButtonFunction && typeof backButtonFunction === "function") { - backButtonFunction() - } else { - closePage() - } - } - } - RowLayout { Header2TextType { id: header diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 2c0fbd851..19958205f 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -6,10 +6,7 @@ import "TextTypes" Item { id: root - property string backButtonImage property string actionButtonImage - - property var backButtonFunction property var actionButtonFunction property string headerText @@ -22,25 +19,6 @@ Item { id: content anchors.fill: parent - ImageButtonType { - id: backButton - - Layout.leftMargin: -6 - - image: root.backButtonImage - imageColor: "#D7D8DB" - - visible: image ? true : false - - onClicked: { - if (backButtonFunction && typeof backButtonFunction === "function") { - backButtonFunction() - } else { - closePage() - } - } - } - RowLayout { Header1TextType { id: header diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml index f2f2370a8..183c3736b 100644 --- a/client/ui/qml/Controls2/ProgressBarType.qml +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -11,9 +11,11 @@ ProgressBar { color: "#412102" } - contentItem: Rectangle { - width: root.visualPosition * root.width - height: root.height - color: "#FBB26A" + contentItem: Item { + Rectangle { + width: root.visualPosition * parent.width + height: parent.height + color: "#FBB26A" + } } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index e4df04faf..b593ece84 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -59,7 +59,6 @@ Switch { } } - contentItem: ColumnLayout { contentItem: ColumnLayout { id: content diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3458f7411..b41f0b400 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -6,7 +6,6 @@ Item { id: root property string headerText - property string textFieldText property string textFieldPlaceholderText property bool textFieldEditable: true @@ -14,6 +13,7 @@ Item { property var clickedFunc property alias textField: textField + property alias textFieldText: textField.text implicitHeight: 74 @@ -53,7 +53,6 @@ Item { id: textField enabled: root.textFieldEditable - text: root.textFieldText color: "#d7d8db" placeholderText: textFieldPlaceholderText diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml new file mode 100644 index 000000000..1dc542e05 --- /dev/null +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -0,0 +1,91 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + id: fl + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Repeater { + model: proxyServersModel + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("Removing services from ") + name + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + Timer { + id: timer + + interval: 300 + repeat: true + running: true + onTriggered: { + progressBar.value += 0.001 + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: "Обычно это занимает не больше 5 минут" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 0ecbafcc3..4f3c4b6a5 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -113,20 +113,6 @@ PageType { width: parent.width height: parent.height * 0.90 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - - color: "#1C1D21" - border.color: root.borderColor - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - ColumnLayout { id: serversMenuHeader anchors.top: parent.top diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index d3b87c191..300421b71 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -71,7 +71,6 @@ PageType { iconImage: "qrc:/images/controls/app.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } @@ -85,7 +84,6 @@ PageType { iconImage: "qrc:/images/controls/save.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } @@ -99,7 +97,6 @@ PageType { iconImage: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 3ab762993..c1f84cb09 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -9,6 +9,7 @@ import ProtocolEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -33,7 +34,19 @@ PageType { descriptionText: "May be needed when changing other settings" clickedFunction: function() { - ContainersModel.clearCachedProfiles() + questionDrawer.headerText = qsTr("Clear cached profiles?") + questionDrawer.descriptionText = qsTr("some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + ContainersModel.clearCachedProfiles() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } @@ -46,15 +59,27 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { - ConnectionController.closeVpnConnection() + questionDrawer.headerText = qsTr("Remove server?") + questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + ConnectionController.closeConnection() + } + ServersModel.removeServer() + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + } } - ServersModel.removeServer() - if (!ServersModel.getServersCount()) { - PageController.replaceStartPage() - } else { - goToStartPage() + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false } + questionDrawer.visible = true } } @@ -67,14 +92,32 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { - ConnectionController.closeVpnConnection() + questionDrawer.headerText = qsTr("Clear server from Amnezia software?") + questionDrawer.descriptionText = qsTr(" All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + ConnectionController.closeVpnConnection() + } + ContainersModel.removeAllContainers() + closePage() } - ContainersModel.removeAllContainers() + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } DividerType {} + + QuestionDrawer { + id: questionDrawer + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 29a0c3c1a..5e4222304 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -30,8 +30,6 @@ PageType { } ColumnLayout { - id: content - anchors.fill: parent spacing: 16 @@ -40,24 +38,27 @@ PageType { id: header model: proxyServersModel - delegate: HeaderType { - Layout.fillWidth: true + delegate: ColumnLayout { + id: content + Layout.topMargin: 20 Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/edit-3.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: name - descriptionText: hostName - - actionButtonFunction: function() { - connectionTypeSelection.visible = true + BackButtonType { } - backButtonFunction: function() { - closePage() + HeaderType { + Layout.fillWidth: true + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + + headerText: name + descriptionText: hostName + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } } } } @@ -74,23 +75,14 @@ PageType { TabButtonType { isSelected: tabBar.currentIndex === 0 text: qsTr("Protocols") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerProtocols) -// } } TabButtonType { isSelected: tabBar.currentIndex === 1 text: qsTr("Services") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerServices) -// } } TabButtonType { isSelected: tabBar.currentIndex === 2 text: qsTr("Data") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerData) -// } } } @@ -110,25 +102,5 @@ PageType { stackView: root.stackView } } - -// StackViewType { -// id: tabBarStackView - -// Layout.preferredWidth: root.width -// Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight - -// function goToTabBarPage(page) { -// var pagePath = PageController.getPagePath(page) -// while (tabBarStackView.depth > 1) { -// tabBarStackView.pop() -// } -// tabBarStackView.replace(pagePath, { "objectName" : pagePath }) -// } - -// Component.onCompleted: { -// var pagePath = PageController.getPagePath(PageEnum.PageSettingsServerProtocols) -// tabBarStackView.push(pagePath, { "objectName" : pagePath }) -// } -// } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 63a40cc0c..9c5ddc74d 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -17,7 +17,7 @@ import "../Components" PageType { id: root - HeaderType { + ColumnLayout { id: header anchors.top: parent.top @@ -28,13 +28,19 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" + BackButtonType { + } - headerText: "Серверы" + HeaderType { + Layout.fillWidth: true - actionButtonFunction: function() { - connectionTypeSelection.visible = true + actionButtonImage: "qrc:/images/controls/plus.svg" + + headerText: "Серверы" + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 25b88f761..79e596af7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,14 +13,6 @@ import "../Config" PageType { id: root - Connections { - target: ImportController - - function onImportFinished() { - - } - } - FlickableType { id: fl anchors.top: root.top @@ -34,13 +26,18 @@ PageType { anchors.left: parent.left anchors.right: parent.right + BackButtonType { + Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + } + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 Layout.rightMargin: 16 Layout.leftMargin: 16 - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n @@ -71,7 +68,8 @@ PageType { FileDialog { id: fileDialog onAccepted: { - ImportController.importFromFile(selectedFile) + ImportController.extractConfigFromFile(selectedFile) + goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 95463c3e8..491979626 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,9 +11,20 @@ import "../Config" PageType { id: root + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + FlickableType { id: fl - anchors.top: root.top + anchors.top: backButton.bottom anchors.bottom: root.bottom contentHeight: content.height @@ -30,9 +41,6 @@ PageType { HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 - - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 8707c19b3..99f6ccfba 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -30,11 +30,22 @@ PageType { } } + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + FlickableType { id: fl - anchors.top: root.top + anchors.top: backButton.bottom anchors.bottom: root.bottom - contentHeight: content.implicitHeight + buttonContinue.anchors.bottomMargin + contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin Column { id: content @@ -44,7 +55,6 @@ PageType { anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 - anchors.topMargin: 20 spacing: 16 @@ -53,9 +63,7 @@ PageType { implicitWidth: parent.width - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: qsTr("What is the level of Internet control in your region?") + headerText: qsTr("What is the level of internet control in your region?") } ListView { @@ -118,11 +126,10 @@ PageType { } BasicButtonType { - id: buttonContinue + id: continueButton implicitWidth: parent.width - anchors.topMargin: 24 - anchors.bottomMargin: 32 + anchors.bottomMargin: 24 text: qsTr("Continue") diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index cf7b1d28c..c18c4d279 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -109,7 +109,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 - value: progressBarValue +// value: progressBarValue Timer { id: timer @@ -118,7 +118,7 @@ PageType { repeat: true running: true onTriggered: { - progressBarValue += 0.001 + progressBar.value += 0.001 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index fa3ac8383..39761cee3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -62,13 +62,14 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 + BackButtonType { + Layout.topMargin: 20 + } + HeaderType { id: header Layout.fillWidth: true - Layout.topMargin: 20 - - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка " + name descriptionText: "Эти настройки можно будет изменить позже" diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index cdacaf040..75b16afd7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -48,9 +48,12 @@ PageType { spacing: 16 + BackButtonType { + width: parent.width + } + HeaderType { width: parent.width - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Протокол подключения" descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 43dbda0e5..f74c37339 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -29,23 +29,26 @@ PageType { spacing: 16 + BackButtonType { + Layout.topMargin: 20 + } + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: "Ключ для подключения" - descriptionText: "Строка, которая начинается с vpn://..." + headerText: qsTr("Connection key") + descriptionText: qsTr("A line that starts with vpn://...") } TextFieldWithHeaderType { + id: textKey + Layout.fillWidth: true Layout.topMargin: 32 - headerText: "Ключ" + headerText: qsTr("Key") textFieldPlaceholderText: "vpn://" - buttonText: "Вставить" + buttonText: qsTr("Insert") clickedFunc: function() { textField.text = "" @@ -63,10 +66,11 @@ PageType { anchors.leftMargin: 16 anchors.bottomMargin: 32 - text: qsTr("Подключиться") + text: qsTr("Continue") onClicked: function() { -// goToPage(PageEnum.PageSetupWizardInstalling) + ImportController.extractConfigFromCode(textKey.textFieldText) + goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml new file mode 100644 index 000000000..ca679e79a --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -0,0 +1,152 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + property bool showContent: false + + Connections { + target: ImportController + + function onImportErrorOccurred(errorMessage) { + closePage() + PageController.showErrorMessage(errorMessage) + } + + function onImportFinished() { + goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + } else { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + stackView.replace(pagePath, { "objectName" : pagePath }) + } + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.implicitHeight + connectButton.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + headerText: qsTr("New connection") + } + + RowLayout { + Layout.topMargin: 32 + spacing: 8 + + visible: fileName.text !== "" + + Image { + source: "qrc:/images/controls/file-cog-2.svg" + } + + Header2TextType { + id: fileName + + Layout.fillWidth: true + + text: ImportController.getConfigFileName() + } + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Do not use connection code from public sources. It could be created to intercept your data.") + color: "#878B91" + } + + BasicButtonType { + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + + text: showContent ? qsTr("Collapse content") : qsTr("Show content") + + onClicked: { + showContent = !showContent + } + } + + Rectangle { + Layout.fillWidth: true + Layout.bottomMargin: 16 + + implicitHeight: configContent.implicitHeight + + radius: 10 + color: "#1C1D21" + + visible: showContent + + ParagraphTextType { + id: configContent + + anchors.fill: parent + anchors.margins: 16 + + text: ImportController.getConfig() + } + } + } + } + + ColumnLayout { + id: connectButton + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Connect") + onClicked: { + ImportController.importConfig() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 445770370..436194b24 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -46,6 +46,8 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) tabBarStackView.push(pagePath, { "objectName" : pagePath }) + ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index bda1c709b..9bb6aa28d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -36,7 +36,7 @@ Window { focus: true Component.onCompleted: { - var pagePath = PageController.getPagePath(PageEnum.PageStart) + var pagePath = PageController.getInitialPage() rootStackView.push(pagePath, { "objectName" : pagePath }) } } From 80fca589af50d626ea8ea864a73b2b57c493a455 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 22:40:35 +0800 Subject: [PATCH 026/131] added ConnectionController error handling --- client/amnezia_application.cpp | 95 ++++--------------- client/amnezia_application.h | 4 +- .../ui/controllers/connectionController.cpp | 45 ++++++--- client/ui/controllers/connectionController.h | 8 ++ client/ui/controllers/importController.cpp | 44 ++++----- client/ui/controllers/installController.cpp | 1 - client/ui/models/containers_model.cpp | 14 +-- client/ui/qml/Components/ConnectButton.qml | 53 ++++++++--- client/ui/qml/Pages2/PageHome.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 7 +- client/ui/qml/main2.qml | 1 - 11 files changed, 134 insertions(+), 140 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index c8bdcf3bb..dbeaac38f 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -4,36 +4,15 @@ #include #include #include +#include -#include "core/servercontroller.h" #include "logger.h" #include "defines.h" -#include #include "platforms/ios/QRCodeReaderBase.h" #include "ui/pages.h" -#include "ui/pages_logic/AppSettingsLogic.h" -#include "ui/pages_logic/GeneralSettingsLogic.h" -#include "ui/pages_logic/NetworkSettingsLogic.h" -#include "ui/pages_logic/NewServerProtocolsLogic.h" -#include "ui/pages_logic/QrDecoderLogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ServerListLogic.h" -#include "ui/pages_logic/ServerSettingsLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ShareConnectionLogic.h" -#include "ui/pages_logic/SitesLogic.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "ui/pages_logic/VpnLogic.h" -#include "ui/pages_logic/WizardLogic.h" - -#include "ui/pages_logic/protocols/CloakLogic.h" -#include "ui/pages_logic/protocols/OpenVpnLogic.h" -#include "ui/pages_logic/protocols/ShadowSocksLogic.h" - #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif @@ -76,10 +55,6 @@ AmneziaApplication::~AmneziaApplication() QObject::disconnect(m_engine, 0,0,0); delete m_engine; } - if (m_uiLogic) { - QObject::disconnect(m_uiLogic, 0,0,0); - delete m_uiLogic; - } if (m_protocolProps) delete m_protocolProps; if (m_containerProps) delete m_containerProps; @@ -88,7 +63,6 @@ AmneziaApplication::~AmneziaApplication() void AmneziaApplication::init() { m_engine = new QQmlApplicationEngine; - m_uiLogic = new UiLogic(m_settings, m_configurator); const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, @@ -108,14 +82,8 @@ void AmneziaApplication::init() m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, - m_connectionController.get(), &ConnectionController::connectionStateChanged); - connect(m_connectionController.get(), &ConnectionController::connectToVpn, - m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, - m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_pageController.reset(new PageController(m_serversModel)); @@ -128,17 +96,12 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); // - m_uiLogic->registerPagesLogic(); - -#if defined(Q_OS_IOS) - setStartPageLogic(m_uiLogic->pageLogic()); -#endif m_engine->load(url); - if (m_engine->rootObjects().size() > 0) { - m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); - } +// if (m_engine->rootObjects().size() > 0) { +// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); +// } if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -146,23 +109,23 @@ void AmneziaApplication::init() } } -#ifdef Q_OS_WIN - if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); - else emit m_uiLogic->show(); -#else - m_uiLogic->showOnStartup(); -#endif +//#ifdef Q_OS_WIN +// if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); +// else emit m_uiLogic->show(); +//#else +// m_uiLogic->showOnStartup(); +//#endif - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ - qDebug() << "Secondary instance started, showing this window instead"; - emit m_uiLogic->show(); - emit m_uiLogic->raise(); - }); - } -#endif +// // TODO - fix +//#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +// if (isPrimary()) { +// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ +// qDebug() << "Secondary instance started, showing this window instead"; +// emit m_uiLogic->show(); +// emit m_uiLogic->raise(); +// }); +// } +//#endif } @@ -174,16 +137,10 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("TransportProto"); qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); - qRegisterMetaType("Page"); - qRegisterMetaType("PageProtocolLogicBase *"); - - -// declareQmlPageEnum(); declareQmlProtocolEnum(); declareQmlContainerEnum(); - qmlRegisterType("PageType", 1, 0, "PageType"); qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); m_containerProps = new ContainerProps; @@ -201,16 +158,6 @@ void AmneziaApplication::loadFonts() { QQuickStyle::setStyle("Basic"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BoldItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Italic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Light.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-LightItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index f4156d159..0fd6842cf 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -11,7 +11,6 @@ #include "settings.h" #include "vpnconnection.h" -#include "ui/uilogic.h" #include "configurators/vpn_configurator.h" #include "ui/models/servers_model.h" @@ -54,7 +53,6 @@ public: private: QQmlApplicationEngine *m_engine {}; - UiLogic *m_uiLogic {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; @@ -67,7 +65,7 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; - QScopedPointer m_vpnConnection; + QSharedPointer m_vpnConnection; QScopedPointer m_connectionController; QScopedPointer m_pageController; diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index c4717012a..fbbb67d98 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -2,11 +2,31 @@ #include +#include "core/errorstrings.h" + ConnectionController::ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel) + const QSharedPointer &vpnConnection, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_vpnConnection(vpnConnection) { - + connect(m_vpnConnection.get(), + &VpnConnection::connectionStateChanged, + this, + &ConnectionController::connectionStateChanged); + connect(this, + &ConnectionController::connectToVpn, + m_vpnConnection.get(), + &VpnConnection::connectToVpn, + Qt::QueuedConnection); + connect(this, + &ConnectionController::disconnectFromVpn, + m_vpnConnection.get(), + &VpnConnection::disconnectFromVpn, + Qt::QueuedConnection); } void ConnectionController::openConnection() @@ -21,19 +41,11 @@ void ConnectionController::openConnection() const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); -// if (m_settings->containers(serverIndex).isEmpty()) { -// set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); -// set_pushButtonConnectChecked(false); -// return; -// } + if (container == DockerContainer::None) { + emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); + return; + } -// if (container == DockerContainer::None) { -// set_labelErrorText(tr("VPN Protocol not chosen")); -// set_pushButtonConnectChecked(false); -// return; -// } - - //todo error handling qApp->processEvents(); emit connectToVpn(serverIndex, credentials, container, containerConfig); } @@ -43,6 +55,11 @@ void ConnectionController::closeConnection() emit disconnectFromVpn(); } +QString ConnectionController::getLastConnectionError() +{ + return errorString(m_vpnConnection->lastError()); +} + bool ConnectionController::isConnected() { return m_isConnected; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index b1b0ff98b..93ee28a7e 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -4,6 +4,7 @@ #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" #include "protocols/vpnprotocol.h" +#include "vpnconnection.h" class ConnectionController : public QObject { @@ -14,6 +15,7 @@ public: explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &vpnConnection, QObject *parent = nullptr); bool isConnected(); @@ -23,16 +25,22 @@ public slots: void openConnection(); void closeConnection(); + QString getLastConnectionError(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(Vpn::ConnectionState state); void isConnectedChanged(); + void connectionErrorOccurred(QString errorMessage); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_vpnConnection; + bool m_isConnected = false; }; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 48e811e09..2724f9cd0 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -6,33 +6,32 @@ #include "core/errorstrings.h" namespace { - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard - }; +enum class ConfigTypes { Amnezia, OpenVpn, WireGuard }; - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; +ConfigTypes checkConfigFormat(const QString &config) +{ + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; - if (config.contains(openVpnConfigPatternCli) && - (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && - (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { - return ConfigTypes::WireGuard; - } - return ConfigTypes::Amnezia; + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) + || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) + || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; } + return ConfigTypes::Amnezia; } +} // namespace ImportController::ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -64,6 +63,7 @@ void ImportController::extractConfigFromFile(const QUrl &fileUrl) void ImportController::extractConfigFromCode(QString code) { m_config = extractAmneziaConfig(code); + m_configFileName = ""; } QString ImportController::getConfig() diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 15a24c31c..2fe969628 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -10,7 +10,6 @@ InstallController::InstallController(const QSharedPointer &servers const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - } void InstallController::install(DockerContainer container, int port, TransportProto transportProto) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index b240878d9..e65e79d73 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -22,17 +22,19 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case NameRole: -// return ContainerProps::containerHumanNames().value(container); + // return ContainerProps::containerHumanNames().value(container); case DescRole: -// return ContainerProps::containerDescriptions().value(container); + // return ContainerProps::containerDescriptions().value(container); case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); case ServiceTypeRole: -// return ContainerProps::containerService(container); + // return ContainerProps::containerService(container); case DockerContainerRole: -// return container; + // return container; case IsInstalledRole: -// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); case IsDefaultRole: m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); emit defaultContainerChanged(); diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 06c148f17..3c34a6b09 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,27 +7,48 @@ import ConnectionState 1.0 Button { id: root + Connections { + target: ConnectionController + + function onConnectionErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + text: qsTr("Connect") - background: Image { - id: border + background: Item { + clip: true - source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + implicitHeight: border.implicitHeight + implicitWidth: border.implicitWidth - RotationAnimator { - id: connectionProccess + Image { + id: border - target: border - running: false - from: 0 - to: 360 - loops: Animation.Infinite - duration: 1250 + source: connectionProccess.running ? "/images/connectionProgress.svg" : + ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + RotationAnimator { + id: connectionProccess + + target: border + running: false + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } } - Behavior on source { - PropertyAnimation { duration: 200 } + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false } } @@ -46,7 +67,7 @@ Button { } onClicked: { - ConnectionController.isConnected ? ConnectionController.closeConnection() : ConnectionController.openConnection() + connectionProccess.running ? ConnectionController.closeConnection() : ConnectionController.openConnection() } Connections { @@ -98,6 +119,8 @@ Button { case ConnectionState.Error: { console.log("Error") connectionProccess.running = false + root.text = qsTr("Connect") + PageController.showErrorMessage(ConnectionController.getLastConnectionError()) break } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 4f3c4b6a5..e55217872 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -262,7 +262,7 @@ PageType { Layout.fillWidth: true text: name - descriptionText: "description" + descriptionText: hostName checked: index === serversMenuContent.currentIndex diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 39761cee3..19f81d099 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -39,8 +39,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - spacing: 16 - ListView { // todo change id naming id: containers @@ -63,6 +61,8 @@ PageType { anchors.leftMargin: 16 BackButtonType { + id: backButton + Layout.topMargin: 20 } @@ -136,6 +136,7 @@ PageType { id: port Layout.fillWidth: true + Layout.topMargin: 16 headerText: "Port" } @@ -143,7 +144,7 @@ PageType { // todo make it dynamic implicitHeight: root.height - port.implicitHeight - transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - - header.implicitHeight - installButton.implicitHeight - 100 + header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 color: "transparent" } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 9bb6aa28d..44a859258 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -3,7 +3,6 @@ import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts -import PageType 1.0 import PageEnum 1.0 import "Config" From 68d9394d9fb3d0bbb3d46953714303bbfc565569 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 17:49:20 +0300 Subject: [PATCH 027/131] fixed windows build errors after refactoring --- client/configurators/ssh_configurator.cpp | 6 ++-- .../protocols/ikev2_vpn_protocol_windows.cpp | 30 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index e1435bc37..d94e44684 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -69,14 +69,14 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) p->setProcessEnvironment(prepareEnv()); p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); - if (credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("PRIVATE KEY")) { // todo: connect by key // p->setNativeArguments(QString("%1@%2") -// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); +// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } else { p->setNativeArguments(QString("%1@%2 -pw %3") - .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); + .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } #else p->setProgram("/bin/bash"); diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 7564f969f..5c471e220 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -35,14 +35,14 @@ Ikev2Protocol::~Ikev2Protocol() void Ikev2Protocol::stop() { - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); { if (! disconnect_vpn() ){ qDebug()<<"We don't disconnect"; - setConnectionState(VpnProtocol::Error); + setConnectionState(Vpn::ConnectionState::Error); } else { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } } } @@ -55,40 +55,40 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE { case RASCS_OpenPort: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_PortOpened: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_ConnectDevice: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_DeviceConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AllDevicesConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_Authenticate: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthNotify: //qDebug()<<__FUNCTION__ << __LINE__; if (dwError != 0) { //qDebug() << "have error" << dwError; - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } else { //qDebug() << "RASCS_AuthNotify but no error" << dwError; } break; case RASCS_AuthRetry: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthCallback: qDebug()<<__FUNCTION__ << __LINE__; @@ -151,16 +151,16 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_PasswordExpired: - setConnectionState(Error); + setConnectionState(Vpn::ConnectionState::Error); qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Connected: // = RASCS_DONE: - setConnectionState(Connected); + setConnectionState(Vpn::ConnectionState::Connected); //qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Disconnected: - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); //qDebug()<<__FUNCTION__ << __LINE__; break; default: @@ -177,7 +177,7 @@ void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) ErrorCode Ikev2Protocol::start() { QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); - setConnectionState(Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); QTemporaryFile certFile; certFile.setAutoRemove(false); From c3f39ad24d7f617c846fb5a818fa0b8b1a34f10d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 7 Jun 2023 13:17:48 +0300 Subject: [PATCH 028/131] added caching of servers and containers in models --- client/ui/models/containers_model.cpp | 52 +++++++++++-------- client/ui/models/containers_model.h | 5 ++ client/ui/models/servers_model.cpp | 27 ++++++---- client/ui/models/servers_model.h | 3 ++ .../Components/SettingsContainersListView.qml | 2 - client/ui/qml/Controls2/BackButtonType.qml | 16 ------ client/ui/qml/Pages2/PageHome.qml | 30 +++++------ client/ui/qml/Pages2/PageStart.qml | 4 +- 8 files changed, 69 insertions(+), 70 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index e65e79d73..b6574439c 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -21,23 +21,25 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - // return ContainerProps::containerHumanNames().value(container); - case DescRole: - // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); - case ServiceTypeRole: - // return ContainerProps::containerService(container); - case DockerContainerRole: - // return container; - case IsInstalledRole: - // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); - emit defaultContainerChanged(); + case NameRole: + // return ContainerProps::containerHumanNames().value(container); + case DescRole: + // return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); + case ServiceTypeRole: + // return ContainerProps::containerService(container); + case DockerContainerRole: + // return container; + case IsInstalledRole: + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: { + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); + } } emit dataChanged(index, index); @@ -58,8 +60,10 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::containerHumanNames().value(container); case DescRole: return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - return m_settings->containerConfig(m_currentlyProcessedServerIndex, container); + case ConfigRole: { + if (container == DockerContainer::None) return QJsonObject(); + return m_containers.value(container); + } case ServiceTypeRole: return ContainerProps::containerService(container); case DockerContainerRole: @@ -71,11 +75,11 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); case IsInstalledRole: - return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + return m_containers.contains(container); case IsCurrentlyInstalledRole: return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: - return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); + return container == m_defaultContainerIndex; case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); } @@ -87,6 +91,8 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) { beginResetModel(); m_currentlyProcessedServerIndex = index; + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + m_defaultContainerIndex = m_settings->defaultContainer(m_currentlyProcessedServerIndex); endResetModel(); emit defaultContainerChanged(); } @@ -98,12 +104,12 @@ void ContainersModel::setCurrentlyInstalledContainerIndex(int index) DockerContainer ContainersModel::getDefaultContainer() { - return m_settings->defaultContainer(m_currentlyProcessedServerIndex); + return m_defaultContainerIndex; } QString ContainersModel::getDefaultContainerName() { - return ContainerProps::containerHumanNames().value(getDefaultContainer()); + return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } int ContainersModel::getCurrentlyInstalledContainerIndex() diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 3ce7bd6b9..5753a1dd5 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -55,8 +55,13 @@ protected: QHash roleNames() const override; private: + QMap m_containers; + + int m_currentlyProcessedServerIndex; int m_currentlyInstalledContainerIndex; + DockerContainer m_defaultContainerIndex; + std::shared_ptr m_settings; }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index d05bb6959..bb08e88c3 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,24 +2,28 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_settings->serversCount()); + return static_cast(m_servers.size()); } bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_settings->serversCount())) { + || index.row() >= static_cast(m_servers.size())) { return false; } switch (role) { - case IsDefaultRole: m_settings->setDefaultServer(index.row()); + case IsDefaultRole: { + m_settings->setDefaultServer(index.row()); + m_defaultServerIndex = m_settings->defaultServerIndex(); + } default: return true; } @@ -29,12 +33,11 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int QVariant ServersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_settings->serversCount())) { + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { return QVariant(); } - const QJsonArray &servers = m_settings->serversArray(); - const QJsonObject server = servers.at(index.row()).toObject(); + const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { case NameRole: { @@ -49,7 +52,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); case IsDefaultRole: - return index.row() == m_settings->defaultServerIndex(); + return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; } @@ -59,12 +62,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const int ServersModel::getDefaultServerIndex() { - return m_settings->defaultServerIndex(); + return m_defaultServerIndex; } const int ServersModel::getServersCount() { - return m_settings->serversCount(); + return m_servers.count(); } void ServersModel::setCurrentlyProcessedServerIndex(int index) @@ -74,7 +77,7 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) bool ServersModel::isDefaultServerCurrentlyProcessed() { - return m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex; + return m_defaultServerIndex == m_currenlyProcessedServerIndex; } ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() @@ -86,6 +89,7 @@ void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); m_settings->addServer(server); + m_servers = m_settings->serversArray(); endResetModel(); } @@ -93,6 +97,7 @@ void ServersModel::removeServer() { beginResetModel(); m_settings->removeServer(m_currenlyProcessedServerIndex); + m_servers = m_settings->serversArray(); if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { m_settings->setDefaultServer(0); diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 54ac5ef49..593babc33 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -46,8 +46,11 @@ protected: QHash roleNames() const override; private: + QJsonArray m_servers; + std::shared_ptr m_settings; + int m_defaultServerIndex; int m_currenlyProcessedServerIndex; }; diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 5175fd49d..abc508373 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -37,8 +37,6 @@ ListView { ButtonGroup.group: containersRadioButtonGroup - checked: isDefault - indicator: Rectangle { anchors.fill: parent color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index bec318238..389191e8b 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -34,22 +34,6 @@ Item { Layout.fillWidth: true color: "transparent" - - ShaderEffectSource { - id: effectSource - - sourceItem: background - anchors.fill: background - sourceRect: Qt.rect(x,y, width, height) - } - - FastBlur { - id: blur - anchors.fill: effectSource - - source: effectSource - radius: 100 - } } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index e55217872..227870c51 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -137,21 +137,6 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - DropDownType { id: containersDropDown @@ -176,7 +161,20 @@ PageType { listView: HomeContainersListView { rootWidth: root.width - model: proxyContainersModel + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } currentIndex: ContainersModel.getDefaultContainer() } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 436194b24..83b641a1c 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -45,9 +45,9 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) - tabBarStackView.push(pagePath, { "objectName" : pagePath }) ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -71,8 +71,8 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - tabBarStackView.goToTabBarPage(PageEnum.PageHome) ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + tabBarStackView.goToTabBarPage(PageEnum.PageHome) } } TabImageButtonType { From 1fd48a1cf827236ec2fcc7b98db6b44469b33042 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 7 Jun 2023 18:28:32 +0800 Subject: [PATCH 029/131] added protocol settings page and openvpn settings page --- client/resources.qrc | 3 + client/ui/controllers/pageController.h | 28 +++++-- .../qml/Components/HomeContainersListView.qml | 1 + .../Components/Protocols/OpenVpnSettings.qml | 81 +++++++++++++++++++ .../Components/SettingsContainersListView.qml | 4 +- .../qml/Components/TransportProtoSelector.qml | 59 ++++++++++++++ .../qml/Controls2/HorizontalRadioButton.qml | 10 +-- client/ui/qml/Pages2/PageHome.qml | 5 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 24 ++++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 1 + .../PageSetupWizardProtocolSettings.qml | 59 +++----------- client/ui/qml/Pages2/PageStart.qml | 1 + 12 files changed, 209 insertions(+), 67 deletions(-) create mode 100644 client/ui/qml/Components/Protocols/OpenVpnSettings.qml create mode 100644 client/ui/qml/Components/TransportProtoSelector.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerProtocol.qml diff --git a/client/resources.qrc b/client/resources.qrc index 185c4469c..e2f16853b 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -254,5 +254,8 @@ ui/qml/Components/QuestionDrawer.qml ui/qml/Pages2/PageDeinstalling.qml ui/qml/Controls2/BackButtonType.qml + ui/qml/Pages2/PageSettingsServerProtocol.qml + ui/qml/Components/Protocols/OpenVpnSettings.qml + ui/qml/Components/TransportProtoSelector.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 22ee0bb3c..946c1ce55 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,14 +9,30 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageShare, PageDeinstalling, + enum class PageEnum { + PageStart = 0, + PageHome, + PageShare, + PageDeinstalling, - PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, - PageSettingsServerProtocols, PageSettingsServerServices, + PageSettingsServersList, + PageSettings, + PageSettingsServerData, + PageSettingsServerInfo, + PageSettingsServerProtocols, + PageSettingsServerServices, + PageSettingsServerProtocol, - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey, PageSetupWizardViewConfig + PageSetupWizardStart, + PageTest, + PageSetupWizardCredentials, + PageSetupWizardProtocols, + PageSetupWizardEasy, + PageSetupWizardProtocolSettings, + PageSetupWizardInstalling, + PageSetupWizardConfigSource, + PageSetupWizardTextKey, + PageSetupWizardViewConfig }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 0d42b8e99..926302c43 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -20,6 +20,7 @@ ListView { height: menuContent.contentItem.height clip: true + interactive: false ButtonGroup { id: containersRadioButtonGroup diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml new file mode 100644 index 000000000..8c036fc00 --- /dev/null +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -0,0 +1,81 @@ +import QtQuick 2.15 +import QtQuick.Controls +import QtQuick.Layouts + +import "../../Controls2" +import "../../Controls2/TextTypes" +import "../../Components" + +Item { + id: root + + ColumnLayout { + anchors.fill: parent + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + Header2TextType { + Layout.fillWidth: true + + text: "OpenVpn" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + + headerText: qsTr("VPN Addresses Subnet") + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + Layout.fillWidth: true + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + + headerText: qsTr("Port") + } + + SwitcherType { + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + } + + DropDownType { + Layout.fillWidth: true + + } + + DropDownType { + Layout.fillWidth: true + + } + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("TLS auth") + } + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + } + + SwitcherType { + Layout.fillWidth: true + + text: qsTr("Additional configuration commands") + } + } +} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 5175fd49d..334696b0a 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -18,6 +18,7 @@ ListView { height: root.contentItem.height clip: true + interactive: false ButtonGroup { id: containersRadioButtonGroup @@ -89,8 +90,7 @@ ListView { onClicked: { if (isInstalled) { -// isDefault = true -// root.currentIndex = index + goToPage(PageEnum.PageSettingsServerProtocol) } else { ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml new file mode 100644 index 000000000..6f5aa44b2 --- /dev/null +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +Rectangle { + id: root + + property var rootWidth: root.width + property int currentIndex + + property alias mouseArea: transportProtoButtonMouseArea + + implicitWidth: transportProtoButtonGroup.implicitWidth + implicitHeight: transportProtoButtonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: transportProtoButtonGroup + + spacing: 0 + + HorizontalRadioButton { + checked: root.currentIndex === 0 + + implicitWidth: (rootWidth - 32) / 2 + text: "UDP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + root.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: root.currentIndex === 1 + + implicitWidth: (rootWidth - 32) / 2 + text: "TCP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + root.currentIndex = 1 + } + } + } + + MouseArea { + id: transportProtoButtonMouseArea + + anchors.fill: parent + } +} diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 6f00d2105..86218b3f3 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + RadioButton { id: root @@ -74,15 +76,9 @@ RadioButton { anchors.fill: parent spacing: 16 - Text { + ButtonTextType { text: root.text - wrapMode: Text.WordWrap - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - height: 24 Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index e55217872..276764a33 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -164,7 +164,7 @@ PageType { text: root.currentContainerName textColor: "#0E0E11" - headerText: "Протокол подключения" + headerText: qsTr("Протокол подключения") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" onRootButtonClicked: function() { @@ -198,7 +198,7 @@ PageType { actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: "Серверы" + headerText: qsTr("Servers") actionButtonFunction: function() { menu.visible = false @@ -237,6 +237,7 @@ PageType { currentIndex: ServersModel.getDefaultServerIndex() clip: true + interactive: false delegate: Item { id: menuContentDelegate diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml new file mode 100644 index 000000000..800041f09 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" +import "../Components/Protocols" + +PageType { + id: root + + OpenVpnSettings { + anchors.fill: parent + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 9c5ddc74d..fa4ce86c9 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -67,6 +67,7 @@ PageType { model: ServersModel clip: true + interactive: false delegate: Item { implicitWidth: servers.width diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 19f81d099..57fa497d8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -12,6 +12,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -83,53 +84,11 @@ PageType { text: "Network protocol" } - Rectangle { - id: transportProtoBackground + TransportProtoSelector { + id: transportProtoSelector - implicitWidth: transportProtoButtonGroup.implicitWidth - implicitHeight: transportProtoButtonGroup.implicitHeight - - color: "#1C1D21" - radius: 16 - - RowLayout { - id: transportProtoButtonGroup - - property int currentIndex - spacing: 0 - - HorizontalRadioButton { - checked: transportProtoButtonGroup.currentIndex === 0 - - implicitWidth: (root.width - 32) / 2 - text: "UDP" - - hoverEnabled: !transportProtoButtonMouseArea.enabled - - onClicked: { - transportProtoButtonGroup.currentIndex = 0 - } - } - - HorizontalRadioButton { - checked: transportProtoButtonGroup.currentIndex === 1 - - implicitWidth: (root.width - 32) / 2 - text: "TCP" - - hoverEnabled: !transportProtoButtonMouseArea.enabled - - onClicked: { - transportProtoButtonGroup.currentIndex = 1 - } - } - } - - MouseArea { - id: transportProtoButtonMouseArea - - anchors.fill: parent - } + Layout.fillWidth: true + rootWidth: root.width } TextFieldWithHeaderType { @@ -143,7 +102,7 @@ PageType { Rectangle { // todo make it dynamic implicitHeight: root.height - port.implicitHeight - - transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - + transportProtoSelector.implicitHeight - transportProtoHeader.implicitHeight - header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 color: "transparent" @@ -159,7 +118,7 @@ PageType { onClicked: function() { goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) + InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) } } @@ -172,10 +131,10 @@ PageType { } else { port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) } - transportProtoButtonGroup.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - transportProtoButtonMouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.mouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 436194b24..a801bb661 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -78,6 +78,7 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" + onClicked: {} } TabImageButtonType { isSelected: tabBar.currentIndex === 2 From cd3263db5059e164119bb5c97556a675305758a4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 10 Jun 2023 05:25:41 +0300 Subject: [PATCH 030/131] made libssh::ssh_connect a non-blocking feature - extended error handling when connecting via ssh --- client/core/defs.h | 2 +- client/core/errorstrings.cpp | 1 + client/core/sshclient.cpp | 49 +++++++++++++------ client/core/sshclient.h | 2 +- client/ui/controllers/connectionController.h | 3 +- client/ui/qml/Controls2/BasicButtonType.qml | 4 +- client/ui/qml/Controls2/PopupType.qml | 12 +++-- .../ui/qml/Pages2/PageSettingsServerData.qml | 1 + client/ui/qml/main2.qml | 8 ++- client/vpnconnection.cpp | 6 +-- 10 files changed, 54 insertions(+), 34 deletions(-) diff --git a/client/core/defs.h b/client/core/defs.h index 61c45e4e3..1c2682464 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -36,7 +36,7 @@ enum ErrorCode // Ssh connection errors SshRequsetDeniedError, SshInterruptedError, SshInternalError, - SshPrivateKeyError, SshPrivateKeyFormatError, + SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError, // Ssh sftp errors SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index dd298c76f..cd66186d1 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -24,6 +24,7 @@ QString errorString(ErrorCode code){ case(SshInternalError): return QObject::tr("Ssh internal error"); case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered"); case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); + case(SshTimeoutError): return QObject::tr("Timeout connecting to server"); // Libssh sftp errors case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered"); diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 0367dc1f5..e8d021ad3 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -10,6 +10,8 @@ const uint32_t S_IRWXU = 0644; #endif namespace libssh { + const QString libsshTimeoutError = "Timeout connecting to"; + std::function Client::m_passphraseCallback; Client::Client(QObject *parent) : QObject(parent) @@ -45,11 +47,20 @@ namespace libssh { ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); - int connectionResult = ssh_connect(m_session); + QFutureWatcher watcher; + QFuture future = QtConcurrent::run([this]() { + return ssh_connect(m_session); + }); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + int connectionResult = watcher.result(); if (connectionResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } std::string authUsername = credentials.userName.toStdString(); @@ -78,8 +89,8 @@ namespace libssh { ssh_key_free(privateKey); } if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << ssh_get_error(m_session); + ErrorCode errorCode = fromLibsshErrorCode(); if (errorCode == ErrorCode::NoError) { errorCode = ErrorCode::SshPrivateKeyFormatError; } @@ -88,8 +99,7 @@ namespace libssh { } else { authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } } } @@ -188,16 +198,15 @@ namespace libssh { ErrorCode Client::writeResponse(const QString &data) { if (m_channel == nullptr) { - qDebug() << "ssh channel not initialized"; - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << "ssh channel not initialized"; + return fromLibsshErrorCode(); } int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size()); if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) { - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::closeChannel() @@ -212,8 +221,7 @@ namespace libssh { ssh_channel_free(m_channel); m_channel = nullptr; } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) @@ -312,12 +320,21 @@ namespace libssh { sftp_free(m_sftpSession); m_sftpSession = nullptr; } - qDebug() << ssh_get_error(m_session); + qCritical() << ssh_get_error(m_session); return errorCode; } - ErrorCode Client::fromLibsshErrorCode(int errorCode) + ErrorCode Client::fromLibsshErrorCode() { + int errorCode = ssh_get_error_code(m_session); + if (errorCode != SSH_NO_ERROR) { + QString errorMessage = ssh_get_error(m_session); + qCritical() << errorMessage; + if (errorMessage.contains(libsshTimeoutError)) { + return ErrorCode::SshTimeoutError; + } + } + switch (errorCode) { case(SSH_NO_ERROR): return ErrorCode::NoError; case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError; diff --git a/client/core/sshclient.h b/client/core/sshclient.h index db22d0ddf..4e08faaa1 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -40,7 +40,7 @@ namespace libssh { private: ErrorCode closeChannel(); ErrorCode closeSftpSession(); - ErrorCode fromLibsshErrorCode(int errorCode); + ErrorCode fromLibsshErrorCode(); ErrorCode fromLibsshSftpErrorCode(int errorCode); static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 93ee28a7e..c1e81ea31 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,13 +19,14 @@ public: QObject *parent = nullptr); bool isConnected(); - void setIsConnected(bool isConnected); + void setIsConnected(bool isConnected); //todo take state from vpnconnection? public slots: void openConnection(); void closeConnection(); QString getLastConnectionError(); + Vpn::ConnectionState connectionState(){return {};}; //todo update ConnectButton text on page change signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 266beefe1..05074fa9c 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -24,10 +24,10 @@ Button { radius: 16 color: { if (root.enabled) { - if(root.pressed) { + if (root.pressed) { return pressedColor } - return hovered ? hoveredColor : defaultColor + return root.hovered ? hoveredColor : defaultColor } else { return disabledColor } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index dd92d5fed..61bdfd18e 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -27,15 +27,17 @@ Popup { background: Rectangle { anchors.fill: parent - color: Qt.rgba(215/255, 216/255, 219/255, 0.95) + color: "white"//Qt.rgba(215/255, 216/255, 219/255, 0.95) radius: 4 } contentItem: RowLayout { - width: parent.width + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 CaptionTextType { - horizontalAlignment: Text.AlignHCenter + horizontalAlignment: Text.AlignLeft Layout.fillWidth: true text: root.popupErrorMessageText @@ -44,7 +46,7 @@ Popup { BasicButtonType { visible: closeButtonVisible - defaultColor: Qt.rgba(215/255, 216/255, 219/255, 0.95) + defaultColor: "white"//"transparent"//Qt.rgba(215/255, 216/255, 219/255, 0.95) hoveredColor: "#C1C2C5" pressedColor: "#AEB0B7" disabledColor: "#494B50" @@ -52,7 +54,7 @@ Popup { textColor: "#0E0E11" borderWidth: 0 - text: "Close" + text: qsTr("Close") onClicked: { root.close() } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index c1f84cb09..fe09ed01e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -74,6 +74,7 @@ PageType { PageController.replaceStartPage() } else { goToStartPage() + goToPage(PageEnum.PageSettingsServersList) } } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 44a859258..0be8e3682 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -16,6 +16,9 @@ Window { minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + color: "#0E0E11" + + // todo onClosing: function() { console.debug("QML onClosing signal") UiLogic.onCloseWindow() @@ -23,11 +26,6 @@ Window { title: "AmneziaVPN" - Rectangle { - anchors.fill: parent - color: "#0E0E11" - } - StackViewType { id: rootStackView diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 468a6d967..1f0902fb6 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -336,7 +336,7 @@ void VpnConnection::connectToVpn(int serverIndex, ErrorCode e = ErrorCode::NoError; - m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig); + m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); if (e) { emit connectionStateChanged(Vpn::ConnectionState::Error); return; @@ -345,7 +345,7 @@ void VpnConnection::connectToVpn(int serverIndex, #if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { - emit Vpn::ConnectionState::Error; + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } m_vpnProtocol->prepare(); @@ -371,7 +371,7 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit Vpn::ConnectionState::Error; + if (e) emit connectionStateChanged(Vpn::ConnectionState::Error); } void VpnConnection::createProtocolConnections() { From be7386f0d76645b97e44b775c6b90b3cc740ab5f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 13 Jun 2023 20:03:20 +0900 Subject: [PATCH 031/131] added exportController and PageShare - added a blank PageSettingsProtocol --- client/amnezia_application.cpp | 7 +- client/amnezia_application.h | 11 +- client/images/controls/check_off.png | Bin 17930 -> 0 bytes client/images/controls/check_on.png | Bin 18032 -> 0 bytes .../radio-button-inner-circle-pressed.png | Bin 0 -> 5125 bytes .../controls/radio-button-inner-circle.png | Bin 0 -> 7720 bytes .../images/controls/radio-button-pressed.svg | 3 + client/images/controls/radio-button.svg | 3 + client/images/controls/radio_off.png | Bin 491 -> 0 bytes client/images/controls/radio_on.png | Bin 624 -> 0 bytes client/resources.qrc | 10 +- client/ui/controllers/exportController.cpp | 156 +++++++++ client/ui/controllers/exportController.h | 44 +++ client/ui/controllers/importController.h | 1 + client/ui/controllers/installController.cpp | 3 +- client/ui/controllers/pageController.h | 1 - .../protocolSettingsController.cpp | 19 + .../controllers/protocolSettingsController.h | 31 ++ client/ui/models/containers_model.cpp | 22 +- client/ui/models/containers_model.h | 8 +- client/ui/models/servers_model.cpp | 5 + client/ui/models/servers_model.h | 1 + .../qml/Components/HomeContainersListView.qml | 2 +- .../Components/Protocols/OpenVpnSettings.qml | 70 ++++ .../Components/SettingsContainersListView.qml | 3 +- .../qml/Components/ShareConnectionDrawer.qml | 177 ++++++++++ client/ui/qml/Controls2/BackButtonType.qml | 2 + client/ui/qml/Controls2/DropDownType.qml | 13 +- client/ui/qml/Controls2/ListViewType.qml | 107 ++++++ .../ui/qml/Controls2/VerticalRadioButton.qml | 94 ++--- client/ui/qml/Pages2/PageHome.qml | 4 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 59 +++- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 327 +++++++++++++++++- client/ui/qml/Pages2/PageStart.qml | 4 +- 38 files changed, 1080 insertions(+), 115 deletions(-) delete mode 100644 client/images/controls/check_off.png delete mode 100644 client/images/controls/check_on.png create mode 100644 client/images/controls/radio-button-inner-circle-pressed.png create mode 100644 client/images/controls/radio-button-inner-circle.png create mode 100644 client/images/controls/radio-button-pressed.svg create mode 100644 client/images/controls/radio-button.svg delete mode 100644 client/images/controls/radio_off.png delete mode 100644 client/images/controls/radio_on.png create mode 100644 client/ui/controllers/exportController.cpp create mode 100644 client/ui/controllers/exportController.h create mode 100644 client/ui/controllers/protocolSettingsController.cpp create mode 100644 client/ui/controllers/protocolSettingsController.h create mode 100644 client/ui/qml/Components/ShareConnectionDrawer.qml create mode 100644 client/ui/qml/Controls2/ListViewType.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index dbeaac38f..598645d8b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -46,7 +46,6 @@ #endif m_settings = std::shared_ptr(new Settings); - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); } AmneziaApplication::~AmneziaApplication() @@ -80,10 +79,10 @@ void AmneziaApplication::init() m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_pageController.reset(new PageController(m_serversModel)); @@ -95,6 +94,10 @@ void AmneziaApplication::init() m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + m_exportController.reset( + new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + // m_engine->load(url); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 0fd6842cf..510f580f2 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -13,12 +13,13 @@ #include "configurators/vpn_configurator.h" -#include "ui/models/servers_model.h" -#include "ui/models/containers_model.h" #include "ui/controllers/connectionController.h" -#include "ui/controllers/pageController.h" -#include "ui/controllers/installController.h" +#include "ui/controllers/exportController.h" #include "ui/controllers/importController.h" +#include "ui/controllers/installController.h" +#include "ui/controllers/pageController.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -71,7 +72,7 @@ private: QScopedPointer m_pageController; QScopedPointer m_installController; QScopedPointer m_importController; - + QScopedPointer m_exportController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/check_off.png b/client/images/controls/check_off.png deleted file mode 100644 index 0a7fbf709ea09e47560231777967e2308702adad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17930 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 e0OgL1C<3cKPR^?j<#awf_=36S6b diff --git a/client/images/controls/check_on.png b/client/images/controls/check_on.png deleted file mode 100644 index 8b3b683b11f7d401dcbf33c7464c168180bf4f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18032 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 aD}jaet7)Ar2^CO>0Y_U`n?mb#+x`t5|GQ)W diff --git a/client/images/controls/radio-button-inner-circle-pressed.png b/client/images/controls/radio-button-inner-circle-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..efcd6f1f97111a9b48dc7276a95bead4aadba225 GIT binary patch literal 5125 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 literal 0 HcmV?d00001 diff --git a/client/images/controls/radio-button-inner-circle.png b/client/images/controls/radio-button-inner-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..da29d54aa12cae758b9d6a8ed105d32ba7edebf7 GIT binary patch literal 7720 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 + + diff --git a/client/images/controls/radio-button.svg b/client/images/controls/radio-button.svg new file mode 100644 index 000000000..75b1b5b46 --- /dev/null +++ b/client/images/controls/radio-button.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/radio_off.png b/client/images/controls/radio_off.png deleted file mode 100644 index 685980bdfd97a2c58261f3d3e1abc6fa1d0dbde7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 491 zcmV9;+9u;NR)d-VWlj)f*T94f;_JM^ zYe~CLtJPZx9DtOvNv7ifZ1D|bWOPQ@7gM!Owx#SFfro}$?*u`Jy6YD6O65==3XXt= z!T3GPo^dRrR!hDPhmR&r60;Ay*6Vf+`>s!6Cu_IlERLScsbF7@v6Av`=a6laa_mmc zdbeG3DTafAVUAxyO8ZdFj)7x0Syt)%(skWt5QexE9NVb79Y9<=71EgJc_@n@3?PiT zaSUCs=ldv&wN{%B{uxZD7xM+mLgjhUnht#?(`kybP`P5GiveGG8QCN&+02-$sG%`< ziQ^cTVmul#$N0}AXBkD2ITswW-FlVHPpwvaqgvjPIIqGJMb4*+<499>_siw-YsX>p zd~shV2WrY|+`qQxOW1ZMr@jyvHK-*$&xhx)I*H{qO;da><`b@aW_e((gj>WCEA_Kw hooO-?BGUU}egJE$3_uvVdIJCe002ovPDHLkV1feU&3pg= diff --git a/client/images/controls/radio_on.png b/client/images/controls/radio_on.png deleted file mode 100644 index 48560e537843f963e8e0360c77b49dc8220fadb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 624 zcmV-$0+0QPP)IfyJ~7R$Umx2RJI9(0$THKLJ#+v9l}i1;C=o8vdX`&JUD$QIJ3? z85V^;7O|>6Qspt7>pE%19oy|$*fV$TJ$LThhk-1tw;51$0KuZYAp#6cjt@&gnu(z& z+NsKKg;_<%S)2uXP>jUEzaE199(Z@arIQ*qmg>m8(fW?>eC!B+_e7+D&e*$Of^@H< zNG1Exakmb|ZQ*Vm2(rpB%^3u-|pVfBYFJpFyq{~ zn=4NhAGL3XD1YW?{W(S08$~i&SHJAZvm-^rolow`!;izoFG;DkRo^nW`<$-E) z8dtR{n5k2?N(JO0>nR#V-|_VJo`Z#}Q6w%$~h`>T?WC%YgE6qVOJ;$*AWh{qT8wZD6sdaH4d>&92s zfOTcDMqM;6d@8Rv0WG<0=mxBwaE!NY8-oI)Zi>}8l^In=n#m>X+15fe=Ae)O0000< KMNUMnLSTXd5EgR) diff --git a/client/resources.qrc b/client/resources.qrc index e2f16853b..d60acfdd9 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -6,10 +6,6 @@ images/favorites_disabled.png images/favorites_enabled.png images/favorites_hover.png - images/controls/check_off.png - images/controls/check_on.png - images/controls/radio_off.png - images/controls/radio_on.png images/download.png images/upload.png images/tray/active.png @@ -257,5 +253,11 @@ ui/qml/Pages2/PageSettingsServerProtocol.qml ui/qml/Components/Protocols/OpenVpnSettings.qml ui/qml/Components/TransportProtoSelector.qml + ui/qml/Controls2/ListViewType.qml + images/controls/radio-button.svg + images/controls/radio-button-inner-circle.png + images/controls/radio-button-pressed.svg + images/controls/radio-button-inner-circle-pressed.png + ui/qml/Components/ShareConnectionDrawer.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp new file mode 100644 index 000000000..9ff73f33f --- /dev/null +++ b/client/ui/controllers/exportController.cpp @@ -0,0 +1,156 @@ +#include "exportController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qrcodegen.hpp" + +ExportController::ExportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &configurator, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_settings(settings) + , m_configurator(configurator) +{} + +void ExportController::generateFullAccessConfig() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QJsonObject config = m_settings->server(serverIndex); + + QByteArray compressedConfig = QJsonDocument(config).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + m_amneziaCode = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + + m_qrCodes = generateQrCodeImageSeries(compressedConfig); +} + +void ExportController::generateConnectionConfig() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + + DockerContainer container = static_cast( + m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = qvariant_cast( + m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode e = ErrorCode::NoError; + for (Proto p : ContainerProps::protocolsForContainer(container)) { + QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, p); + + QString cfg = m_configurator->genVpnProtocolConfig(credentials, + container, + containerConfig, + p, + &e); + if (e) { + cfg = "Error generating config"; + break; + } + protoConfig.insert(config_key::last_config, cfg); + containerConfig.insert(ProtocolProps::protoToString(p), protoConfig); + } + + QJsonObject config = m_settings->server(serverIndex); + if (!e) { + config.remove(config_key::userName); + config.remove(config_key::password); + config.remove(config_key::port); + config.insert(config_key::containers, QJsonArray{containerConfig}); + config.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + auto dns = m_configurator->getDnsForConfig(serverIndex); + config.insert(config_key::dns1, dns.first); + config.insert(config_key::dns2, dns.second); + + } /*else { + set_textEditShareAmneziaCodeText(tr("Error while generating connection profile")); + return; + }*/ + QByteArray compressedConfig = QJsonDocument(config).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + m_amneziaCode = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + + m_qrCodes = generateQrCodeImageSeries(compressedConfig); +} + +QString ExportController::getAmneziaCode() +{ + return m_amneziaCode; +} + +QList ExportController::getQrCodes() +{ + return m_qrCodes; +} + +void ExportController::saveFile() +{ + QString fileExtension = ".vpn"; + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileName; + fileName = QFileDialog::getSaveFileUrl(nullptr, + tr("Save AmneziaVPN config"), + QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), + "*" + fileExtension); + if (fileName.isEmpty()) + return; + if (!fileName.toString().endsWith(fileExtension)) { + fileName = QUrl(fileName.toString() + fileExtension); + } + if (fileName.isEmpty()) + return; + + QFile save(fileName.toLocalFile()); + + save.open(QIODevice::WriteOnly); + save.write(m_amneziaCode.toUtf8()); + save.close(); + + QFileInfo fi(fileName.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +} + +QList ExportController::generateQrCodeImageSeries(const QByteArray &data) +{ + double k = 850; + + quint8 chunksCount = std::ceil(data.size() / k); + QList chunks; + for (int i = 0; i < data.size(); i = i + k) { + QByteArray chunk; + QDataStream s(&chunk, QIODevice::WriteOnly); + s << amnezia::qrMagicCode << chunksCount << (quint8) std::round(i / k) << data.mid(i, k); + + QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); + QString svg = QString::fromStdString(toSvgString(qr, 0)); + chunks.append(svgToBase64(svg)); + } + + return chunks; +} + +QString ExportController::svgToBase64(const QString &image) +{ + return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h new file mode 100644 index 000000000..c50541434 --- /dev/null +++ b/client/ui/controllers/exportController.h @@ -0,0 +1,44 @@ +#ifndef EXPORTCONTROLLER_H +#define EXPORTCONTROLLER_H + +#include + +#include "configurators/vpn_configurator.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" + +class ExportController : public QObject +{ + Q_OBJECT +public: + explicit ExportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &configurator, + QObject *parent = nullptr); + +public slots: + void generateFullAccessConfig(); + void generateConnectionConfig(); + QString getAmneziaCode(); + QList getQrCodes(); + + void saveFile(); + +signals: + void generateConfig(bool isFullAccess); + +private: + QList generateQrCodeImageSeries(const QByteArray &data); + QString svgToBase64(const QString &image); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + std::shared_ptr m_configurator; + + QString m_amneziaCode; + QList m_qrCodes; +}; + +#endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 114bb5310..561ea19c3 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -27,6 +27,7 @@ public slots: signals: void importFinished(); void importErrorOccurred(QString errorMessage); + private: QJsonObject extractAmneziaConfig(QString &data); QJsonObject extractOpenVpnConfig(const QString &data); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2fe969628..8f9e1f886 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -9,8 +9,7 @@ InstallController::InstallController(const QSharedPointer &servers const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{ -} +{} void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 946c1ce55..dcf2fc664 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -24,7 +24,6 @@ namespace PageLoader PageSettingsServerProtocol, PageSetupWizardStart, - PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp new file mode 100644 index 000000000..48414021c --- /dev/null +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -0,0 +1,19 @@ +#include "protocolSettingsController.h" + +ProtocolSettingsController::ProtocolSettingsController( + const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_settings(settings) +{} + +QByteArray ProtocolSettingsController::getOpenVpnConfig() +{ + auto containerIndex = m_containersModel->index( + m_containersModel->getCurrentlyProcessedContainerIndex()); + auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); +} diff --git a/client/ui/controllers/protocolSettingsController.h b/client/ui/controllers/protocolSettingsController.h new file mode 100644 index 000000000..730cbda76 --- /dev/null +++ b/client/ui/controllers/protocolSettingsController.h @@ -0,0 +1,31 @@ +#ifndef PROTOCOLSETTINGSCONTROLLER_H +#define PROTOCOLSETTINGSCONTROLLER_H + +#include + +#include "containers/containers_defs.h" +#include "core/defs.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" + +class ProtocolSettingsController : public QObject +{ + Q_OBJECT +public: + explicit ProtocolSettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + QByteArray getOpenVpnConfig(); + +signals: + +private: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; +}; + +#endif // PROTOCOLSETTINGSCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index b6574439c..1caf69443 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -25,10 +25,10 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return ContainerProps::containerHumanNames().value(container); case DescRole: // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); + case ConfigRole: //todo save to model also + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); case ServiceTypeRole: // return ContainerProps::containerService(container); case DockerContainerRole: @@ -76,8 +76,8 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::easySetupDescription(container); case IsInstalledRole: return m_containers.contains(container); - case IsCurrentlyInstalledRole: - return container == static_cast(m_currentlyInstalledContainerIndex); + case IsCurrentlyProcessedRole: + return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; case IsSupportedRole: @@ -97,9 +97,9 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) emit defaultContainerChanged(); } -void ContainersModel::setCurrentlyInstalledContainerIndex(int index) +void ContainersModel::setCurrentlyProcessedContainerIndex(int index) { - m_currentlyInstalledContainerIndex = index; + m_currentlyProcessedContainerIndex = index; } DockerContainer ContainersModel::getDefaultContainer() @@ -112,9 +112,9 @@ QString ContainersModel::getDefaultContainerName() return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } -int ContainersModel::getCurrentlyInstalledContainerIndex() +int ContainersModel::getCurrentlyProcessedContainerIndex() { - return m_currentlyInstalledContainerIndex; + return m_currentlyProcessedContainerIndex; } void ContainersModel::removeAllContainers() @@ -153,7 +153,7 @@ QHash ContainersModel::roleNames() const { roles[EasySetupDescriptionRole] = "easySetupDescription"; roles[IsInstalledRole] = "isInstalled"; - roles[IsCurrentlyInstalledRole] = "isCurrentlyInstalled"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsDefaultRole] = "isDefault"; roles[IsSupportedRole] = "isSupported"; return roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 5753a1dd5..7bb587550 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -27,7 +27,7 @@ public: EasySetupDescriptionRole, IsInstalledRole, - IsCurrentlyInstalledRole, + IsCurrentlyProcessedRole, IsDefaultRole, IsSupportedRole }; @@ -45,8 +45,8 @@ public slots: QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(int index); - void setCurrentlyInstalledContainerIndex(int index); - int getCurrentlyInstalledContainerIndex(); + void setCurrentlyProcessedContainerIndex(int index); + int getCurrentlyProcessedContainerIndex(); void removeAllContainers(); void clearCachedProfiles(); @@ -59,7 +59,7 @@ private: int m_currentlyProcessedServerIndex; - int m_currentlyInstalledContainerIndex; + int m_currentlyProcessedContainerIndex; DockerContainer m_defaultContainerIndex; std::shared_ptr m_settings; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index bb08e88c3..9ef87d379 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -75,6 +75,11 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) m_currenlyProcessedServerIndex = index; } +int ServersModel::getCurrentlyProcessedServerIndex() +{ + return m_currenlyProcessedServerIndex; +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_currenlyProcessedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 593babc33..6f55176ee 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -37,6 +37,7 @@ public slots: const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); + int getCurrentlyProcessedServerIndex(); ServerCredentials getCurrentlyProcessedServerCredentials(); void addServer(const QJsonObject &server); diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 926302c43..28f81b2e2 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -59,7 +59,7 @@ ListView { menuContent.currentIndex = index containersDropDown.menuVisible = false } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml index 8c036fc00..5c574832c 100644 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -8,8 +8,12 @@ import "../../Components" Item { id: root + implicitHeight: col.implicitHeight + implicitWidth: col.implicitWidth ColumnLayout { + id: col + anchors.fill: parent anchors.leftMargin: 16 @@ -51,13 +55,79 @@ Item { } DropDownType { + id: hash Layout.fillWidth: true + implicitHeight: 74 + rootButtonBorderWidth: 0 + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + listView: ListViewType { + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("SHA512") } + ListElement { name : qsTr("SHA384") } + ListElement { name : qsTr("SHA256") } + ListElement { name : qsTr("SHA3-512") } + ListElement { name : qsTr("SHA3-384") } + ListElement { name : qsTr("SHA3-256") } + ListElement { name : qsTr("whirlpool") } + ListElement { name : qsTr("BLAKE2b512") } + ListElement { name : qsTr("BLAKE2s256") } + ListElement { name : qsTr("SHA1") } + } + currentIndex: 0 + + clickedFunction: { + hash.text = selectedText + hash.menuVisible = false + } + + Component.onCompleted: { + hash.text = selectedText + } + } } DropDownType { + id: cipher Layout.fillWidth: true + implicitHeight: 74 + rootButtonBorderWidth: 0 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("AES-256-GCM") } + ListElement { name : qsTr("AES-192-GCM") } + ListElement { name : qsTr("AES-128-GCM") } + ListElement { name : qsTr("AES-256-CBC") } + ListElement { name : qsTr("AES-192-CBC") } + ListElement { name : qsTr("AES-128-CBC") } + ListElement { name : qsTr("ChaCha20-Poly1305") } + ListElement { name : qsTr("ARIA-256-CBC") } + ListElement { name : qsTr("CAMELLIA-256-CBC") } + ListElement { name : qsTr("none") } + } + currentIndex: 0 + + clickedFunction: { + cipher.text = selectedText + cipher.menuVisible = false + } + + Component.onCompleted: { + cipher.text = selectedText + } + } } CheckBoxType { diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index d5664541d..1b8ea40c9 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -88,9 +88,10 @@ ListView { onClicked: { if (isInstalled) { + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) goToPage(PageEnum.PageSettingsServerProtocol) } else { - ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) goToPage(PageEnum.PageSetupWizardProtocolSettings) } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml new file mode 100644 index 000000000..5c9ceee8c --- /dev/null +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -0,0 +1,177 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + property var qrCodes: [] + property alias configText: configContent.text + property alias headerText: header.headerText + + width: parent.width + height: parent.height * 0.9 + + Item{ + anchors.fill: parent + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height + 32 + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + Header2Type { + id: header + Layout.fillWidth: true + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save connection code") + + onClicked: { + ExportController.saveFile() + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Copy") + + onClicked: { + configContent.selectAll() + configContent.copy() + configContent.select(0, 0) + } + } + + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + + text: showContent ? qsTr("Collapse content") : qsTr("Show content") + + onClicked: { + showContent = !showContent + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + + radius: 10 + color: "#2C2D30" + + visible: showContent + + height: 24 + + TextField { + id: configContent + + anchors.fill: parent + anchors.margins: 16 + + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + wrapMode: Text.Wrap + + enabled: false + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: width + Layout.topMargin: 20 + + color: "white" + + Image { + anchors.fill: parent + smooth: false + + Timer { + property int idx: 0 + interval: 1000 + running: qrCodes.length > 0 + repeat: true + onTriggered: { + idx++ + if (idx >= qrCodes.length) { + idx = 0 + } + parent.source = qrCodes[idx] + } + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } + + visible: qrCodes.length > 0 + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 32 + + horizontalAlignment: Text.AlignHCenter + text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"") + } + } + } + } +} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 389191e8b..0ccb7345e 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -11,6 +11,8 @@ Item { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight + visible: backButtonImage !== "" + RowLayout { id: content diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 700d9981b..6456bbadd 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,11 +19,12 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#494B50" property string rootButtonDefaultColor: "#1C1D21" - property int rootButtonMaximumWidth + property int rootButtonMaximumWidth: 0 property string rootButtonBorderColor: "#494B50" property int rootButtonBorderWidth: 1 + property real drawerHeight: 0.9 property Component listView property alias menuVisible: menu.visible @@ -55,7 +56,9 @@ Item { Layout.leftMargin: 16 LabelTextType { - horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter visible: root.descriptionText !== "" @@ -65,7 +68,9 @@ Item { } ButtonTextType { - horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth @@ -115,7 +120,7 @@ Item { id: menu width: parent.width - height: parent.height * 0.9 + height: parent.height * drawerHeight ColumnLayout { id: header diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml new file mode 100644 index 000000000..421b82a32 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +ListView { + id: menuContent + + property var rootWidth + property var selectedText + property string imageSource + property var clickedFunction + property bool dividerVisible: false + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + interactive: false + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + spacing: 16 + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: name + } + + Image { + source: imageSource ? imageSource : "qrc:/images/controls/check.svg" + visible: imageSource ? true : radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: menuContent.currentIndex === index + + onClicked: { + menuContent.currentIndex = index + menuContent.selectedText = name + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.bottomMargin: 16 + + visible: dividerVisible + } + } + + Component.onCompleted: { + if (menuContent.currentIndex === index) { + menuContent.selectedText = name + } + } + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 50af3c79c..7f2411fba 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -23,12 +23,6 @@ RadioButton { property string defaultBodredColor: "transparent" property int borderWidth: 0 - property string defaultCircleBorderColor: "#878B91" - property string selectedCircleBorderColor: "#A85809" - property string pressedCircleBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) - - property string defaultInnerCircleColor: "#FBB26A" - property string imageSource property bool showImage @@ -61,80 +55,38 @@ RadioButton { } Image { - source: imageSource - visible: showImage + source: { + if (showImage) { + return imageSource + } else if (root.pressed) { + return "qrc:/images/controls/radio-button-inner-circle-pressed.png" + } else if (root.checked) { + return "qrc:/images/controls/radio-button-inner-circle.png" + } + + return "" + } anchors.centerIn: parent width: 24 height: 24 } - - Rectangle { - id: outerCircle - - width: 24 - height: 24 - radius: 16 - - visible: !showImage + Image { + source: { + if (showImage) { + return "" + } else if (root.pressed || root.checked) { + return "qrc:/images/controls/radio-button-pressed.svg" + } else { + return "qrc:/images/controls/radio-button.svg" + } + } anchors.centerIn: parent - color: "transparent" - border.color: { - if (root.enabled) { - if (root.pressed) { - return pressedCircleBorderColor - } else if (root.checked) { - return selectedCircleBorderColor - } - } - return defaultCircleBorderColor - } - - border.width: 1 - - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - - Rectangle { - id: innerCircle - - width: 12 - height: 12 - radius: 16 - - anchors.centerIn: parent - - color: "transparent" - border.color: defaultInnerCircleColor - border.width: { - if (root.enabled) { - if(root.checked) { - return 6 - } - return root.pressed ? 6 : 0 - } else { - return 0 - } - } - - Behavior on border.width { - PropertyAnimation { duration: 200 } - } - } - - DropShadow { - anchors.fill: innerCircle - horizontalOffset: 0 - verticalOffset: 0 - radius: 12 - samples: 13 - color: "#FBB26A" - source: innerCircle - } + width: 24 + height: 24 } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 23ca9e011..780f3aefd 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -111,7 +111,7 @@ PageType { id: menu width: parent.width - height: parent.height * 0.90 + height: parent.height * 0.9 ColumnLayout { id: serversMenuHeader @@ -247,8 +247,8 @@ PageType { ColumnLayout { id: serverRadioButtonContent - anchors.fill: parent + anchors.fill: parent anchors.rightMargin: 16 anchors.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 800041f09..498006dfb 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -18,7 +18,64 @@ import "../Components/Protocols" PageType { id: root - OpenVpnSettings { + FlickableType { + id: fl anchors.fill: parent + contentHeight: content.height + openVpnSettings.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + ListView { + // todo change id naming + id: container + width: parent.width + height: container.contentItem.height + clip: true + interactive: false + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + delegate: Item { + implicitWidth: container.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: name + } + } + } + } + + OpenVpnSettings { + id: openVpnSettings + + width: parent.width + } + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 99f6ccfba..1836f8925 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -134,7 +134,7 @@ PageType { text: qsTr("Continue") onClicked: function() { - ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) + ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index c18c4d279..9631e2588 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -54,7 +54,7 @@ PageType { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "isCurrentlyInstalled" + roleName: "isCurrentlyProcessed" value: true } ] diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 57fa497d8..c0483df4b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -22,7 +22,7 @@ PageType { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "isCurrentlyInstalled" + roleName: "isCurrentlyProcessed" value: true } ] diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 75b16afd7..e2ae4a164 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -90,7 +90,7 @@ PageType { buttonImage: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 5560aee72..a731e425a 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -1,5 +1,330 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs -Item { +import SortFilterProxyModel 0.2 +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + Connections { + target: ExportController + + function onGenerateConfig(isFullAccess) { + if (isFullAccess) { + ExportController.generateFullAccessConfig() + } else { + ExportController.generateConnectionConfig() + } + + shareConnectionDrawer.configText = ExportController.getAmneziaCode() + shareConnectionDrawer.qrCodes = ExportController.getQrCodes() + } + } + + property bool showContent: false + property list connectionTypesModel: [ + amneziaConnectionFormat + ] + + QtObject { + id: amneziaConnectionFormat + property string name: qsTr("For the AmnesiaVPN app") + property var func: function() { + ExportController.generateConfig(false) + } + } + QtObject { + id: openVpnConnectionFormat + property string name: qsTr("OpenVpn native format") + property var func: function() { + console.log("Item 3 clicked") + } + } + QtObject { + id: wireGuardConnectionFormat + property string name: qsTr("WireGuard native format") + property var func: function() { + console.log("Item 3 clicked") + } + } + + FlickableType { + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("VPN Access") + } + + Rectangle { + id: accessTypeSelector + + property int currentIndex + + Layout.topMargin: 32 + + implicitWidth: accessTypeSelectorContent.implicitWidth + implicitHeight: accessTypeSelectorContent.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: accessTypeSelectorContent + + spacing: 0 + + HorizontalRadioButton { + checked: accessTypeSelector.currentIndex === 0 + + implicitWidth: (root.width - 32) / 2 + text: qsTr("Connection") + + onClicked: { + accessTypeSelector.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: root.currentIndex === 1 + + implicitWidth: (root.width - 32) / 2 + text: qsTr("Full") + + onClicked: { + accessTypeSelector.currentIndex = 1 + } + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("VPN access without the ability to manage the server") + color: "#878B91" + } + + DropDownType { + id: serverSelector + + Layout.fillWidth: true + Layout.topMargin: 24 + + implicitHeight: 74 + + rootButtonBorderWidth: 0 + drawerHeight: 0.4375 + + descriptionText: qsTr("Server and service") + headerText: qsTr("Server") + + listView: ListViewType { + rootWidth: root.width + dividerVisible: true + + imageSource: "qrc:/images/controls/chevron-right.svg" + + model: ServersModel + currentIndex: ServersModel.getDefaultServerIndex() + + clickedFunction: function() { + serverSelector.text = selectedText + ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + protocolSelector.visible = true + } + + Component.onCompleted: { + serverSelector.text = selectedText + ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + } + } + + DrawerType { + id: protocolSelector + + width: parent.width + height: parent.height * 0.5 + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + BackButtonType { + backButtonImage: "qrc:/images/controls/arrow-left.svg" + backButtonFunction: function() { + protocolSelector.visible = false + } + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Header2TextType { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + text: qsTr("Protocols and services") + wrapMode: Text.WordWrap + } + + ListViewType { + rootWidth: root.width + dividerVisible: true + + imageSource: "qrc:/images/controls/chevron-right.svg" + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isInstalled" + value: true + } + + ] + } + + currentIndex: 0 + + clickedFunction: function () { + serverSelector.text += ", " + selectedText + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + + protocolSelector.visible = false + serverSelector.menuVisible = false + + fillConnectionTypeModel() + } + + Component.onCompleted: { + serverSelector.text += ", " + selectedText + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + + fillConnectionTypeModel() + } + + function fillConnectionTypeModel() { + connectionTypesModel = [amneziaConnectionFormat] + + if (currentIndex === ContainerProps.containerFromString("OpenVpn")) { + connectionTypesModel.push(openVpnConnectionFormat) + } else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) { + connectionTypesModel.push(amneziaConnectionFormat) + } + } + } + } + } + } + } + + DropDownType { + id: connectionTypeSelector + + property int currentIndex + + Layout.fillWidth: true + Layout.topMargin: 16 + + implicitHeight: 74 + + rootButtonBorderWidth: 0 + drawerHeight: 0.4375 + + visible: accessTypeSelector.currentIndex === 0 + enabled: connectionTypesModel.length > 1 + + descriptionText: qsTr("Connection format") + headerText: qsTr("Connection format") + + listView: ListViewType { + id: connectionTypeSelectorListView + + rootWidth: root.width + dividerVisible: true + + imageSource: "qrc:/images/controls/chevron-right.svg" + + model: connectionTypesModel + currentIndex: 0 + + clickedFunction: function() { + connectionTypeSelector.text = selectedText + connectionTypeSelector.currentIndex = currentIndex + connectionTypeSelector.menuVisible = false + } + + Component.onCompleted: { + connectionTypeSelector.text = selectedText + connectionTypeSelector.currentIndex = currentIndex + } + } + } + + ShareConnectionDrawer { + id: shareConnectionDrawer + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Share") + + onClicked: { + if (accessTypeSelector.currentIndex === 0) { + connectionTypesModel[connectionTypeSelector.currentIndex].func() + } else { + ExportController.generateConfig(true) + } + shareConnectionDrawer.visible = true + } + } + } + } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 16dd00d33..8b9cefa42 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -78,7 +78,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" - onClicked: {} + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageShare) + } } TabImageButtonType { isSelected: tabBar.currentIndex === 2 From 7b14ad9616f4437b4569c10659c07d634c500300 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 16 Jun 2023 13:43:55 +0900 Subject: [PATCH 032/131] added PageSettingsAbout, PageSettingsApplication, PageSettingsBackup, PageSettingsConnection, PageSettingsDns - added SettingsController --- client/amnezia_application.cpp | 4 + client/amnezia_application.h | 2 + client/images/controls/delete.svg | 5 + client/images/controls/github.svg | 4 + client/images/controls/mail.svg | 4 + client/images/controls/telegram.svg | 3 + client/resources.qrc | 9 + client/ui/controllers/exportController.cpp | 42 ++-- client/ui/controllers/exportController.h | 10 + client/ui/controllers/pageController.h | 5 + client/ui/controllers/settingsController.cpp | 113 ++++++++++ client/ui/controllers/settingsController.h | 56 +++++ .../Components/Protocols/OpenVpnSettings.qml | 4 - .../qml/Components/ShareConnectionDrawer.qml | 14 +- client/ui/qml/Controls2/DropDownType.qml | 44 +++- client/ui/qml/Controls2/ImageButtonType.qml | 9 +- client/ui/qml/Controls2/SwitcherType.qml | 37 +++- client/ui/qml/Pages2/PageHome.qml | 4 +- client/ui/qml/Pages2/PageSettings.qml | 4 + client/ui/qml/Pages2/PageSettingsAbout.qml | 207 ++++++++++++++++++ .../ui/qml/Pages2/PageSettingsApplication.qml | 74 +++++++ client/ui/qml/Pages2/PageSettingsBackup.qml | 184 ++++++++++++++++ .../ui/qml/Pages2/PageSettingsConnection.qml | 107 +++++++++ client/ui/qml/Pages2/PageSettingsDns.qml | 87 ++++++++ client/ui/qml/Pages2/PageShare.qml | 10 +- client/utilities.cpp | 54 +++++ client/utilities.h | 15 +- service/server/CMakeLists.txt | 6 +- 28 files changed, 1054 insertions(+), 63 deletions(-) create mode 100644 client/images/controls/delete.svg create mode 100644 client/images/controls/github.svg create mode 100644 client/images/controls/mail.svg create mode 100644 client/images/controls/telegram.svg create mode 100644 client/ui/controllers/settingsController.cpp create mode 100644 client/ui/controllers/settingsController.h create mode 100644 client/ui/qml/Pages2/PageSettingsAbout.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApplication.qml create mode 100644 client/ui/qml/Pages2/PageSettingsBackup.qml create mode 100644 client/ui/qml/Pages2/PageSettingsConnection.qml create mode 100644 client/ui/qml/Pages2/PageSettingsDns.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 598645d8b..b9aa0f744 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -98,6 +98,10 @@ void AmneziaApplication::init() new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + m_settingsController.reset( + new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + // m_engine->load(url); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 510f580f2..893511b69 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -18,6 +18,7 @@ #include "ui/controllers/importController.h" #include "ui/controllers/installController.h" #include "ui/controllers/pageController.h" +#include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" @@ -73,6 +74,7 @@ private: QScopedPointer m_installController; QScopedPointer m_importController; QScopedPointer m_exportController; + QScopedPointer m_settingsController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/delete.svg b/client/images/controls/delete.svg new file mode 100644 index 000000000..78ef3eead --- /dev/null +++ b/client/images/controls/delete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/github.svg b/client/images/controls/github.svg new file mode 100644 index 000000000..7b1f250ad --- /dev/null +++ b/client/images/controls/github.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/mail.svg b/client/images/controls/mail.svg new file mode 100644 index 000000000..1debe4f14 --- /dev/null +++ b/client/images/controls/mail.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/telegram.svg b/client/images/controls/telegram.svg new file mode 100644 index 000000000..7b76e5068 --- /dev/null +++ b/client/images/controls/telegram.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index d60acfdd9..a8f8718ea 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -259,5 +259,14 @@ images/controls/radio-button-pressed.svg images/controls/radio-button-inner-circle-pressed.png ui/qml/Components/ShareConnectionDrawer.qml + ui/qml/Pages2/PageSettingsConnection.qml + ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsApplication.qml + ui/qml/Pages2/PageSettingsBackup.qml + images/controls/delete.svg + ui/qml/Pages2/PageSettingsAbout.qml + images/controls/github.svg + images/controls/mail.svg + images/controls/telegram.svg diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 9ff73f33f..98cbebd10 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,6 +11,8 @@ #include "qrcodegen.hpp" +#include "core/errorstrings.h" + ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, @@ -35,6 +37,7 @@ void ExportController::generateFullAccessConfig() | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); } void ExportController::generateConnectionConfig() @@ -49,25 +52,25 @@ void ExportController::generateConnectionConfig() m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - ErrorCode e = ErrorCode::NoError; - for (Proto p : ContainerProps::protocolsForContainer(container)) { - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, p); + ErrorCode errorCode = ErrorCode::NoError; + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); - QString cfg = m_configurator->genVpnProtocolConfig(credentials, - container, - containerConfig, - p, - &e); - if (e) { - cfg = "Error generating config"; - break; + QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, + container, + containerConfig, + protocol, + &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; } - protoConfig.insert(config_key::last_config, cfg); - containerConfig.insert(ProtocolProps::protoToString(p), protoConfig); + protocolConfig.insert(config_key::last_config, vpnConfig); + containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); } QJsonObject config = m_settings->server(serverIndex); - if (!e) { + if (!errorCode) { config.remove(config_key::userName); config.remove(config_key::password); config.remove(config_key::port); @@ -77,11 +80,8 @@ void ExportController::generateConnectionConfig() auto dns = m_configurator->getDnsForConfig(serverIndex); config.insert(config_key::dns1, dns.first); config.insert(config_key::dns2, dns.second); + } - } /*else { - set_textEditShareAmneziaCodeText(tr("Error while generating connection profile")); - return; - }*/ QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); m_amneziaCode = QString("vpn://%1") @@ -89,6 +89,7 @@ void ExportController::generateConnectionConfig() | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); } QString ExportController::getAmneziaCode() @@ -154,3 +155,8 @@ QString ExportController::svgToBase64(const QString &image) { return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); } + +int ExportController::getQrCodesCount() +{ + return m_qrCodes.size(); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index c50541434..63997efd8 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -17,9 +17,14 @@ public: const std::shared_ptr &configurator, QObject *parent = nullptr); + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) + Q_PROPERTY(QString amneziaCode READ getAmneziaCode NOTIFY exportConfigChanged) + public slots: void generateFullAccessConfig(); void generateConnectionConfig(); + QString getAmneziaCode(); QList getQrCodes(); @@ -27,11 +32,16 @@ public slots: signals: void generateConfig(bool isFullAccess); + void exportErrorOccurred(QString errorMessage); + + void exportConfigChanged(); private: QList generateQrCodeImageSeries(const QByteArray &data); QString svgToBase64(const QString &image); + int getQrCodesCount(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index dcf2fc664..587e0e385 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -22,6 +22,11 @@ namespace PageLoader PageSettingsServerProtocols, PageSettingsServerServices, PageSettingsServerProtocol, + PageSettingsConnection, + PageSettingsDns, + PageSettingsApplication, + PageSettingsBackup, + PageSettingsAbout, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp new file mode 100644 index 000000000..b86b1cffa --- /dev/null +++ b/client/ui/controllers/settingsController.cpp @@ -0,0 +1,113 @@ +#include "settingsController.h" + +#include + +#include "defines.h" +#include "logger.h" +#include "utilities.h" + +SettingsController::SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_settings(settings) +{ + m_appVersion = QString("%1: %2 (%3)") + .arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); +} + +void SettingsController::setAmneziaDns(bool enable) +{ + m_settings->setUseAmneziaDns(enable); +} + +bool SettingsController::isAmneziaDnsEnabled() +{ + return m_settings->useAmneziaDns(); +} + +QString SettingsController::getPrimaryDns() +{ + return m_settings->primaryDns(); +} + +void SettingsController::setPrimaryDns(const QString &dns) +{ + m_settings->setPrimaryDns(dns); + emit primaryDnsChanged(); +} + +QString SettingsController::getSecondaryDns() +{ + return m_settings->secondaryDns(); +} + +void SettingsController::setSecondaryDns(const QString &dns) +{ + return m_settings->setSecondaryDns(dns); + emit secondaryDnsChanged(); +} + +bool SettingsController::isSaveLogsEnabled() +{ + return m_settings->isSaveLogs(); +} + +void SettingsController::setSaveLogs(bool enable) +{ + m_settings->setSaveLogs(enable); +} + +void SettingsController::openLogsFolder() +{ + Logger::openLogsFolder(); +} + +void SettingsController::exportLogsFile() +{ + Utils::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); +} + +void SettingsController::clearLogs() +{ + Logger::clearLogs(); + Logger::clearServiceLogs(); +} + +void SettingsController::backupAppConfig() +{ + Utils::saveFile(".backup", + tr("Backup application config"), + "AmneziaVPN", + m_settings->backupAppConfig()); +} + +void SettingsController::restoreAppConfig() +{ + QString fileName = Utils::getFileName(Q_NULLPTR, + tr("Open backup"), + QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation), + "*.backup"); + + //todo error processing + if (fileName.isEmpty()) + return; + + QFile file(fileName); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + + bool ok = m_settings->restoreAppConfig(data); + if (ok) { + // emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); + } +} + +QString SettingsController::getAppVersion() +{ + return m_appVersion; +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h new file mode 100644 index 000000000..77de424c7 --- /dev/null +++ b/client/ui/controllers/settingsController.h @@ -0,0 +1,56 @@ +#ifndef SETTINGSCONTROLLER_H +#define SETTINGSCONTROLLER_H + +#include + +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" + +class SettingsController : public QObject +{ + Q_OBJECT +public: + explicit SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + + Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) + Q_PROPERTY( + QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + +public slots: + void setAmneziaDns(bool enable); + bool isAmneziaDnsEnabled(); + + QString getPrimaryDns(); + void setPrimaryDns(const QString &dns); + + QString getSecondaryDns(); + void setSecondaryDns(const QString &dns); + + bool isSaveLogsEnabled(); + void setSaveLogs(bool enable); + + void openLogsFolder(); + void exportLogsFile(); + void clearLogs(); + + void backupAppConfig(); + void restoreAppConfig(); + + QString getAppVersion(); + +signals: + void primaryDnsChanged(); + void secondaryDnsChanged(); + +private: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + + QString m_appVersion; +}; + +#endif // SETTINGSCONTROLLER_H diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml index 5c574832c..f937dcfc9 100644 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -59,8 +59,6 @@ Item { Layout.fillWidth: true implicitHeight: 74 - rootButtonBorderWidth: 0 - descriptionText: qsTr("Hash") headerText: qsTr("Hash") @@ -97,8 +95,6 @@ Item { Layout.fillWidth: true implicitHeight: 74 - rootButtonBorderWidth: 0 - descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 5c9ceee8c..1b92c7529 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -15,8 +15,6 @@ import "../Controls2/TextTypes" DrawerType { id: root - property var qrCodes: [] - property alias configText: configContent.text property alias headerText: header.headerText width: parent.width @@ -120,6 +118,8 @@ DrawerType { font.weight: Font.Medium font.family: "PT Root UI VF" + text: ExportController.amneziaCode + wrapMode: Text.Wrap enabled: false @@ -135,6 +135,8 @@ DrawerType { Layout.preferredHeight: width Layout.topMargin: 20 + visible: ExportController.qrCodesCount > 0 + color: "white" Image { @@ -144,22 +146,20 @@ DrawerType { Timer { property int idx: 0 interval: 1000 - running: qrCodes.length > 0 + running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { idx++ - if (idx >= qrCodes.length) { + if (idx >= ExportController.qrCodesCount) { idx = 0 } - parent.source = qrCodes[idx] + parent.source = ExportController.qrCodes[idx] } } Behavior on source { PropertyAnimation { duration: 200 } } - - visible: qrCodes.length > 0 } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 6456bbadd..38cd0afa0 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -15,13 +15,15 @@ Item { property string headerText property string headerBackButtonImage - property var onRootButtonClicked + property var rootButtonClickedFunction property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" - property string rootButtonImageColor: "#494B50" - property string rootButtonDefaultColor: "#1C1D21" + property string rootButtonImageColor: "#D7D8DB" + property string rootButtonBackgroundColor: "#1C1D21" property int rootButtonMaximumWidth: 0 - property string rootButtonBorderColor: "#494B50" + property string rootButtonHoveredBorderColor: "#494B50" + property string rootButtonDefaultBorderColor: "transparent" + property string rootButtonPressedBorderColor: "#D7D8DB" property int rootButtonBorderWidth: 1 property real drawerHeight: 0.9 @@ -32,18 +34,31 @@ Item { implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight + onMenuVisibleChanged: { + if (menuVisible) { + rootButtonBackground.border.color = rootButtonPressedBorderColor + rootButtonBackground.border.width = rootButtonBorderWidth + } else { + rootButtonBackground.border.color = rootButtonDefaultBorderColor + rootButtonBackground.border.width = 0 + } + } + Rectangle { id: rootButtonBackground anchors.fill: rootButtonContent radius: 16 - color: rootButtonDefaultColor - border.color: rootButtonBorderColor - border.width: rootButtonBorderWidth + color: rootButtonBackgroundColor + border.color: rootButtonDefaultBorderColor + border.width: 0 Behavior on border.width { PropertyAnimation { duration: 200 } } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -83,7 +98,6 @@ Item { } } - //todo change to image type ImageButtonType { Layout.leftMargin: 4 Layout.rightMargin: 16 @@ -100,16 +114,22 @@ Item { hoverEnabled: true onEntered: { - rootButtonBackground.border.width = rootButtonBorderWidth + if (menu.visible === false) { + rootButtonBackground.border.width = rootButtonBorderWidth + rootButtonBackground.border.color = rootButtonHoveredBorderColor + } } onExited: { - rootButtonBackground.border.width = 0 + if (menu.visible === false) { + rootButtonBackground.border.width = 0 + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } } onClicked: { - if (onRootButtonClicked && typeof onRootButtonClicked === "function") { - onRootButtonClicked() + if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { + rootButtonClickedFunction() } else { menu.visible = true } diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index 72c783424..843599a40 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -10,8 +10,10 @@ Button { property string hoveredColor: Qt.rgba(1, 1, 1, 0.08) property string defaultColor: "transparent" property string pressedColor: Qt.rgba(1, 1, 1, 0.12) + property string disableColor: "#2C2D30" property string imageColor: "#878B91" + property string disableImageColor: "#2C2D30" implicitWidth: 40 implicitHeight: 40 @@ -19,7 +21,11 @@ Button { hoverEnabled: true icon.source: image - icon.color: imageColor + icon.color: root.enabled ? imageColor : disableImageColor + + Behavior on icon.color { + PropertyAnimation { duration: 200 } + } background: Rectangle { id: background @@ -33,6 +39,7 @@ Button { } return hovered ? hoveredColor : defaultColor } + return defaultColor } Behavior on color { PropertyAnimation { duration: 200 } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b593ece84..b63c64fbc 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -2,9 +2,13 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Switch { id: root + property alias descriptionText: description.text + property string checkedIndicatorColor: "#412102" property string defaultIndicatorColor: "transparent" property string checkedIndicatorBorderColor: "#412102" @@ -16,10 +20,18 @@ Switch { property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" + implicitWidth: content.implicitWidth + switcher.implicitWidth + implicitHeight: content.implicitHeight + indicator: Rectangle { + id: switcher + + anchors.left: content.right + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 52 implicitHeight: 32 - x: content.width - width + radius: 16 color: root.checked ? checkedIndicatorColor : defaultIndicatorColor border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor @@ -62,16 +74,23 @@ Switch { contentItem: ColumnLayout { id: content - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + anchors.fill: parent + anchors.rightMargin: switcher.implicitWidth - height: 22 + ListItemTitleType { Layout.fillWidth: true - Layout.bottomMargin: 16 + + text: root.text + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + + color: "#878B91" + + visible: text !== "" } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 780f3aefd..9571b1fb3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -145,14 +145,14 @@ PageType { rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" rootButtonMaximumWidth: 150 //todo make it dynamic - rootButtonDefaultColor: "#D7D8DB" + rootButtonBackgroundColor: "#D7D8DB" text: root.currentContainerName textColor: "#0E0E11" headerText: qsTr("Протокол подключения") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" - onRootButtonClicked: function() { + rootButtonClickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) containersDropDown.menuVisible = true diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 300421b71..a74725805 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -58,6 +58,7 @@ PageType { iconImage: "qrc:/images/controls/radio.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsConnection) } } @@ -71,6 +72,7 @@ PageType { iconImage: "qrc:/images/controls/app.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsApplication) } } @@ -84,6 +86,7 @@ PageType { iconImage: "qrc:/images/controls/save.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsBackup) } } @@ -97,6 +100,7 @@ PageType { iconImage: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml new file mode 100644 index 000000000..88c927b95 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -0,0 +1,207 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Support the project with a donation") + horizontalAlignment: Text.AlignHCenter + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + horizontalAlignment: Text.AlignHCenter + + height: 20 + font.pixelSize: 14 + + text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app.") + color: "#CCCAC8" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Card on Patreon") + + onClicked: { + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Show other methods on Github") + + onClicked: { + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Contacts") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Telegram group") + descriptionText: qsTr("To discuss features") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/telegram.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Mail") + descriptionText: qsTr("For reviews and bug reports") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/mail.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Github") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/github.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Website") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + + horizontalAlignment: Text.AlignHCenter + + text: SettingsController.getAppVersion() + color: "#878B91" + } + + BasicButtonType { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 8 + Layout.bottomMargin: 16 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Check for updates") + + onClicked: { + Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml new file mode 100644 index 000000000..9a6eab3cc --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Application") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Language") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Reset settings and remove all data from the application") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml new file mode 100644 index 000000000..0cc629793 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -0,0 +1,184 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Backup") + } + + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save logs") + + checked: SettingsController.isSaveLogsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isSaveLogsEnabled()) { + SettingsController.setSaveLogs(checked) + } + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/folder-open.svg" + + onClicked: SettingsController.openLogsFolder() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Open folder with logs") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/save.svg" + + onClicked: SettingsController.exportLogsFile() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Save logs to file") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/delete.svg" + + onClicked: SettingsController.clearLogs() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + + ListItemTitleType { + Layout.fillWidth: true + Layout.topMargin: 10 + + text: qsTr("Configuration backup") + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: -12 + + text: qsTr("It will help you instantly restore connection settings at the next installation") + color: "#878B91" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 14 + + text: qsTr("Make a backup") + + onClicked: { + SettingsController.backupAppConfig() + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: -8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Restore from backup") + + onClicked: { + SettingsController.restoreAppConfig() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml new file mode 100644 index 000000000..fb0bb0007 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Connection") + } + + SwitcherType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Use AmnesiaDNS if installed on the server") + descriptionText: qsTr("Internal IP address 172.29.172.254") + + checked: SettingsController.isAmneziaDnsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAmneziaDnsEnabled()) { + SettingsController.setAmneziaDns(checked) + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("DNS servers") + descriptionText: qsTr("If AmneziaDNS is not used or installed") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsDns) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Split site tunneling") + descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Separate application tunneling") + descriptionText: qsTr("Allows you to use the VPN only for certain applications") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml new file mode 100644 index 000000000..2a06438b9 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("DNS servers") + } + + ParagraphTextType { + text: qsTr("If AmneziaDNS is not used or installed") + } + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + headerText: "Primary DNS" + + textFieldText: SettingsController.primaryDns + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + headerText: "Secondary DNS" + + textFieldText: SettingsController.secondaryDns + } + + BasicButtonType { + Layout.fillWidth: true + + text: qsTr("Save") + + onClicked: function() { + if (primaryDns.textFieldText !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textFieldText + } + if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textFieldText + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index a731e425a..646410947 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -25,9 +25,6 @@ PageType { } else { ExportController.generateConnectionConfig() } - - shareConnectionDrawer.configText = ExportController.getAmneziaCode() - shareConnectionDrawer.qrCodes = ExportController.getQrCodes() } } @@ -125,7 +122,8 @@ PageType { ParagraphTextType { Layout.fillWidth: true - text: qsTr("VPN access without the ability to manage the server") + text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : + qsTr("Full access to server") color: "#878B91" } @@ -137,7 +135,6 @@ PageType { implicitHeight: 74 - rootButtonBorderWidth: 0 drawerHeight: 0.4375 descriptionText: qsTr("Server and service") @@ -234,6 +231,7 @@ PageType { clickedFunction: function () { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) protocolSelector.visible = false serverSelector.menuVisible = false @@ -244,6 +242,7 @@ PageType { Component.onCompleted: { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) fillConnectionTypeModel() } @@ -273,7 +272,6 @@ PageType { implicitHeight: 74 - rootButtonBorderWidth: 0 drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 diff --git a/client/utilities.cpp b/client/utilities.cpp index 13e36c2f7..bf06ddac9 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include "defines.h" #include "utilities.h" @@ -247,6 +249,58 @@ QString Utils::certUtilPath() #endif } +void Utils::saveFile(const QString &fileExtension, + const QString &caption, + const QString &fileName, + const QString &data) +{ + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, + caption, + QUrl::fromLocalFile(docDir + "/" + fileName), + "*" + fileExtension); + if (fileUrl.isEmpty()) + return; + if (!fileUrl.toString().endsWith(fileExtension)) { + fileUrl = QUrl(fileUrl.toString() + fileExtension); + } + if (fileUrl.isEmpty()) + return; + + QFile save(fileUrl.toLocalFile()); + + //todo check if save successful + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +} + +QString Utils::getFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + QFileDialog::Options options) +{ + QString fileName + = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep{"raw%3A%2F"}; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} + #ifdef Q_OS_WIN // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 diff --git a/client/utilities.h b/client/utilities.h index aeb068651..191fa5c18 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -1,9 +1,10 @@ #ifndef UTILITIES_H #define UTILITIES_H +#include #include -#include #include +#include #ifdef Q_OS_WIN #include "Windows.h" @@ -50,6 +51,18 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); + static void saveFile(const QString &fileExtension, + const QString &caption, + const QString &fileName, + const QString &data); + + static QString getFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = QFileDialog::Options()); + #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 687b382ad..2b3ff8001 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat) +find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat Widgets) qt_standard_project_setup() set(HEADERS @@ -89,14 +89,14 @@ include_directories( ) add_executable(${PROJECT} ${SOURCES} ${HEADERS}) -target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::Widgets ${LIBS}) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) if(NOT IOS) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep) endif() -# deploy artifacts required to run the application to the debug build folder +# copy deploy artifacts required to run the application to the debug build folder if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") set(DEPLOY_ARTIFACT_PATH "windows/x64") From 3a264e6bafbd279605291f2b75b99ee126373a58 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 20 Jun 2023 10:25:24 +0900 Subject: [PATCH 033/131] added a drawer to change the server name and moved the display of the exported config to a separate drawer --- client/images/controls/telegram.svg | 2 +- .../ui/controllers/connectionController.cpp | 5 +- client/ui/controllers/exportController.cpp | 3 +- client/ui/controllers/installController.cpp | 4 +- client/ui/models/servers_model.cpp | 35 ++++-- client/ui/models/servers_model.h | 3 +- .../ConnectionTypeSelectionDrawer.qml | 4 +- .../qml/Components/ShareConnectionDrawer.qml | 100 ++++++++++++------ .../ui/qml/Controls2/LabelWithButtonType.qml | 76 ++++++++----- client/ui/qml/Controls2/ListViewType.qml | 3 +- client/ui/qml/Pages2/PageHome.qml | 12 +-- client/ui/qml/Pages2/PageSettings.qml | 24 ++--- client/ui/qml/Pages2/PageSettingsAbout.qml | 23 ++-- .../ui/qml/Pages2/PageSettingsApplication.qml | 13 ++- client/ui/qml/Pages2/PageSettingsBackup.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 12 +-- client/ui/qml/Pages2/PageSettingsDns.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 4 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 54 +++++++++- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 16 +-- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 4 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 4 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 8 +- client/ui/qml/Pages2/PageStart.qml | 4 +- client/ui/qml/Pages2/PageTest.qml | 4 +- 30 files changed, 276 insertions(+), 159 deletions(-) diff --git a/client/images/controls/telegram.svg b/client/images/controls/telegram.svg index 7b76e5068..92bb6a79c 100644 --- a/client/images/controls/telegram.svg +++ b/client/images/controls/telegram.svg @@ -1,3 +1,3 @@ - + diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index fbbb67d98..dcc15958a 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -32,9 +32,8 @@ ConnectionController::ConnectionController(const QSharedPointer &s void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); - QModelIndex serverModelIndex = m_serversModel->index(serverIndex); - ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverModelIndex, - ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials credentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 98cbebd10..cbb5a1bbf 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -43,7 +43,8 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + ServerCredentials credentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); DockerContainer container = static_cast( m_containersModel->getCurrentlyProcessedContainerIndex()); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 8f9e1f886..08ef69d44 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -61,7 +61,9 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co void InstallController::installContainer(DockerContainer container, QJsonObject& config) { //todo check if container already installed - ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); ServerController serverController(m_settings); ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 9ef87d379..f027a90d1 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -19,12 +19,23 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int return false; } + QJsonObject server = m_servers.at(index.row()).toObject(); + switch (role) { - case IsDefaultRole: { - m_settings->setDefaultServer(index.row()); - m_defaultServerIndex = m_settings->defaultServerIndex(); - } - default: return true; + case NameRole: { + server.insert(config_key::description, value.toString()); + m_settings->editServer(index.row(), server); + m_servers.replace(index.row(), server); + break; + } + case IsDefaultRole: { + m_settings->setDefaultServer(index.row()); + m_defaultServerIndex = m_settings->defaultServerIndex(); + break; + } + default: { + return true; + } } emit dataChanged(index, index); @@ -51,6 +62,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return server.value(config_key::hostName).toString(); case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case CredentialsLoginRole: + return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: @@ -60,6 +73,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } +QVariant ServersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} + const int ServersModel::getDefaultServerIndex() { return m_defaultServerIndex; @@ -85,11 +104,6 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() return m_defaultServerIndex == m_currenlyProcessedServerIndex; } -ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() -{ - return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); -} - void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); @@ -121,6 +135,7 @@ QHash ServersModel::roleNames() const { roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; roles[CredentialsRole] = "credentials"; + roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 6f55176ee..be13d61b5 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -19,6 +19,7 @@ public: NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, + CredentialsLoginRole, IsDefaultRole, IsCurrentlyProcessedRole }; @@ -29,6 +30,7 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const int index, int role = Qt::DisplayRole) const; public slots: const int getDefaultServerIndex(); @@ -38,7 +40,6 @@ public slots: void setCurrentlyProcessedServerIndex(int index); int getCurrentlyProcessedServerIndex(); - ServerCredentials getCurrentlyProcessedServerCredentials(); void addServer(const QJsonObject &server); void removeServer(); diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 3c1ecd5bf..51bffc039 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -36,7 +36,7 @@ DrawerType { Layout.topMargin: 16 text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardCredentials) @@ -50,7 +50,7 @@ DrawerType { Layout.fillWidth: true text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardConfigSource) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 1b92c7529..627eba81d 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -16,11 +16,12 @@ DrawerType { id: root property alias headerText: header.headerText + property alias configContentHeaderText: configContentHeader.headerText width: parent.width height: parent.height * 0.9 - Item{ + Item { anchors.fill: parent FlickableType { @@ -86,46 +87,77 @@ DrawerType { disabledColor: "#878B91" textColor: "#D7D8DB" - text: showContent ? qsTr("Collapse content") : qsTr("Show content") + text: qsTr("Show content") onClicked: { - showContent = !showContent + configContentDrawer.visible = true } } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + DrawerType { + id: configContentDrawer - radius: 10 - color: "#2C2D30" + width: parent.width + height: parent.height * 0.9 - visible: showContent + BackButtonType { + id: backButton - height: 24 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 16 - TextField { - id: configContent + backButtonFunction: function() { + configContentDrawer.visible = false + } + } - anchors.fill: parent - anchors.margins: 16 + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - height: 24 + ColumnLayout { + id: configContent - color: "#D7D8DB" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - text: ExportController.amneziaCode - - wrapMode: Text.Wrap - - enabled: false - background: Rectangle { anchors.fill: parent - color: "transparent" + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: configContentHeader + Layout.fillWidth: true + Layout.topMargin: 16 + } + + TextField { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: ExportController.amneziaCode + + wrapMode: Text.Wrap + + readOnly: true + background: Rectangle { + color: "transparent" + } + } } } } @@ -143,17 +175,19 @@ DrawerType { anchors.fill: parent smooth: false + source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" + Timer { - property int idx: 0 + property int index: 0 interval: 1000 running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { - idx++ - if (idx >= ExportController.qrCodesCount) { - idx = 0 + index++ + if (index >= ExportController.qrCodesCount) { + index = 0 } - parent.source = ExportController.qrCodes[idx] + parent.source = ExportController.qrCodes[index] } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index e4e711a6b..27ead16ce 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -12,8 +12,8 @@ Item { property var clickedFunction - property alias buttonImage: button.image - property string iconImage + property string rightImageSource + property string leftImageSource property string textColor: "#d7d8db" @@ -26,17 +26,34 @@ Item { anchors.leftMargin: 16 anchors.rightMargin: 16 - Image { - id: icon - source: iconImage - visible: iconImage ? true : false - Layout.rightMargin: visible ? 16 : 0 + Rectangle { + id: leftImageBackground + + visible: leftImageSource ? true : false + + Layout.preferredHeight: rightImageSource ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource ? 16 : 0 + + radius: 12 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Image { + id: leftImage + + anchors.centerIn: parent + source: leftImageSource + } } ColumnLayout { ListItemTitleType { text: root.text - color: textColor + color: root.textColor Layout.fillWidth: true Layout.topMargin: 16 @@ -63,25 +80,20 @@ Item { } ImageButtonType { - id: button + id: rightImage hoverEnabled: false - image: buttonImage - onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } + image: rightImageSource + visible: rightImageSource ? true : false Layout.alignment: Qt.AlignRight Rectangle { - id: imageBackground - anchors.fill: button + id: rightImageBackground + anchors.fill: parent radius: 12 color: "transparent" - Behavior on color { PropertyAnimation { duration: 200 } } @@ -106,31 +118,39 @@ Item { hoverEnabled: true onEntered: { - if (buttonImage) { - imageBackground.color = button.hoveredColor + if (rightImageSource) { + rightImageBackground.color = rightImage.hoveredColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.hoveredColor } else { - background.color = button.hoveredColor + background.color = rightImage.hoveredColor } } onExited: { - if (buttonImage) { - imageBackground.color = button.defaultColor + if (rightImageSource) { + rightImageBackground.color = rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.defaultColor } else { - background.color = button.defaultColor + background.color = rightImage.defaultColor } } onPressedChanged: { - if (buttonImage) { - imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + if (rightImageSource) { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } else { - background.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + background.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } } onClicked: { - button.clicked() + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } } } } diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index 421b82a32..4251f0fde 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -31,7 +31,6 @@ ListView { id: content anchors.fill: parent - spacing: 16 RadioButton { id: radioButton @@ -92,7 +91,7 @@ ListView { DividerType { Layout.fillWidth: true - Layout.bottomMargin: 16 + Layout.bottomMargin: 4 visible: dividerVisible } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 9571b1fb3..282c1408c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -51,7 +51,6 @@ PageType { Rectangle { id: buttonBackground anchors.fill: buttonContent - anchors.bottomMargin: -radius radius: 16 color: root.defaultColor @@ -59,11 +58,12 @@ PageType { border.width: 1 Rectangle { - width: parent.width - height: 1 - y: parent.height - height - parent.radius - - color: root.borderColor + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a74725805..f430a0049 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -15,8 +15,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -40,8 +40,8 @@ PageType { Layout.topMargin: 16 text: qsTr("Servers") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/server.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/server.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsServersList) @@ -54,8 +54,8 @@ PageType { Layout.fillWidth: true text: qsTr("Connection") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/radio.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/radio.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsConnection) @@ -68,8 +68,8 @@ PageType { Layout.fillWidth: true text: qsTr("Application") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/app.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/app.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsApplication) @@ -82,8 +82,8 @@ PageType { Layout.fillWidth: true text: qsTr("Backup") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/save.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/save.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsBackup) @@ -96,8 +96,8 @@ PageType { Layout.fillWidth: true text: qsTr("About AmneziaVPN") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/amnezia.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsAbout) diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 88c927b95..a1bdeb268 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -27,7 +27,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -102,8 +102,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Show other methods on Github") - onClicked: { - } + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") } ParagraphTextType { @@ -121,11 +120,10 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Telegram group") descriptionText: qsTr("To discuss features") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/telegram.svg" + leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + Qt.openUrlExternally("https://t.me/amnezia_vpn_dev") } } @@ -136,11 +134,9 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Mail") descriptionText: qsTr("For reviews and bug reports") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/mail.svg" + leftImageSource: "qrc:/images/controls/mail.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) } } @@ -150,11 +146,10 @@ And if you don't like the app, all the more support it - the donation will be us Layout.fillWidth: true text: qsTr("Github") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/github.svg" + leftImageSource: "qrc:/images/controls/github.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") } } @@ -164,11 +159,9 @@ And if you don't like the app, all the more support it - the donation will be us Layout.fillWidth: true text: qsTr("Website") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/amnezia.svg" + leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 9a6eab3cc..08a5ec0d5 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -35,22 +35,21 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: qsTr("Application") } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: 16 text: qsTr("Language") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } @@ -62,7 +61,7 @@ PageType { Layout.fillWidth: true text: qsTr("Reset settings and remove all data from the application") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 0cc629793..a5445754e 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index fb0bb0007..ad8524f59 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -25,7 +25,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -35,8 +35,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - spacing: 16 - HeaderType { Layout.fillWidth: true Layout.leftMargin: 16 @@ -47,6 +45,8 @@ PageType { SwitcherType { Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -68,7 +68,7 @@ PageType { text: qsTr("DNS servers") descriptionText: qsTr("If AmneziaDNS is not used or installed") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsDns) @@ -82,7 +82,7 @@ PageType { text: qsTr("Split site tunneling") descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } @@ -95,7 +95,7 @@ PageType { text: qsTr("Separate application tunneling") descriptionText: qsTr("Allows you to use the VPN only for certain applications") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 2a06438b9..1c989c0cd 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index fe09ed01e..6179983b6 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -16,8 +16,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 5e4222304..3f2562da6 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -54,10 +54,60 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: name - descriptionText: hostName + descriptionText: credentialsLogin + " · " + hostName actionButtonFunction: function() { - connectionTypeSelection.visible = true + serverNameEditDrawer.visible = true + } + } + + DrawerType { + id: serverNameEditDrawer + + width: root.width + height: root.height * 0.35 + + onVisibleChanged: { + if (serverNameEditDrawer.visible) { + serverName.textField.forceActiveFocus() + } + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: serverName + + Layout.fillWidth: true + headerText: qsTr("Server name") + textFieldText: name + } + + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Save") + + onClicked: { + if (serverName.textFieldText !== name) { + name = serverName.textFieldText + serverNameEditDrawer.visible = false + } + } + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index fa4ce86c9..a601199a2 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -86,7 +86,7 @@ PageType { text: name descriptionText: hostName - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(index) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 79e596af7..2ef38067b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -15,8 +15,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -58,8 +58,8 @@ PageType { Layout.topMargin: 16 text: "Файл с настройками подключения" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/folder-open.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { onClicked: fileDialog.open() @@ -81,8 +81,8 @@ PageType { Layout.fillWidth: true text: "QR-код" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/qr-code.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { } @@ -94,8 +94,8 @@ PageType { Layout.fillWidth: true text: "Ключ в виде текста" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/text-cursor.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/text-cursor.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardTextKey) diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 491979626..3f037035e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -25,7 +25,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 1836f8925..19f06d5ff 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -44,7 +44,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin Column { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index e2ae4a164..527df8307 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -32,8 +32,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height Column { @@ -87,7 +87,7 @@ PageType { text: name descriptionText: description - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 6358f00dd..ad91303e8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -24,8 +24,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index f74c37339..d7dbf43de 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -14,8 +14,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index ca679e79a..c7b774b48 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -50,7 +50,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.implicitHeight + connectButton.implicitHeight ColumnLayout { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 646410947..504b0cb18 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -56,8 +56,8 @@ PageType { } FlickableType { - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -121,6 +121,7 @@ PageType { ParagraphTextType { Layout.fillWidth: true + Layout.topMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : qsTr("Full access to server") @@ -222,7 +223,6 @@ PageType { roleName: "isInstalled" value: true } - ] } @@ -231,6 +231,7 @@ PageType { clickedFunction: function () { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) protocolSelector.visible = false @@ -242,6 +243,7 @@ PageType { Component.onCompleted: { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) fillConnectionTypeModel() diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8b9cefa42..a91cab6af 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -59,11 +59,13 @@ PageType { anchors.bottom: parent.bottom topPadding: 8 - bottomPadding: 34 + bottomPadding: 8//34 leftPadding: 96 rightPadding: 96 background: Rectangle { + border.width: 1 + border.color: "#2C2D30" color: "#1C1D21" } diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index 53ec99fc9..d33608f87 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -122,7 +122,7 @@ Item { Layout.rightMargin: 16 text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" } Rectangle { @@ -139,7 +139,7 @@ Item { Layout.rightMargin: 16 text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" } Rectangle { From 249be451f75bfb69778992b742feb23b0f326120 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 21 Jun 2023 20:56:00 +0900 Subject: [PATCH 034/131] moved handling of connection states from qml in connectionController - added a check for already installed containers before installing the server/container - added a button to scan the server for installed containers - added separation for read/write and readonly servers for pageHome --- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 98 ++++++++---- client/ui/controllers/connectionController.h | 16 +- client/ui/controllers/exportController.cpp | 2 +- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/installController.cpp | 150 ++++++++++++++---- client/ui/controllers/installController.h | 28 ++-- client/ui/controllers/pageController.cpp | 2 +- client/ui/controllers/pageController.h | 16 +- client/ui/models/containers_model.cpp | 90 +++++------ client/ui/models/servers_model.cpp | 46 +++--- client/ui/models/servers_model.h | 11 +- client/ui/qml/Components/ConnectButton.qml | 79 +++------ .../qml/Components/ShareConnectionDrawer.qml | 1 + .../qml/Controls2/TextFieldWithHeaderType.qml | 9 +- .../qml/Controls2/TextTypes/SmallTextType.qml | 12 ++ client/ui/qml/Pages2/PageHome.qml | 71 +++++++-- .../ui/qml/Pages2/PageSettingsServerData.qml | 32 +++- .../qml/Pages2/PageSetupWizardCredentials.qml | 17 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 23 ++- client/ui/qml/Pages2/PageStart.qml | 5 + 21 files changed, 466 insertions(+), 245 deletions(-) create mode 100644 client/ui/qml/Controls2/TextTypes/SmallTextType.qml diff --git a/client/resources.qrc b/client/resources.qrc index a8f8718ea..5b5fd593a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -268,5 +268,6 @@ images/controls/github.svg images/controls/mail.svg images/controls/telegram.svg + ui/qml/Controls2/TextTypes/SmallTextType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index dcc15958a..aace28577 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -6,39 +6,27 @@ ConnectionController::ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_vpnConnection(vpnConnection) + const QSharedPointer &vpnConnection, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_vpnConnection(vpnConnection) { - connect(m_vpnConnection.get(), - &VpnConnection::connectionStateChanged, - this, - &ConnectionController::connectionStateChanged); - connect(this, - &ConnectionController::connectToVpn, - m_vpnConnection.get(), - &VpnConnection::connectToVpn, + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, this, + &ConnectionController::onConnectionStateChanged); + connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, - &ConnectionController::disconnectFromVpn, - m_vpnConnection.get(), - &VpnConnection::disconnectFromVpn, + connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); } void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); - ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); - const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, - ContainersModel::Roles::ConfigRole)); + const QJsonObject &containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); if (container == DockerContainer::None) { emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); @@ -59,13 +47,65 @@ QString ConnectionController::getLastConnectionError() return errorString(m_vpnConnection->lastError()); } -bool ConnectionController::isConnected() +void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) +{ + m_isConnected = false; + m_connectionStateText = tr("Connection..."); + switch (state) { + case Vpn::ConnectionState::Connected: { + m_isConnectionInProgress = false; + m_isConnected = true; + m_connectionStateText = tr("Disconnect"); + break; + } + case Vpn::ConnectionState::Connecting: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Reconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Reconnection..."); + break; + } + case Vpn::ConnectionState::Disconnected: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + break; + } + case Vpn::ConnectionState::Disconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Disconnection..."); + break; + } + case Vpn::ConnectionState::Preparing: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Error: { + m_isConnectionInProgress = false; + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + case Vpn::ConnectionState::Unknown: { + m_isConnectionInProgress = false; + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + } + emit connectionStateChanged(); +} + +QString ConnectionController::connectionStateText() const +{ + return m_connectionStateText; +} + +bool ConnectionController::isConnectionInProgress() const +{ + return m_isConnectionInProgress; +} + +bool ConnectionController::isConnected() const { return m_isConnected; } - -void ConnectionController::setIsConnected(bool isConnected) -{ - m_isConnected = isConnected; - emit isConnectedChanged(); -} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index c1e81ea31..421ae84f2 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -11,28 +11,30 @@ class ConnectionController : public QObject Q_OBJECT public: - Q_PROPERTY(bool isConnected READ isConnected WRITE setIsConnected NOTIFY isConnectedChanged) + Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) + Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) + Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); - bool isConnected(); - void setIsConnected(bool isConnected); //todo take state from vpnconnection? + bool isConnected() const; + bool isConnectionInProgress() const; + QString connectionStateText() const; public slots: void openConnection(); void closeConnection(); QString getLastConnectionError(); - Vpn::ConnectionState connectionState(){return {};}; //todo update ConnectButton text on page change + void onConnectionStateChanged(Vpn::ConnectionState state); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); - void connectionStateChanged(Vpn::ConnectionState state); - void isConnectedChanged(); + void connectionStateChanged(); void connectionErrorOccurred(QString errorMessage); @@ -43,6 +45,8 @@ private: QSharedPointer m_vpnConnection; bool m_isConnected = false; + bool m_isConnectionInProgress = false; + QString m_connectionStateText = tr("Connect"); }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index cbb5a1bbf..b6cd7abb3 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -44,7 +44,7 @@ void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); DockerContainer container = static_cast( m_containersModel->getCurrentlyProcessedContainerIndex()); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 2724f9cd0..99de3131a 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -89,7 +89,7 @@ void ImportController::importConfig() if (!m_config.value(config_key::containers).toArray().isEmpty()) { auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); } emit importFinished(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 08ef69d44..5d3bfc2f7 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -2,40 +2,54 @@ #include -#include "core/servercontroller.h" #include "core/errorstrings.h" +#include "core/servercontroller.h" +#include "utilities.h" InstallController::InstallController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{} + const QSharedPointer &containersModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ +} void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject containerConfig { - { config_key::port, QString::number(port) }, - { config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto) } - }; - QJsonObject config { - { config_key::container, ContainerProps::containerToString(container) }, - { ProtocolProps::protoToString(mainProto), containerConfig } - }; + QJsonObject containerConfig { { config_key::port, QString::number(port) }, + { config_key::transport_proto, + ProtocolProps::transportProtoToString(transportProto, mainProto) } }; + QJsonObject config { { config_key::container, ContainerProps::containerToString(container) }, + { ProtocolProps::protoToString(mainProto), containerConfig } }; if (m_shouldCreateServer) { + if (isServerAlreadyExists()) { + return; + } installServer(container, config); } else { installContainer(container, config); } } -void InstallController::installServer(DockerContainer container, QJsonObject& config) +void InstallController::installServer(DockerContainer container, QJsonObject &config) { - //todo check if container already installed ServerController serverController(m_settings); - ErrorCode errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + + QMap installedContainers; + ErrorCode errorCode = + serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + installedContainers.insert(container, config); + } + + bool isInstalledContainerFound = false; + if (!installedContainers.isEmpty()) { + isInstalledContainerFound = true; + } + if (errorCode == ErrorCode::NoError) { QJsonObject server; server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); @@ -44,43 +58,123 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); - server.insert(config_key::containers, QJsonArray{ config }); + QJsonArray containerConfigs; + for (const QJsonObject &containerConfig : qAsConst(installedContainers)) { + containerConfigs.append(containerConfig); + } + + server.insert(config_key::containers, containerConfigs); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); - emit installServerFinished(); + emit installServerFinished(isInstalledContainerFound); return; } emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::installContainer(DockerContainer container, QJsonObject& config) +void InstallController::installContainer(DockerContainer container, QJsonObject &config) { - //todo check if container already installed int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials serverCredentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); ServerController serverController(m_settings); - ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + bool isInstalledContainerFound = false; + if (!installedContainers.isEmpty()) { + isInstalledContainerFound = true; + } + + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(serverCredentials, container, config); + installedContainers.insert(container, config); + } + if (errorCode == ErrorCode::NoError) { - m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); - emit installContainerFinished(); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + } + } + + emit installContainerFinished(isInstalledContainerFound); return; } emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) +bool InstallController::isServerAlreadyExists() +{ + for (int i = 0; i < m_serversModel->getServersCount(); i++) { + auto modelIndex = m_serversModel->index(i); + const ServerCredentials credentials = + qvariant_cast(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole)); + if (m_currentlyInstalledServerCredentials.hostName == credentials.hostName + && m_currentlyInstalledServerCredentials.port == credentials.port) { + emit serverAlreadyExists(i); + return true; + } + } + return false; +} + +void InstallController::scanServerForInstalledContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + ServerController serverController(m_settings); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + if (errorCode == ErrorCode::NoError) { + bool isInstalledContainerAddedToGui = false; + + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + isInstalledContainerAddedToGui = true; + } + } + + emit scanServerFinished(isInstalledContainerAddedToGui); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +QRegularExpression InstallController::ipAddressPortRegExp() +{ + return Utils::ipAddressPortRegExp(); +} + +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData) { m_currentlyInstalledServerCredentials.hostName = hostName; if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { - m_currentlyInstalledServerCredentials.port = m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); + m_currentlyInstalledServerCredentials.port = + m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); } m_currentlyInstalledServerCredentials.userName = userName; diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index d9e1ad95d..6b01e102c 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -3,10 +3,10 @@ #include -#include "core/defs.h" #include "containers/containers_defs.h" -#include "ui/models/servers_model.h" +#include "core/defs.h" #include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" class InstallController : public QObject { @@ -14,22 +14,32 @@ class InstallController : public QObject public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); public slots: void install(DockerContainer container, int port, TransportProto transportProto); - void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); + void scanServerForInstalledContainers(); + + QRegularExpression ipAddressPortRegExp(); + signals: - void installContainerFinished(); - void installServerFinished(); + void installContainerFinished(bool isInstalledContainerFound); + void installServerFinished(bool isInstalledContainerFound); + + void scanServerFinished(bool isInstalledContainerFound); void installationErrorOccurred(QString errorMessage); + + void serverAlreadyExists(int serverIndex); + private: - void installServer(DockerContainer container, QJsonObject& config); - void installContainer(DockerContainer container, QJsonObject& config); + void installServer(DockerContainer container, QJsonObject &config); + void installContainer(DockerContainer container, QJsonObject &config); + bool isServerAlreadyExists(); QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index e49177a55..4ee9b3bf9 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -10,7 +10,7 @@ QString PageController::getInitialPage() if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { auto defaultServerIndex = m_serversModel->index(0); - m_serversModel->setData(defaultServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(defaultServerIndex, true, ServersModel::Roles::IsDefaultRole); } return getPagePath(PageLoader::PageEnum::PageStart); } else { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 587e0e385..384d3c8d5 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -40,14 +40,9 @@ namespace PageLoader }; Q_ENUM_NS(PageEnum) - static void declareQmlPageEnum() { - qmlRegisterUncreatableMetaObject( - PageLoader::staticMetaObject, - "PageEnum", - 1, 0, - "PageEnum", - "Error: only enums" - ); + static void declareQmlPageEnum() + { + qmlRegisterUncreatableMetaObject(PageLoader::staticMetaObject, "PageEnum", 1, 0, "PageEnum", "Error: only enums"); } } @@ -55,8 +50,7 @@ class PageController : public QObject { Q_OBJECT public: - explicit PageController(const QSharedPointer &serversModel, - QObject *parent = nullptr); + explicit PageController(const QSharedPointer &serversModel, QObject *parent = nullptr); public slots: QString getInitialPage(); @@ -64,9 +58,11 @@ public slots: signals: void goToPageHome(); + void goToPageSettings(); void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); void showErrorMessage(QString errorMessage); + void showInfoMessage(QString message); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 1caf69443..f095dd02d 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -2,7 +2,8 @@ #include "core/servercontroller.h" -ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { } @@ -21,25 +22,25 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - // return ContainerProps::containerHumanNames().value(container); - case DescRole: - // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: //todo save to model also - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); - case ServiceTypeRole: - // return ContainerProps::containerService(container); - case DockerContainerRole: - // return container; - case IsInstalledRole: - // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: { - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); - m_defaultContainerIndex = container; - emit defaultContainerChanged(); - } + case NameRole: + // return ContainerProps::containerHumanNames().value(container); + case DescRole: + // return ContainerProps::containerDescriptions().value(container); + case ConfigRole: { + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + } + case ServiceTypeRole: + // return ContainerProps::containerService(container); + case DockerContainerRole: + // return container; + case IsInstalledRole: + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: { + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); + } } emit dataChanged(index, index); @@ -48,40 +49,30 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i QVariant ContainersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { return QVariant(); } DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - return ContainerProps::containerHumanNames().value(container); - case DescRole: - return ContainerProps::containerDescriptions().value(container); - case ConfigRole: { - if (container == DockerContainer::None) return QJsonObject(); - return m_containers.value(container); + case NameRole: return ContainerProps::containerHumanNames().value(container); + case DescRole: return ContainerProps::containerDescriptions().value(container); + case ConfigRole: { + if (container == DockerContainer::None) { + return QJsonObject(); } - case ServiceTypeRole: - return ContainerProps::containerService(container); - case DockerContainerRole: - return container; - case IsEasySetupContainerRole: - return ContainerProps::isEasySetupContainer(container); - case EasySetupHeaderRole: - return ContainerProps::easySetupHeader(container); - case EasySetupDescriptionRole: - return ContainerProps::easySetupDescription(container); - case IsInstalledRole: - return m_containers.contains(container); - case IsCurrentlyProcessedRole: - return container == static_cast(m_currentlyProcessedContainerIndex); - case IsDefaultRole: - return container == m_defaultContainerIndex; - case IsSupportedRole: - return ContainerProps::isSupportedByCurrentPlatform(container); + return m_containers.value(container); + } + case ServiceTypeRole: return ContainerProps::containerService(container); + case DockerContainerRole: return container; + case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); + case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); + case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); + case IsInstalledRole: return m_containers.contains(container); + case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); + case IsDefaultRole: return container == m_defaultContainerIndex; + case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); } return QVariant(); @@ -130,7 +121,7 @@ void ContainersModel::removeAllContainers() endResetModel(); } - //todo process errors + // todo process errors } void ContainersModel::clearCachedProfiles() @@ -141,7 +132,8 @@ void ContainersModel::clearCachedProfiles() } } -QHash ContainersModel::roleNames() const { +QHash ContainersModel::roleNames() const +{ QHash roles; roles[NameRole] = "name"; roles[DescRole] = "description"; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index f027a90d1..94267ed15 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,6 +1,7 @@ #include "servers_model.h" -ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); @@ -14,8 +15,7 @@ int ServersModel::rowCount(const QModelIndex &parent) const bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_servers.size())) { + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { return false; } @@ -29,8 +29,7 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int break; } case IsDefaultRole: { - m_settings->setDefaultServer(index.row()); - m_defaultServerIndex = m_settings->defaultServerIndex(); + setDefaultServerIndex(index.row()); break; } default: { @@ -58,16 +57,11 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const } return description; } - case HostNameRole: - return server.value(config_key::hostName).toString(); - case CredentialsRole: - return QVariant::fromValue(m_settings->serverCredentials(index.row())); - case CredentialsLoginRole: - return m_settings->serverCredentials(index.row()).userName; - case IsDefaultRole: - return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: - return index.row() == m_currenlyProcessedServerIndex; + case HostNameRole: return server.value(config_key::hostName).toString(); + case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; + case IsDefaultRole: return index.row() == m_defaultServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; } return QVariant(); @@ -92,6 +86,7 @@ const int ServersModel::getServersCount() void ServersModel::setCurrentlyProcessedServerIndex(int index) { m_currenlyProcessedServerIndex = index; + emit currentlyProcessedServerIndexChanged(); } int ServersModel::getCurrentlyProcessedServerIndex() @@ -104,6 +99,12 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() return m_defaultServerIndex == m_currenlyProcessedServerIndex; } +bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() +{ + auto credentials = m_settings->serverCredentials(m_currenlyProcessedServerIndex); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); +} + void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); @@ -119,18 +120,19 @@ void ServersModel::removeServer() m_servers = m_settings->serversArray(); if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { - m_settings->setDefaultServer(0); + setDefaultServerIndex(0); } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { - m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); + setDefaultServerIndex(m_settings->defaultServerIndex() - 1); } if (m_settings->serversCount() == 0) { - m_settings->setDefaultServer(-1); + setDefaultServerIndex(-1); } endResetModel(); } -QHash ServersModel::roleNames() const { +QHash ServersModel::roleNames() const +{ QHash roles; roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; @@ -140,3 +142,9 @@ QHash ServersModel::roleNames() const { roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; } + +void ServersModel::setDefaultServerIndex(const int index) +{ + m_settings->setDefaultServer(index); + m_defaultServerIndex = m_settings->defaultServerIndex(); +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index be13d61b5..891d6ad16 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -5,7 +5,8 @@ #include "settings.h" -struct ServerModelContent { +struct ServerModelContent +{ QString desc; QString address; bool isDefault; @@ -15,7 +16,7 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - enum ServersModelRoles { + enum Roles { NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, @@ -35,6 +36,7 @@ public: public slots: const int getDefaultServerIndex(); bool isDefaultServerCurrentlyProcessed(); + bool isCurrentlyProcessedServerHasWriteAccess(); const int getServersCount(); @@ -47,7 +49,12 @@ public slots: protected: QHash roleNames() const override; +signals: + void currentlyProcessedServerIndexChanged(); + private: + void setDefaultServerIndex(const int index); + QJsonArray m_servers; std::shared_ptr m_settings; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 3c34a6b09..626c4ffb1 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -15,7 +15,7 @@ Button { } } - text: qsTr("Connect") + text: ConnectionController.connectionStateText background: Item { clip: true @@ -26,13 +26,21 @@ Button { Image { id: border - source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + source: { + if (ConnectionController.isConnectionInProgress) { + return "/images/connectionProgress.svg" + } else if (ConnectionController.isConnected) { + return "/images/connectionOff.svg" + } else { + return "/images/connectionOn.svg" + } + } + RotationAnimator { id: connectionProccess target: border - running: false + running: ConnectionController.isConnectionInProgress from: 0 to: 360 loops: Animation.Infinite @@ -67,63 +75,12 @@ Button { } onClicked: { - connectionProccess.running ? ConnectionController.closeConnection() : ConnectionController.openConnection() - } - - Connections { - target: ConnectionController - function onConnectionStateChanged(state) { - switch(state) { - case ConnectionState.Unknown: { - console.log("Unknown") - break - } - case ConnectionState.Disconnected: { - console.log("Disconnected") - connectionProccess.running = false - root.text = qsTr("Connect") - ConnectionController.isConnected = false - break - } - case ConnectionState.Preparing: { - console.log("Preparing") - connectionProccess.running = true - root.text = qsTr("Connection...") - break - } - case ConnectionState.Connecting: { - console.log("Connecting") - connectionProccess.running = true - root.text = qsTr("Connection...") - break - } - case ConnectionState.Connected: { - console.log("Connected") - connectionProccess.running = false - root.text = qsTr("Disconnect") - ConnectionController.isConnected = true - break - } - case ConnectionState.Disconnecting: { - console.log("Disconnecting") - connectionProccess.running = true - root.text = qsTr("Disconnection...") - break - } - case ConnectionState.Reconnecting: { - console.log("Reconnecting") - connectionProccess.running = true - root.text = qsTr("Reconnection...") - break - } - case ConnectionState.Error: { - console.log("Error") - connectionProccess.running = false - root.text = qsTr("Connect") - PageController.showErrorMessage(ConnectionController.getLastConnectionError()) - break - } - } + if (ConnectionController.isConnectionInProgress) { + ConnectionController.closeConnection() + } else if (ConnectionController.isConnected) { + ConnectionController.closeConnection() + } else { + ConnectionController.openConnection() } } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 627eba81d..e03738c84 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -141,6 +141,7 @@ DrawerType { Layout.bottomMargin: 16 padding: 0 + leftPadding: 0 height: 24 color: "#D7D8DB" diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index b41f0b400..8ad92d542 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -34,15 +36,10 @@ Item { anchors.fill: backgroud ColumnLayout { - Text { + LabelTextType { text: root.headerText color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 - height: 16 Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml new file mode 100644 index 000000000..96f3342dd --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 20 + + color: "#D7D8DB" + font.pixelSize: 14 + font.weight: Font.Normal + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 282c1408c..a4e901174 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -21,8 +21,8 @@ PageType { property string borderColor: "#2C2D30" - property string currentServerName: serversMenuContent.currentItem.delegateData.name - property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName + property string currentServerName + property string currentServerHostName property string currentContainerName ConnectButton { @@ -93,7 +93,15 @@ PageType { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentContainerName + " | " + root.currentServerHostName + text: { + var string = "" + if (SettingsController.isAmneziaDnsEnabled()) { + string += "Amnezia DNS | " + } + + string += root.currentContainerName + " | " + root.currentServerHostName + return string + } } } @@ -153,6 +161,8 @@ PageType { headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { + // todo check if server index changed before set Currently processed + // todo make signal slot for change server index in containersModel ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) containersDropDown.menuVisible = true @@ -161,20 +171,45 @@ PageType { listView: HomeContainersListView { rootWidth: root.width + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = [serviceTypeFilter, supportedFilter] + } else { + proxyContainersModel.filters = installedFilter + } + } + } + + ValueFilter { + id: serviceTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Vpn + } + ValueFilter { + id: supportedFilter + roleName: "isSupported" + value: true + } + ValueFilter { + id: installedFilter + roleName: "isInstalled" + value: true + } + model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] + + Component.onCompleted: updateContainersModelFilters() } + currentIndex: ContainersModel.getDefaultContainer() } } @@ -272,6 +307,9 @@ PageType { isDefault = true ContainersModel.setCurrentlyProcessedServerIndex(index) + + root.currentServerName = name + root.currentServerHostName = hostName } MouseArea { @@ -302,6 +340,13 @@ PageType { Layout.fillWidth: true } } + + Component.onCompleted: { + if (serversMenuContent.currentIndex === index) { + root.currentServerName = name + root.currentServerHostName = hostName + } + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 6179983b6..b98d2b8c7 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -14,6 +14,21 @@ import "../Components" PageType { id: root + Connections { + target: InstallController + + function onScanServerFinished(isInstalledContainerFound) { + var message = "" + if (isInstalledContainerFound) { + message = qsTr("All installed containers have been added to the application") + } else { + message = qsTr("Не найдено установленных контейнеров") + } + + PageController.showErrorMessage(message) + } + } + FlickableType { id: fl anchors.top: parent.top @@ -30,8 +45,8 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "Clear Amnezia cache" - descriptionText: "May be needed when changing other settings" + text: qsTr("Clear Amnezia cache") + descriptionText: qsTr("May be needed when changing other settings") clickedFunction: function() { questionDrawer.headerText = qsTr("Clear cached profiles?") @@ -52,6 +67,19 @@ PageType { DividerType {} + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") + descriptionText: qsTr("Добавим их в приложение, если они не отображались") + + clickedFunction: function() { + InstallController.scanServerForInstalledContainers() + } + } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 3f037035e..fa6b1f928 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -7,6 +7,7 @@ import PageEnum 1.0 import "./" import "../Controls2" import "../Config" +import "../Controls2/TextTypes" PageType { id: root @@ -42,28 +43,32 @@ PageType { HeaderType { Layout.fillWidth: true - headerText: "Подключение к серверу" + headerText: qsTr("Server connection") } TextFieldWithHeaderType { id: hostname Layout.fillWidth: true - headerText: "Server IP address [:port]" + headerText: qsTr("Server IP address [:port]") + textFieldPlaceholderText: qsTr("Enter the address in the format 255.255.255.255:88") + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressPortRegExp() + } } TextFieldWithHeaderType { id: username Layout.fillWidth: true - headerText: "Login to connect via SSH" + headerText: qsTr("Login to connect via SSH") } TextFieldWithHeaderType { id: secretData Layout.fillWidth: true - headerText: "Password / Private key" + headerText: qsTr("Password / Private key") textField.echoMode: TextInput.Password } @@ -71,7 +76,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - text: qsTr("Настроить сервер простым образом") + text: qsTr("Set up a server the easy way") onClicked: function() { InstallController.setShouldCreateServer(true) @@ -92,7 +97,7 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("Выбрать протокол для установки") + text: qsTr("Select protocol to install") onClicked: function() { InstallController.setShouldCreateServer(true) diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 9631e2588..d0fdeabd0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,7 +24,7 @@ PageType { PageController.showErrorMessage(errorMessage) } - function onInstallContainerFinished() { + function onInstallContainerFinished(isInstalledContainerFound) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -34,9 +34,15 @@ PageType { } else { goToPage(PageEnum.PageHome) } + + if (isInstalledContainerFound) { + //todo change to info message + PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + + "All installed containers have been added to the application")) + } } - function onInstallServerFinished() { + function onInstallServerFinished(isInstalledContainerFound) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() @@ -46,6 +52,19 @@ PageType { var pagePath = PageController.getPagePath(PageEnum.PageStart) stackView.replace(pagePath, { "objectName" : pagePath }) } + + if (isInstalledContainerFound) { + PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + + "All installed containers have been added to the application")) + } + } + + function onServerAlreadyExists(serverIndex) { + goToStartPage() + ServersModel.setCurrentlyProcessedServerIndex(serverIndex) + goToPage(PageEnum.PageSettingsServerInfo, false) + + PageController.showErrorMessage(qsTr("The server has already been added to the application")) } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index a91cab6af..1307ee050 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -20,6 +20,11 @@ PageType { tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) } + function onGoToPageSettings() { + tabBar.currentIndex = 2 + tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) + } + function onShowErrorMessage(errorMessage) { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() From 2ef53c6df9b7d520058dd075c2fb013292ada48a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 23 Jun 2023 15:24:40 +0900 Subject: [PATCH 035/131] added separation for read/write and readonly servers for pageSettingsServerProtocols, PageSettingsServerServices, PageSettingsServerData - added fields validations for pageSetupWizardCredentials --- client/amnezia_application.cpp | 94 +++++------ client/resources.qrc | 1 + client/ui/controllers/exportController.cpp | 55 +++---- client/ui/controllers/importController.cpp | 70 ++++---- client/ui/controllers/installController.cpp | 3 +- client/ui/models/containers_model.cpp | 2 +- client/ui/models/containers_model.h | 7 +- client/ui/models/servers_model.cpp | 30 ++-- client/ui/models/servers_model.h | 23 ++- client/ui/pages_logic/ServerListLogic.cpp | 38 +++-- .../qml/Controls2/TextFieldWithHeaderType.qml | 151 ++++++++++-------- .../ui/qml/Filters/ContainersModelFilters.qml | 47 ++++++ client/ui/qml/Pages2/PageHome.qml | 46 ++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 29 +++- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 14 +- .../Pages2/PageSettingsServerProtocols.qml | 40 +++-- .../qml/Pages2/PageSettingsServerServices.qml | 40 +++-- .../ui/qml/Pages2/PageSettingsServersList.qml | 3 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 29 ++++ .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 40 +++-- client/ui/qml/Pages2/PageStart.qml | 27 +++- 22 files changed, 466 insertions(+), 325 deletions(-) create mode 100644 client/ui/qml/Filters/ContainersModelFilters.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index b9aa0f744..b89e5ba9c 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -1,29 +1,28 @@ #include "amnezia_application.h" #include +#include #include #include #include -#include -#include "logger.h" #include "defines.h" +#include "logger.h" #include "platforms/ios/QRCodeReaderBase.h" #include "ui/pages.h" #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - AmneziaApplication::AmneziaApplication(int &argc, char *argv[]): - AMNEZIA_BASE_CLASS(argc, argv) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) #else - AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, - SingleApplication::Options options, int timeout, const QString &userData): - SingleApplication(argc, argv, allowSecondary, options, timeout, userData) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, + int timeout, const QString &userData) + : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) #endif { setQuitOnLastWindowClosed(false); @@ -51,12 +50,14 @@ AmneziaApplication::~AmneziaApplication() { if (m_engine) { - QObject::disconnect(m_engine, 0,0,0); + QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; } - if (m_protocolProps) delete m_protocolProps; - if (m_containerProps) delete m_containerProps; + if (m_protocolProps) + delete m_protocolProps; + if (m_containerProps) + delete m_containerProps; } void AmneziaApplication::init() @@ -64,11 +65,13 @@ void AmneziaApplication::init() m_engine = new QQmlApplicationEngine; const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); - QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, - this, [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, Qt::QueuedConnection); + QObject::connect( + m_engine, &QQmlApplicationEngine::objectCreated, this, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); @@ -78,6 +81,8 @@ void AmneziaApplication::init() m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); @@ -94,21 +99,19 @@ void AmneziaApplication::init() m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - m_exportController.reset( - new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - m_settingsController.reset( - new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); // m_engine->load(url); -// if (m_engine->rootObjects().size() > 0) { -// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); -// } + // if (m_engine->rootObjects().size() > 0) { + // m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); + // } if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -116,24 +119,23 @@ void AmneziaApplication::init() } } -//#ifdef Q_OS_WIN -// if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); -// else emit m_uiLogic->show(); -//#else -// m_uiLogic->showOnStartup(); -//#endif - -// // TODO - fix -//#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -// if (isPrimary()) { -// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ -// qDebug() << "Secondary instance started, showing this window instead"; -// emit m_uiLogic->show(); -// emit m_uiLogic->raise(); -// }); -// } -//#endif + // #ifdef Q_OS_WIN + // if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); + // else emit m_uiLogic->show(); + // #else + // m_uiLogic->showOnStartup(); + // #endif + // // TODO - fix + // #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + // if (isPrimary()) { + // QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ + // qDebug() << "Secondary instance started, showing this window instead"; + // emit m_uiLogic->show(); + // emit m_uiLogic->raise(); + // }); + // } + // #endif } void AmneziaApplication::registerTypes() @@ -156,6 +158,9 @@ void AmneziaApplication::registerTypes() m_protocolProps = new ProtocolProps; qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, + "ContainersModelFilters"); + // Vpn::declareQmlVpnConnectionStateEnum(); PageLoader::declareQmlPageEnum(); @@ -182,19 +187,17 @@ bool AmneziaApplication::parseCommands() m_parser.addHelpOption(); m_parser.addVersionOption(); - QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"}; + QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; m_parser.addOption(c_autostart); - QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"}; + QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; m_parser.addOption(c_cleanup); m_parser.process(*this); if (m_parser.isSet(c_cleanup)) { Logger::cleanUp(); - QTimer::singleShot(100, this, [this]{ - quit(); - }); + QTimer::singleShot(100, this, [this] { quit(); }); exec(); return false; } @@ -205,4 +208,3 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } - diff --git a/client/resources.qrc b/client/resources.qrc index 5b5fd593a..9aba0a6ba 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -269,5 +269,6 @@ images/controls/mail.svg images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml + ui/qml/Filters/ContainersModelFilters.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index b6cd7abb3..04264624b 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -16,14 +16,14 @@ ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, - const std::shared_ptr &configurator, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) - , m_configurator(configurator) -{} + const std::shared_ptr &configurator, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_settings(settings), + m_configurator(configurator) +{ +} void ExportController::generateFullAccessConfig() { @@ -33,8 +33,8 @@ void ExportController::generateFullAccessConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -43,25 +43,21 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - DockerContainer container = static_cast( - m_containersModel->getCurrentlyProcessedContainerIndex()); + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); QModelIndex containerModelIndex = m_containersModel->index(container); - QJsonObject containerConfig = qvariant_cast( - m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); ErrorCode errorCode = ErrorCode::NoError; for (Proto protocol : ContainerProps::protocolsForContainer(container)) { QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); - QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, - container, - containerConfig, - protocol, - &errorCode); + QString vpnConfig = + m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; @@ -75,7 +71,7 @@ void ExportController::generateConnectionConfig() config.remove(config_key::userName); config.remove(config_key::password); config.remove(config_key::port); - config.insert(config_key::containers, QJsonArray{containerConfig}); + config.insert(config_key::containers, QJsonArray { containerConfig }); config.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); auto dns = m_configurator->getDnsForConfig(serverIndex); @@ -86,8 +82,8 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -108,10 +104,8 @@ void ExportController::saveFile() QString fileExtension = ".vpn"; QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; - fileName = QFileDialog::getSaveFileUrl(nullptr, - tr("Save AmneziaVPN config"), - QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), - "*" + fileExtension); + fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"), + QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension); if (fileName.isEmpty()) return; if (!fileName.toString().endsWith(fileExtension)) { @@ -139,10 +133,9 @@ QList ExportController::generateQrCodeImageSeries(const QByteArray &dat for (int i = 0; i < data.size(); i = i + k) { QByteArray chunk; QDataStream s(&chunk, QIODevice::WriteOnly); - s << amnezia::qrMagicCode << chunksCount << (quint8) std::round(i / k) << data.mid(i, k); + s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); - QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals); + QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); QString svg = QString::fromStdString(toSvgString(qr, 0)); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 99de3131a..5b4b7a835 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -5,40 +5,42 @@ #include "core/errorstrings.h" -namespace { -enum class ConfigTypes { Amnezia, OpenVpn, WireGuard }; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternProto1) - || config.contains(openVpnConfigPatternProto2)) - && (config.contains(openVpnConfigPatternDriver1) - || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) - && config.contains(wireguardConfigPatternSectionPeer)) { - return ConfigTypes::WireGuard; + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } + return ConfigTypes::Amnezia; } - return ConfigTypes::Amnezia; -} } // namespace ImportController::ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - } void ImportController::extractConfigFromFile(const QUrl &fileUrl) @@ -88,8 +90,7 @@ void ImportController::importConfig() m_serversModel->addServer(m_config); if (!m_config.value(config_key::containers).toArray().isEmpty()) { - auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); } emit importFinished(); @@ -116,12 +117,12 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data) return QJsonDocument::fromJson(ba).object(); } -//bool ImportController::importConnectionFromQr(const QByteArray &data) +// bool ImportController::importConnectionFromQr(const QByteArray &data) //{ -// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); -// if (!dataObj.isEmpty()) { -// return importConnection(dataObj); -// } +// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); +// if (!dataObj.isEmpty()) { +// return importConnection(dataObj); +// } // QByteArray ba_uncompressed = qUncompress(data); // if (!ba_uncompressed.isEmpty()) { @@ -159,7 +160,6 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) config[config_key::defaultContainer] = "amnezia-openvpn"; config[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data); if (dnsMatch.hasNext()) { @@ -206,7 +206,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) config[config_key::defaultContainer] = "amnezia-wireguard"; config[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); if (dnsMatch.hasMatch()) { config[config_key::dns1] = dnsMatch.captured(1); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 5d3bfc2f7..1809e082c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -67,8 +67,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); - auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(isInstalledContainerFound); return; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index f095dd02d..7a6946f5b 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -78,7 +78,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } -void ContainersModel::setCurrentlyProcessedServerIndex(int index) +void ContainersModel::setCurrentlyProcessedServerIndex(const int index) { beginResetModel(); m_currentlyProcessedServerIndex = index; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 7bb587550..ebf47497d 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -3,11 +3,11 @@ #include #include -#include #include +#include -#include "settings.h" #include "containers/containers_defs.h" +#include "settings.h" class ContainersModel : public QAbstractListModel { @@ -44,7 +44,7 @@ public slots: DockerContainer getDefaultContainer(); QString getDefaultContainerName(); - void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyProcessedServerIndex(const int index); void setCurrentlyProcessedContainerIndex(int index); int getCurrentlyProcessedContainerIndex(); @@ -57,7 +57,6 @@ protected: private: QMap m_containers; - int m_currentlyProcessedServerIndex; int m_currentlyProcessedContainerIndex; DockerContainer m_defaultContainerIndex; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 94267ed15..9df243fec 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -5,6 +5,7 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currenlyProcessedServerIndex = m_defaultServerIndex; } int ServersModel::rowCount(const QModelIndex &parent) const @@ -28,10 +29,6 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int m_servers.replace(index.row(), server); break; } - case IsDefaultRole: { - setDefaultServerIndex(index.row()); - break; - } default: { return true; } @@ -62,6 +59,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; + case HasWriteAccess: { + auto credentials = m_settings->serverCredentials(index.row()); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); + } } return QVariant(); @@ -73,6 +74,13 @@ QVariant ServersModel::data(const int index, int role) const return data(modelIndex, role); } +void ServersModel::setDefaultServerIndex(const int index) +{ + m_settings->setDefaultServer(index); + m_defaultServerIndex = m_settings->defaultServerIndex(); + emit defaultServerIndexChanged(); +} + const int ServersModel::getDefaultServerIndex() { return m_defaultServerIndex; @@ -83,10 +91,10 @@ const int ServersModel::getServersCount() return m_servers.count(); } -void ServersModel::setCurrentlyProcessedServerIndex(int index) +void ServersModel::setCurrentlyProcessedServerIndex(const int index) { m_currenlyProcessedServerIndex = index; - emit currentlyProcessedServerIndexChanged(); + emit currentlyProcessedServerIndexChanged(m_currenlyProcessedServerIndex); } int ServersModel::getCurrentlyProcessedServerIndex() @@ -101,8 +109,7 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - auto credentials = m_settings->serverCredentials(m_currenlyProcessedServerIndex); - return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccess)); } void ServersModel::addServer(const QJsonObject &server) @@ -140,11 +147,6 @@ QHash ServersModel::roleNames() const roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; + roles[HasWriteAccess] = "hasWriteAccess"; return roles; } - -void ServersModel::setDefaultServerIndex(const int index) -{ - m_settings->setDefaultServer(index); - m_defaultServerIndex = m_settings->defaultServerIndex(); -} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 891d6ad16..6cb9859e3 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -5,13 +5,6 @@ #include "settings.h" -struct ServerModelContent -{ - QString desc; - QString address; - bool isDefault; -}; - class ServersModel : public QAbstractListModel { Q_OBJECT @@ -22,7 +15,8 @@ public: CredentialsRole, CredentialsLoginRole, IsDefaultRole, - IsCurrentlyProcessedRole + IsCurrentlyProcessedRole, + HasWriteAccess }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -33,14 +27,20 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const int index, int role = Qt::DisplayRole) const; + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex + NOTIFY currentlyProcessedServerIndexChanged) + public slots: + void setDefaultServerIndex(const int index); const int getDefaultServerIndex(); bool isDefaultServerCurrentlyProcessed(); + bool isCurrentlyProcessedServerHasWriteAccess(); const int getServersCount(); - void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyProcessedServerIndex(const int index); int getCurrentlyProcessedServerIndex(); void addServer(const QJsonObject &server); @@ -50,11 +50,10 @@ protected: QHash roleNames() const override; signals: - void currentlyProcessedServerIndexChanged(); + void currentlyProcessedServerIndexChanged(const int index); + void defaultServerIndexChanged(); private: - void setDefaultServerIndex(const int index); - QJsonArray m_servers; std::shared_ptr m_settings; diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 56a682b85..16775bc0d 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -1,14 +1,12 @@ #include "ServerListLogic.h" -#include "vpnconnection.h" #include "../models/servers_model.h" #include "../uilogic.h" +#include "vpnconnection.h" -ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_serverListModel{new ServersModel(m_settings, this)} +ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), m_serverListModel { new ServersModel(m_settings, this) } { - } void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) @@ -31,19 +29,19 @@ int ServerListLogic::currServerIdx() const void ServerListLogic::onUpdatePage() { - const QJsonArray &servers = m_settings->serversArray(); - int defaultServer = m_settings->defaultServerIndex(); - QVector serverListContent; - for(int i = 0; i < servers.size(); i++) { - ServerModelContent c; - auto server = servers.at(i).toObject(); - c.desc = server.value(config_key::description).toString(); - c.address = server.value(config_key::hostName).toString(); - if (c.desc.isEmpty()) { - c.desc = c.address; - } - c.isDefault = (i == defaultServer); - serverListContent.push_back(c); - } -// qobject_cast(m_serverListModel)->setContent(serverListContent); + // const QJsonArray &servers = m_settings->serversArray(); + // int defaultServer = m_settings->defaultServerIndex(); + // QVector serverListContent; + // for(int i = 0; i < servers.size(); i++) { + // ServerModelContent c; + // auto server = servers.at(i).toObject(); + // c.desc = server.value(config_key::description).toString(); + // c.address = server.value(config_key::hostName).toString(); + // if (c.desc.isEmpty()) { + // c.desc = c.address; + // } + // c.isDefault = (i == defaultServer); + // serverListContent.push_back(c); + // } + // qobject_cast(m_serverListModel)->setContent(serverListContent); } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 8ad92d542..85d651ef7 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -8,96 +8,115 @@ Item { id: root property string headerText - property string textFieldPlaceholderText - property bool textFieldEditable: true + property alias errorText: errorField.text property string buttonText property var clickedFunc property alias textField: textField property alias textFieldText: textField.text + property string textFieldPlaceholderText + property bool textFieldEditable: true - implicitHeight: 74 + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - Rectangle { - id: backgroud + ColumnLayout { + id: content anchors.fill: parent - color: "#1c1d21" - radius: 16 - border.color: textField.focus ? "#d7d8db" : "#2C2D30" - border.width: 1 - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - } + Rectangle { + id: backgroud + Layout.fillWidth: true + Layout.preferredHeight: 74 + color: "#1c1d21" + radius: 16 + border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.width: 1 - RowLayout { - anchors.fill: backgroud - ColumnLayout { - - LabelTextType { - text: root.headerText - color: "#878b91" - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 + Behavior on border.color { + PropertyAnimation { duration: 200 } } - TextField { - id: textField + RowLayout { + anchors.fill: backgroud + ColumnLayout { + LabelTextType { + text: root.headerText + color: "#878b91" - enabled: root.textFieldEditable - color: "#d7d8db" + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } - placeholderText: textFieldPlaceholderText - placeholderTextColor: "#494B50" + TextField { + id: textField - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" + enabled: root.textFieldEditable + color: "#d7d8db" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + placeholderText: textFieldPlaceholderText + placeholderTextColor: "#494B50" - height: 24 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - topPadding: 0 - rightPadding: 0 - leftPadding: 0 - bottomPadding: 0 + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" - background: Rectangle { - anchors.fill: parent - color: "#1c1d21" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + + onTextChanged: { + root.errorText = "" + } + } + } + + BasicButtonType { + visible: root.buttonText !== "" + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 0 + + text: buttonText + + Layout.rightMargin: 24 + + onClicked: { + if (clickedFunc && typeof clickedFunc === "function") { + clickedFunc() + } + } } } } - BasicButtonType { - visible: root.buttonText !== "" + SmallTextType { + id: errorField - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 0 - - text: buttonText - - Layout.rightMargin: 24 - - onClicked: { - if (clickedFunc && typeof clickedFunc === "function") { - clickedFunc() - } - } + text: root.errorText + visible: root.errorText !== "" + color: "#EB5757" } } } diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml new file mode 100644 index 000000000..fa8bd83b9 --- /dev/null +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -0,0 +1,47 @@ +pragma Singleton + +import QtQuick 2.15 + +import SortFilterProxyModel 0.2 + +import ProtocolEnum 1.0 + +Item { + ValueFilter { + id: vpnTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Vpn + } + + ValueFilter { + id: serviceTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Other + } + + ValueFilter { + id: supportedFilter + roleName: "isSupported" + value: true + } + + ValueFilter { + id: installedFilter + roleName: "isInstalled" + value: true + } + + function getWriteAccessProtocolsListFilters() { + return [vpnTypeFilter, supportedFilter] + } + function getReadAccessProtocolsListFilters() { + return [vpnTypeFilter, supportedFilter, installedFilter] + } + + function getWriteAccessServicesListFilters() { + return [serviceTypeFilter, supportedFilter] + } + function getReadAccessServicesListFilters() { + return [serviceTypeFilter, supportedFilter, installedFilter] + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a4e901174..9f1212735 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -161,10 +162,7 @@ PageType { headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { - // todo check if server index changed before set Currently processed - // todo make signal slot for change server index in containersModel - ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) - ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + ServersModel.currentlyProcessedIndex = serversMenuContent.currentIndex containersDropDown.menuVisible = true } @@ -177,39 +175,22 @@ PageType { function onCurrentlyProcessedServerIndexChanged() { updateContainersModelFilters() } + } - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = [serviceTypeFilter, supportedFilter] - } else { - proxyContainersModel.filters = installedFilter - } + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() } } - ValueFilter { - id: serviceTypeFilter - roleName: "serviceType" - value: ProtocolEnum.Vpn - } - ValueFilter { - id: supportedFilter - roleName: "isSupported" - value: true - } - ValueFilter { - id: installedFilter - roleName: "isInstalled" - value: true - } - model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel - - Component.onCompleted: updateContainersModelFilters() } + Component.onCompleted: updateContainersModelFilters() currentIndex: ContainersModel.getDefaultContainer() } } @@ -267,7 +248,7 @@ PageType { height: serversMenuContent.contentItem.height model: ServersModel - currentIndex: ServersModel.getDefaultServerIndex() + currentIndex: ServersModel.defaultIndex clip: true interactive: false @@ -305,8 +286,8 @@ PageType { onClicked: { serversMenuContent.currentIndex = index - isDefault = true - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index + ServersModel.defaultIndex = index root.currentServerName = name root.currentServerHostName = hostName @@ -328,8 +309,7 @@ PageType { z: 1 onClicked: function() { - ServersModel.setCurrentlyProcessedServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index goToPage(PageEnum.PageSettingsServerInfo) menu.visible = false } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b98d2b8c7..319399dc4 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -29,6 +29,14 @@ PageType { } } + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + content.isServerWithWriteAccess = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + } + } + FlickableType { id: fl anchors.top: parent.top @@ -42,7 +50,10 @@ PageType { anchors.left: parent.left anchors.right: parent.right + property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() //todo make it property? + LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true text: qsTr("Clear Amnezia cache") @@ -65,9 +76,12 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") @@ -78,12 +92,14 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } LabelWithButtonType { Layout.fillWidth: true - text: "Remove server from application" + text: qsTr("Remove server from application") textColor: "#EB5757" clickedFunction: function() { @@ -115,9 +131,10 @@ PageType { DividerType {} LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: "Clear server from Amnezia software" + text: qsTr("Clear server from Amnezia software") textColor: "#EB5757" clickedFunction: function() { @@ -142,7 +159,9 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } QuestionDrawer { id: questionDrawer diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 3f2562da6..aa882aa5a 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -54,7 +54,13 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: name - descriptionText: credentialsLogin + " · " + hostName + descriptionText: { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + return credentialsLogin + " · " + hostName + } else { + return hostName + } + } actionButtonFunction: function() { serverNameEditDrawer.visible = true @@ -123,10 +129,14 @@ PageType { } TabButtonType { + visible: protocolsPage.installedProtocolsCount + width: protocolsPage.installedProtocolsCount ? undefined : 0 isSelected: tabBar.currentIndex === 0 text: qsTr("Protocols") } TabButtonType { + visible: servicesPage.installedServicesCount + width: servicesPage.installedServicesCount ? undefined : 0 isSelected: tabBar.currentIndex === 1 text: qsTr("Services") } @@ -143,9 +153,11 @@ PageType { currentIndex: tabBar.currentIndex PageSettingsServerProtocols { + id: protocolsPage stackView: root.stackView } PageSettingsServerServices { + id: servicesPage stackView: root.stackView } PageSettingsServerData { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index e93aa5280..1d806d07e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -17,22 +18,31 @@ import "../Components" PageType { id: root - SortFilterProxyModel { - id: containersProxyModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } + property var installedProtocolsCount SettingsContainersListView { - model: containersProxyModel + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 7351f5857..1e3d87220 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -17,22 +18,31 @@ import "../Components" PageType { id: root - SortFilterProxyModel { - id: containersProxyModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Other - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } + property var installedServicesCount SettingsContainersListView { - model: containersProxyModel + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index a601199a2..7f2b1688e 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -89,8 +89,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ServersModel.setCurrentlyProcessedServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index fa6b1f928..fc8bf9ae9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -79,6 +79,10 @@ PageType { text: qsTr("Set up a server the easy way") onClicked: function() { + if (!isCredentialsFilled()) { + return + } + InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) @@ -100,6 +104,10 @@ PageType { text: qsTr("Select protocol to install") onClicked: function() { + if (!isCredentialsFilled()) { + return + } + InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) @@ -108,4 +116,25 @@ PageType { } } } + + function isCredentialsFilled() { + var hasEmptyField = false + + if (hostname.textFieldText === "") { + hostname.errorText = qsTr("ip address cannot be empty") + hasEmptyField = true + } else if (!hostname.textField.acceptableInput) { + hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") + } + + if (username.textFieldText === "") { + username.errorText = qsTr("login cannot be empty") + hasEmptyField = true + } + if (secretData.textFieldText === "") { + secretData.errorText = qsTr("password/private key cannot be empty") + hasEmptyField = true + } + return !hasEmptyField + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index d0fdeabd0..f1f5324e9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -61,7 +61,7 @@ PageType { function onServerAlreadyExists(serverIndex) { goToStartPage() - ServersModel.setCurrentlyProcessedServerIndex(serverIndex) + ServersModel.currentlyProcessedIndex = serverIndex goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 504b0cb18..30430073c 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -147,18 +147,28 @@ PageType { imageSource: "qrc:/images/controls/chevron-right.svg" - model: ServersModel - currentIndex: ServersModel.getDefaultServerIndex() + model: SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "hasWriteAccess" + value: true + } + ] + } + + currentIndex: 0 clickedFunction: function() { serverSelector.text = selectedText - ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + ServersModel.currentlyProcessedIndex = currentIndex protocolSelector.visible = true } Component.onCompleted: { serverSelector.text = selectedText - ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + ServersModel.currentlyProcessedIndex = currentIndex } } @@ -169,7 +179,7 @@ PageType { height: parent.height * 0.5 ColumnLayout { - id: header + id: protocolSelectorHeader anchors.top: parent.top anchors.left: parent.left @@ -187,12 +197,12 @@ PageType { } FlickableType { - anchors.top: header.bottom + anchors.top: protocolSelectorHeader.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + contentHeight: protocolSelectorContent.implicitHeight Column { - id: col + id: protocolSelectorContent anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -265,7 +275,7 @@ PageType { } DropDownType { - id: connectionTypeSelector + id: exportTypeSelector property int currentIndex @@ -283,8 +293,6 @@ PageType { headerText: qsTr("Connection format") listView: ListViewType { - id: connectionTypeSelectorListView - rootWidth: root.width dividerVisible: true @@ -294,14 +302,14 @@ PageType { currentIndex: 0 clickedFunction: function() { - connectionTypeSelector.text = selectedText - connectionTypeSelector.currentIndex = currentIndex - connectionTypeSelector.menuVisible = false + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.menuVisible = false } Component.onCompleted: { - connectionTypeSelector.text = selectedText - connectionTypeSelector.currentIndex = currentIndex + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 1307ee050..e2b83056e 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -50,8 +50,7 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) - ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) - ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -65,8 +64,8 @@ PageType { topPadding: 8 bottomPadding: 8//34 - leftPadding: 96 - rightPadding: 96 + leftPadding: shareTabButton.visible ? 96 : 128 + rightPadding: shareTabButton.visible ? 96 : 128 background: Rectangle { border.width: 1 @@ -78,11 +77,25 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.goToTabBarPage(PageEnum.PageHome) } } TabImageButtonType { + id: shareTabButton + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + shareTabButton.visible = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + shareTabButton.width = ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + } + } + + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + width: visible ? undefined : 0 + isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" onClicked: { @@ -100,8 +113,8 @@ PageType { MouseArea { anchors.fill: tabBar - anchors.leftMargin: 96 - anchors.rightMargin: 96 + anchors.leftMargin: shareTabButton.visible ? 96 : 128 + anchors.rightMargin: shareTabButton.visible ? 96 : 128 cursorShape: Qt.PointingHandCursor enabled: false } From 795405c47d3c4fceffbe82aa6ce0be1e8970d12e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 27 Jun 2023 19:07:42 +0900 Subject: [PATCH 036/131] added display of amnesia dns container activity on the main page --- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 2 + client/ui/controllers/settingsController.cpp | 26 +--- client/ui/controllers/settingsController.h | 6 +- client/ui/models/containers_model.cpp | 11 ++ client/ui/models/containers_model.h | 3 + client/ui/models/servers_model.cpp | 23 ++- client/ui/models/servers_model.h | 6 +- client/ui/qml/Components/ConnectButton.qml | 2 + .../qml/Components/ShareConnectionDrawer.qml | 1 - .../ui/qml/Components/ShowDetailsDrawer.qml | 10 ++ .../qml/Components/TransportProtoSelector.qml | 14 +- client/ui/qml/Controls2/DropDownType.qml | 3 - .../qml/Controls2/HorizontalRadioButton.qml | 6 + client/ui/qml/Pages2/PageHome.qml | 42 ++++-- .../Pages2/PageSettingsServerProtocols.qml | 3 +- .../qml/Pages2/PageSettingsServerServices.qml | 3 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- .../PageSetupWizardProtocolSettings.qml | 139 ++++++++++++++++-- client/ui/qml/Pages2/PageShare.qml | 12 +- client/ui/qml/Pages2/PageStart.qml | 4 +- 21 files changed, 238 insertions(+), 85 deletions(-) create mode 100644 client/ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/resources.qrc b/client/resources.qrc index 9aba0a6ba..b306c1541 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -270,5 +270,6 @@ images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml + ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index aace28577..7d1b6bed1 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -83,11 +83,13 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) } case Vpn::ConnectionState::Error: { m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); emit connectionErrorOccurred(getLastConnectionError()); break; } case Vpn::ConnectionState::Unknown: { m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); emit connectionErrorOccurred(getLastConnectionError()); break; } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b86b1cffa..fcfcc7afe 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,15 +8,10 @@ SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - m_appVersion = QString("%1: %2 (%3)") - .arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } void SettingsController::setAmneziaDns(bool enable) @@ -79,21 +74,16 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig() { - Utils::saveFile(".backup", - tr("Backup application config"), - "AmneziaVPN", - m_settings->backupAppConfig()); + Utils::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig() { - QString fileName = Utils::getFileName(Q_NULLPTR, - tr("Open backup"), - QStandardPaths::writableLocation( - QStandardPaths::DocumentsLocation), - "*.backup"); + QString fileName = + Utils::getFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - //todo error processing + // todo error processing if (fileName.isEmpty()) return; diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 77de424c7..f961a37d0 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -12,12 +12,10 @@ class SettingsController : public QObject public: explicit SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) - Q_PROPERTY( - QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) public slots: void setAmneziaDns(bool enable); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 7a6946f5b..ecacc184a 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -132,6 +132,17 @@ void ContainersModel::clearCachedProfiles() } } +bool ContainersModel::isAmneziaDnsContainerInstalled() +{ + return m_containers.contains(DockerContainer::Dns); +} + +bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) +{ + QMap containers = m_settings->containers(serverIndex); + return containers.contains(DockerContainer::Dns); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index ebf47497d..7331ef22a 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -51,6 +51,9 @@ public slots: void removeAllContainers(); void clearCachedProfiles(); + bool isAmneziaDnsContainerInstalled(); + bool isAmneziaDnsContainerInstalled(const int serverIndex); + protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 9df243fec..b427f15bd 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -59,10 +59,14 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; - case HasWriteAccess: { + case HasWriteAccessRole: { auto credentials = m_settings->serverCredentials(index.row()); return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); } + case ContainsAmneziaDnsRole: { + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; + } } return QVariant(); @@ -109,7 +113,12 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccess)); + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); +} + +bool ServersModel::isDefaultServerHasWriteAccess() +{ + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) @@ -138,6 +147,13 @@ void ServersModel::removeServer() endResetModel(); } +bool ServersModel::isDefaultServerConfigContainsAmneziaDns() +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; +} + QHash ServersModel::roleNames() const { QHash roles; @@ -147,6 +163,7 @@ QHash ServersModel::roleNames() const roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; - roles[HasWriteAccess] = "hasWriteAccess"; + roles[HasWriteAccessRole] = "hasWriteAccess"; + roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 6cb9859e3..0ec78e7eb 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -16,7 +16,8 @@ public: CredentialsLoginRole, IsDefaultRole, IsCurrentlyProcessedRole, - HasWriteAccess + HasWriteAccessRole, + ContainsAmneziaDnsRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -37,6 +38,7 @@ public slots: bool isDefaultServerCurrentlyProcessed(); bool isCurrentlyProcessedServerHasWriteAccess(); + bool isDefaultServerHasWriteAccess(); const int getServersCount(); @@ -46,6 +48,8 @@ public slots: void addServer(const QJsonObject &server); void removeServer(); + bool isDefaultServerConfigContainsAmneziaDns(); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 626c4ffb1..1f27973ad 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -17,6 +17,8 @@ Button { text: ConnectionController.connectionStateText + enabled: !ConnectionController.isConnectionInProgress + background: Item { clip: true diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index e03738c84..720e3206f 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -76,7 +76,6 @@ DrawerType { } } - BasicButtonType { Layout.fillWidth: true Layout.topMargin: 8 diff --git a/client/ui/qml/Components/ShowDetailsDrawer.qml b/client/ui/qml/Components/ShowDetailsDrawer.qml new file mode 100644 index 000000000..2f6d26560 --- /dev/null +++ b/client/ui/qml/Components/ShowDetailsDrawer.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +Item { + +} diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml index 6f5aa44b2..dd315deb2 100644 --- a/client/ui/qml/Components/TransportProtoSelector.qml +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -8,11 +8,9 @@ import "../Controls2/TextTypes" Rectangle { id: root - property var rootWidth: root.width + property real rootWidth: root.width property int currentIndex - property alias mouseArea: transportProtoButtonMouseArea - implicitWidth: transportProtoButtonGroup.implicitWidth implicitHeight: transportProtoButtonGroup.implicitHeight @@ -30,8 +28,6 @@ Rectangle { implicitWidth: (rootWidth - 32) / 2 text: "UDP" - hoverEnabled: !transportProtoButtonMouseArea.enabled - onClicked: { root.currentIndex = 0 } @@ -43,17 +39,9 @@ Rectangle { implicitWidth: (rootWidth - 32) / 2 text: "TCP" - hoverEnabled: !transportProtoButtonMouseArea.enabled - onClicked: { root.currentIndex = 1 } } } - - MouseArea { - id: transportProtoButtonMouseArea - - anchors.fill: parent - } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 38cd0afa0..9b9c718a5 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,7 +19,6 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#D7D8DB" property string rootButtonBackgroundColor: "#1C1D21" - property int rootButtonMaximumWidth: 0 property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "transparent" @@ -88,8 +87,6 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth - color: root.textColor text: root.text diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 86218b3f3..4a2a13ddf 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -88,4 +88,10 @@ RadioButton { horizontalAlignment: Qt.AlignHCenter } } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 9f1212735..b97acad1b 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -95,13 +95,20 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter text: { - var string = "" - if (SettingsController.isAmneziaDnsEnabled()) { - string += "Amnezia DNS | " + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns) { + description += "Amnezia DNS | " + } } - string += root.currentContainerName + " | " + root.currentServerHostName - return string + description += root.currentContainerName + " | " + root.currentServerHostName + return description } } } @@ -153,7 +160,6 @@ PageType { rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" - rootButtonMaximumWidth: 150 //todo make it dynamic rootButtonBackgroundColor: "#D7D8DB" text: root.currentContainerName @@ -194,14 +200,6 @@ PageType { currentIndex: ContainersModel.getDefaultContainer() } } - - BasicButtonType { - id: dnsButton - - implicitHeight: 40 - - text: "Amnezia DNS" - } } Header2Type { @@ -277,7 +275,21 @@ PageType { Layout.fillWidth: true text: name - descriptionText: hostName + descriptionText: { + var description = "" + if (hasWriteAccess) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(index)) { + description += "AmneziaDNS | " + } + } else { + if (containsAmneziaDns) { + description += "AmneziaDNS | " + } + } + + return description += hostName + } checked: index === serversMenuContent.currentIndex diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 1d806d07e..2b67afca3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -21,11 +21,12 @@ PageType { property var installedProtocolsCount SettingsContainersListView { + id: settingsContainersListView Connections { target: ServersModel function onCurrentlyProcessedServerIndexChanged() { - updateContainersModelFilters() + settingsContainersListView.updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 1e3d87220..282e7e9ec 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -21,11 +21,12 @@ PageType { property var installedServicesCount SettingsContainersListView { + id: settingsContainersListView Connections { target: ServersModel function onCurrentlyProcessedServerIndexChanged() { - updateContainersModelFilters() + settingsContainersListView.updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index fc8bf9ae9..5a2f7d882 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -121,18 +121,18 @@ PageType { var hasEmptyField = false if (hostname.textFieldText === "") { - hostname.errorText = qsTr("ip address cannot be empty") + hostname.errorText = qsTr("Ip address cannot be empty") hasEmptyField = true } else if (!hostname.textField.acceptableInput) { hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") } if (username.textFieldText === "") { - username.errorText = qsTr("login cannot be empty") + username.errorText = qsTr("Login cannot be empty") hasEmptyField = true } if (secretData.textFieldText === "") { - secretData.errorText = qsTr("password/private key cannot be empty") + secretData.errorText = qsTr("Password/private key cannot be empty") hasEmptyField = true } return !hasEmptyField diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index c0483df4b..5ca75f6f5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -29,7 +29,6 @@ PageType { } FlickableType { - id: fl anchors.fill: parent contentHeight: content.height @@ -41,18 +40,17 @@ PageType { anchors.right: parent.right ListView { - // todo change id naming - id: containers + id: processedContainerListView width: parent.width - height: containers.contentItem.height + height: contentItem.height currentIndex: -1 clip: true interactive: false model: proxyContainersModel delegate: Item { - implicitWidth: containers.width - implicitHeight: delegateContent.implicitHeight + implicitWidth: processedContainerListView.width + implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height ColumnLayout { id: delegateContent @@ -72,8 +70,122 @@ PageType { Layout.fillWidth: true - headerText: "Установка " + name - descriptionText: "Эти настройки можно будет изменить позже" + headerText: qsTr("Installing ") + name + descriptionText: qsTr("protocol description") + } + + BasicButtonType { + id: showDetailsButton + + Layout.topMargin: 16 + Layout.leftMargin: -8 + + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("More detailed") + + onClicked: { + showDetailsDrawer.open() + } + } + + DrawerType { + id: showDetailsDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: showDetailsBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 16 + + backButtonFunction: function() { + showDetailsDrawer.close() + } + } + + FlickableType { + anchors.top: showDetailsBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: { + var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin + + return (showDetailsDrawerContent.implicitHeight > emptySpaceHeight) ? + showDetailsDrawerContent.implicitHeight : emptySpaceHeight + } + + ColumnLayout { + id: showDetailsDrawerContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: showDetailsDrawerHeader + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: name + } + + TextField { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: qsTr("detailed protocol description") + + wrapMode: Text.WordWrap + + readOnly: true + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + } + + Rectangle { + Layout.fillHeight: true + color: "transparent" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Close") + + onClicked: function() { + showDetailsDrawer.close() + } + } + } + } } ParagraphTextType { @@ -96,15 +208,12 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + headerText: "Port" } Rectangle { - // todo make it dynamic - implicitHeight: root.height - port.implicitHeight - - transportProtoSelector.implicitHeight - transportProtoHeader.implicitHeight - - header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 - + Layout.fillHeight: true color: "transparent" } @@ -134,7 +243,9 @@ PageType { transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - transportProtoSelector.mouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.visible = protocolSelectorVisible + transportProtoHeader.visible = protocolSelectorVisible } } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 30430073c..1e3d02e8c 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -260,12 +260,12 @@ PageType { } function fillConnectionTypeModel() { - connectionTypesModel = [amneziaConnectionFormat] + root.connectionTypesModel = [amneziaConnectionFormat] if (currentIndex === ContainerProps.containerFromString("OpenVpn")) { - connectionTypesModel.push(openVpnConnectionFormat) + root.connectionTypesModel.push(openVpnConnectionFormat) } else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) { - connectionTypesModel.push(amneziaConnectionFormat) + root.connectionTypesModel.push(amneziaConnectionFormat) } } } @@ -287,7 +287,7 @@ PageType { drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 - enabled: connectionTypesModel.length > 1 + enabled: root.connectionTypesModel.length > 1 descriptionText: qsTr("Connection format") headerText: qsTr("Connection format") @@ -298,7 +298,7 @@ PageType { imageSource: "qrc:/images/controls/chevron-right.svg" - model: connectionTypesModel + model: root.connectionTypesModel currentIndex: 0 clickedFunction: function() { @@ -326,7 +326,7 @@ PageType { onClicked: { if (accessTypeSelector.currentIndex === 0) { - connectionTypesModel[connectionTypeSelector.currentIndex].func() + root.connectionTypesModel[accessTypeSelector.currentIndex].func() } else { ExportController.generateConfig(true) } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e2b83056e..76450b165 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -77,8 +77,8 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.goToTabBarPage(PageEnum.PageHome) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex } } TabImageButtonType { @@ -94,7 +94,7 @@ PageType { } visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() - width: visible ? undefined : 0 + width: ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" From 464d77dfb58ae284cb2ae248b8a3541d2c8cc4a3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 10:40:43 +0900 Subject: [PATCH 037/131] added functionality to change app language via settings --- client/CMakeLists.txt | 18 +- client/amnezia_application.cpp | 46 +- client/amnezia_application.h | 21 +- client/resources.qrc | 3 +- client/settings.h | 131 +- client/translations/amneziavpn_ru.qm | Bin 25539 -> 0 bytes client/translations/amneziavpn_ru.ts | 4382 +++++++++++------ client/ui/controllers/settingsController.cpp | 2 +- client/ui/models/languageModel.cpp | 62 + client/ui/models/languageModel.h | 62 + .../ConnectionTypeSelectionDrawer.qml | 2 + .../qml/Components/SelectLanguageDrawer.qml | 137 + .../qml/Components/ShareConnectionDrawer.qml | 2 - .../ui/qml/Components/ShowDetailsDrawer.qml | 10 - client/ui/qml/Controls2/BackButtonType.qml | 1 + client/ui/qml/Controls2/BasicButtonType.qml | 8 +- client/ui/qml/Controls2/DividerType.qml | 4 + client/ui/qml/Controls2/DropDownType.qml | 2 - .../Controls2/TextTypes/ButtonTextType.qml | 5 +- .../Controls2/TextTypes/CaptionTextType.qml | 5 +- .../Controls2/TextTypes/Header1TextType.qml | 7 +- .../Controls2/TextTypes/Header2TextType.qml | 5 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 5 +- .../Controls2/TextTypes/ListItemTitleType.qml | 5 +- .../Controls2/TextTypes/ParagraphTextType.qml | 5 +- .../qml/Controls2/TextTypes/SmallTextType.qml | 5 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 - .../ui/qml/Pages2/PageSettingsApplication.qml | 9 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 2 - .../ui/qml/Pages2/PageSettingsConnection.qml | 2 - client/ui/qml/Pages2/PageSettingsDns.qml | 2 - .../ui/qml/Pages2/PageSettingsServerInfo.qml | 4 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 4 +- .../Pages2/PageSetupWizardConfigSource.qml | 9 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 - client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 - .../PageSetupWizardProtocolSettings.qml | 4 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 22 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 1 + .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 6 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 2 - client/ui/qml/Pages2/PageShare.qml | 2 - 42 files changed, 3333 insertions(+), 1677 deletions(-) delete mode 100644 client/translations/amneziavpn_ru.qm create mode 100644 client/ui/models/languageModel.cpp create mode 100644 client/ui/models/languageModel.h create mode 100644 client/ui/qml/Components/SelectLanguageDrawer.qml delete mode 100644 client/ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index f2346ec53..ad9a4cf43 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -19,15 +19,12 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml - RemoteObjects Quick Svg QuickControls2 - Core5Compat Concurrent + Widgets Core Gui Network Xml + RemoteObjects Quick Svg QuickControls2 + Core5Compat Concurrent LinguistTools ) if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia - ) + set(PACKAGES ${PACKAGES} Multimedia) endif() find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES}) @@ -38,11 +35,9 @@ set(LIBS ${LIBS} Qt6::Quick Qt6::Svg Qt6::QuickControls2 Qt6::Core5Compat Qt6::Concurrent ) + if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS ${LIBS} Qt6::Multimedia) endif() qt_standard_project_setup() @@ -349,6 +344,7 @@ if(CMAKE_OSX_SYSROOT STREQUAL "iphoneos") endif() qt_add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) + qt_add_translations(${PROJECT} TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7159aa4f2..7c3b466b1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -3,12 +3,11 @@ #include #include #include +#include #include #include #include #include -#include - #include "core/servercontroller.h" #include "logger.h" @@ -88,6 +87,10 @@ void AmneziaApplication::init() connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), &ContainersModel::setCurrentlyProcessedServerIndex); + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); @@ -130,18 +133,16 @@ void AmneziaApplication::init() // m_uiLogic->showOnStartup(); // #endif -#endif - - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ - qDebug() << "Secondary instance started, showing this window instead"; - emit m_uiLogic->show(); - emit m_uiLogic->raise(); - }); - } -#endif +// // TODO - fix +// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +// if (isPrimary()) { +// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ +// qDebug() << "Secondary instance started, showing this window instead"; +// emit m_uiLogic->show(); +// emit m_uiLogic->raise(); +// }); +// } +// #endif // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 @@ -195,12 +196,27 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { + auto locale = m_settings->getAppLanguage(); m_translator = new QTranslator; - if (m_translator->load(QLocale(), QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/translations"))) { + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { installTranslator(m_translator); } } +void AmneziaApplication::updateTranslator(const QLocale &locale) +{ + QResource::registerResource(":/translations.qrc"); + if (!m_translator->isEmpty()) + QCoreApplication::removeTranslator(m_translator); + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator)) { + m_settings->setAppLanguage(locale); + } + + m_engine->retranslate(); + } +} + bool AmneziaApplication::parseCommands() { m_parser.setApplicationDescription(APPLICATION_NAME); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 893511b69..4e9cc3489 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -20,17 +20,17 @@ #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" #include "ui/models/servers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) - #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QApplication + #define AMNEZIA_BASE_CLASS QApplication #else - #define AMNEZIA_BASE_CLASS SingleApplication - #define QAPPLICATION_CLASS QApplication - #include "singleapplication.h" + #define AMNEZIA_BASE_CLASS SingleApplication + #define QAPPLICATION_CLASS QApplication + #include "singleapplication.h" #endif class AmneziaApplication : public AMNEZIA_BASE_CLASS @@ -41,7 +41,8 @@ public: AmneziaApplication(int &argc, char *argv[]); #else AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, - SingleApplication::Options options = SingleApplication::User, int timeout = 1000, const QString &userData = {} ); + SingleApplication::Options options = SingleApplication::User, int timeout = 1000, + const QString &userData = {}); #endif virtual ~AmneziaApplication(); @@ -49,6 +50,7 @@ public: void registerTypes(); void loadFonts(); void loadTranslator(); + void updateTranslator(const QLocale &locale); bool parseCommands(); QQmlApplicationEngine *qmlEngine() const; @@ -58,14 +60,15 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - ContainerProps* m_containerProps {}; - ProtocolProps* m_protocolProps {}; + ContainerProps *m_containerProps {}; + ProtocolProps *m_protocolProps {}; - QTranslator* m_translator; + QTranslator *m_translator; QCommandLineParser m_parser; QSharedPointer m_containersModel; QSharedPointer m_serversModel; + QScopedPointer m_languageModel; QSharedPointer m_vpnConnection; diff --git a/client/resources.qrc b/client/resources.qrc index b306c1541..357cb40c8 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -1,6 +1,5 @@ - translations/amneziavpn_ru.qm images/close.png images/settings.png images/favorites_disabled.png @@ -270,6 +269,6 @@ images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml - ui/qml/Components/ShowDetailsDrawer.qml + ui/qml/Components/SelectLanguageDrawer.qml diff --git a/client/settings.h b/client/settings.h index 28a49e183..9bf40ac7f 100644 --- a/client/settings.h +++ b/client/settings.h @@ -2,15 +2,15 @@ #define SETTINGS_H #include -#include #include +#include -#include #include +#include #include -#include "core/defs.h" #include "containers/containers_defs.h" +#include "core/defs.h" #include "secure_qsettings.h" using namespace amnezia; @@ -22,13 +22,19 @@ class Settings : public QObject Q_OBJECT public: - explicit Settings(QObject* parent = nullptr); + explicit Settings(QObject *parent = nullptr); ServerCredentials defaultServerCredentials() const; ServerCredentials serverCredentials(int index) const; - QJsonArray serversArray() const { return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); } - void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); } + QJsonArray serversArray() const + { + return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); + } + void setServersArray(const QJsonArray &servers) + { + m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); + } // Servers section int serversCount() const; @@ -37,9 +43,18 @@ public: void removeServer(int index); bool editServer(int index, const QJsonObject &server); - int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); } - void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); } - QJsonObject defaultServer() const { return server(defaultServerIndex()); } + int defaultServerIndex() const + { + return m_settings.value("Servers/defaultServerIndex", 0).toInt(); + } + void setDefaultServer(int index) + { + m_settings.setValue("Servers/defaultServerIndex", index); + } + QJsonObject defaultServer() const + { + return server(defaultServerIndex()); + } void setDefaultContainer(int serverIndex, DockerContainer container); DockerContainer defaultContainer(int serverIndex) const; @@ -61,13 +76,28 @@ public: QString nextAvailableServerName() const; // App settings section - bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", false).toBool(); } - void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); } + bool isAutoConnect() const + { + return m_settings.value("Conf/autoConnect", false).toBool(); + } + void setAutoConnect(bool enabled) + { + m_settings.setValue("Conf/autoConnect", enabled); + } - bool isStartMinimized() const { return m_settings.value("Conf/startMinimized", false).toBool(); } - void setStartMinimized(bool enabled) { m_settings.setValue("Conf/startMinimized", enabled); } + bool isStartMinimized() const + { + return m_settings.value("Conf/startMinimized", false).toBool(); + } + void setStartMinimized(bool enabled) + { + m_settings.setValue("Conf/startMinimized", enabled); + } - bool isSaveLogs() const { return m_settings.value("Conf/saveLogs", false).toBool(); } + bool isSaveLogs() const + { + return m_settings.value("Conf/saveLogs", false).toBool(); + } void setSaveLogs(bool enabled); enum RouteMode { @@ -75,16 +105,29 @@ public: VpnOnlyForwardSites, VpnAllExceptSites }; - Q_ENUM (RouteMode) + Q_ENUM(RouteMode) QString routeModeString(RouteMode mode) const; - RouteMode routeMode() const { return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); } - void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); } + RouteMode routeMode() const + { + return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); + } + void setRouteMode(RouteMode mode) + { + m_settings.setValue("Conf/routeMode", mode); + } - QVariantMap vpnSites(RouteMode mode) const { return m_settings.value("Conf/" + routeModeString(mode)).toMap(); } - void setVpnSites(RouteMode mode, const QVariantMap &sites) { m_settings.setValue("Conf/"+ routeModeString(mode), sites); m_settings.sync(); } - void addVpnSite(RouteMode mode, const QString &site, const QString &ip= ""); + QVariantMap vpnSites(RouteMode mode) const + { + return m_settings.value("Conf/" + routeModeString(mode)).toMap(); + } + void setVpnSites(RouteMode mode, const QVariantMap &sites) + { + m_settings.setValue("Conf/" + routeModeString(mode), sites); + m_settings.sync(); + } + void addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); void addVpnSites(RouteMode mode, const QMap &sites); // map QStringList getVpnIps(RouteMode mode) const; void removeVpnSite(RouteMode mode, const QString &site); @@ -92,33 +135,59 @@ public: void addVpnIps(RouteMode mode, const QStringList &ip); void removeVpnSites(RouteMode mode, const QStringList &sites); - bool useAmneziaDns() const { return m_settings.value("Conf/useAmneziaDns", true).toBool(); } - void setUseAmneziaDns(bool enabled) { m_settings.setValue("Conf/useAmneziaDns", enabled); } + bool useAmneziaDns() const + { + return m_settings.value("Conf/useAmneziaDns", true).toBool(); + } + void setUseAmneziaDns(bool enabled) + { + m_settings.setValue("Conf/useAmneziaDns", enabled); + } QString primaryDns() const; QString secondaryDns() const; - //QString primaryDns() const { return m_primaryDns; } - void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); } + // QString primaryDns() const { return m_primaryDns; } + void setPrimaryDns(const QString &primaryDns) + { + m_settings.setValue("Conf/primaryDns", primaryDns); + } - //QString secondaryDns() const { return m_secondaryDns; } - void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); } + // QString secondaryDns() const { return m_secondaryDns; } + void setSecondaryDns(const QString &secondaryDns) + { + m_settings.setValue("Conf/secondaryDns", secondaryDns); + } static const char cloudFlareNs1[]; static const char cloudFlareNs2[]; -// static constexpr char openNicNs5[] = "94.103.153.176"; -// static constexpr char openNicNs13[] = "144.76.103.143"; + // static constexpr char openNicNs5[] = "94.103.153.176"; + // static constexpr char openNicNs13[] = "144.76.103.143"; - QByteArray backupAppConfig() const { return m_settings.backupAppConfig(); } - bool restoreAppConfig(const QByteArray &cfg) { return m_settings.restoreAppConfig(cfg); } + QByteArray backupAppConfig() const + { + return m_settings.backupAppConfig(); + } + bool restoreAppConfig(const QByteArray &cfg) + { + return m_settings.restoreAppConfig(cfg); + } + + QLocale getAppLanguage() + { + return m_settings.value("Conf/appLanguage", QLocale()).toLocale(); + }; + void setAppLanguage(QLocale locale) + { + m_settings.setValue("Conf/appLanguage", locale); + }; signals: void saveLogsChanged(); private: SecureQSettings m_settings; - }; #endif // SETTINGS_H diff --git a/client/translations/amneziavpn_ru.qm b/client/translations/amneziavpn_ru.qm deleted file mode 100644 index f0e3aaea97fb44a56c38e26996633d05308393e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25539 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 LCgW%Av1|SxmH|-1 diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index befce5d55..b1e7817f4 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,1145 +2,2481 @@ - MainWindow + AdvancedServerSettingsLogic - - Connect to the already created VPN server - Подключиться к уже созданному серверу VPN - - - - Connection code - Код для подключения - - - - Connecting... - Подключение... - - - - - - - - Connect - Подключиться - - - - - Set up your own server - Настроить собственный сервер - - - Connect your server to use VPN - Подключите ваш сервер, чтобы использовать VPN - - - - Server IP address - IP адрес сервера - - - - Login to connect via SSH - Логин для подключения по SSH - - - - - - Password - Пароль - - - - Where to get connection data → - Где взять логин для подключения → - - - - vpn://... - - - - - - - - - Please wait, configuring process may take up to 5 minutes - Пожалуйста подождите, настройка может занять до 5 минут - - - - - Setup your server to use VPN - Настроить ваш сервер для VPN - - - - root - - - - - - Connect using SSH key - Использовать SSH ключ - - - Select VPN protocols to install - Выберите VPN протоколы для установки - - - - OpenVPN and ShadowSocks - with masking using Cloak plugin - OpenVPN и ShadowSocks - с маскировкой плагином Cloak - - - - Port (TCP) - Порт (TCP) - - - - 443 - - - - - - Fake Web Site - Сайт маскировки - - - - - mail.ru - - - - - ShadowSocks - - - - - Port(TCP) - Порт (TCP) - - - - 6789 - - - - - Encryption - Шифрование - - - - chacha20-ietf-poly1305 - - - - - xchacha20-ietf-poly1305 - - - - - - - aes-256-gcm - - - - - - aes-192-gcm - - - - - - - aes-128-gcm - - - - - OpenVPN - - - - - - - - Port - Порт - - - - Protocol - Протокол - - - - DNS settings - Настройки DNS - - - - UDP - - - - - TCP - - - - - AmneziaVPN will install OpenVPN protocol with public/private key pairs generated on server and client sides. You can also configure connection on your mobile device by copying exported ".ovpn" file to your device and setting up official OpenVPN client. We recommend do not use messengers for sending connection profile - it contains VPN private keys. - AmneziaVPN установит протокол OpenVPN и сгенерирует публичные/приваные пары ключей для серверной и клиентской стороны. Вы сможете так же настроить подключение для вашего мобильного устройства, экспортировав конфиг ".ovpn" на устройство и установив официальный клиент OpenVPN. Мы рекоммендуем не передавать конфиг через мессенджеры - он содержит приватный ключ для VPN. - - - - - - - - - - Configuring... - Настройка... - - - - Setup server - Установить сервер - - - - - 0 Mbps - 0 Мбит/сек - - - Add site - Добавить сайт - - - - Connected - Подключено - - - - How to use VPN - Как использовать VPN - - - - For all connections - Для всех соединений - - - - For selected sites - Для выбранных сайтов - - - - Error text - - - - - List of the most popular prohibited sites - Список самых популярных запрещенных сайтов - - - For example, rutor.org or 17.21.111.8 - Например, rutor.org или 17.21.111.8 - - - Anyone who logs in with this code will have the same rights to use the VPN as you. To create a new code, change your login and / or password for connection in your server settings. - Тот, кто зайдёт с этим кодом, будет иметь те же права на использование VPN, что и вы. Чтобы создать новый код смените логин и/или пароль для подлючения в настройках вашего сервера. - - - These sites will open via VPN - These sites will open via VPN - - - Delete selected item - Удалить выбранный элемент - - - Hostname or IP address - Имя хоста или IP адрес - - - - + - + - - - - Server settings - Настройки сервера - - - - Share connection - Поделиться подключением - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Lato'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + Clear server from Amnezia software - - Configure VPN protocols manually - Выбрать протоколы + + Service: + - - Run Setup Wizard - Мастер настройки - - - - If you want easily configure your server just run Wizard - Для облегченной настройки сервера запустите мастер настройки - - - - Press configure manually to choose VPN protocols you want to install - Выбрать протоколы для установки самостоятельно - - - - - - - - Setup Wizard - Мастер настройки - - - - High censorship level - Высокий уровень цензуры - - - - Medium censorship level - Средний уровень цензуры - - - - Low censorship level - Низкий уровень цензуры - - - - I'm living in country with high censorship level. Many of foreign web sites and VPNs blocked by my government. I want to setup reliable VPN, which is invisible for government. - Я живу в стране с высоким уровнем цензуры. Многие зарубежные сайты и VPN сервисы заблокированы. Я хочу установить надёжный VPN, невидимый для надзорных органов. - - - - I'm living in country with medium censorship level. Some web sites blocked by my government, but VPNs are not blocked at all. I want to setup flexible solution. - Я живу в стране со средним уровнем цензуры. Некоторые зарубежные сайты заблокированы, но VPN сервисы в целом работают. Я хочу установить гибкое решение. - - - - I just want to improve my privacy in internet. - Я просто хочу повысить уровень моей приватности в интернете. - - - - - - Next - Далее - - - - AmneziaVPN will install VPN protocol which is not visible for your internet provider and government firewall. Your VPN connection will be detected by your provider as regular web traffic to particular web site. - -You SHOULD set this web site address to some foreign web site which is not blocked by your internet provider. Other words you need to type below some foreign web site address which is accessible without VPN. - -Please note, this protocol still does not support export connection profile to mobile devices. Keep for updates. - AmneziaVPN установит VPN протокол невидимый для вашего провайдера и гос. фаервола. Ваше VPN соединение будет определяться как обычные запросы к определнному web сайту. -Вы ДОЛЖНЫ установить адрес этого сайта таким, который не заблокирован вашим провайдером, и находится за границей. Другими словами, вы должны ввести адрес какого-либо зарубежного сайта, который доступен и без VPN. - -Заметьте, этот протокол пока не имеет функции экспорта настроек подключения для мобильных устаройств. Следите за обновлениями. - - - - OpenVPN over Cloak (VPN obfuscation) profile will be installed - Будет установлен профиль OpenVPN over Cloak (VPN маскировка) - - - - Type web site address for mask - Адрес сайта для маскировки - - - - Optional. - -We recommend to enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. - -Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. - Опционально. - -Мы рекомендуем включить режим VPN "Для выбранных сайтов" и добавить вручную заблокированные сайты, которые вы хотите посещать. Если вы выберите эту опцию, вы должны будете добавить каждый заблокированный сайт, который вы хотите посетить в список. Позже вы сможете переключаться между режимами работы VPN. - -Мы рекомендуем добавлять сайты в список только после того, как соединение VPN будет подключено. Вы сможете добавить любые домены, URL или IP адреса. - - - - - Start configuring - Начать настройку - - - - Turn on mode "VPN for selected sites" - Включить режим -"VPN для выбранных сайтов" - - - - AmneziaVPN will install VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "web traffic masking". - -This protocol support export connection profile to mobile devices using QR code (you should launch 3rd party opensource VPN client - ShadowSocks VPN). - AmneziaVPN установит VPN протокол, который трудно детектировать интернет провайдерам (но возможно). В большинстве случаев, это наиболее подходящий выбор. Этот протокол быстрее, по сравнению с VPN протоколами с полной маскировкой трафика. - -Этот протокол поддерживает экспорт профиля подключения с помощью QR кода для настройки на мобильных устройствах (вам нужно будет установить сторонний клиент VPN - ShadowSocks VPN). - - - - OpenVPN over ShadowSocks profile will be installed - Будет установлен профиль -OpenVPN over ShadowSocks - - - - OpenVPN profile will be installed - Будет установлен профиль OpenVPN - - - - Please wait. - Пожалуйста, ждите. - - - - Select VPN protocols - Выберите протоколы - - - - udp - - - - - tcp - - - - - + Add site - + Добавить сайт - - - - These sites will be opened using VPN - Эти сайты будут открываться через VPN - - - For example, yousite.com or 17.21.111.8 - Например, yousite.com или 17.21.111.8 - - - Web site or hostname or IP address - Web-сайт, имя хоста или IP-адрес - - - - - Reinstall server, clear server - Переустановить сервер, очистить сервер - - - - Server management - Управление сервером - - - - - Exit - Выход из приложения - - - - Auto start, Auto connect - Авто-запуск, авто-соединение - - - - App settings - Настройки приложения - - - - Network settings - Настройки сети - - - - Servers - Список серверов - - - - Add or import new server - Добавить или импортировать новый сервер - - - - Add server - Добавить сервер - - - - Servers list - Список серверов - - - - Auto start - Авто старт - - - - Application Settings - Настройки приложения - - - - Auto connect - Авто соединение - - - - Check for updates - Проверить обновления - - - - Start minimized - Запускать свёрнутым - - - - Open logs folder - Открыть папки с логами - - - - DNS Servers - DNS сервера - - - - - Reset to default value - Сбросить по умолчанию - - - - Primary DNS server - Первичный DSN сервер - - - - Secondary DNS server - Вторичный DNS сервер - - - - - Clear client cached profile - Удалить закешировнный профиль - - - - - Clear server from Amnezia software - Очистить сервер от Amnezia - - - - Forget this server - Забыть этот сервер - - - - root@yourserver.org - - - - - VPN protocols - VPN протоколы - - - - VPN Protocol: - VPN протокол: - - - - Protocols - Протоколы - - - - Cloak container - Cloak контейнер - - - - - - OpenVPN settings - Настройки OpenVPN - - - - - ShadowSocks settings - Настройки ShadowSocks - - - - Cloak settings - Настройки Cloak - - - - ShadowSocks container - ShadowSocks контейнер - - - - OpenVPN container - OpenVPN контейнер - - - - Full access - Полный доступ - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Consolas'; font-size:20px; font-weight:600; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:20pt;">vpn:\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></p></body></html> - - - - - Anyone who logs in with this code will have the same permissions to use VPN and your server as you. -This code includes your server credentials! -Provide this code only to TRUSTED users. - Любой, кто получит этот код, получит полный доступ к серверу и использованию VPN. Этот код содержит пароль от сервера. -Предоставляйте этот код только доверенным пользователям. - - - - - - - Share for Amnezia client - Расшарить для Amnezia - - - - Anyone who logs in with this code will be able to connect to this VPN server. -This code does not include server credentials. - Любой, кто получит этот код, получит возможность подключаться к этому VPN серверу -Этот код не содержи пароль от сервера. - - - - - - - Generate config - Сгенерировать конфиг - - - - - Share for OpenVPN client - Расшарить для OpenVPN - - - - - Save file - Сохранить файл - - - - AmneziaVPN - - - - - Except selected sites - Кроме выбранных сайтов - - - - yousite.com or IP address - - - - - Web site/Hostname/IP address/Subnet - Web сайт/хост/IP адрес/подсеть - - - - Delete selected - Удалить выбранные - - - - Software version: X.X.X (01.06.2021) - - - - - Share Server (FULL ACCESS) - Расшарить сервер (FULL ACCESS) - - - - - Share for ShadowSocks client - Расшарить для ShadowSocks - - - - - Server: - Сервер: - - - - - Encryption: - Шифрование: - - - - - Port: - Порт: - - - - Password: - Пароль: - - - - Connection string - Строка подключения - - - - Share for Cloak client - Расшарить для Cloak - - - - OpenVPN Settings - Настройки OpenVPN - - - - Hash - Хеш - - - - - - Cipher - Шифр - - - - Network protocol - Сетевой протокол - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - VPN Addresses Subnet - Подсеть для VPN - - - - - - Save and restart VPN - Сохранить и перезапустить VPN - - - - Auto-negotiate encryption - Авто-согласование шифрования - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - Block DNS requests outside of VPN - Блокировать запросы DNS мимо VPN - - - - Enable TLS auth - Включить TLS auth - - - - ShadowSocks Settings - Настройки ShadowSocks - - - - - chacha20-poly1305 - - - - - Cloak Settings - Настройки Cloak - - - - plain - - - - - - - - - - - - Copy - Копировать - - - - Cannot open logs folder! - Невозможно открыть папку с логами! - - - - - Please fill in all fields - Пожалуйста, заполните все поля - - - - It's public key. Private key required - Это публичный ключ. Требуется приватный - - - - of - из - - - - - - Error occurred while configuring server. - Ошибка во время настройки сервера - - - - Amnezia server installed - Amnezia сервер установлен - - - - Operation finished - Операция завершена - - - + Uninstalling Amnezia software... - Удаление Amnezia... + - + + Error occurred while cleaning the server. + + + + + + Error message: + + + + + See logs for details. - Смотрите логи для подробных деталей + - + Amnezia server successfully uninstalled - Amnezia сервер удален + - - Show - Показать окно + + Error occurred while scanning the server. + - - - Disconnect - Отключиться + + All containers installed on the server are added to the GUI + - - Visit Website - Посетить сайт + + No installed containers found on the server + + + + + AppSettingsLogic + + + Software version + - - Quit - Выход + + Save log + - - Do you really want to quit? - Вы действительно хотите выйти? + + Open backup + - - Import IP addresses - Импортовать IP адреса + + Can't import config, file is corrupted. + + + + + ClientInfoLogic + + + Service: + + + + + ClientManagementLogic + + + Service: + - - Import connection - Импортировать соединение + + An error occurred while getting the list of clients. + + + + ConnectionController - - Private key - Приватный ключ - - - - Connect using SSH password - Соединиться с паролем SSH - - - - Cache cleared - Кеш очищен - - - - - - - Copied - Скопировано - - - - Save AmneziaVPN config - Сохранить конфиг AmneziaVPN - - - - - Generating... - Генерация... - - - - Error while generating connection profile - Ошибка во время генерации профиля - - - - Save OpenVPN config - Сохранить OpenVPN конфиг - - - + VPN Protocols is not installed. Please install VPN container at first - VPN протоколы ещё не установлены. Установите VPN контейнеры + - - VPN Protocol not chosen - VPN протокол не выбран + + Connection... + - - Software version - Версия программы + + Disconnect + - - Protocol: - Протокол: + + Reconnection... + - - Press Generate config - Нажмите Сгенерировать конфиг + + + + + Connect + - - Share server full access - Расшарить полный доступ + + Disconnection... + + + + + ContextMenu + + + C&ut + + + + + &Copy + + + + + &Paste + + + + + &SelectAll + + + + + ExportController + + + Save AmneziaVPN config + + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + + + + + VPN Disconnected + + + + + AmneziaVPN notification + + + + + Unsecured network detected: + + + + + OpenVpnSettings + + + VPN Addresses Subnet + + + + + Network protocol + + + + + Port + + + + + Auto-negotiate encryption + + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional configuration commands + + + + + PageAbout + + + About Amnezia + + + + + AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. +<ul> +<li>Sources on <a href="https://github.com/amnezia-vpn/desktop-client">GitHub</a></li> +<li><a href="https://amnezia.org/">Web Site</a></li> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a></li> +<li><a href="https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh">Signal group</a></li> +</ul> + + + + + + Support + + + + + Have questions? You can get support by: +<ul> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a> (preferred way)</li> +<li>Create issue on <a href="https://github.com/amnezia-vpn/desktop-client/issues">GitHub</a></li> +<li>Email to: <a href="support@amnezia.org">support@amnezia.org</a></li> +</ul> + + + + + Donate + + + + + Please support Amnezia project by donation, we really need it now more than ever. +<ul> +<li>By credit card on <a href="https://www.patreon.com/amneziavpn">Patreon</a> (starting from $1)</li> +<li>Send some coins to addresses listed <a href="https://github.com/amnezia-vpn/desktop-client/blob/master/README.md">on GitHub page</a></li> +</ul> + + + + + + PageAdvancedServerSettings + + + Advanced server settings + + + + + Clients Management + + + + + PageAppSetting + + + Application Settings + + + + + Auto connect + + + + + Auto start + + + + + Start minimized + + + + + Check for updates + + + + + Keep logs + + + + + Open logs folder + + + + + Export logs + + + + + Clear logs + + + + + Cleared + + + + + Backup and restore configuration + + + + + Backup app config + + + + + Restore app config + + + + + PageClientInfoOpenVPN + + + Client Info + + + + + Client name + + + + + Certificate id + + + + + Certificate + + + + + Revoke Certificate + + + + + PageClientInfoWireGuard + + + Client Info + + + + + Client name + + + + + Public Key + + + + + Revoke Key + + + + + PageClientManagement + + + Clients Management + + + + + PageDeinstalling + + + Removing services from + + + + + PageGeneralSettings + + + App settings + + + + + Network settings + + + + + Server Settings + + + + + Share connection + + + + + Servers + + + + + Add server + + + + + Exit + + + + + PageHome + + + Протокол подключения + + + + + Servers + + + + + PageNetworkSetting + + + DNS Servers + + + + + Use AmneziaDNS service (recommended) + + + + + Use AmneziaDNS container on your server, when it installed. + +Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254 + +If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used: + + + + + Primary DNS server + + + + + + Reset to default + + + + + Secondary DNS server + + + + + PageNewServer + + + Setup your server to use VPN + + + + + If you want easily configure your server just run Wizard + + + + + Run Setup Wizard + + + + + Press configure manually to choose VPN protocols you want to install + + + + + Configure + + + + + PageNewServerProtocols + + + Select VPN protocols + + + + + Setup server + + + + + Select protocol container + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + PageProtoCloak + + + Cloak Settings + + + + + Cipher + + + + + chacha20-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Fake Web Site + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoOpenVPN + + + OpenVPN Settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + TCP + + + + + UDP + + + + + Port + + + + + Auto-negotiate encryption + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + Enable TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client config commands → + + + + + Additional server config commands → + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoSftp + + + SFTP settings + + + + + Port + + + + + User Name + + + + + Password + + + + + Restore drive when client starts + + + + + Mount drive + + + + + PageProtoShadowSocks + + + ShadowSocks Settings + + + + + Cipher + + + + + chacha20-ietf-poly1305 + + + + + xchacha20-ietf-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoTorWebSite + + + Tor Web Site settings + + + + + Web site onion address + + + + + Notes:<ul> +<li>Use <a href="https://www.torproject.org/download/">Tor Browser</a> to open this url.</li> +<li>After installation it takes several minutes while your onion site will become available in the Tor Network.</li> +<li>When configuring WordPress set the domain as this onion address.</li> +</ul> + + + + + + PageProtoWireGuard + + + WireGuard Settings + + + + + PageQrDecoderIos + + + Import configuration + + + + + PageServerConfiguringProgress + + + Configuring... + + + + + Please wait. + + + + + Cancel + + + + + PageServerContainers + + + + Install new service + + + + + Installed services + + + + + Default + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + Cancel + + + + + Continue + Продолжить + + + + Installed Protocols and Services + + + + + Remove container + + + + + This action will erase all data of this container on the server. + + + + + PageServerList + + + Servers + + + + + PageServerSettings + + + Server settings + + + + + Protocols and Services + + + + + Share Server (FULL ACCESS) + + + + + Advanced server settings + + + + + Forget this server + + + + + PageSettings + + + Settings + + + + + Servers + + + + + Connection + + + + + Application + + + + + Backup + + + + + About AmneziaVPN + + + + + PageSettingsAbout + + + Support the project with a donation + + + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + + + + + Card on Patreon + + + + + Show other methods on Github + + + + + Contacts + + + + + Telegram group + + + + + To discuss features + + + + + Mail + + + + + For reviews and bug reports + + + + + Github + + + + + Website + + + + + Check for updates + + + + + PageSettingsApplication + + + Application + + + + + Language + + + + + Reset settings and remove all data from the application + + + + + PageSettingsBackup + + + Backup + + + + + Save logs + + + + + Open folder with logs + + + + + Save logs to file + + + + + Clear logs + + + + + Configuration backup + + + + + It will help you instantly restore connection settings at the next installation + + + + + Make a backup + + + + + Restore from backup + + + + + PageSettingsConnection + + + Connection + + + + + Use AmnesiaDNS if installed on the server + + + + + Internal IP address 172.29.172.254 + + + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Split site tunneling + + + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + + + + + Separate application tunneling + + + + + Allows you to use the VPN only for certain applications + + + + + PageSettingsDns + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Save + + + + + PageSettingsServerData + + + All installed containers have been added to the application + + + + + Не найдено установленных контейнеров + + + + + Clear Amnezia cache + + + + + May be needed when changing other settings + + + + + Clear cached profiles? + Очистить закешированные профили + + + + some description + + + + + + + Continue + Продолжить + + + + + + Cancel + + + + + Проверить сервер на наличие ранее установленных сервисов Amnezia + + + + + Добавим их в приложение, если они не отображались + + + + + Remove server from application + + + + + Remove server? + + + + + All installed AmneziaVPN services will still remain on the server. + + + + + Clear server from Amnezia software + + + + + Clear server from Amnezia software? + + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + + + + + PageSettingsServerInfo + + + Server name + + + + + Save + + + + + Protocols + + + + + Services + + + + + Data + + + + + PageSetupWizard + + + Setup your server to use VPN + + + + + High censorship level + + + + + I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. +OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. + + + + + + Medium censorship level + + + + + I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. +OpenVPN over ShadowSocks profile will be installed. + + + + + + Low censorship level + + + + + I want to improve my privacy on the internet. +OpenVPN profile will be installed. + + + + + + Next + + + + + PageSetupWizardCredentials + + + Server connection + + + + + Server IP address [:port] + + + + + + Enter the address in the format 255.255.255.255:88 + + + + + Login to connect via SSH + + + + + Password / Private key + + + + + Set up a server the easy way + + + + + Select protocol to install + + + + + Ip address cannot be empty + + + + + Login cannot be empty + + + + + Password/private key cannot be empty + + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + + + + + Continue + Продолжить + + + + PageSetupWizardHighLevel + + + Setup Wizard + + + + + AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. + +You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN. + + + + + Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN. + + + + + OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. + +This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin). + + + + + Next + + + + + PageSetupWizardInstalling + + + + The container you are trying to install is already installed on the server. All installed containers have been added to the application + + + + + The server has already been added to the application + + + + + PageSetupWizardLowLevel + + + Setup Wizard + + + + + AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. + +You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. + +We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys. + + + + + OpenVPN profile will be installed + + + + + Start configuring + + + + + PageSetupWizardMediumLevel + + + Setup Wizard + + + + + AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking". + +This protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN). + + + + + OpenVPN over ShadowSocks profile will be installed + + + + + Next + + + + + PageSetupWizardProtocolSettings + + + Installing + + + + + protocol description + + + + + More detailed + + + + + detailed protocol description + + + + + Close + + + + + Установить + + + + + PageSetupWizardStart + + + У меня есть данные для подключения + + + + + У меня ничего нет + + + + + PageSetupWizardTextKey + + + Connection key + + + + + A line that starts with vpn://... + + + + + Key + + + + + Insert + + + + + Continue + Продолжить + + + + PageSetupWizardVPNMode + + + Setup Wizard + + + + + Optional. + +You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. + +Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. + + + + + Turn on mode "VPN for selected sites" + + + + + Start configuring + + + + + PageSetupWizardViewConfig + + + New connection + + + + + Do not use connection code from public sources. It could be created to intercept your data. + + + + + Collapse content + + + + + Show content + + + + + Connect + + + + + PageShare + + + For the AmnesiaVPN app + + + + + OpenVpn native format + + + + + WireGuard native format + + + + + VPN Access + + + + + Connection + + + + + Full + + + + + VPN access without the ability to manage the server + + + + + Full access to server + + + + + Server and service + + + + + Server + + + + + Protocols and services + + + + + + Connection to + + + + + + File with connection settings to + + + + + + Connection format + + + + + Share + + + + + PageShareConnection + + + Share protocol config + + + + + Share for Amnezia + + + + + Share for + + + + + PageShareProtoAmnezia + + + Share for Amnezia + + + + + Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. + +This code includes your server credentials! + +Provide this code only to TRUSTED users. + + + + + Anyone who logs in with this code will be able to connect to this VPN server. + +This code does not include server credentials. + +New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + Scan QR code using AmneziaVPN mobile + + + + + PageShareProtoCloak + + + Share Cloak Settings + + + + + Note: Cloak protocol using same password for all connections + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + PageShareProtoIkev2 + + + Share IKEv2 Settings + + + + + + Export p12 certificate + + + + + + Export config for Apple + + + + + + Export config for StrongSwan + + + + + PageShareProtoOpenVPN + + + Share OpenVPN Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtoSftp + + + Share SFTP settings + + + + + PageShareProtoShadowSocks + + + Share ShadowSocks Settings + + + + + Note: ShadowSocks protocol using same password for all connections + + + + + Copy config + + + + + Connection string + + + + + Copy string + + + + + PageShareProtoTorWebSite + + + Share Tor Web site + + + + + PageShareProtoWireGuard + + + Share WireGuard Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtocolBase + + + Generate config + + + + + Generating config... + + + + + Show config + + + + + PageSites + + + Web site/Hostname/IP address/Subnet + + + + + yousite.com or IP address + + + + + Import IP addresses + + + + + Delete selected + + + + + Select all + + + + + Export all + + + + + PageStart + + + Setup your server to use VPN + + + + + Connect to the already created VPN server + + + + + + Set up your own server + + + + + Import connection + + + + + Connection code + + + + + Connect + + + + + Open file + + + + + Scan QR code + + + + + Restore app config + + + + + How to get own server? → + + + + + Server IP address [:port] + + + + + Login to connect via SSH + + + + + + Password + + + + + + Connect using SSH key + + + + + Private key + + + + + Connect using SSH password + + + + + PageTest + + + Протоколы + + + + + Сервисы + + + + + Данные + + + + + + Forget this server + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + + Auto-negotiate encryption + + + + + PageVPN + + + Donate + + + + + Server + + + + + Profile + + + + + Proto + + + + + DNS + + + + + How to use VPN + + + + + For all connections + + + + + Except selected sites + + + + + For selected sites + + + + + + Add site + + + + + PageViewConfig + + + Check config + + + + + Attention! +The config above contains cached OpenVPN connection profile. +AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it. + + + + + Suspicious string: + + + + + Cached connection profile: + + + + + Cancel + + + + + Import config + + + + + PopupType + + + Close + QObject - - AmneziaVPN is already running. - Приложение AmneziaVPN уже запущено. - No error @@ -1166,656 +2502,636 @@ This code does not include server credentials. Server port already used. Check for another software + + + Server error: Docker container missing + + + + + Server error: Docker failed + + - Ssh connection error + Installation canceled by user - Ssh connection timeout - - - - - Ssh protocol error - - - - - Ssh server ket check failed + The user does not have permission to use sudo - Ssh key file error + Ssh request was denied - Ssh authentication error + Ssh request was interrupted - Ssh session closed - - - - Ssh internal error - - Failed to create remote process on server + + Invalid private key or invalid passphrase entered - - Failed to start remote process on server + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + + + + + Timeout connecting to server - Remote process on server crashed + Sftp error: End-of-file encountered + + + + + Sftp error: File does not exist + + + + + Sftp error: Permission denied - Failed to save config to disk + Sftp error: Generic failure - OpenVPN config missing + Sftp error: Garbage received from server - OpenVPN management server error + Sftp error: No connection has been set up - EasyRSA runtime error + Sftp error: There was a connection, but we lost it + + + + + Sftp error: Operation not supported by libssh yet + + + + + Sftp error: Invalid file handle - OpenVPN executable missing + Sftp error: No such file or directory path exists - EasyRsa executable missing + Sftp error: An attempt to create an already existing file or directory has been made - Amnezia helper service error - Ошибка локального сервиса Amnezia + Sftp error: Write-protected filesystem + - + + Sftp error: No media was in remote drive + + + + + Failed to save config to disk + + + + + OpenVPN config missing + + + + + OpenVPN management server error + + + + + OpenVPN executable missing + + + + + ShadowSocks (ss-local) executable missing + + + + + Cloak (ck-client) executable missing + + + + + Amnezia helper service error + + + + + OpenSSL failed + + + + Can't connect: another VPN connection is active - + + Can't setup OpenVPN TAP network adapter + + + + + VPN pool error: no available addresses + + + + + The config does not contain any containers and credentiaks for connecting to the server + + + + Internal error - - - QSsh::Internal::SftpChannelPrivate - - Server could not start SFTP subsystem. + + IPsec - - The SFTP server finished unexpectedly with exit code %1. + + + Web site in Tor network - - The SFTP server crashed: %1. + + + DNS Service - - Unexpected packet of type %1. + + Sftp file sharing service - - Protocol version mismatch: Expected %1, got %2 + + OpenVPN container - - Unknown error. + + Container with OpenVpn and ShadowSocks - - Created remote directory "%1". + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - - Remote directory "%1" already exists. + + WireGuard container - - Error creating directory "%1": %2 + + IPsec container - - Could not open local file "%1": %2 + + Sftp file sharing service - is secure FTP service - - Remote directory could not be opened for reading. + + Sftp service - - Failed to list remote directory contents. - - - - - Failed to close remote directory. - - - - - Failed to open remote file for reading. - - - - - Failed to retrieve information on the remote file ('stat' failed). - - - - - Failed to read remote file. - - - - - - Failed to close remote file. - - - - - Failed to open remote file for writing. - - - - - Failed to write remote file. - - - - - Cannot append to remote file: Server does not support the file size attribute. - - - - - SFTP channel closed unexpectedly. - - - - - Server could not start session: %1 - - - - - Error reading local file: %1 + + An error occurred while saving the list of clients. - QSsh::Internal::SshChannelManager + SelectContainer - - Unexpected request success packet. + + VPN containers - - Unexpected request failure packet. - - - - - Invalid channel id %1 + + Other containers - QSsh::Internal::SshConnectionPrivate + SelectLanguageDrawer - - SSH Protocol error: %1 - - - - - Botan library exception: %1 - - - - - Server identification string is %n characters long, but the maximum allowed length is 255. - - - - - - - - - Server identification string contains illegal NUL character. - - - - - Server Identification string "%1" is invalid. - - - - - Server protocol version is "%1", but needs to be 2.0 or 1.99. - - - - - Server identification string is invalid (missing carriage return). - - - - - Server reports protocol version 1.99, but sends data before the identification string, which is not allowed. - - - - - - - - Unexpected packet of type %1. - - - - - Password expired. - - - - - Server rejected key. - - - - - Server rejected password. - - - - - The server sent an unexpected SSH packet of type SSH_MSG_UNIMPLEMENTED. - - - - - Server closed connection: %1 - - - - - Connection closed unexpectedly. - - - - - Timeout waiting for reply from server. - - - - - No private key file given. + + Choose language - QSsh::Internal::SshRemoteProcessPrivate + ServerConfiguringProgressLogic - - Process killed by signal + + + Please wait, configuring process may take up to 5 minutes - - Server sent invalid signal "%1" + + Configuring... + + + + + Operation finished - QSsh::SftpFileSystemModel + ServerContainersLogic - - File Type + + Error occurred while configuring server. - - File Name + + Error message: - - Error getting "stat" info about "%1": %2 - - - - - Error listing contents of directory "%1": %2 + + See logs for details. - QSsh::Ssh + ServerSettingsLogic - - Failed to open key file "%1" for reading: %2 + + + Clear client cached profile - - Failed to open key file "%1" for writing: %2 + + Service: - - Password Required + + Cache cleared - - - Please enter the password for your private key. - - - - - QSsh::SshKeyCreationDialog - - - SSH Key Configuration - - - - - Options - - - - - Key algorithm: - - - - - &RSA - - - - - &DSA - - - - - ECDSA - - - - - Key &size: - - - - - Private key file: - - - - - - Browse... - - - - - Public key file: - - - - - &Generate And Save Key Pair - - - - - &Cancel - - - - - Choose... - - - - - Key Generation Failed - - - - - Choose Private Key File Name - - - - - Cannot Save Key File - - - - - Failed to create directory: "%1". - - - - - Cannot Save Private Key File - - - - - The private key file could not be saved: %1 - - - - - Cannot Save Public Key File - - - - - The public key file could not be saved: %1 - - - - - File Exists - - - - - There already is a file of that name. Do you want to overwrite it? - - - - - ServerWidget - - - Form - - - - - Description - - - - - Address - - - - - Set as default - - - - - Share connection - Поделиться подключением - - - - Connection - - - - - Server settings - Настройки сервера - Settings - + Server #1 - - + + Server - SshConnection + SettingsController - - Server and client capabilities don't match. Client list was: %1. -Server list was %2. + + Software version + + + + + Save log + + + + + Backup application config + + + + + Open backup - SshKeyGenerator + ShareConnectionButtonCopyType - - Error generating key: %1 + + Copy - - Password for Private Key + + Copied + + + + + ShareConnectionDrawer + + + Save connection code - - It is recommended that you secure your private key -with a password, which you can enter below. + + Copy - - Encrypt Key File + + Show content - - Do Not Encrypt Key File + + To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" + + + + + ShareConnectionLogic + + + Error while generating connection profile + + + + + Error occurred while generating the config. + + + + + Error message: + + + + + See logs for details. + + + + + SitesLogic + + + These sites will be opened using VPN + + + + + These sites will be excepted from VPN + + + + + StartPageLogic + + + + Connect + + + + + + Please fill in all fields + + + + + Connecting... + + + + + Open config file + + + + + SystemTrayNotificationHandler + + + Show + + + + + Connect + + + + + Disconnect + + + + + Visit Website + + + + + Quit + + + + + UiLogic + + + Error occurred while configuring server. + + + + + Error message: + + + + + See logs for details. VpnConnection - + Mbps + + VpnLogic + + + + 0 Mbps + + + + + AmneziaVPN not supporting selected protocol on this device. Select another protocol. + + + + + VPN Protocols is not installed. + Please install VPN container at first + + + + + VPN Protocol not chosen + + + VpnProtocol - + Unknown - Неизвестно + - + Disconnected - Отключено + - + Preparing - Подготовка + - + Connecting... - Подключение... + - + Connected - Подключено + - + Disconnecting... - Отключение... + - + Reconnecting... - Переподключение... + - + Error - Ошибка + + + + + amnezia::ContainerProps + + + Low + + + + + High + + + + + Medium + + + + + Many foreign websites and VPN providers are blocked + + + + + Some foreign sites are blocked, but VPN providers are not blocked + + + + + I just want to increase the level of privacy + + + + + main + + + It's public key. Private key required + + + + + Ssh log + + + + + App log + + + + + Wrap words + diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index fcfcc7afe..42dd22316 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,9 +2,9 @@ #include -#include "defines.h" #include "logger.h" #include "utilities.h" +#include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp new file mode 100644 index 000000000..e95b2ccf3 --- /dev/null +++ b/client/ui/models/languageModel.cpp @@ -0,0 +1,62 @@ +#include "languageModel.h" + +LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); i++) { + m_availableLanguages.push_back( + LanguageModelData { metaEnum.valueToKey(i), static_cast(i) }); + } +} + +int LanguageModel::rowCount(const QModelIndex &parent) const +{ + return static_cast(m_availableLanguages.size()); +} + +QVariant LanguageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_availableLanguages.size())) { + return QVariant(); + } + + switch (role) { + case NameRole: { + return m_availableLanguages[index.row()].name; + break; + } + case IndexRole: { + return static_cast(m_availableLanguages[index.row()].index); + break; + } + } + return QVariant(); +} + +QHash LanguageModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "languageName"; + roles[IndexRole] = "languageIndex"; + return roles; +} + +void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) +{ + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; + case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; + default: emit updateTranslations(QLocale::English); break; + } +} + +int LanguageModel::getCurrentLanguageIndex() +{ + auto locale = m_settings->getAppLanguage(); + switch (locale.language()) { + case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + } +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h new file mode 100644 index 000000000..b3ff4f6e0 --- /dev/null +++ b/client/ui/models/languageModel.h @@ -0,0 +1,62 @@ +#ifndef LANGUAGEMODEL_H +#define LANGUAGEMODEL_H + +#include +#include + +#include "settings.h" + +namespace LanguageSettings +{ + Q_NAMESPACE + enum class AvailableLanguageEnum { + English, + Russian + }; + Q_ENUM_NS(AvailableLanguageEnum) + + static void declareQmlAvailableLanguageEnum() + { + qmlRegisterUncreatableMetaObject(LanguageSettings::staticMetaObject, "AvailableLanguageEnum", 1, 0, + "AvailableLanguageEnum", QString()); + } +} + +struct LanguageModelData +{ + QString name; + LanguageSettings::AvailableLanguageEnum index; +}; + +class LanguageModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + NameRole = Qt::UserRole + 1, + IndexRole + }; + + LanguageModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); + int getCurrentLanguageIndex(); + +signals: + void updateTranslations(const QLocale &locale); + +protected: + QHash roleNames() const override; + +private: + QVector m_availableLanguages; + + std::shared_ptr m_settings; +}; + +#endif // LANGUAGEMODEL_H diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 51bffc039..71299b1fc 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -18,12 +18,14 @@ DrawerType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + spacing: 0 Header2TextType { Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.bottomMargin: 32 Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml new file mode 100644 index 000000000..f1ffa4163 --- /dev/null +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -0,0 +1,137 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + width: parent.width + height: parent.height * 0.9 + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + BackButtonType { + backButtonImage: "qrc:/images/controls/arrow-left.svg" + backButtonFunction: function() { + root.close() + } + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Choose language") + } + + ListView { + id: listView + + Layout.fillWidth: true + height: listView.contentItem.height + + clip: true + interactive: false + + model: LanguageModel + currentIndex: LanguageModel.getCurrentLanguageIndex() + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: root.width + implicitHeight: content.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 0 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: languageName + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: listView.currentIndex === index + + onClicked: { + listView.currentIndex = index + LanguageModel.changeLanguage(languageIndex) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 720e3206f..99e172cc9 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -105,8 +105,6 @@ DrawerType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 16 backButtonFunction: function() { diff --git a/client/ui/qml/Components/ShowDetailsDrawer.qml b/client/ui/qml/Components/ShowDetailsDrawer.qml deleted file mode 100644 index 2f6d26560..000000000 --- a/client/ui/qml/Components/ShowDetailsDrawer.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import "../Controls2" -import "../Controls2/TextTypes" - -Item { - -} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 0ccb7345e..91f5f28fc 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -17,6 +17,7 @@ Item { id: content anchors.fill: parent + anchors.leftMargin: 8 ImageButtonType { image: backButtonImage diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 05074fa9c..d91684668 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Controls +import "TextTypes" + Button { id: root @@ -46,12 +48,8 @@ Button { cursorShape: Qt.PointingHandCursor } - contentItem: Text { + contentItem: ButtonTextType { anchors.fill: background - font.family: "PT Root UI VF" - font.styleName: "normal" - font.weight: 400 - font.pixelSize: 16 color: textColor text: root.text horizontalAlignment: Text.AlignHCenter diff --git a/client/ui/qml/Controls2/DividerType.qml b/client/ui/qml/Controls2/DividerType.qml index 6341807a5..bf01e7a19 100644 --- a/client/ui/qml/Controls2/DividerType.qml +++ b/client/ui/qml/Controls2/DividerType.qml @@ -3,6 +3,10 @@ import QtQuick.Layouts Rectangle { Layout.fillWidth: true + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 color: "#2C2D30" } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 9b9c718a5..85989ae62 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -146,8 +146,6 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 BackButtonType { backButtonImage: root.headerBackButtonImage diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index d26594d68..e3b14e63a 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 24 + lineHeight: 24 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 16 - font.weight: Font.Medium + font.weight: 500 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml index 15cc96c10..b9e41da2d 100644 --- a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 16 + lineHeight: 16 + lineHeightMode: Text.FixedHeight color: "#0E0E11" font.pixelSize: 13 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml index 99addc7bd..86b61d7a0 100644 --- a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -1,13 +1,14 @@ import QtQuick Text { - height: 38 + lineHeight: 38 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 36 - font.weight: Font.Bold + font.weight: 700 font.family: "PT Root UI VF" - font.letterSpacing: -0.03 + font.letterSpacing: -1.08 wrapMode: Text.WordWrap } diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index ed96f6f1c..9e2a8ae98 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 30 + lineHeight: 30 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 25 - font.weight: Font.Bold + font.weight: 700 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml index a2ffa18b8..d47d460d7 100644 --- a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 16 + lineHeight: 16 + lineHeightMode: Text.FixedHeight color: "#878B91" font.pixelSize: 13 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml index 23069db20..30bd7900d 100644 --- a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 21.6 + lineHeight: 21.6 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 18 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 74b155abf..a53ca67ec 100644 --- a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 24 + lineHeight: 24 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 16 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml index 96f3342dd..d1b24e6a0 100644 --- a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 20 + lineHeight: 20 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 14 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index a1bdeb268..01b11bda9 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -19,8 +19,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 08a5ec0d5..b378a6c87 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -18,8 +19,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } @@ -52,10 +51,14 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + selectLanguageDrawer.open() } } - DividerType {} + SelectLanguageDrawer { + id: selectLanguageDrawer + } + LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index a5445754e..ddf9f2bac 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index ad8524f59..1cf8a1a52 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -17,8 +17,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 1c989c0cd..c51f9092a 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index aa882aa5a..7b6dce682 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -42,14 +42,14 @@ PageType { id: content Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 BackButtonType { } HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 actionButtonImage: "qrc:/images/controls/edit-3.svg" diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 7f2b1688e..4c6a98398 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -25,14 +25,14 @@ PageType { anchors.right: parent.right anchors.topMargin: 20 - anchors.leftMargin: 16 - anchors.rightMargin: 16 BackButtonType { } HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 actionButtonImage: "qrc:/images/controls/plus.svg" diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 2ef38067b..45e4c29ca 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -26,19 +26,18 @@ PageType { anchors.left: parent.left anchors.right: parent.right + spacing: 0 + BackButtonType { Layout.topMargin: 20 - Layout.rightMargin: 16 - Layout.leftMargin: 16 } HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 8 Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n Всё в порядке, если код передал друг." @@ -46,7 +45,7 @@ PageType { Header2TextType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 48 Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 5a2f7d882..487bdbde3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 19f06d5ff..2a6e1909e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -36,8 +36,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 5ca75f6f5..e593393c9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -63,6 +63,8 @@ PageType { id: backButton Layout.topMargin: 20 + Layout.rightMargin: -16 + Layout.leftMargin: -16 } HeaderType { @@ -107,8 +109,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 16 backButtonFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 527df8307..edc443d40 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -42,8 +42,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 spacing: 16 @@ -52,11 +50,23 @@ PageType { width: parent.width } - HeaderType { + Item { width: parent.width + height: header.implicitHeight - headerText: "Протокол подключения" - descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + HeaderType { + id: header + + anchors.fill: parent + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + width: parent.width + + headerText: "Протокол подключения" + descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + } } ListView { @@ -78,8 +88,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: -16 - anchors.leftMargin: -16 LabelWithButtonType { id: container diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index ad91303e8..78ccfa950 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -34,6 +34,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + spacing: 0 Image { id: image diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index d7dbf43de..504645d15 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -24,8 +24,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 @@ -35,6 +33,8 @@ PageType { HeaderType { Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 headerText: qsTr("Connection key") descriptionText: qsTr("A line that starts with vpn://...") @@ -45,6 +45,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 headerText: qsTr("Key") textFieldPlaceholderText: "vpn://" diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index c7b774b48..e1b933023 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -42,8 +42,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 1e3d02e8c..7f567b8df 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -185,8 +185,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 BackButtonType { backButtonImage: "qrc:/images/controls/arrow-left.svg" From 9b07909ed8fe2b7efa2173d2f6fb07a1588807a6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 13:45:11 +0900 Subject: [PATCH 038/131] added FlickableType to PageSettingsServerProtocols and PageSettingsServerServices --- client/translations/amneziavpn_ru.ts | 8 +-- .../protocolSettingsController.cpp | 20 +++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../Pages2/PageSettingsServerProtocols.qml | 59 ++++++++++++------- .../qml/Pages2/PageSettingsServerServices.qml | 59 ++++++++++++------- .../qml/Pages2/PageSetupWizardProtocols.qml | 26 ++++---- 6 files changed, 103 insertions(+), 71 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index b1e7817f4..89ca3dcd4 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -3114,22 +3114,22 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull main - + It's public key. Private key required - + Ssh log - + App log - + Wrap words diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp index 48414021c..11b6a904e 100644 --- a/client/ui/controllers/protocolSettingsController.cpp +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -1,19 +1,15 @@ #include "protocolSettingsController.h" -ProtocolSettingsController::ProtocolSettingsController( - const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) -{} +ProtocolSettingsController::ProtocolSettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ +} QByteArray ProtocolSettingsController::getOpenVpnConfig() { - auto containerIndex = m_containersModel->index( - m_containersModel->getCurrentlyProcessedContainerIndex()); + auto containerIndex = m_containersModel->index(m_containersModel->getCurrentlyProcessedContainerIndex()); auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); + return QByteArray(); } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 319399dc4..5db3ae961 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -50,7 +50,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() //todo make it property? + property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() LabelWithButtonType { visible: content.isServerWithWriteAccess diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 2b67afca3..21401bf5b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -20,30 +20,45 @@ PageType { property var installedProtocolsCount - SettingsContainersListView { - id: settingsContainersListView - Connections { - target: ServersModel + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight - function onCurrentlyProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } - - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() - } - root.installedProtocolsCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - } - - Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 282e7e9ec..4f8326511 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -20,30 +20,45 @@ PageType { property var installedServicesCount - SettingsContainersListView { - id: settingsContainersListView - Connections { - target: ServersModel + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight - function onCurrentlyProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } - - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() - } - root.installedServicesCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - } - - Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index edc443d40..f343fabfa 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -26,15 +26,27 @@ PageType { roleName: "isSupported" value: true } - ] } + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + FlickableType { id: fl - anchors.top: parent.top + anchors.top: backButton.top anchors.bottom: parent.bottom - contentHeight: content.height + contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin Column { id: content @@ -42,13 +54,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - spacing: 16 - - BackButtonType { - width: parent.width - } + anchors.bottomMargin: 20 Item { width: parent.width From 35660ff5e75eb7bc6d31e93a09b7a7cf0988d51d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 18:14:47 +0300 Subject: [PATCH 039/131] speed optimization of ui on windows --- .../protocolSettingsController.cpp | 1 + client/ui/models/servers_model.cpp | 43 +++++++++++++------ client/ui/models/servers_model.h | 4 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 5 --- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp index 48414021c..97d39d5d5 100644 --- a/client/ui/controllers/protocolSettingsController.cpp +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -16,4 +16,5 @@ QByteArray ProtocolSettingsController::getOpenVpnConfig() auto containerIndex = m_containersModel->index( m_containersModel->getCurrentlyProcessedContainerIndex()); auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); + return QByteArray(); } diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b427f15bd..866edca40 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -5,7 +5,7 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); - m_currenlyProcessedServerIndex = m_defaultServerIndex; + m_currentlyProcessedServerIndex = m_defaultServerIndex; } int ServersModel::rowCount(const QModelIndex &parent) const @@ -44,6 +44,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } + qDebug() << "d"; + const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { @@ -55,12 +57,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return description; } case HostNameRole: return server.value(config_key::hostName).toString(); - case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); - case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; + case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); + case CredentialsLoginRole: return serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_currentlyProcessedServerIndex; case HasWriteAccessRole: { - auto credentials = m_settings->serverCredentials(index.row()); + auto credentials = serverCredentials(index.row()); return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); } case ContainsAmneziaDnsRole: { @@ -97,28 +99,28 @@ const int ServersModel::getServersCount() void ServersModel::setCurrentlyProcessedServerIndex(const int index) { - m_currenlyProcessedServerIndex = index; - emit currentlyProcessedServerIndexChanged(m_currenlyProcessedServerIndex); + m_currentlyProcessedServerIndex = index; + emit currentlyProcessedServerIndexChanged(m_currentlyProcessedServerIndex); } int ServersModel::getCurrentlyProcessedServerIndex() { - return m_currenlyProcessedServerIndex; + return m_currentlyProcessedServerIndex; } bool ServersModel::isDefaultServerCurrentlyProcessed() { - return m_defaultServerIndex == m_currenlyProcessedServerIndex; + return m_defaultServerIndex == m_currentlyProcessedServerIndex; } bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); } bool ServersModel::isDefaultServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) @@ -132,12 +134,12 @@ void ServersModel::addServer(const QJsonObject &server) void ServersModel::removeServer() { beginResetModel(); - m_settings->removeServer(m_currenlyProcessedServerIndex); + m_settings->removeServer(m_currentlyProcessedServerIndex); m_servers = m_settings->serversArray(); - if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { + if (m_settings->defaultServerIndex() == m_currentlyProcessedServerIndex) { setDefaultServerIndex(0); - } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { + } else if (m_settings->defaultServerIndex() > m_currentlyProcessedServerIndex) { setDefaultServerIndex(m_settings->defaultServerIndex() - 1); } @@ -167,3 +169,16 @@ QHash ServersModel::roleNames() const roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; return roles; } + +ServerCredentials ServersModel::serverCredentials(int index) const +{ + const QJsonObject &s = m_servers.at(index).toObject(); + + ServerCredentials credentials; + credentials.hostName = s.value(config_key::hostName).toString(); + credentials.userName = s.value(config_key::userName).toString(); + credentials.secretData = s.value(config_key::password).toString(); + credentials.port = s.value(config_key::port).toInt(); + + return credentials; +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 0ec78e7eb..f149d85b3 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -58,12 +58,14 @@ signals: void defaultServerIndexChanged(); private: + ServerCredentials serverCredentials(int index) const; + QJsonArray m_servers; std::shared_ptr m_settings; int m_defaultServerIndex; - int m_currenlyProcessedServerIndex; + int m_currentlyProcessedServerIndex; }; #endif // SERVERSMODEL_H diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 7f2411fba..c833ca27d 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -18,11 +18,6 @@ RadioButton { property string textColor: "#D7D8DB" property string selectedTextColor: "#FBB26A" - property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) - property string selectedBorderColor: "#FBB26A" - property string defaultBodredColor: "transparent" - property int borderWidth: 0 - property string imageSource property bool showImage From 43261f8469ae9ab7327060de65ad983e41009e59 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 4 Jul 2023 09:58:19 +0900 Subject: [PATCH 040/131] added busy indicator component - replaced the image of the connect button with native rendering --- client/resources.qrc | 1 + client/translations/amneziavpn_ru.ts | 6 +- client/ui/controllers/pageController.h | 1 + client/ui/models/servers_model.cpp | 2 - client/ui/qml/Components/ConnectButton.qml | 117 +++++++++++++----- .../qml/Components/ShareConnectionDrawer.qml | 19 +-- client/ui/qml/Controls2/BusyIndicatorType.qml | 68 ++++++++++ .../ui/qml/Pages2/PageSettingsServerData.qml | 2 + client/ui/qml/Pages2/PageShare.qml | 11 +- client/ui/qml/Pages2/PageStart.qml | 12 ++ 10 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 client/ui/qml/Controls2/BusyIndicatorType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 357cb40c8..b7bdd4633 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -270,5 +270,6 @@ ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml ui/qml/Components/SelectLanguageDrawer.qml + ui/qml/Controls2/BusyIndicatorType.qml diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 89ca3dcd4..197c9d409 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1275,17 +1275,17 @@ And if you don't like the app, all the more support it - the donation will PageSettingsApplication - + Application - + Language - + Reset settings and remove all data from the application diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 384d3c8d5..f67da2750 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -63,6 +63,7 @@ signals: void replaceStartPage(); void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); + void showBusyIndicator(bool visible); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 866edca40..1bacdd455 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -44,8 +44,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } - qDebug() << "d"; - const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 1f27973ad..f8e29c476 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -1,12 +1,21 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Shapes +import Qt5Compat.GraphicalEffects import ConnectionState 1.0 Button { id: root + property string defaultButtonColor: "#D7D8DB" + property string progressButtonColor: "#D7D8DB" + property string connectedButtonColor: "#FBB26A" + + implicitWidth: 190 + implicitHeight: 190 + Connections { target: ConnectionController @@ -17,48 +26,98 @@ Button { text: ConnectionController.connectionStateText - enabled: !ConnectionController.isConnectionInProgress +// enabled: !ConnectionController.isConnectionInProgress background: Item { - clip: true + implicitWidth: parent.width + implicitHeight: parent.height + transformOrigin: Item.Center - implicitHeight: border.implicitHeight - implicitWidth: border.implicitWidth + Shape { + id: backgroundCircle + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + layer.effect: DropShadow { + anchors.fill: backgroundCircle + horizontalOffset: 0 + verticalOffset: 0 + radius: 10 + samples: 25 + color: "#FBB26A" + source: backgroundCircle + } - Image { - id: border + ShapePath { + fillColor: "transparent" + strokeColor: { + if (ConnectionController.isConnectionInProgress) { + return Qt.rgba(251/255, 178/255, 106/255, 1) + } else if (ConnectionController.isConnected) { + return connectedButtonColor + } else { + return defaultButtonColor + } + } + strokeWidth: 3 + capStyle: ShapePath.RoundCap - source: { - if (ConnectionController.isConnectionInProgress) { - return "/images/connectionProgress.svg" - } else if (ConnectionController.isConnected) { - return "/images/connectionOff.svg" - } else { - return "/images/connectionOn.svg" + PathAngleArc { + centerX: backgroundCircle.width / 2 + centerY: backgroundCircle.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 0 + sweepAngle: 360 + } + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + visible: ConnectionController.isConnectionInProgress + + ShapePath { + fillColor: "transparent" + strokeColor: "#D7D8DB" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 245 + sweepAngle: -180 } } RotationAnimator { - id: connectionProccess - - target: border + target: shape running: ConnectionController.isConnectionInProgress from: 0 to: 360 loops: Animation.Infinite - duration: 1250 + duration: 1000 } - - Behavior on source { - PropertyAnimation { duration: 200 } - } - } - - MouseArea { - anchors.fill: parent - - cursorShape: Qt.PointingHandCursor - enabled: false } } @@ -69,7 +128,7 @@ Button { font.weight: 700 font.pixelSize: 20 - color: "#D7D8DB" + color: ConnectionController.isConnected ? connectedButtonColor : defaultButtonColor text: root.text horizontalAlignment: Text.AlignHCenter diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 99e172cc9..b677c5b1a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -17,6 +17,7 @@ DrawerType { property alias headerText: header.headerText property alias configContentHeaderText: configContentHeader.headerText + property alias contentVisible: content.visible width: parent.width height: parent.height * 0.9 @@ -24,8 +25,18 @@ DrawerType { Item { anchors.fill: parent - FlickableType { + Header2Type { + id: header anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + FlickableType { + anchors.top: header.bottom anchors.bottom: parent.bottom contentHeight: content.height + 32 @@ -36,15 +47,9 @@ DrawerType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 anchors.leftMargin: 16 anchors.rightMargin: 16 - Header2Type { - id: header - Layout.fillWidth: true - } - BasicButtonType { Layout.fillWidth: true Layout.topMargin: 16 diff --git a/client/ui/qml/Controls2/BusyIndicatorType.qml b/client/ui/qml/Controls2/BusyIndicatorType.qml new file mode 100644 index 000000000..7e92998c1 --- /dev/null +++ b/client/ui/qml/Controls2/BusyIndicatorType.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + anchors.centerIn: parent + + modal: true + closePolicy: Popup.NoAutoClose + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + BusyIndicator { + id: busyIndicator + + visible: true + running: true + + contentItem: Item { + implicitWidth: 46 + implicitHeight: 46 + transformOrigin: Item.Center + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + ShapePath { + fillColor: "transparent" + strokeColor: "#787878" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 18 + radiusY: 18 + startAngle: 225 + sweepAngle: -90 + } + } + RotationAnimator { + target: shape + running: busyIndicator.visible && busyIndicator.running + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 5db3ae961..c5af47e0e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -88,7 +88,9 @@ PageType { descriptionText: qsTr("Добавим их в приложение, если они не отображались") clickedFunction: function() { + PageController.showBusyIndicator(true) InstallController.scanServerForInstalledContainers() + PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 7f567b8df..decd461a4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -20,11 +20,21 @@ PageType { target: ExportController function onGenerateConfig(isFullAccess) { + shareConnectionDrawer.open() + shareConnectionDrawer.contentVisible = false + PageController.showBusyIndicator(true) if (isFullAccess) { ExportController.generateFullAccessConfig() } else { ExportController.generateConnectionConfig() } + PageController.showBusyIndicator(false) + shareConnectionDrawer.contentVisible = true + } + + function onExportErrorOccurred(errorMessage) { + shareConnectionDrawer.close() + PageController.showErrorMessage(errorMessage) } } @@ -328,7 +338,6 @@ PageType { } else { ExportController.generateConfig(true) } - shareConnectionDrawer.visible = true } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 76450b165..09bebaa10 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -29,6 +29,12 @@ PageType { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + tabBarStackView.enabled = !visible + tabBar.enabled = !visible + } } StackViewType { @@ -130,4 +136,10 @@ PageType { id: popupErrorMessage } } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } } From a97417fd38cfaea7cbcafdc30ccb9b85d6eff6a1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 5 Jul 2023 10:15:38 +0900 Subject: [PATCH 041/131] added config export in native format openvpn and wireguard --- client/ui/controllers/exportController.cpp | 101 ++++++++++++++++-- client/ui/controllers/exportController.h | 16 +-- client/ui/qml/Components/ConnectButton.qml | 2 +- .../qml/Components/ShareConnectionDrawer.qml | 19 ++-- client/ui/qml/Pages2/PageShare.qml | 38 ++++--- 5 files changed, 137 insertions(+), 39 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 04264624b..5cd9a83db 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -9,6 +9,8 @@ #include #include +#include "configurators/openvpn_configurator.h" +#include "configurators/wireguard_configurator.h" #include "qrcodegen.hpp" #include "core/errorstrings.h" @@ -27,14 +29,17 @@ ExportController::ExportController(const QSharedPointer &serversMo void ExportController::generateFullAccessConfig() { + clearPreviousConfig(); + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); QJsonObject config = m_settings->server(serverIndex); QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + m_rawConfig = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + m_formattedConfig = m_rawConfig; m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -42,6 +47,8 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { + clearPreviousConfig(); + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); @@ -81,17 +88,86 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + m_rawConfig = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + m_formattedConfig = m_rawConfig; m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } -QString ExportController::getAmneziaCode() +void ExportController::generateOpenVpnConfig() { - return m_amneziaCode; + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = + m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); + + m_rawConfig = config; + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_formattedConfig.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +void ExportController::generateWireGuardConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, + &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); + + m_rawConfig = config; + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_formattedConfig.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +QString ExportController::getFormattedConfig() +{ + return m_formattedConfig; } QList ExportController::getQrCodes() @@ -117,7 +193,7 @@ void ExportController::saveFile() QFile save(fileName.toLocalFile()); save.open(QIODevice::WriteOnly); - save.write(m_amneziaCode.toUtf8()); + save.write(m_rawConfig.toUtf8()); save.close(); QFileInfo fi(fileName.toLocalFile()); @@ -154,3 +230,10 @@ int ExportController::getQrCodesCount() { return m_qrCodes.size(); } + +void ExportController::clearPreviousConfig() +{ + m_rawConfig.clear(); + m_formattedConfig.clear(); + m_qrCodes.clear(); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 63997efd8..85144978b 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -14,24 +14,25 @@ public: explicit ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, - const std::shared_ptr &configurator, - QObject *parent = nullptr); + const std::shared_ptr &configurator, QObject *parent = nullptr); Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) - Q_PROPERTY(QString amneziaCode READ getAmneziaCode NOTIFY exportConfigChanged) + Q_PROPERTY(QString formattedConfig READ getFormattedConfig NOTIFY exportConfigChanged) public slots: void generateFullAccessConfig(); void generateConnectionConfig(); + void generateOpenVpnConfig(); + void generateWireGuardConfig(); - QString getAmneziaCode(); + QString getFormattedConfig(); QList getQrCodes(); void saveFile(); signals: - void generateConfig(bool isFullAccess); + void generateConfig(int type); void exportErrorOccurred(QString errorMessage); void exportConfigChanged(); @@ -42,12 +43,15 @@ private: int getQrCodesCount(); + void clearPreviousConfig(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; std::shared_ptr m_configurator; - QString m_amneziaCode; + QString m_rawConfig; + QString m_formattedConfig; QList m_qrCodes; }; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index f8e29c476..85cc345af 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -55,7 +55,7 @@ Button { fillColor: "transparent" strokeColor: { if (ConnectionController.isConnectionInProgress) { - return Qt.rgba(251/255, 178/255, 106/255, 1) + return "#261E1A" } else if (ConnectionController.isConnected) { return connectedButtonColor } else { diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index b677c5b1a..d5ed1029d 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -137,7 +137,7 @@ DrawerType { Layout.topMargin: 16 } - TextField { + TextArea { Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 @@ -147,16 +147,17 @@ DrawerType { height: 24 color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" font.pixelSize: 16 font.weight: Font.Medium font.family: "PT Root UI VF" - text: ExportController.amneziaCode + text: ExportController.formattedConfig wrapMode: Text.Wrap - readOnly: true background: Rectangle { color: "transparent" } @@ -186,11 +187,13 @@ DrawerType { running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { - index++ - if (index >= ExportController.qrCodesCount) { - index = 0 + if (ExportController.qrCodesCount > 0) { + index++ + if (index >= ExportController.qrCodesCount) { + index = 0 + } + parent.source = ExportController.qrCodes[index] } - parent.source = ExportController.qrCodes[index] } } @@ -205,6 +208,8 @@ DrawerType { Layout.topMargin: 24 Layout.bottomMargin: 32 + visible: ExportController.qrCodesCount > 0 + horizontalAlignment: Text.AlignHCenter text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"") } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index decd461a4..c83b59d5e 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -16,18 +16,30 @@ import "../Components" PageType { id: root + enum ConfigType { + AmneziaConnection, + AmenziaFullAccess, + OpenVpn, + WireGuard + } + Connections { target: ExportController - function onGenerateConfig(isFullAccess) { + function onGenerateConfig(type) { shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) - if (isFullAccess) { - ExportController.generateFullAccessConfig() - } else { - ExportController.generateConnectionConfig() + + console.log(type) + + switch (type) { + case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; + case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; + case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; + case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; } + PageController.showBusyIndicator(false) shareConnectionDrawer.contentVisible = true } @@ -46,23 +58,17 @@ PageType { QtObject { id: amneziaConnectionFormat property string name: qsTr("For the AmnesiaVPN app") - property var func: function() { - ExportController.generateConfig(false) - } + property var type: PageShare.ConfigType.AmneziaConnection } QtObject { id: openVpnConnectionFormat property string name: qsTr("OpenVpn native format") - property var func: function() { - console.log("Item 3 clicked") - } + property var type: PageShare.ConfigType.OpenVpn } QtObject { id: wireGuardConnectionFormat property string name: qsTr("WireGuard native format") - property var func: function() { - console.log("Item 3 clicked") - } + property var type: PageShare.ConfigType.WireGuard } FlickableType { @@ -334,9 +340,9 @@ PageType { onClicked: { if (accessTypeSelector.currentIndex === 0) { - root.connectionTypesModel[accessTypeSelector.currentIndex].func() + ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } else { - ExportController.generateConfig(true) + ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess) } } } From c13b9754eb2fb90f0e00547004d03bb3f7d2fee7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 13 Jul 2023 11:29:26 +0900 Subject: [PATCH 042/131] added protocol settings pages and models for openvpn, cloak and shadowsocks --- client/CMakeLists.txt | 4 +- client/amnezia_application.cpp | 86 ++-- client/amnezia_application.h | 20 + client/protocols/protocols_defs.h | 406 ++++++++------- client/resources.qrc | 4 +- client/ui/controllers/installController.cpp | 27 +- client/ui/controllers/installController.h | 4 + client/ui/controllers/pageController.h | 8 +- client/ui/models/containers_model.cpp | 38 +- client/ui/models/containers_model.h | 4 + client/ui/models/languageModel.cpp | 10 +- .../ui/models/protocols/cloakConfigModel.cpp | 81 +++ client/ui/models/protocols/cloakConfigModel.h | 40 ++ .../ui/models/protocols/ikev2ConfigModel.cpp | 76 +++ client/ui/models/protocols/ikev2ConfigModel.h | 39 ++ .../models/protocols/openvpnConfigModel.cpp | 152 ++++++ .../ui/models/protocols/openvpnConfigModel.h | 52 ++ .../protocols/shadowsocksConfigModel.cpp | 76 +++ .../models/protocols/shadowsocksConfigModel.h | 39 ++ .../models/protocols/wireguardConfigModel.cpp | 70 +++ .../models/protocols/wireguardConfigModel.h | 39 ++ client/ui/models/protocols_model.cpp | 75 +-- client/ui/models/protocols_model.h | 30 +- client/ui/models/servers_model.cpp | 1 + .../ui/pages_logic/GeneralSettingsLogic.cpp | 14 +- .../ui/pages_logic/ServerContainersLogic.cpp | 62 +-- .../Components/Protocols/OpenVpnSettings.qml | 147 ------ .../Components/SettingsContainersListView.qml | 29 +- .../qml/Components/ShareConnectionDrawer.qml | 8 +- .../qml/Components/TransportProtoSelector.qml | 4 + client/ui/qml/Controls2/CheckBoxType.qml | 46 +- client/ui/qml/Controls2/DropDownType.qml | 33 +- .../qml/Controls2/HorizontalRadioButton.qml | 25 +- .../qml/Controls2/TextFieldWithHeaderType.qml | 30 +- client/ui/qml/Pages2/PageHome.qml | 1 - .../qml/Pages2/PageProtocolCloakSettings.qml | 176 +++++++ .../Pages2/PageProtocolOpenVpnSettings.qml | 465 ++++++++++++++++++ .../PageProtocolShadowSocksSettings.qml | 162 ++++++ .../qml/Pages2/PageSettingsServerProtocol.qml | 93 +++- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 - .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 24 +- 42 files changed, 2130 insertions(+), 576 deletions(-) create mode 100644 client/ui/models/protocols/cloakConfigModel.cpp create mode 100644 client/ui/models/protocols/cloakConfigModel.h create mode 100644 client/ui/models/protocols/ikev2ConfigModel.cpp create mode 100644 client/ui/models/protocols/ikev2ConfigModel.h create mode 100644 client/ui/models/protocols/openvpnConfigModel.cpp create mode 100644 client/ui/models/protocols/openvpnConfigModel.h create mode 100644 client/ui/models/protocols/shadowsocksConfigModel.cpp create mode 100644 client/ui/models/protocols/shadowsocksConfigModel.h create mode 100644 client/ui/models/protocols/wireguardConfigModel.cpp create mode 100644 client/ui/models/protocols/wireguardConfigModel.h delete mode 100644 client/ui/qml/Components/Protocols/OpenVpnSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolCloakSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ad9a4cf43..be1c3b923 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -129,8 +129,8 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/ file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp) file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7c3b466b1..436dfc3fc 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -79,38 +79,12 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); // - m_containersModel.reset(new ContainersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); - - m_serversModel.reset(new ServersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); - connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), - &ContainersModel::setCurrentlyProcessedServerIndex); - - m_languageModel.reset(new LanguageModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); - connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - - m_pageController.reset(new PageController(m_serversModel)); - m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); - - m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); - m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - - m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + initModels(); + initControllers(); // @@ -244,3 +218,59 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } + +void AmneziaApplication::initModels() +{ + m_containersModel.reset(new ContainersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); + + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + + m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); + m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + + m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); + m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + + m_cloakConfigModel.reset(new CloakConfigModel(this)); + m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + + m_wireguardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireguardConfigModel.get()); + +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); + m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); +#endif +} + +void AmneziaApplication::initControllers() +{ + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + m_pageController.reset(new PageController(m_serversModel)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); +} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 4e9cc3489..fabc78188 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -21,6 +21,14 @@ #include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" +#include "ui/models/protocols/cloakConfigModel.h" +#ifdef Q_OS_WINDOWS + #include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/shadowsocksConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -56,6 +64,9 @@ public: QQmlApplicationEngine *qmlEngine() const; private: + void initModels(); + void initControllers(); + QQmlApplicationEngine *m_engine {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; @@ -69,6 +80,15 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; QScopedPointer m_languageModel; + QScopedPointer m_protocolsModel; + + QScopedPointer m_openVpnConfigModel; + QScopedPointer m_shadowSocksConfigModel; + QScopedPointer m_cloakConfigModel; + QScopedPointer m_wireguardConfigModel; +#ifdef Q_OS_WINDOWS + QScopedPointer m_ikev2ConfigModel; +#endif QSharedPointer m_vpnConnection; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 1f890f4cf..73c2abdf6 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -1,229 +1,223 @@ #ifndef PROTOCOLS_DEFS_H #define PROTOCOLS_DEFS_H -#include #include +#include #include -namespace amnezia { -namespace config_key { - -// Json config strings -constexpr char hostName[] = "hostName"; -constexpr char userName[] = "userName"; -constexpr char password[] = "password"; -constexpr char port[] = "port"; -constexpr char local_port[] = "local_port"; - -constexpr char dns1[] = "dns1"; -constexpr char dns2[] = "dns2"; - - -constexpr char description[] = "description"; -constexpr char cert[] = "cert"; -constexpr char config[] = "config"; - - -constexpr char containers[] = "containers"; -constexpr char container[] = "container"; -constexpr char defaultContainer[] = "defaultContainer"; - -constexpr char vpnproto[] = "protocol"; -constexpr char protocols[] = "protocols"; - -constexpr char remote[] = "remote"; -constexpr char transport_proto[] = "transport_proto"; -constexpr char cipher[] = "cipher"; -constexpr char hash[] = "hash"; -constexpr char ncp_disable[] = "ncp_disable"; -constexpr char tls_auth[] = "tls_auth"; - -constexpr char client_priv_key[] = "client_priv_key"; -constexpr char client_pub_key[] = "client_pub_key"; -constexpr char server_priv_key[] = "server_priv_key"; -constexpr char server_pub_key[] = "server_pub_key"; -constexpr char psk_key[] = "psk_key"; - -constexpr char client_ip[] = "client_ip"; // internal ip address - -constexpr char site[] = "site"; -constexpr char block_outside_dns[] = "block_outside_dns"; - -constexpr char subnet_address[] = "subnet_address"; -constexpr char subnet_mask[] = "subnet_mask"; -constexpr char subnet_cidr[] = "subnet_cidr"; - -constexpr char additional_client_config[] = "additional_client_config"; -constexpr char additional_server_config[] = "additional_server_config"; - -// proto config keys -constexpr char last_config[] = "last_config"; - -constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; - -constexpr char openvpn[] = "openvpn"; -constexpr char wireguard[] = "wireguard"; - -} - -namespace protocols { - -namespace dns { -constexpr char amneziaDnsIp[] = "172.29.172.254"; -} - -namespace openvpn { -constexpr char defaultSubnetAddress[] = "10.8.0.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; -constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; -constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; -constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; -constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; -constexpr char defaultPort[] = "1194"; -constexpr char defaultTransportProto[] = "udp"; -constexpr char defaultCipher[] = "AES-256-GCM"; -constexpr char defaultHash[] = "SHA512"; -constexpr bool defaultBlockOutsideDns = true; -constexpr bool defaultNcpDisable = false; -constexpr bool defaultTlsAuth = true; -constexpr char ncpDisableString[] = "ncp-disable"; -constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; - -constexpr char defaultAdditionalClientConfig[] = ""; -constexpr char defaultAdditionalServerConfig[] = ""; -} - -namespace shadowsocks { -constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; -constexpr char defaultPort[] = "6789"; -constexpr char defaultLocalProxyPort[] = "8585"; -constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; -} - -namespace cloak { -constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; -constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; -constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; -constexpr char defaultPort[] = "443"; -constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; -constexpr char defaultCipher[] = "chacha20-poly1305"; - -} - -namespace wireguard { -constexpr char defaultSubnetAddress[] = "10.8.1.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char defaultPort[] = "51820"; -constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; -constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; -constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; - -} - -namespace sftp { -constexpr char defaultUserName[] = "sftp_user"; - -} // namespace sftp - -} // namespace protocols - -namespace ProtocolEnumNS { -Q_NAMESPACE - -enum TransportProto { - Udp, - Tcp -}; -Q_ENUM_NS(TransportProto) - -enum Proto { - Any = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ikev2, - L2tp, - - // non-vpn - TorWebSite, - Dns, - FileShare, - Sftp -}; -Q_ENUM_NS(Proto) - -enum ServiceType { - None = 0, - Vpn, - Other -}; -Q_ENUM_NS(ServiceType) -} // namespace ProtocolEnumNS - -using namespace ProtocolEnumNS; - -class ProtocolProps : public QObject +namespace amnezia { - Q_OBJECT + namespace config_key + { -public: - Q_INVOKABLE static QList allProtocols(); + // Json config strings + constexpr char hostName[] = "hostName"; + constexpr char userName[] = "userName"; + constexpr char password[] = "password"; + constexpr char port[] = "port"; + constexpr char local_port[] = "local_port"; - // spelling may differ for various protocols - TCP for OpenVPN, tcp for others - Q_INVOKABLE static TransportProto transportProtoFromString(QString p); - Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + constexpr char dns1[] = "dns1"; + constexpr char dns2[] = "dns2"; - Q_INVOKABLE static Proto protoFromString(QString p); - Q_INVOKABLE static QString protoToString(Proto p); + constexpr char description[] = "description"; + constexpr char cert[] = "cert"; + constexpr char config[] = "config"; - Q_INVOKABLE static QMap protocolHumanNames(); - Q_INVOKABLE static QMap protocolDescriptions(); + constexpr char containers[] = "containers"; + constexpr char container[] = "container"; + constexpr char defaultContainer[] = "defaultContainer"; - Q_INVOKABLE static ServiceType protocolService(Proto p); + constexpr char vpnproto[] = "protocol"; + constexpr char protocols[] = "protocols"; - Q_INVOKABLE static int defaultPort(Proto p); - Q_INVOKABLE static bool defaultPortChangeable(Proto p); + constexpr char remote[] = "remote"; + constexpr char transport_proto[] = "transport_proto"; + constexpr char cipher[] = "cipher"; + constexpr char hash[] = "hash"; + constexpr char ncp_disable[] = "ncp_disable"; + constexpr char tls_auth[] = "tls_auth"; - Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); - Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + constexpr char client_priv_key[] = "client_priv_key"; + constexpr char client_pub_key[] = "client_pub_key"; + constexpr char server_priv_key[] = "server_priv_key"; + constexpr char server_pub_key[] = "server_pub_key"; + constexpr char psk_key[] = "psk_key"; + constexpr char client_ip[] = "client_ip"; // internal ip address - Q_INVOKABLE static QString key_proto_config_data(Proto p); - Q_INVOKABLE static QString key_proto_config_path(Proto p); + constexpr char site[] = "site"; + constexpr char block_outside_dns[] = "block_outside_dns"; -}; + constexpr char subnet_address[] = "subnet_address"; + constexpr char subnet_mask[] = "subnet_mask"; + constexpr char subnet_cidr[] = "subnet_cidr"; -static void declareQmlProtocolEnum() { - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ProtocolEnum", - "Error: only enums" - ); + constexpr char additional_client_config[] = "additional_client_config"; + constexpr char additional_server_config[] = "additional_server_config"; - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "TransportProto", - "Error: only enums" - ); + // proto config keys + constexpr char last_config[] = "last_config"; - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ServiceType", - "Error: only enums" - ); -} + constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + + constexpr char openvpn[] = "openvpn"; + constexpr char wireguard[] = "wireguard"; + constexpr char shadowsocks[] = "shadowsocks"; + constexpr char cloak[] = "cloak"; + + } + + namespace protocols + { + + namespace dns + { + constexpr char amneziaDnsIp[] = "172.29.172.254"; + } + + namespace openvpn + { + constexpr char defaultSubnetAddress[] = "10.8.0.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; + constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; + constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; + constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; + constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; + constexpr char defaultPort[] = "1194"; + constexpr char defaultTransportProto[] = "udp"; + constexpr char defaultCipher[] = "AES-256-GCM"; + constexpr char defaultHash[] = "SHA512"; + constexpr bool defaultBlockOutsideDns = true; + constexpr bool defaultNcpDisable = false; + constexpr bool defaultTlsAuth = true; + constexpr char ncpDisableString[] = "ncp-disable"; + constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; + + constexpr char defaultAdditionalClientConfig[] = ""; + constexpr char defaultAdditionalServerConfig[] = ""; + } + + namespace shadowsocks + { + constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; + constexpr char defaultPort[] = "6789"; + constexpr char defaultLocalProxyPort[] = "8585"; + constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; + } + + namespace cloak + { + constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; + constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; + constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; + constexpr char defaultPort[] = "443"; + constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; + constexpr char defaultCipher[] = "chacha20-poly1305"; + + } + + namespace wireguard + { + constexpr char defaultSubnetAddress[] = "10.8.1.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char defaultPort[] = "51820"; + constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; + + } + + namespace sftp + { + constexpr char defaultUserName[] = "sftp_user"; + + } // namespace sftp + + } // namespace protocols + + namespace ProtocolEnumNS + { + Q_NAMESPACE + + enum TransportProto { + Udp, + Tcp + }; + Q_ENUM_NS(TransportProto) + + enum Proto { + Any = 0, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Ikev2, + L2tp, + + // non-vpn + TorWebSite, + Dns, + FileShare, + Sftp + }; + Q_ENUM_NS(Proto) + + enum ServiceType { + None = 0, + Vpn, + Other + }; + Q_ENUM_NS(ServiceType) + } // namespace ProtocolEnumNS + + using namespace ProtocolEnumNS; + + class ProtocolProps : public QObject + { + Q_OBJECT + + public: + Q_INVOKABLE static QList allProtocols(); + + // spelling may differ for various protocols - TCP for OpenVPN, tcp for others + Q_INVOKABLE static TransportProto transportProtoFromString(QString p); + Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + + Q_INVOKABLE static Proto protoFromString(QString p); + Q_INVOKABLE static QString protoToString(Proto p); + + Q_INVOKABLE static QMap protocolHumanNames(); + Q_INVOKABLE static QMap protocolDescriptions(); + + Q_INVOKABLE static ServiceType protocolService(Proto p); + + Q_INVOKABLE static int defaultPort(Proto p); + Q_INVOKABLE static bool defaultPortChangeable(Proto p); + + Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); + Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + + Q_INVOKABLE static QString key_proto_config_data(Proto p); + Q_INVOKABLE static QString key_proto_config_path(Proto p); + }; + + static void declareQmlProtocolEnum() + { + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "ProtocolEnum", + "Error: only enums"); + + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "TransportProto", + "Error: only enums"); + + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "ServiceType", + "Error: only enums"); + } } // namespace amnezia diff --git a/client/resources.qrc b/client/resources.qrc index b7bdd4633..dd75555f5 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -250,7 +250,6 @@ ui/qml/Pages2/PageDeinstalling.qml ui/qml/Controls2/BackButtonType.qml ui/qml/Pages2/PageSettingsServerProtocol.qml - ui/qml/Components/Protocols/OpenVpnSettings.qml ui/qml/Components/TransportProtoSelector.qml ui/qml/Controls2/ListViewType.qml images/controls/radio-button.svg @@ -271,5 +270,8 @@ ui/qml/Filters/ContainersModelFilters.qml ui/qml/Components/SelectLanguageDrawer.qml ui/qml/Controls2/BusyIndicatorType.qml + ui/qml/Pages2/PageProtocolOpenVpnSettings.qml + ui/qml/Pages2/PageProtocolShadowSocksSettings.qml + ui/qml/Pages2/PageProtocolCloakSettings.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 1809e082c..6fc5f4e96 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -69,7 +69,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co m_serversModel->addServer(server); m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit installServerFinished(isInstalledContainerFound); + emit installServerFinished(false); // todo incorrect notification about found containers return; } @@ -108,7 +108,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject } } - emit installContainerFinished(isInstalledContainerFound); + emit installContainerFinished(false); // todo incorrect notification about found containers return; } @@ -162,6 +162,29 @@ void InstallController::scanServerForInstalledContainers() emit installationErrorOccurred(errorString(errorCode)); } +void InstallController::updateContainer(QJsonObject config) +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + auto modelIndex = m_containersModel->index(container); + QJsonObject oldContainerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + + ServerController serverController(m_settings); + + auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + if (errorCode == ErrorCode::NoError) { + m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); + emit updateContainerFinished(); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + QRegularExpression InstallController::ipAddressPortRegExp() { return Utils::ipAddressPortRegExp(); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 6b01e102c..75f4fdefb 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -24,12 +24,16 @@ public slots: void scanServerForInstalledContainers(); + void updateContainer(QJsonObject config); + QRegularExpression ipAddressPortRegExp(); signals: void installContainerFinished(bool isInstalledContainerFound); void installServerFinished(bool isInstalledContainerFound); + void updateContainerFinished(); + void scanServerFinished(bool isInstalledContainerFound); void installationErrorOccurred(QString errorMessage); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f67da2750..468068b8b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,7 +36,13 @@ namespace PageLoader PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - PageSetupWizardViewConfig + PageSetupWizardViewConfig, + + PageProtocolOpenVpnSettings, + PageProtocolShadowSocksSettings, + PageProtocolCloakSettings, + PageProtocolWireGuardSettings, + PageProtocolIKev2Settings }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index ecacc184a..7b6ffaeea 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -29,6 +29,11 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i case ConfigRole: { m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + if (m_defaultContainerIndex != DockerContainer::None) { + break; + } else if (ContainerProps::containerService(container) == ServiceType::Other) { + break; + } } case ServiceTypeRole: // return ContainerProps::containerService(container); @@ -108,6 +113,11 @@ int ContainersModel::getCurrentlyProcessedContainerIndex() return m_currentlyProcessedContainerIndex; } +QString ContainersModel::getCurrentlyProcessedContainerName() +{ + return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); +} + void ContainersModel::removeAllContainers() { @@ -116,14 +126,39 @@ void ContainersModel::removeAllContainers() if (errorCode == ErrorCode::NoError) { beginResetModel(); + m_settings->setContainers(m_currentlyProcessedServerIndex, {}); - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + setData(index(DockerContainer::None, 0), true, IsDefaultRole); endResetModel(); } // todo process errors } +void ContainersModel::removeCurrentlyProcessedContainer() +{ + ServerController serverController(m_settings); + auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); + auto dockerContainer = static_cast(m_currentlyProcessedContainerIndex); + + ErrorCode e = serverController.removeContainer(credentials, dockerContainer); + + beginResetModel(); // todo change to begin remove rows? + m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { + if (m_containers.isEmpty()) { + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + } else { + setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + } + } + endResetModel(); +} + void ContainersModel::clearCachedProfiles() { const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); @@ -150,6 +185,7 @@ QHash ContainersModel::roleNames() const roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; + roles[ConfigRole] = "config"; roles[IsEasySetupContainerRole] = "isEasySetupContainer"; roles[EasySetupHeaderRole] = "easySetupHeader"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 7331ef22a..b79b058ac 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -45,10 +45,14 @@ public slots: QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(const int index); + void setCurrentlyProcessedContainerIndex(int index); int getCurrentlyProcessedContainerIndex(); + QString getCurrentlyProcessedContainerName(); + void removeAllContainers(); + void removeCurrentlyProcessedContainer(); void clearCachedProfiles(); bool isAmneziaDnsContainerInstalled(); diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index e95b2ccf3..76eeb37dd 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -22,14 +22,8 @@ QVariant LanguageModel::data(const QModelIndex &index, int role) const } switch (role) { - case NameRole: { - return m_availableLanguages[index.row()].name; - break; - } - case IndexRole: { - return static_cast(m_availableLanguages[index.row()].index); - break; - } + case NameRole: return m_availableLanguages[index.row()].name; + case IndexRole: return static_cast(m_availableLanguages[index.row()].index); } return QVariant(); } diff --git a/client/ui/models/protocols/cloakConfigModel.cpp b/client/ui/models/protocols/cloakConfigModel.cpp new file mode 100644 index 000000000..203f08b52 --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.cpp @@ -0,0 +1,81 @@ +#include "cloakConfigModel.h" + +#include "protocols/protocols_defs.h" + +CloakConfigModel::CloakConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int CloakConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool CloakConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant CloakConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort); + case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher); + case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite); + } + + return QVariant(); +} + +void CloakConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::cloak).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort)); + + m_protocolConfig.insert(config_key::site, + protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite)); + + endResetModel(); +} + +QJsonObject CloakConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::cloak, m_protocolConfig); + return m_fullConfig; +} + +QHash CloakConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + roles[SiteRole] = "site"; + + return roles; +} diff --git a/client/ui/models/protocols/cloakConfigModel.h b/client/ui/models/protocols/cloakConfigModel.h new file mode 100644 index 000000000..31ff8c53f --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.h @@ -0,0 +1,40 @@ +#ifndef CLOAKCONFIGMODEL_H +#define CLOAKCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class CloakConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole, + SiteRole + }; + + explicit CloakConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // CLOAKCONFIGMODEL_H diff --git a/client/ui/models/protocols/ikev2ConfigModel.cpp b/client/ui/models/protocols/ikev2ConfigModel.cpp new file mode 100644 index 000000000..f22b965c5 --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.cpp @@ -0,0 +1,76 @@ +#include "ikev2ConfigModel.h".h " + +#include "protocols/protocols_defs.h" + +Ikev2ConfigModel::Ikev2ConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int Ikev2ConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool Ikev2ConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant Ikev2ConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void Ikev2ConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject Ikev2ConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash Ikev2ConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/ikev2ConfigModel.h b/client/ui/models/protocols/ikev2ConfigModel.h new file mode 100644 index 000000000..e005f6a4e --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.h @@ -0,0 +1,39 @@ +#ifndef IKEV2CONFIGMODEL_H +#define IKEV2CONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class Ikev2ConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit Ikev2ConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // IKEV2CONFIGMODEL_H diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp new file mode 100644 index 000000000..1b73c987f --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -0,0 +1,152 @@ +#include "openvpnConfigModel.h" + +#include "protocols/protocols_defs.h" + +OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int OpenVpnConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + m_protocolConfig.insert(amnezia::config_key::subnet_address, value.toString()); + break; + case Roles::TransportProtoRole: m_protocolConfig.insert(config_key::transport_proto, value.toString()); break; + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.insert(config_key::ncp_disable, !value.toBool()); break; + case Roles::HashRole: m_protocolConfig.insert(config_key::hash, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::TlsAuthRole: m_protocolConfig.insert(config_key::tls_auth, value.toBool()); break; + case Roles::BlockDnsRole: m_protocolConfig.insert(config_key::block_outside_dns, value.toBool()); break; + case Roles::AdditionalClientCommandsRole: + m_protocolConfig.insert(config_key::additional_client_config, value.toString()); + break; + case Roles::AdditionalServerCommandsRole: + m_protocolConfig.insert(config_key::additional_server_config, value.toString()); + break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + return m_protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress); + case Roles::TransportProtoRole: + return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); + case Roles::AutoNegotiateEncryprionRole: + return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); + case Roles::HashRole: return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); + case Roles::TlsAuthRole: + return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); + case Roles::BlockDnsRole: + return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); + case Roles::AdditionalClientCommandsRole: + return m_protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig); + case Roles::AdditionalServerCommandsRole: + return m_protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig); + case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false; + } + return QVariant(); +} + +void OpenVpnConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = + ContainerProps::containerFromString(config.value(config_key::container).toString()); // todo maybe unused + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); + + m_protocolConfig.insert(config_key::subnet_address, + protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress)); + + QString transportProto; + if (m_container == DockerContainer::OpenVpn) { + transportProto = + protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + } else { + transportProto = "tcp"; + } + + m_protocolConfig.insert(config_key::transport_proto, transportProto); + + m_protocolConfig.insert(config_key::ncp_disable, + protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable)); + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher)); + m_protocolConfig.insert(config_key::hash, + protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash)); + m_protocolConfig.insert(config_key::block_outside_dns, + protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)); + m_protocolConfig.insert( + config_key::tls_auth, + protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); + m_protocolConfig.insert(config_key::additional_client_config, + protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig)); + m_protocolConfig.insert(config_key::additional_server_config, + protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig)); + + endResetModel(); +} + +QJsonObject OpenVpnConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::openvpn, m_protocolConfig); + return m_fullConfig; +} + +QHash OpenVpnConfigModel::roleNames() const +{ + QHash roles; + + roles[SubnetAddressRole] = "subnetAddress"; + roles[TransportProtoRole] = "transportProto"; + roles[PortRole] = "port"; + roles[AutoNegotiateEncryprionRole] = "autoNegotiateEncryprion"; + roles[HashRole] = "hash"; + roles[CipherRole] = "cipher"; + roles[TlsAuthRole] = "tlsAuth"; + roles[BlockDnsRole] = "blockDns"; + roles[AdditionalClientCommandsRole] = "additionalClientCommands"; + roles[AdditionalServerCommandsRole] = "additionalServerCommands"; + + roles[IsPortEditable] = "isPortEditable"; + roles[IsTransportProtoEditable] = "isTransportProtoEditable"; + + roles[HasRemoveButton] = "hasRemoveButton"; + + return roles; +} diff --git a/client/ui/models/protocols/openvpnConfigModel.h b/client/ui/models/protocols/openvpnConfigModel.h new file mode 100644 index 000000000..0357700c8 --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.h @@ -0,0 +1,52 @@ +#ifndef OPENVPNCONFIGMODEL_H +#define OPENVPNCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class OpenVpnConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SubnetAddressRole = Qt::UserRole + 1, + TransportProtoRole, + PortRole, + AutoNegotiateEncryprionRole, + HashRole, + CipherRole, + TlsAuthRole, + BlockDnsRole, + AdditionalClientCommandsRole, + AdditionalServerCommandsRole, + + IsPortEditable, + IsTransportProtoEditable, + + HasRemoveButton + }; + + explicit OpenVpnConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // OPENVPNCONFIGMODEL_H diff --git a/client/ui/models/protocols/shadowsocksConfigModel.cpp b/client/ui/models/protocols/shadowsocksConfigModel.cpp new file mode 100644 index 000000000..60c8feeea --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.cpp @@ -0,0 +1,76 @@ +#include "shadowsocksConfigModel.h" + +#include "protocols/protocols_defs.h" + +ShadowSocksConfigModel::ShadowSocksConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int ShadowSocksConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool ShadowSocksConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant ShadowSocksConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void ShadowSocksConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject ShadowSocksConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash ShadowSocksConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/shadowsocksConfigModel.h b/client/ui/models/protocols/shadowsocksConfigModel.h new file mode 100644 index 000000000..d8fa036b1 --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SHADOWSOCKSCONFIGMODEL_H +#define SHADOWSOCKSCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class ShadowSocksConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit ShadowSocksConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SHADOWSOCKSCONFIGMODEL_H diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp new file mode 100644 index 000000000..15e898655 --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.cpp @@ -0,0 +1,70 @@ +#include "wireguardConfigModel.h" + +#include "protocols/protocols_defs.h" + +WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int WireGuardConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void WireGuardConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); + + endResetModel(); +} + +QJsonObject WireGuardConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::wireguard, m_protocolConfig); + return m_fullConfig; +} + +QHash WireGuardConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/wireguardConfigModel.h b/client/ui/models/protocols/wireguardConfigModel.h new file mode 100644 index 000000000..1deeacaf5 --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.h @@ -0,0 +1,39 @@ +#ifndef WIREGUARDCONFIGMODEL_H +#define WIREGUARDCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class WireGuardConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit WireGuardConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // WIREGUARDCONFIGMODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 7359bb364..ac271eb93 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -1,62 +1,73 @@ #include "protocols_model.h" -ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) +ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { - } int ProtocolsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return ProtocolProps::allProtocols().size(); + return m_content.size(); } -QHash ProtocolsModel::roleNames() const { +QHash ProtocolsModel::roleNames() const +{ QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; + + roles[ProtocolNameRole] = "protocolName"; + roles[ProtocolPageRole] = "protocolPage"; + return roles; } QVariant ProtocolsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ProtocolProps::allProtocols().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= m_content.size()) { return QVariant(); } - Proto p = ProtocolProps::allProtocols().at(index.row()); - if (role == NameRole) { - return ProtocolProps::protocolHumanNames().value(p); + switch (role) { + case ProtocolNameRole: { + amnezia::Proto proto = ProtocolProps::protoFromString(m_content.keys().at(index.row())); + return ProtocolProps::protocolHumanNames().value(proto); } - if (role == DescRole) { - return ProtocolProps::protocolDescriptions().value(p); - } - if (role == ServiceTypeRole) { - return ProtocolProps::protocolService(p); - } - if (role == IsInstalledRole) { - return ContainerProps::protocolsForContainer(m_selectedDockerContainer).contains(p); + case ProtocolPageRole: + return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); } + return QVariant(); } -void ProtocolsModel::setSelectedServerIndex(int index) +void ProtocolsModel::updateModel(const QJsonObject &content) { - beginResetModel(); - m_selectedServerIndex = index; - endResetModel(); + m_container = ContainerProps::containerFromString(content.value(config_key::container).toString()); + + m_content = content; + m_content.remove(config_key::container); } -void ProtocolsModel::setSelectedDockerContainer(DockerContainer c) +QJsonObject ProtocolsModel::getConfig() { - beginResetModel(); - m_selectedDockerContainer = c; - endResetModel(); + QJsonObject config = m_content; + config.insert(config_key::container, ContainerProps::containerToString(m_container)); + return config; } - +PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const +{ + switch (protocol) { + case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Cloak: return PageLoader::PageEnum::PageProtocolCloakSettings; + case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; + case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; + case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; + case Proto::L2tp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + // non-vpn + case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + } +} diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index 48b6eeb64..1e279cfbf 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -3,38 +3,40 @@ #include #include -#include -#include +#include "../controllers/pageController.h" #include "settings.h" -#include "containers/containers_defs.h" class ProtocolsModel : public QAbstractListModel { Q_OBJECT public: - ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { - NameRole = Qt::UserRole + 1, - DescRole, - ServiceTypeRole, - IsInstalledRole + enum Roles { + ProtocolNameRole = Qt::UserRole + 1, + ProtocolPageRole }; + ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); - Q_INVOKABLE void setSelectedDockerContainer(DockerContainer c); + +public slots: + void updateModel(const QJsonObject &content); + + QJsonObject getConfig(); protected: QHash roleNames() const override; private: - int m_selectedServerIndex; - DockerContainer m_selectedDockerContainer; + PageLoader::PageEnum protocolPage(Proto protocol) const; + std::shared_ptr m_settings; + + DockerContainer m_container; + QJsonObject m_content; }; #endif // PROTOCOLS_MODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 1bacdd455..6fad9af66 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -123,6 +123,7 @@ bool ServersModel::isDefaultServerHasWriteAccess() void ServersModel::addServer(const QJsonObject &server) { + // todo beginInsertRows()? beginResetModel(); m_settings->addServer(server); m_servers = m_settings->serversArray(); diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp index 0e92f8c94..141308f40 100644 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ b/client/ui/pages_logic/GeneralSettingsLogic.cpp @@ -1,13 +1,11 @@ #include "GeneralSettingsLogic.h" #include "ShareConnectionLogic.h" -#include "../uilogic.h" #include "../models/protocols_model.h" +#include "../uilogic.h" -GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) +GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) { - } void GeneralSettingsLogic::onUpdatePage() @@ -32,9 +30,11 @@ void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); + // qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + uiLogic()->m_selectedDockerContainer); emit uiLogic()->goToPage(Page::ShareConnection); } diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index 2b556dcc4..a2971cf8b 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -1,6 +1,6 @@ #include "ServerContainersLogic.h" -#include "ShareConnectionLogic.h" #include "ServerConfiguringProgressLogic.h" +#include "ShareConnectionLogic.h" #include @@ -9,24 +9,22 @@ #include "core/servercontroller.h" #include -#include "../uilogic.h" #include "../pages_logic/VpnLogic.h" -#include "vpnconnection.h" +#include "../uilogic.h" #include "core/errorstrings.h" +#include "vpnconnection.h" - -ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) +ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) { } void ServerContainersLogic::onUpdatePage() { -// ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); -// c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); + // ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); + // c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); + // p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); @@ -35,25 +33,29 @@ void ServerContainersLogic::onUpdatePage() void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) { - qDebug()<< "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; + qDebug() << "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; uiLogic()->m_selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage(m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), - uiLogic()->m_selectedDockerContainer, - m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + uiLogic()->protocolLogic(p)->updateProtocolPage( + m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), + uiLogic()->m_selectedDockerContainer, m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); emit uiLogic()->goToProtocolPage(p); } void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) { - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) return; + if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) + return; m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); uiLogic()->onUpdateAllPages(); - if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) return; - if (!uiLogic()->m_vpnConnection) return; - if (!uiLogic()->m_vpnConnection->isConnected()) return; + if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) + return; + if (!uiLogic()->m_vpnConnection) + return; + if (!uiLogic()->m_vpnConnection->isConnected()) + return; uiLogic()->pageLogic()->onDisconnect(); uiLogic()->pageLogic()->onConnect(); @@ -67,16 +69,19 @@ void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) { - //buttonSetEnabledFunc(false); + // buttonSetEnabledFunc(false); ServerController serverController(m_settings); - ErrorCode e = serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); + ErrorCode e = + serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); - //buttonSetEnabledFunc(true); + // buttonSetEnabledFunc(true); if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); - if (c.isEmpty()) m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - else m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); + if (c.isEmpty()) + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); + else + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); } uiLogic()->onUpdateAllPages(); } @@ -96,7 +101,8 @@ void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int p if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { auto installAction = [this, c, &config]() { ServerController serverController(m_settings); - return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); + return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + c, config); }; errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); @@ -107,16 +113,16 @@ void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int p } } } else { - emit uiLogic()->showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); + emit uiLogic()->showWarningMessage( + "Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); } uiLogic()->onUpdateAllPages(); } if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); + emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + + errorString(errorCode) + "\n" + tr("See logs for details.")); } emit uiLogic()->closePage(); } diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml deleted file mode 100644 index f937dcfc9..000000000 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ /dev/null @@ -1,147 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls -import QtQuick.Layouts - -import "../../Controls2" -import "../../Controls2/TextTypes" -import "../../Components" - -Item { - id: root - implicitHeight: col.implicitHeight - implicitWidth: col.implicitWidth - - ColumnLayout { - id: col - - anchors.fill: parent - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 - - Header2TextType { - Layout.fillWidth: true - - text: "OpenVpn" - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - - headerText: qsTr("VPN Addresses Subnet") - } - - ParagraphTextType { - Layout.fillWidth: true - - text: qsTr("Network protocol") - } - - TransportProtoSelector { - Layout.fillWidth: true - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - - headerText: qsTr("Port") - } - - SwitcherType { - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - } - - DropDownType { - id: hash - Layout.fillWidth: true - implicitHeight: 74 - - descriptionText: qsTr("Hash") - headerText: qsTr("Hash") - - listView: ListViewType { - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("SHA512") } - ListElement { name : qsTr("SHA384") } - ListElement { name : qsTr("SHA256") } - ListElement { name : qsTr("SHA3-512") } - ListElement { name : qsTr("SHA3-384") } - ListElement { name : qsTr("SHA3-256") } - ListElement { name : qsTr("whirlpool") } - ListElement { name : qsTr("BLAKE2b512") } - ListElement { name : qsTr("BLAKE2s256") } - ListElement { name : qsTr("SHA1") } - } - currentIndex: 0 - - clickedFunction: { - hash.text = selectedText - hash.menuVisible = false - } - - Component.onCompleted: { - hash.text = selectedText - } - } - } - - DropDownType { - id: cipher - Layout.fillWidth: true - implicitHeight: 74 - - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - listView: ListViewType { - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("AES-256-GCM") } - ListElement { name : qsTr("AES-192-GCM") } - ListElement { name : qsTr("AES-128-GCM") } - ListElement { name : qsTr("AES-256-CBC") } - ListElement { name : qsTr("AES-192-CBC") } - ListElement { name : qsTr("AES-128-CBC") } - ListElement { name : qsTr("ChaCha20-Poly1305") } - ListElement { name : qsTr("ARIA-256-CBC") } - ListElement { name : qsTr("CAMELLIA-256-CBC") } - ListElement { name : qsTr("none") } - } - currentIndex: 0 - - clickedFunction: { - cipher.text = selectedText - cipher.menuVisible = false - } - - Component.onCompleted: { - cipher.text = selectedText - } - } - } - - CheckBoxType { - Layout.fillWidth: true - - text: qsTr("TLS auth") - } - - CheckBoxType { - Layout.fillWidth: true - - text: qsTr("Block DNS requests outside of VPN") - } - - SwitcherType { - Layout.fillWidth: true - - text: qsTr("Additional configuration commands") - } - } -} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 1b8ea40c9..7204ac16b 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" @@ -88,8 +89,32 @@ ListView { onClicked: { if (isInstalled) { - ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) - goToPage(PageEnum.PageSettingsServerProtocol) + var containerIndex = root.model.mapToSource(index) + ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + switch (containerIndex) { + case ContainerEnum.OpenVpn: { + OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolOpenVpnSettings) + break + } + case ContainerEnum.WireGuard: { + WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolWireGuardSettings) + break + } + case ContainerEnum.Ipsec: { + Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolIKev2Settings) + break + } + default: { + if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageSettingsServerProtocol) + } + } + } + } else { ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index d5ed1029d..275e41ea9 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -75,9 +75,9 @@ DrawerType { text: qsTr("Copy") onClicked: { - configContent.selectAll() - configContent.copy() - configContent.select(0, 0) + configText.selectAll() + configText.copy() + configText.select(0, 0) } } @@ -138,6 +138,8 @@ DrawerType { } TextArea { + id: configText + Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml index dd315deb2..bfd82cb1e 100644 --- a/client/ui/qml/Components/TransportProtoSelector.qml +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -25,6 +25,8 @@ Rectangle { HorizontalRadioButton { checked: root.currentIndex === 0 + hoverEnabled: root.enabled + implicitWidth: (rootWidth - 32) / 2 text: "UDP" @@ -36,6 +38,8 @@ Rectangle { HorizontalRadioButton { checked: root.currentIndex === 1 + hoverEnabled: root.enabled + implicitWidth: (rootWidth - 32) / 2 text: "TCP" diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 1a22f326b..6ce5ac4ef 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import "TextTypes" + CheckBox { id: root @@ -26,6 +28,8 @@ CheckBox { indicator: Rectangle { id: checkBoxBackground + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 implicitHeight: 56 radius: 16 @@ -57,43 +61,41 @@ CheckBox { anchors.centerIn: parent source: root.pressed ? imageSource : root.checked ? imageSource : "" - - ColorOverlay { - id: imageColor - anchors.fill: indicator - source: indicator - - color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + layer { + enabled: true + effect: ColorOverlay { + color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + } } } } } contentItem: ColumnLayout { - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right anchors.leftMargin: 8 + checkBoxBackground.width - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + spacing: 4 - height: 22 + ListItemTitleType { Layout.fillWidth: true +// Layout.topMargin: 16 +// Layout.bottomMargin: description.visible ? 0 : 16 + + text: root.text } - Text { + CaptionTextType { + id: description + + Layout.fillWidth: true + Layout.bottomMargin: 16 + text: root.descriptionText color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 - height: 16 - Layout.fillWidth: true + visible: root.descriptionText !== "" } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 85989ae62..7a4538ba4 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -9,8 +9,11 @@ Item { property string text property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" property string headerText property string headerBackButtonImage @@ -23,7 +26,6 @@ Item { property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "transparent" property string rootButtonPressedBorderColor: "#D7D8DB" - property int rootButtonBorderWidth: 1 property real drawerHeight: 0.9 property Component listView @@ -36,10 +38,18 @@ Item { onMenuVisibleChanged: { if (menuVisible) { rootButtonBackground.border.color = rootButtonPressedBorderColor - rootButtonBackground.border.width = rootButtonBorderWidth } else { rootButtonBackground.border.color = rootButtonDefaultBorderColor - rootButtonBackground.border.width = 0 + } + } + + onEnabledChanged: { + if (enabled) { + rootButtonBackground.color = rootButtonBackgroundColor + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } else { + rootButtonBackground.color = "transparent" + rootButtonBackground.border.color = rootButtonHoveredBorderColor } } @@ -48,13 +58,10 @@ Item { anchors.fill: rootButtonContent radius: 16 - color: rootButtonBackgroundColor - border.color: rootButtonDefaultBorderColor - border.width: 0 + color: root.enabled ? rootButtonBackgroundColor : "transparent" + border.color: root.enabled ? rootButtonDefaultBorderColor : rootButtonHoveredBorderColor + border.width: 1 - Behavior on border.width { - PropertyAnimation { duration: 200 } - } Behavior on border.color { PropertyAnimation { duration: 200 } } @@ -77,7 +84,7 @@ Item { visible: root.descriptionText !== "" - color: "#878B91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor text: root.descriptionText } @@ -87,7 +94,7 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - color: root.textColor + color: root.enabled ? root.textColor : root.textDisabledColor text: root.text wrapMode: Text.NoWrap @@ -108,18 +115,16 @@ Item { MouseArea { anchors.fill: rootButtonContent cursorShape: Qt.PointingHandCursor - hoverEnabled: true + hoverEnabled: root.enabled ? true : false onEntered: { if (menu.visible === false) { - rootButtonBackground.border.width = rootButtonBorderWidth rootButtonBackground.border.color = rootButtonHoveredBorderColor } } onExited: { if (menu.visible === false) { - rootButtonBackground.border.width = 0 rootButtonBackground.border.color = rootButtonDefaultBorderColor } } diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 4a2a13ddf..88fc2531d 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -22,45 +22,44 @@ RadioButton { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight - hoverEnabled: true - indicator: Rectangle { anchors.fill: parent radius: 16 color: { - if (root.enabled) { +// if (root.enabled) { if (root.hovered) { return hoveredColor } else if (root.checked) { return selectedColor } return defaultColor - } else { - return disabledColor - } +// } else { +// return disabledColor +// } } border.color: { - if (root.enabled) { +// if (root.enabled) { if (root.pressed) { return pressedBorderColor } else if (root.checked) { return selectedBorderColor } - } - return defaultBodredColor + return defaultBodredColor +// } +// return defaultBodredColor } border.width: { - if (root.enabled) { +// if (root.enabled) { if(root.checked) { return 1 } return root.pressed ? 1 : 0 - } else { - return 0 - } +// } else { +// return 0 +// } } Behavior on color { diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 85d651ef7..13a7ea6e5 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -8,6 +8,9 @@ Item { id: root property string headerText + property string headerTextDisabledColor: "#494B50" + property string headerTextColor: "#878b91" + property alias errorText: errorField.text property string buttonText @@ -15,9 +18,18 @@ Item { property alias textField: textField property alias textFieldText: textField.text + property string textFieldTextColor: "#d7d8db" + property string textFieldTextDisabledColor: "#878B91" + property string textFieldPlaceholderText property bool textFieldEditable: true + property string borderColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + + property string backgroundColor: "#1c1d21" + property string backgroundDisabledColor: "transparent" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -29,9 +41,9 @@ Item { id: backgroud Layout.fillWidth: true Layout.preferredHeight: 74 - color: "#1c1d21" + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 - border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.color: textField.focus ? root.borderFocusedColor : root.borderColor border.width: 1 Behavior on border.color { @@ -43,7 +55,7 @@ Item { ColumnLayout { LabelTextType { text: root.headerText - color: "#878b91" + color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor Layout.fillWidth: true Layout.rightMargin: 16 @@ -55,9 +67,9 @@ Item { id: textField enabled: root.textFieldEditable - color: "#d7d8db" + color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor - placeholderText: textFieldPlaceholderText + placeholderText: root.textFieldPlaceholderText placeholderTextColor: "#494B50" selectionColor: "#412102" @@ -79,7 +91,7 @@ Item { background: Rectangle { anchors.fill: parent - color: "#1c1d21" + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor } onTextChanged: { @@ -98,13 +110,13 @@ Item { textColor: "#D7D8DB" borderWidth: 0 - text: buttonText + text: root.buttonText Layout.rightMargin: 24 onClicked: { - if (clickedFunc && typeof clickedFunc === "function") { - clickedFunc() + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b97acad1b..ab1eeba10 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -158,7 +158,6 @@ PageType { implicitHeight: 40 - rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml new file mode 100644 index 000000000..9ee67303f --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -0,0 +1,176 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: CloakConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Cloak settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("Masquerading as traffic from") + textFieldText: site + + textField.onEditingFinished: { + if (textFieldText !== site) { + site = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + implicitHeight: 74 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(CloakConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml new file mode 100644 index 000000000..8e5556d04 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -0,0 +1,465 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: OpenVpnConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("OpenVPN settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("VPN Addresses Subnet") + textFieldText: subnetAddress + + textField.onEditingFinished: { + if (textFieldText !== subnetAddress) { + subnetAddress = textFieldText + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + Layout.fillWidth: true + Layout.topMargin: 16 + rootWidth: root.width + + enabled: isTransportProtoEditable + + currentIndex: { + return transportProto === "tcp" ? 1 : 0 + } + + onCurrentIndexChanged: { + if (transportProto === "tcp" && currentIndex === 0) { + transportProto = "udp" + } else if (transportProto === "udp" && currentIndex === 1) { + transportProto = "tcp" + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + enabled: isPortEditable + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + SwitcherType { + id: autoNegotiateEncryprionSwitcher + + Layout.fillWidth: true + Layout.topMargin: 24 + + text: qsTr("Auto-negotiate encryption") + checked: autoNegotiateEncryprion + + onCheckedChanged: { + if (checked !== autoNegotiateEncryprion) { + autoNegotiateEncryprion = checked + } + } + } + + DropDownType { + id: hashDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + implicitHeight: 74 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + listView: ListViewType { + id: hashListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("SHA512") } + ListElement { name : qsTr("SHA384") } + ListElement { name : qsTr("SHA256") } + ListElement { name : qsTr("SHA3-512") } + ListElement { name : qsTr("SHA3-384") } + ListElement { name : qsTr("SHA3-256") } + ListElement { name : qsTr("whirlpool") } + ListElement { name : qsTr("BLAKE2b512") } + ListElement { name : qsTr("BLAKE2s256") } + ListElement { name : qsTr("SHA1") } + } + + clickedFunction: function() { + hashDropDown.text = selectedText + hash = hashDropDown.text + hashDropDown.menuVisible = false + } + + Component.onCompleted: { + hashDropDown.text = hash + + for (var i = 0; i < hashListView.model.count; i++) { + if (hashListView.model.get(i).name === hashDropDown.text) { + currentIndex = i + } + } + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + implicitHeight: 74 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("AES-256-GCM") } + ListElement { name : qsTr("AES-192-GCM") } + ListElement { name : qsTr("AES-128-GCM") } + ListElement { name : qsTr("AES-256-CBC") } + ListElement { name : qsTr("AES-192-CBC") } + ListElement { name : qsTr("AES-128-CBC") } + ListElement { name : qsTr("ChaCha20-Poly1305") } + ListElement { name : qsTr("ARIA-256-CBC") } + ListElement { name : qsTr("CAMELLIA-256-CBC") } + ListElement { name : qsTr("none") } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.preferredHeight: checkboxLayout.implicitHeight + color: "#1C1D21" + radius: 16 + + ColumnLayout { + id: checkboxLayout + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("TLS auth") + checked: tlsAuth + + onCheckedChanged: { + if (checked !== tlsAuth) { + tlsAuth = checked + } + } + } + + DividerType {} + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + checked: blockDns + + onCheckedChanged: { + if (checked !== blockDns) { + blockDns = checked + } + } + } + } + } + + SwitcherType { + id: additionalClientCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 32 + + checked: additionalClientCommands !== "" + + text: qsTr("Additional client configuration commands") + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + visible: additionalClientCommandsSwitcher.checked + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: additionalClientCommandsTextArea.implicitHeight + TextArea { + id: additionalClientCommandsTextArea + + width: parent.width + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: qsTr("Commands:") + text: additionalClientCommands + + wrapMode: Text.Wrap + + onEditingFinished: { + if (additionalClientCommands !== text) { + additionalClientCommands = text + } + } + } + } + } + + SwitcherType { + id: additionalServerCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 16 + + checked: additionalServerCommands !== "" + + text: qsTr("Additional server configuration commands") + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + visible: additionalServerCommandsSwitcher.checked + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: additionalServerCommandsTextArea.implicitHeight + TextArea { + id: additionalServerCommandsTextArea + + width: parent.width + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: qsTr("Commands:") + text: additionalServerCommands + + wrapMode: Text.Wrap + + onEditingFinished: { + if (additionalServerCommands !== text) { + additionalServerCommands = text + } + } + } + } + } + + BasicButtonType { + Layout.topMargin: 24 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove OpenVPN") + + onClicked: { + questionDrawer.headerText = qsTr("Remove OpenVpn from server?") +// questionDrawer.descriptionText = qsTr("") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml new file mode 100644 index 000000000..57006cc40 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -0,0 +1,162 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: ShadowSocksConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("ShadowSocks settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + implicitHeight: 74 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 498006dfb..ec2ca91ff 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerEnum 1.0 import ContainerProps 1.0 import "./" @@ -13,15 +14,37 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" import "../Components" -import "../Components/Protocols" PageType { id: root + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + FlickableType { id: fl - anchors.fill: parent - contentHeight: content.height + openVpnSettings.implicitHeight + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height Column { id: content @@ -29,8 +52,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - - spacing: 16 + anchors.topMargin: 32 ListView { // todo change id naming @@ -39,16 +61,7 @@ PageType { height: container.contentItem.height clip: true interactive: false - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "isCurrentlyProcessed" - value: true - } - ] - } + model: ProtocolsModel delegate: Item { implicitWidth: container.width @@ -58,24 +71,60 @@ PageType { id: delegateContent anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - HeaderType { + LabelWithButtonType { + id: button + Layout.fillWidth: true - Layout.topMargin: 20 - headerText: name + text: protocolName + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + switch (containerIndex) { + case ContainerEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + } + goToPage(protocolPage); + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } } - OpenVpnSettings { - id: openVpnSettings + LabelWithButtonType { + id: removeButton width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f1f5324e9..2f07db186 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -14,8 +14,6 @@ import "../Config" PageType { id: root - property real progressBarValue: 0 - Connections { target: InstallController @@ -128,8 +126,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 -// value: progressBarValue - Timer { id: timer diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f343fabfa..6a8e301f8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -44,7 +44,7 @@ PageType { FlickableType { id: fl - anchors.top: backButton.top + anchors.top: backButton.bottom anchors.bottom: parent.bottom contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index c83b59d5e..903e56581 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -31,8 +31,6 @@ PageType { shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) - console.log(type) - switch (type) { case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; @@ -51,6 +49,7 @@ PageType { } property bool showContent: false + property bool shareButtonEnabled: false property list connectionTypesModel: [ amneziaConnectionFormat ] @@ -180,6 +179,7 @@ PageType { serverSelector.text = selectedText ServersModel.currentlyProcessedIndex = currentIndex protocolSelector.visible = true + root.shareButtonEnabled = false } Component.onCompleted: { @@ -253,18 +253,24 @@ PageType { currentIndex: 0 clickedFunction: function () { - serverSelector.text += ", " + selectedText - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) + handler() protocolSelector.visible = false serverSelector.menuVisible = false - - fillConnectionTypeModel() } Component.onCompleted: { + handler() + } + + function handler() { + if (!proxyContainersModel.count) { + root.shareButtonEnabled = false + return + } else { + root.shareButtonEnabled = true + } + serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text @@ -336,6 +342,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: shareButtonEnabled + text: qsTr("Share") onClicked: { From 3aaa7b62eff5f532a11b8fcff632878dabd7017f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Jul 2023 13:14:50 +0900 Subject: [PATCH 043/131] added page to display raw config --- client/amnezia_application.cpp | 15 ++ client/amnezia_application.h | 2 + client/resources.qrc | 1 + client/ui/controllers/exportController.cpp | 31 ++- client/ui/controllers/exportController.h | 7 +- client/ui/controllers/pageController.h | 4 +- client/ui/models/protocols_model.cpp | 14 ++ client/ui/models/protocols_model.h | 3 +- .../Components/SettingsContainersListView.qml | 13 +- .../qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 2 + .../Pages2/PageProtocolOpenVpnSettings.qml | 2 + client/ui/qml/Pages2/PageProtocolRaw.qml | 190 ++++++++++++++++++ .../PageProtocolShadowSocksSettings.qml | 2 + client/ui/qml/main2.qml | 6 + 16 files changed, 266 insertions(+), 30 deletions(-) create mode 100644 client/ui/qml/Pages2/PageProtocolRaw.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 436dfc3fc..abd839edd 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -86,6 +86,21 @@ void AmneziaApplication::init() initModels(); initControllers(); + m_notificationHandler.reset(NotificationHandler::create(nullptr)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), + &NotificationHandler::setConnectionState); + + void openConnection(); + void closeConnection(); + + connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), + &PageController::raise); + connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), + &ConnectionController::openConnection); + connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), + &ConnectionController::closeConnection); + // m_engine->load(url); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index fabc78188..6e9fcdf01 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -22,6 +22,7 @@ #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" +#include "ui/notificationhandler.h" #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif @@ -91,6 +92,7 @@ private: #endif QSharedPointer m_vpnConnection; + QScopedPointer m_notificationHandler; QScopedPointer m_connectionController; QScopedPointer m_pageController; diff --git a/client/resources.qrc b/client/resources.qrc index dd75555f5..df6fcb3e1 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -273,5 +273,6 @@ ui/qml/Pages2/PageProtocolOpenVpnSettings.qml ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml + ui/qml/Pages2/PageProtocolRaw.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 5cd9a83db..c989422df 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -36,10 +36,9 @@ void ExportController::generateFullAccessConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_rawConfig = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); - m_formattedConfig = m_rawConfig; + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -88,10 +87,9 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_rawConfig = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); - m_formattedConfig = m_rawConfig; + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -120,12 +118,10 @@ void ExportController::generateOpenVpnConfig() } config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); - m_rawConfig = config; - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { - m_formattedConfig.append(line + "\n"); + m_config.append(line + "\n"); } emit exportConfigChanged(); @@ -154,20 +150,18 @@ void ExportController::generateWireGuardConfig() } config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); - m_rawConfig = config; - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { - m_formattedConfig.append(line + "\n"); + m_config.append(line + "\n"); } emit exportConfigChanged(); } -QString ExportController::getFormattedConfig() +QString ExportController::getConfig() { - return m_formattedConfig; + return m_config; } QList ExportController::getQrCodes() @@ -193,7 +187,7 @@ void ExportController::saveFile() QFile save(fileName.toLocalFile()); save.open(QIODevice::WriteOnly); - save.write(m_rawConfig.toUtf8()); + save.write(m_config.toUtf8()); save.close(); QFileInfo fi(fileName.toLocalFile()); @@ -233,7 +227,6 @@ int ExportController::getQrCodesCount() void ExportController::clearPreviousConfig() { - m_rawConfig.clear(); - m_formattedConfig.clear(); + m_config.clear(); m_qrCodes.clear(); } diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 85144978b..e4a37a966 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -18,7 +18,7 @@ public: Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) - Q_PROPERTY(QString formattedConfig READ getFormattedConfig NOTIFY exportConfigChanged) + Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged) public slots: void generateFullAccessConfig(); @@ -26,7 +26,7 @@ public slots: void generateOpenVpnConfig(); void generateWireGuardConfig(); - QString getFormattedConfig(); + QString getConfig(); QList getQrCodes(); void saveFile(); @@ -50,8 +50,7 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - QString m_rawConfig; - QString m_formattedConfig; + QString m_config; QList m_qrCodes; }; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 468068b8b..05b8fbf91 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -42,7 +42,8 @@ namespace PageLoader PageProtocolShadowSocksSettings, PageProtocolCloakSettings, PageProtocolWireGuardSettings, - PageProtocolIKev2Settings + PageProtocolIKev2Settings, + PageProtocolRaw }; Q_ENUM_NS(PageEnum) @@ -70,6 +71,7 @@ signals: void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); void showBusyIndicator(bool visible); + void raise(); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index ac271eb93..da730f551 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -17,6 +17,7 @@ QHash ProtocolsModel::roleNames() const roles[ProtocolNameRole] = "protocolName"; roles[ProtocolPageRole] = "protocolPage"; + roles[RawConfigRole] = "rawConfig"; return roles; } @@ -34,6 +35,19 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const } case ProtocolPageRole: return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); + case RawConfigRole: { + auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); + auto lastConfigJsonDoc = + QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); + auto lastConfigJson = lastConfigJsonDoc.object(); + + QString rawConfig; + QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &l : lines) { + rawConfig.append(l + "\n"); + } + return rawConfig; + } } return QVariant(); diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index 1e279cfbf..c4ad5c70c 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -13,7 +13,8 @@ class ProtocolsModel : public QAbstractListModel public: enum Roles { ProtocolNameRole = Qt::UserRole + 1, - ProtocolPageRole + ProtocolPageRole, + RawConfigRole }; ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 7204ac16b..d2f3ee81a 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerEnum 1.0 +import ContainerProps 1.0 import "../Controls2" import "../Controls2/TextTypes" @@ -91,19 +92,25 @@ ListView { if (isInstalled) { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + return + } + switch (containerIndex) { case ContainerEnum.OpenVpn: { - OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()) + OpenVpnConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolOpenVpnSettings) break } case ContainerEnum.WireGuard: { - WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()) + WireGuardConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { - Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()) + Ikev2ConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolIKev2Settings) break } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 275e41ea9..05df413cc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -156,7 +156,7 @@ DrawerType { font.weight: Font.Medium font.family: "PT Root UI VF" - text: ExportController.formattedConfig + text: ExportController.config wrapMode: Text.Wrap diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ab1eeba10..b7d44c78c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -102,7 +102,7 @@ PageType { description += "Amnezia DNS | " } } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns) { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { description += "Amnezia DNS | " } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 9ee67303f..33d231b5f 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -53,6 +53,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 8e5556d04..0bad68e94 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -54,6 +54,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml new file mode 100644 index 000000000..377d948e7 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -0,0 +1,190 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + + FlickableType { + id: fl + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + + ListView { + width: parent.width + height: contentItem.height + clip: true + interactive: false + model: ProtocolsModel + + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + LabelWithButtonType { + id: button + + Layout.fillWidth: true + + text: qsTr("Show connection options") + + clickedFunction: function() { + configContentDrawer.open() + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + DrawerType { + id: configContentDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.visible = false + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + + ColumnLayout { + id: configContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Connection options ") + protocolName + } + + TextArea { + id: configText + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: rawConfig + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } + } + } + } + } + } + } + } + + LabelWithButtonType { + id: removeButton + + width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 57006cc40..730e39074 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -53,6 +53,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 0be8e3682..8ad71d0f8 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -48,5 +48,11 @@ Window { } rootStackView.replace(pagePath, { "objectName" : pagePath }) } + + function onRaise() { + root.show() + root.raise() + root.requestActivate() + } } } From 75489c00c28c4c7022ede605789183eecf093042 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Jul 2023 22:59:49 +0900 Subject: [PATCH 044/131] added button 'Reset settings and remove all data from the application' --- client/core/servercontroller.cpp | 486 +++++++++--------- client/core/servercontroller.h | 59 ++- client/resources.qrc | 1 + client/secure_qsettings.cpp | 55 +- client/secure_qsettings.h | 19 +- client/settings.cpp | 80 +-- client/settings.h | 2 + client/ui/controllers/installController.cpp | 17 +- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.cpp | 10 +- client/ui/controllers/settingsController.h | 8 +- client/ui/models/languageModel.cpp | 5 + client/ui/models/languageModel.h | 1 + .../ui/pages_logic/ServerContainersLogic.cpp | 2 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 9 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 34 ++ client/ui/qml/Pages2/PageSettingsBackup.qml | 90 ---- client/ui/qml/Pages2/PageSettingsLogging.qml | 138 +++++ 18 files changed, 585 insertions(+), 432 deletions(-) create mode 100644 client/ui/qml/Pages2/PageSettingsLogging.qml diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 7f4690dc1..409399dcf 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,23 +1,23 @@ #include "servercontroller.h" +#include #include #include -#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include #include +#include #include #include -#include #include +#include #include #include @@ -25,15 +25,14 @@ #include "containers/containers_defs.h" #include "logger.h" +#include "scripts_registry.h" #include "server_defs.h" #include "settings.h" -#include "scripts_registry.h" #include "utilities.h" #include -ServerController::ServerController(std::shared_ptr settings, QObject *parent) : - m_settings(settings) +ServerController::ServerController(std::shared_ptr settings, QObject *parent) : m_settings(settings) { } @@ -42,10 +41,10 @@ ServerController::~ServerController() m_sshClient.disconnectFromHost(); } - ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) { + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) +{ auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -82,7 +81,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr qDebug().noquote() << "EXEC" << lineToExec; Logger::appendSshLog("Run command:" + lineToExec); - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); if (error != ErrorCode::NoError) { return error; @@ -93,36 +91,36 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr return ErrorCode::NoError; } -ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, - DockerContainer container, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) +ErrorCode +ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script); ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); - if (e) return e; + if (e) + return e; QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName); - e = runScript(credentials, - replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); - runScript(credentials, - replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); return e; } -ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode) { ErrorCode e = ErrorCode::NoError; QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStd = [&](const QString &data, libssh::Client &) { @@ -131,61 +129,63 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, }; // mkdir - QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"") - .arg(path); - - e = runScript(credentials, - replaceVars(mkdir, genVarsForScript(credentials, container))); - if (e) return e; + QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); + e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container))); + if (e) + return e; if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; - } - else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { + if (e) + return e; + } else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; + if (e) + return e; - e = runScript(credentials, - replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); - - if (e) return e; - } - else return ErrorCode::NotImplementedError; + e = runScript( + credentials, + replaceVars( + QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); + if (e) + return e; + } else + return ErrorCode::NotImplementedError; if (stdOut.contains("Error: No such container:")) { return ErrorCode::ServerContainerMissingError; } runScript(credentials, - replaceVars(QString("sudo shred %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container))); - runScript(credentials, - replaceVars(QString("sudo rm %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container))); return e; } -QByteArray ServerController::getTextFileFromContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode) +QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &path, ErrorCode *errorCode) { - if (errorCode) *errorCode = ErrorCode::NoError; - - QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\""). - arg(ContainerProps::containerToString(container)).arg(path); + if (errorCode) + *errorCode = ErrorCode::NoError; + QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"") + .arg(ContainerProps::containerToString(container)) + .arg(path); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -197,14 +197,13 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, qDebug().noquote() << "Copy file from container stdout : \n" << stdOut; - - qDebug().noquote() << "Copy file from container END : \n" ; + qDebug().noquote() << "Copy file from container END : \n"; return QByteArray::fromHex(stdOut.toUtf8()); } -ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, + const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -218,7 +217,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential qDebug() << "remotePath" << remotePath; - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); + error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), + "non_desc"); if (error != ErrorCode::NoError) { return error; } @@ -227,41 +227,45 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) { - return runScript(credentials, - amnezia::scriptData(SharedScriptType::remove_all_containers)); + return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); } ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) { return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::remove_container), - genVarsForScript(credentials, container))); + replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + genVarsForScript(credentials, container))); } ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate) { qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); - //qDebug().noquote() << QJsonDocument(config).toJson(); + // qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, container); - if (e) return e; + if (e) + return e; if (!isUpdate) { e = isServerPortBusy(credentials, container, config); - if (e) return e; + if (e) + return e; } e = isServerDpkgBusy(credentials, container); - if (e) return e; + if (e) + return e; e = installDockerWorker(credentials, container); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished"; e = prepareHostWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished"; removeContainer(credentials, container); @@ -269,15 +273,18 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, qDebug().noquote() << "buildContainerWorker start"; e = buildContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished"; e = runContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished"; e = configureContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished"; setupServerFirewall(credentials); @@ -287,46 +294,25 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, } ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig) + const QJsonObject &oldConfig, QJsonObject &newConfig) { bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; + qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" + << reinstallRequired; if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); - } - else { + } else { ErrorCode e = configureContainerWorker(credentials, container, newConfig); - if (e) return e; + if (e) + return e; return startupContainerWorker(credentials, container, newConfig); } } -QJsonObject ServerController::createContainerInitialConfig(DockerContainer container, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(container) } - }; - - QJsonObject protoConfig; - protoConfig.insert(config_key::port, QString::number(port)); - protoConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto)); - - - if (container == DockerContainer::Sftp) { - protoConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - protoConfig.insert(config_key::password, Utils::getRandomString(10)); - } - - config.insert(ProtocolProps::protoToString(mainProto), protoConfig); - - return config; -} - -bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) +bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig) { Proto mainProto = ContainerProps::defaultProtocol(container); @@ -334,25 +320,25 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); if (container == DockerContainer::OpenVpn) { - if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) != - newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) - return true; + if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) + != newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) + return true; - if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) + return true; } if (container == DockerContainer::Cloak) { - if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) + return true; } if (container == DockerContainer::ShadowSocks) { - if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) + return true; } return false; @@ -374,75 +360,88 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent return ErrorCode::NoError; }; - ErrorCode error = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::install_docker), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, + replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); - if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("command not found")) + return ErrorCode::ServerDockerFailedError; return error; } -ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { // create folder on host - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), - genVarsForScript(credentials, container))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); } -ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), - amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); + amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; -// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; -// }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; e = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::build_container), - genVarsForScript(credentials, container, config)), cbReadStdOut); - if (e) return e; + replaceVars(amnezia::scriptData(SharedScriptType::build_container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); + if (e) + return e; return e; } -ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; - // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { - // stdOut += data + "\n"; - // }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; ErrorCode e = runScript(credentials, - replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), - genVarsForScript(credentials, container, config)), cbReadStdOut); + replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); qDebug() << "cbReadStdOut: " << stdOut; + if (stdOut.contains("docker: Error response from daemon")) + return ErrorCode::ServerDockerFailedError; - if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError; - - if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("invalid publish")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("address already in use")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("is already in use by container")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("invalid publish")) + return ErrorCode::ServerDockerFailedError; return e; } -ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -454,19 +453,18 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr return ErrorCode::NoError; }; - ErrorCode e = runContainerScript(credentials, container, - replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), - genVarsForScript(credentials, container, config)), - cbReadStdOut, cbReadStdErr); - + replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut, cbReadStdErr); m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut); return e; } -ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); @@ -475,16 +473,19 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred } ErrorCode e = uploadTextFileToContainer(container, credentials, - replaceVars(script, genVarsForScript(credentials, container, config)), - "/opt/amnezia/start.sh"); - if (e) return e; + replaceVars(script, genVarsForScript(credentials, container, config)), + "/opt/amnezia/start.sh"); + if (e) + return e; return runScript(credentials, - replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", - genVarsForScript(credentials, container, config))); + replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && " + "/opt/amnezia/start.sh\"", + genVarsForScript(credentials, container, config))); } -ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &config) { const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject(); const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); @@ -495,85 +496,102 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential Vars vars; - vars.append({{"$REMOTE_HOST", credentials.hostName}}); + vars.append({ { "$REMOTE_HOST", credentials.hostName } }); // OpenVPN vars - vars.append({{"$OPENVPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) }}); - vars.append({{"$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) }}); - vars.append({{"$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) }}); + vars.append( + { { "$OPENVPN_SUBNET_IP", + openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } }); + vars.append({ { "$OPENVPN_SUBNET_CIDR", + openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } }); + vars.append({ { "$OPENVPN_SUBNET_MASK", + openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } }); - vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) }}); - vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) }}); + vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } }); + vars.append( + { { "$OPENVPN_TRANSPORT_PROTO", + openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } }); bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }}); + vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } }); - vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) }}); - vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) }}); + vars.append({ { "$OPENVPN_CIPHER", + openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } }); + vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } }); bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - vars.append({{"$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" }}); + vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } }); if (!isTlsAuth) { // erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig - vars.append({{"$OPENVPN_TA_KEY", "" }}); + vars.append({ { "$OPENVPN_TA_KEY", "" } }); } - vars.append({{"$OPENVPN_ADDITIONAL_CLIENT_CONFIG", openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig) }}); - vars.append({{"$OPENVPN_ADDITIONAL_SERVER_CONFIG", openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig) }}); + vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", + openvpnConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig) } }); + vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", + openvpnConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig) } }); // ShadowSocks vars - vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) }}); - vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) }}); - vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) }}); + vars.append({ { "$SHADOWSOCKS_SERVER_PORT", + ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } }); + vars.append({ { "$SHADOWSOCKS_LOCAL_PORT", + ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } }); + vars.append({ { "$SHADOWSOCKS_CIPHER", + ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } }); - vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}}); - vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}}); + vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } }); + vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } }); // Cloak vars - vars.append({{"$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) }}); - vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }}); + vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } }); + vars.append({ { "$FAKE_WEB_SITE_ADDRESS", + cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); // Wireguard vars - vars.append({{"$WIREGUARD_SUBNET_IP", wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) }}); - vars.append({{"$WIREGUARD_SUBNET_CIDR", wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) }}); - vars.append({{"$WIREGUARD_SUBNET_MASK", wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) }}); + vars.append( + { { "$WIREGUARD_SUBNET_IP", + wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); + vars.append({ { "$WIREGUARD_SUBNET_CIDR", + wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } }); + vars.append({ { "$WIREGUARD_SUBNET_MASK", + wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } }); - vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }}); + vars.append({ { "$WIREGUARD_SERVER_PORT", + wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } }); // IPsec vars - vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}}); - vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}}); - vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}}); + vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); + vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } }); + vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } }); - vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}}); - vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}}); + vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } }); + vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } }); - vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}}); + vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } }); - vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}}); - vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}}); + vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } }); - vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}}); - - vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}}); - vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}}); + vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } }); + vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } }); + vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } }); // Sftp vars - vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) }}); - vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }}); - vars.append({{"$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() }}); - + vars.append( + { { "$SFTP_PORT", + sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } }); + vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); + vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { - vars.append({{"$SERVER_IP_ADDRESS", serverIp}}); - } - else { + vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); + } else { qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName"; } @@ -592,10 +610,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential return ErrorCode::NoError; }; - ErrorCode e = runScript(credentials, - amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); + ErrorCode e = + runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return stdOut; } @@ -607,23 +626,24 @@ void ServerController::setCancelInstallation(const bool cancel) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) { - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), - genVarsForScript(credentials))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); } QString ServerController::replaceVars(const QString &script, const Vars &vars) { QString s = script; for (const QPair &var : vars) { - //qDebug() << "Replacing" << var.first << var.second; + // qDebug() << "Replacing" << var.first << var.second; s.replace(var.first, var.second); } - //qDebug().noquote() << script; + // qDebug().noquote() << script; return s; } -ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { if (container == DockerContainer::Dns) { return ErrorCode::NoError; @@ -646,8 +666,10 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); QString defaultPort("%1"); - QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); - QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); + QString port = + containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); + QString defaultTransportProto = + ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port); @@ -660,8 +682,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential script = script.append(" | grep LISTEN"); } - ErrorCode errorCode = runScript(credentials, - replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), + cbReadStdOut, cbReadStdErr); if (errorCode != ErrorCode::NoError) { return errorCode; } @@ -689,9 +711,11 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D }; const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); - ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (!stdOut.contains("sudo")) return ErrorCode::ServerUserNotInSudo; + if (!stdOut.contains("sudo")) + return ErrorCode::ServerUserNotInSudo; return error; } @@ -718,7 +742,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential stdOut.clear(); runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); if (!stdOut.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) { emit serverIsBusy(true); QThread::msleep(1000); @@ -738,7 +763,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential return future.result(); } -ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers) +ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -770,13 +796,10 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { - { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, port }, - { config_key::transport_proto, transportProto }} - } - }; + QJsonObject config { { config_key::container, name }, + { ProtocolProps::protoToString(mainProto), + QJsonObject { { config_key::port, port }, + { config_key::transport_proto, transportProto } } } }; installedContainers.insert(container, config); } } @@ -784,7 +807,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential return ErrorCode::NoError; } -ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback) +ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback) { auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); return error; diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 70ac9cc24..cb74d571e 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -4,8 +4,8 @@ #include #include -#include "defs.h" #include "containers/containers_defs.h" +#include "defs.h" #include "sshclient.h" class Settings; @@ -24,52 +24,61 @@ public: ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, - QJsonObject &config, bool isUpdate = false); + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, + bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig); - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers); - // create initial config - generate passwords, etc - QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp); - ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); - ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, - const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + ErrorCode uploadTextFileToContainer( + DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); QString replaceVars(const QString &script, const Vars &vars); - Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); + Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, + const QJsonObject &config = QJsonObject()); ErrorCode runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); - ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + ErrorCode + runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); void setCancelInstallation(const bool cancel); - ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback); + private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); - ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); - ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); + ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); - ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); + ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config); - ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); + ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config); + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); - - ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, - const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + + ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); ErrorCode setupServerFirewall(const ServerCredentials &credentials); diff --git a/client/resources.qrc b/client/resources.qrc index df6fcb3e1..3435a107e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -274,5 +274,6 @@ ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml ui/qml/Pages2/PageProtocolRaw.qml + ui/qml/Pages2/PageSettingsLogging.qml diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index e71eff482..1df101684 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -1,30 +1,28 @@ #include "secure_qsettings.h" #include "platforms/ios/MobileUtils.h" +#include "QAead.h" +#include "QBlockCipher.h" +#include "utilities.h" #include #include #include #include #include #include +#include #include #include -#include "utilities.h" -#include -#include "QAead.h" -#include "QBlockCipher.h" using namespace QKeychain; SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent) - : QObject{parent}, - m_settings(organization, application, parent), - encryptedKeys({"Servers/serversList"}) + : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) { bool encrypted = m_settings.value("Conf/encrypted").toBool(); // convert settings to encrypted for if updated to >= 2.1.0 - if (encryptionRequired() && ! encrypted) { + if (encryptionRequired() && !encrypted) { for (const QString &key : m_settings.allKeys()) { if (encryptedKeys.contains(key)) { const QVariant &val = value(key); @@ -44,15 +42,15 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue return m_cache.value(key); } - if (!m_settings.contains(key)) return defaultValue; + if (!m_settings.contains(key)) + return defaultValue; QVariant retVal; // check if value is not encrypted, v. < 2.0.x retVal = m_settings.value(key); if (retVal.isValid()) { - if (retVal.userType() == QVariant::ByteArray && - retVal.toByteArray().mid(0, magicString.size()) == magicString) { + if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) { qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; @@ -71,8 +69,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue retVal = QVariant(); } } - } - else { + } else { qWarning() << "SecureQSettings::value invalid QVariant value"; retVal = QVariant(); } @@ -95,14 +92,12 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value) QByteArray encryptedValue = encryptText(decryptedValue); m_settings.setValue(key, magicString + encryptedValue); - } - else { + } else { qCritical() << "SecureQSettings::setValue Encryption required, but key is empty"; return; } - } - else { + } else { m_settings.setValue(key, value); } @@ -139,7 +134,8 @@ QByteArray SecureQSettings::backupAppConfig() const bool SecureQSettings::restoreAppConfig(const QByteArray &json) { QJsonObject cfg = QJsonDocument::fromJson(json).object(); - if (cfg.isEmpty()) return false; + if (cfg.isEmpty()) + return false; for (const QString &key : cfg.keys()) { setValue(key, cfg.value(key).toVariant()); @@ -149,14 +145,13 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json) return true; } - -QByteArray SecureQSettings::encryptText(const QByteArray& value) const +QByteArray SecureQSettings::encryptText(const QByteArray &value) const { QSimpleCrypto::QBlockCipher cipher; return cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv()); } -QByteArray SecureQSettings::decryptText(const QByteArray& ba) const +QByteArray SecureQSettings::decryptText(const QByteArray &ba) const { QSimpleCrypto::QBlockCipher cipher; return cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv()); @@ -228,13 +223,11 @@ QByteArray SecureQSettings::getSecTag(const QString &tag) job->setAutoDelete(false); job->setKey(tag); QEventLoop loop; - job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); - if ( job->error() ) { + if (job->error()) { qCritical() << "SecureQSettings::getSecTag Error:" << job->errorString(); } @@ -249,9 +242,7 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) job->setBinaryData(data); QEventLoop loop; QTimer::singleShot(1000, &loop, SLOT(quit())); - job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); @@ -260,4 +251,10 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) } } - +void SecureQSettings::clearSettings() +{ + QMutexLocker locker(&mutex); + m_settings.clear(); + m_cache.clear(); + sync(); +} diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 9b1f6167e..7421ce019 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -1,23 +1,22 @@ #ifndef SECUREQSETTINGS_H #define SECUREQSETTINGS_H -#include -#include #include #include +#include +#include #include "keychain.h" - -constexpr const char* settingsKeyTag = "settingsKeyTag"; -constexpr const char* settingsIvTag = "settingsIvTag"; -constexpr const char* keyChainName = "AmneziaVPN-Keychain"; - +constexpr const char *settingsKeyTag = "settingsKeyTag"; +constexpr const char *settingsIvTag = "settingsIvTag"; +constexpr const char *keyChainName = "AmneziaVPN-Keychain"; class SecureQSettings : public QObject { public: - explicit SecureQSettings(const QString &organization, const QString &application = QString(), QObject *parent = nullptr); + explicit SecureQSettings(const QString &organization, const QString &application = QString(), + QObject *parent = nullptr); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; void setValue(const QString &key, const QVariant &value); @@ -28,7 +27,7 @@ public: bool restoreAppConfig(const QByteArray &json); QByteArray encryptText(const QByteArray &value) const; - QByteArray decryptText(const QByteArray& ba) const; + QByteArray decryptText(const QByteArray &ba) const; bool encryptionRequired() const; @@ -38,6 +37,8 @@ public: static QByteArray getSecTag(const QString &tag); static void setSecTag(const QString &tag, const QByteArray &data); + void clearSettings(); + private: QSettings m_settings; diff --git a/client/settings.cpp b/client/settings.cpp index 1781cb2e2..fbdd63cee 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -1,6 +1,6 @@ -#include "version.h" #include "settings.h" #include "utilities.h" +#include "version.h" #include "containers/containers_defs.h" #include "logger.h" @@ -8,10 +8,7 @@ const char Settings::cloudFlareNs1[] = "1.1.1.1"; const char Settings::cloudFlareNs2[] = "1.0.0.1"; - -Settings::Settings(QObject* parent) : - QObject(parent), - m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) +Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) { // Import old settings if (serversCount() == 0) { @@ -20,7 +17,7 @@ Settings::Settings(QObject* parent) : QString serverName = m_settings.value("Server/serverName").toString(); int port = m_settings.value("Server/serverPort").toInt(); - if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){ + if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { QJsonObject server; server.insert(config_key::userName, user); server.insert(config_key::password, password); @@ -46,7 +43,8 @@ int Settings::serversCount() const QJsonObject Settings::server(int index) const { const QJsonArray &servers = serversArray(); - if (index >= servers.size()) return QJsonObject(); + if (index >= servers.size()) + return QJsonObject(); return servers.at(index).toObject(); } @@ -61,7 +59,8 @@ void Settings::addServer(const QJsonObject &server) void Settings::removeServer(int index) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return; + if (index >= servers.size()) + return; servers.removeAt(index); setServersArray(servers); @@ -70,7 +69,8 @@ void Settings::removeServer(int index) bool Settings::editServer(int index, const QJsonObject &server) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return false; + if (index >= servers.size()) + return false; servers.replace(index, server); setServersArray(servers); @@ -94,8 +94,8 @@ QString Settings::defaultContainerName(int serverIndex) const QString name = server(serverIndex).value(config_key::defaultContainer).toString(); if (name.isEmpty()) { return ContainerProps::containerToString(DockerContainer::None); - } - else return name; + } else + return name; } QMap Settings::containers(int serverIndex) const @@ -104,7 +104,8 @@ QMap Settings::containers(int serverIndex) const QMap containersMap; for (const QJsonValue &val : containers) { - containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); + containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), + val.toObject()); } return containersMap; @@ -114,17 +115,17 @@ void Settings::setContainers(int serverIndex, const QMap &sites) const QString &site = i.key(); const QString &ip = i.value(); - if (allSites.contains(site) && allSites.value(site) == ip) continue; + if (allSites.contains(site) && allSites.value(site) == ip) + continue; allSites.insert(site, ip); } @@ -263,8 +264,7 @@ QStringList Settings::getVpnIps(RouteMode mode) const for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else if (Utils::checkIpSubnetFormat(i.value().toString())) { + } else if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } } @@ -275,7 +275,8 @@ QStringList Settings::getVpnIps(RouteMode mode) const void Settings::removeVpnSite(RouteMode mode, const QString &site) { QVariantMap sites = vpnSites(mode); - if (!sites.contains(site)) return; + if (!sites.contains(site)) + return; sites.remove(site); setVpnSites(mode, sites); @@ -285,7 +286,8 @@ void Settings::addVpnIps(RouteMode mode, const QStringList &ips) { QVariantMap sites = vpnSites(mode); for (const QString &ip : ips) { - if (ip.isEmpty()) continue; + if (ip.isEmpty()) + continue; sites.insert(ip, ""); } @@ -297,7 +299,8 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) { QVariantMap sitesMap = vpnSites(mode); for (const QString &site : sites) { - if (site.isEmpty()) continue; + if (site.isEmpty()) + continue; sitesMap.remove(site); } @@ -305,9 +308,20 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } -QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); } +QString Settings::primaryDns() const +{ + return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); +} -QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); } +QString Settings::secondaryDns() const +{ + return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); +} + +void Settings::clearSettings() +{ + m_settings.clearSettings(); +} ServerCredentials Settings::defaultServerCredentials() const { diff --git a/client/settings.h b/client/settings.h index 9bf40ac7f..00b02c15b 100644 --- a/client/settings.h +++ b/client/settings.h @@ -183,6 +183,8 @@ public: m_settings.setValue("Conf/appLanguage", locale); }; + void clearSettings(); + signals: void saveLogsChanged(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 6fc5f4e96..2259721a6 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -16,12 +16,19 @@ InstallController::InstallController(const QSharedPointer &servers void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); + QJsonObject containerConfig; - QJsonObject containerConfig { { config_key::port, QString::number(port) }, - { config_key::transport_proto, - ProtocolProps::transportProtoToString(transportProto, mainProto) } }; - QJsonObject config { { config_key::container, ContainerProps::containerToString(container) }, - { ProtocolProps::protoToString(mainProto), containerConfig } }; + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto)); + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + QJsonObject config; + config.insert(config_key::container, ContainerProps::containerToString(container)); + config.insert(ProtocolProps::protoToString(mainProto), containerConfig); if (m_shouldCreateServer) { if (isServerAlreadyExists()) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 05b8fbf91..a0b9753b8 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -27,6 +27,7 @@ namespace PageLoader PageSettingsApplication, PageSettingsBackup, PageSettingsAbout, + PageSettingsLogging, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 42dd22316..b501d0850 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -46,14 +46,15 @@ void SettingsController::setSecondaryDns(const QString &dns) emit secondaryDnsChanged(); } -bool SettingsController::isSaveLogsEnabled() +bool SettingsController::isLoggingEnable() { return m_settings->isSaveLogs(); } -void SettingsController::setSaveLogs(bool enable) +void SettingsController::toggleLogging(bool enable) { m_settings->setSaveLogs(enable); + emit loggingStateChanged(); } void SettingsController::openLogsFolder() @@ -101,3 +102,8 @@ QString SettingsController::getAppVersion() { return m_appVersion; } + +void SettingsController::clearSettings() +{ + m_settings->clearSettings(); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index f961a37d0..313f934d1 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -16,6 +16,7 @@ public: Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) public slots: void setAmneziaDns(bool enable); @@ -27,8 +28,8 @@ public slots: QString getSecondaryDns(); void setSecondaryDns(const QString &dns); - bool isSaveLogsEnabled(); - void setSaveLogs(bool enable); + bool isLoggingEnable(); + void toggleLogging(bool enable); void openLogsFolder(); void exportLogsFile(); @@ -39,9 +40,12 @@ public slots: QString getAppVersion(); + void clearSettings(); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); + void loggingStateChanged(); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 76eeb37dd..adbbdaaa6 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -54,3 +54,8 @@ int LanguageModel::getCurrentLanguageIndex() default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } + +QString LanguageModel::getCurrentLanuageName() +{ + return m_availableLanguages[getCurrentLanguageIndex()].name; +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index b3ff4f6e0..4e8a90924 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -46,6 +46,7 @@ public: public slots: void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); int getCurrentLanguageIndex(); + QString getCurrentLanuageName(); signals: void updateTranslations(const QLocale &locale); diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index a2971cf8b..89eeb6c80 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -89,7 +89,7 @@ void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) { ServerController serverController(m_settings); - QJsonObject config = serverController.createContainerInitialConfig(c, port, tp); + QJsonObject config; // = serverController.createContainerInitialConfig(c, port, tp); emit uiLogic()->goToPage(Page::ServerConfiguringProgress); qApp->processEvents(); diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 27ead16ce..c925f8019 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,14 +17,16 @@ Item { property string textColor: "#d7d8db" - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin RowLayout { id: content anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 Rectangle { id: leftImageBackground @@ -56,8 +58,6 @@ Item { color: root.textColor Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -72,7 +72,6 @@ Item { visible: root.descriptionText !== "" Layout.fillWidth: true - Layout.bottomMargin: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index b378a6c87..54d315b03 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -48,6 +48,7 @@ PageType { Layout.topMargin: 16 text: qsTr("Language") + descriptionText: LanguageModel.getCurrentLanuageName() rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -60,6 +61,22 @@ PageType { } + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Logging") + descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsLogging) + } + } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true @@ -67,10 +84,27 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + questionDrawer.headerText = qsTr("Reset settings and remove all data from the application?") + questionDrawer.descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.clearSettings() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } DividerType {} + + QuestionDrawer { + id: questionDrawer + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index ddf9f2bac..1c196aa3b 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -44,96 +44,6 @@ PageType { headerText: qsTr("Backup") } - SwitcherType { - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Save logs") - - checked: SettingsController.isSaveLogsEnabled() - onCheckedChanged: { - if (checked !== SettingsController.isSaveLogsEnabled()) { - SettingsController.setSaveLogs(checked) - } - } - } - - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/folder-open.svg" - - onClicked: SettingsController.openLogsFolder() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Open folder with logs") - color: "#D7D8DB" - } - } - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/save.svg" - - onClicked: SettingsController.exportLogsFile() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Save logs to file") - color: "#D7D8DB" - } - } - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/delete.svg" - - onClicked: SettingsController.clearLogs() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Clear logs") - color: "#D7D8DB" - } - } - } - ListItemTitleType { Layout.fillWidth: true Layout.topMargin: 10 diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml new file mode 100644 index 000000000..998065e41 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Logging") + } + + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save logs") + + checked: SettingsController.isLoggingEnable + onCheckedChanged: { + if (checked !== SettingsController.isLoggingEnable) { + SettingsController.isLoggingEnable = checked + } + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/folder-open.svg" + + onClicked: SettingsController.openLogsFolder() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Open folder with logs") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/save.svg" + + onClicked: SettingsController.exportLogsFile() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Save logs to file") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/delete.svg" + + onClicked: SettingsController.clearLogs() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + } + } +} From 5d677a9115724ded952bd61434777866d4993330 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 18 Jul 2023 11:15:04 +0900 Subject: [PATCH 045/131] added pages for sftp and tor website settings --- client/CMakeLists.txt | 12 +- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 3 + client/images/controls/copy.svg | 4 + client/protocols/protocols_defs.h | 1 + client/resources.qrc | 3 + client/ui/controllers/installController.cpp | 79 +++++ client/ui/controllers/installController.h | 5 + client/ui/controllers/pageController.h | 3 + client/ui/models/containers_model.cpp | 5 + client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 5 + client/ui/models/servers_model.h | 2 + client/ui/models/services/sftpConfigModel.cpp | 64 ++++ client/ui/models/services/sftpConfigModel.h | 39 +++ .../Components/SettingsContainersListView.qml | 11 + .../ui/qml/Controls2/LabelWithButtonType.qml | 24 +- .../Controls2/TextTypes/CaptionTextType.qml | 2 +- .../Controls2/TextTypes/ListItemTitleType.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 + .../ui/qml/Pages2/PageServiceSftpSettings.qml | 280 ++++++++++++++++++ .../Pages2/PageServiceTorWebsiteSettings.qml | 169 +++++++++++ 22 files changed, 713 insertions(+), 6 deletions(-) create mode 100644 client/images/controls/copy.svg create mode 100644 client/ui/models/services/sftpConfigModel.cpp create mode 100644 client/ui/models/services/sftpConfigModel.h create mode 100644 client/ui/qml/Pages2/PageServiceSftpSettings.qml create mode 100644 client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index be1c3b923..8f1c93534 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -129,8 +129,16 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/ file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp) +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h +) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp +) file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index abd839edd..7bf035fa3 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -267,6 +267,9 @@ void AmneziaApplication::initModels() m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); #endif + + m_sftpConfigModel.reset(new SftpConfigModel(this)); + m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); } void AmneziaApplication::initControllers() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 6e9fcdf01..3ba42e41a 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -31,6 +31,7 @@ #include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" +#include "ui/models/services/sftpConfigModel.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -91,6 +92,8 @@ private: QScopedPointer m_ikev2ConfigModel; #endif + QScopedPointer m_sftpConfigModel; + QSharedPointer m_vpnConnection; QScopedPointer m_notificationHandler; diff --git a/client/images/controls/copy.svg b/client/images/controls/copy.svg new file mode 100644 index 000000000..787e71dbd --- /dev/null +++ b/client/images/controls/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 73c2abdf6..2fba272f7 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -65,6 +65,7 @@ namespace amnezia constexpr char wireguard[] = "wireguard"; constexpr char shadowsocks[] = "shadowsocks"; constexpr char cloak[] = "cloak"; + constexpr char sftp[] = "sftp"; } diff --git a/client/resources.qrc b/client/resources.qrc index 3435a107e..e1afa2944 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -275,5 +275,8 @@ ui/qml/Pages2/PageProtocolCloakSettings.qml ui/qml/Pages2/PageProtocolRaw.qml ui/qml/Pages2/PageSettingsLogging.qml + ui/qml/Pages2/PageServiceSftpSettings.qml + images/controls/copy.svg + ui/qml/Pages2/PageServiceTorWebsiteSettings.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2259721a6..84633b54b 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,8 @@ #include "installController.h" +#include #include +#include #include "core/errorstrings.h" #include "core/servercontroller.h" @@ -214,3 +216,80 @@ void InstallController::setShouldCreateServer(bool shouldCreateServer) { m_shouldCreateServer = shouldCreateServer; } + +void InstallController::mountSftpDrive(const QString &port, const QString &password, const QString &username) +{ + QString mountPath; + QString cmd; + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + QString hostname = serverCredentials.hostName; + +#ifdef Q_OS_WINDOWS + mountPath = getNextDriverLetter() + ":"; + // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") + // .arg(labelTftpUserNameText()) + // .arg(labelTftpPortText()) + // .arg(labelTftpPasswordText()); + + cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; +#elif defined AMNEZIA_DESKTOP + mountPath = + QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); + QDir dir(mountPath); + if (!dir.exists()) { + dir.mkpath(mountPath); + } + + cmd = "/usr/local/bin/sshfs"; +#endif + +#ifdef AMNEZIA_DESKTOP + QSharedPointer process; + process.reset(new QProcess()); + m_sftpMountProcesses.append(process); + process->setProcessChannelMode(QProcess::MergedChannels); + + connect(process.get(), &QProcess::readyRead, this, [this, process, mountPath]() { + QString s = process->readAll(); + if (s.contains("The service sshfs has been started")) { + QDesktopServices::openUrl(QUrl("file:///" + mountPath)); + } + qDebug() << s; + }); + + process->setProgram(cmd); + + QString args = QString("%1@%2:/ %3 " + "-o port=%4 " + "-f " + "-o reconnect " + "-o rellinks " + "-o fstypename=SSHFS " + "-o ssh_command=/usr/bin/ssh.exe " + "-o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no " + "-o password_stdin") + .arg(username, hostname, mountPath, port); + + // args.replace("\n", " "); + // args.replace("\r", " "); + // #ifndef Q_OS_WIN + // args.replace("reconnect-orellinks", ""); + // #endif + process->setArguments(args.split(" ", Qt::SkipEmptyParts)); + process->start(); + process->waitForStarted(50); + if (process->state() != QProcess::Running) { + qDebug() << "onPushButtonSftpMountDriveClicked process not started"; + qDebug() << args; + } else { + process->write((password + "\n").toUtf8()); + } + + // qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; + +#endif +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 75f4fdefb..8458b46d7 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -2,6 +2,7 @@ #define INSTALLCONTROLLER_H #include +#include #include "containers/containers_defs.h" #include "core/defs.h" @@ -28,6 +29,8 @@ public slots: QRegularExpression ipAddressPortRegExp(); + void mountSftpDrive(const QString &port, const QString &password, const QString &username); + signals: void installContainerFinished(bool isInstalledContainerFound); void installServerFinished(bool isInstalledContainerFound); @@ -52,6 +55,8 @@ private: ServerCredentials m_currentlyInstalledServerCredentials; bool m_shouldCreateServer; + + QList> m_sftpMountProcesses; }; #endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index a0b9753b8..d79b100a6 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -29,6 +29,9 @@ namespace PageLoader PageSettingsAbout, PageSettingsLogging, + PageServiceSftpSettings, + PageServiceTorWebsiteSettings, + PageSetupWizardStart, PageSetupWizardCredentials, PageSetupWizardProtocols, diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 7b6ffaeea..922542dd4 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -118,6 +118,11 @@ QString ContainersModel::getCurrentlyProcessedContainerName() return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); } +QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() +{ + return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); +} + void ContainersModel::removeAllContainers() { diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index b79b058ac..75ba91142 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -50,6 +50,7 @@ public slots: int getCurrentlyProcessedContainerIndex(); QString getCurrentlyProcessedContainerName(); + QJsonObject getCurrentlyProcessedContainerConfig(); void removeAllContainers(); void removeCurrentlyProcessedContainer(); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 6fad9af66..fabbb8296 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -106,6 +106,11 @@ int ServersModel::getCurrentlyProcessedServerIndex() return m_currentlyProcessedServerIndex; } +QString ServersModel::getCurrentlyProcessedServerHostName() +{ + return qvariant_cast(data(m_currentlyProcessedServerIndex, HostNameRole)); +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_currentlyProcessedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index f149d85b3..a31e3c044 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -45,6 +45,8 @@ public slots: void setCurrentlyProcessedServerIndex(const int index); int getCurrentlyProcessedServerIndex(); + QString getCurrentlyProcessedServerHostName(); + void addServer(const QJsonObject &server); void removeServer(); diff --git a/client/ui/models/services/sftpConfigModel.cpp b/client/ui/models/services/sftpConfigModel.cpp new file mode 100644 index 000000000..3cbb5ebcd --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.cpp @@ -0,0 +1,64 @@ +#include "sftpConfigModel.h" + +#include "protocols/protocols_defs.h" + +SftpConfigModel::SftpConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int SftpConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant SftpConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::UserNameRole: + return m_protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName); + case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString(); + } + + return QVariant(); +} + +void SftpConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::sftp).toObject(); + + m_protocolConfig.insert(config_key::userName, + protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName)); + + m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString()); + + m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString()); + + endResetModel(); +} + +QJsonObject SftpConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::sftp, m_protocolConfig); + return m_fullConfig; +} + +QHash SftpConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[UserNameRole] = "username"; + roles[PasswordRole] = "password"; + + return roles; +} diff --git a/client/ui/models/services/sftpConfigModel.h b/client/ui/models/services/sftpConfigModel.h new file mode 100644 index 000000000..e948591e5 --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SFTPCONFIGMODEL_H +#define SFTPCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class SftpConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + UserNameRole, + PasswordRole + }; + + explicit SftpConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SFTPCONFIGMODEL_H diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index d2f3ee81a..eac473f46 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -92,6 +92,7 @@ ListView { if (isInstalled) { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) goToPage(PageEnum.PageProtocolRaw) @@ -114,6 +115,16 @@ ListView { goToPage(PageEnum.PageProtocolIKev2Settings) break } + case ContainerEnum.Sftp: { + SftpConfigModel.updateModel(config) + goToPage(PageEnum.PageServiceSftpSettings) + break + } + case ContainerEnum.TorWebSite: { + goToPage(PageEnum.PageServiceTorWebsiteSettings) + break + } + default: { if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container ProtocolsModel.updateModel(config) diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index c925f8019..3aca1d57b 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -16,6 +16,10 @@ Item { property string leftImageSource property string textColor: "#d7d8db" + property string descriptionColor: "#878B91" + property string rightImageColor: "#878B91" + + property bool descriptionOnTop: false implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin @@ -53,26 +57,41 @@ Item { } ColumnLayout { + property real textLineHeight: 21.6 + property real descriptionTextLineHeight: 16 + + property int textPixelSize: 18 + property int descriptionTextSize: 13 + ListItemTitleType { text: root.text - color: root.textColor + color: root.descriptionOnTop ? root.descriptionColor : root.textColor Layout.fillWidth: true + lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight + font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize + font.letterSpacing: root.descriptionOnTop ? 0.02 : 0 + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } + CaptionTextType { id: description - color: "#878B91" + color: root.descriptionOnTop ? root.textColor : root.descriptionColor text: root.descriptionText visible: root.descriptionText !== "" Layout.fillWidth: true + lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight + font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize + font.letterSpacing: root.descriptionOnTop ? 0 : 0.02 + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } @@ -83,6 +102,7 @@ Item { hoverEnabled: false image: rightImageSource + imageColor: rightImageColor visible: rightImageSource ? true : false Layout.alignment: Qt.AlignRight diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml index b9e41da2d..f7acb6dde 100644 --- a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -10,5 +10,5 @@ Text { font.family: "PT Root UI VF" font.letterSpacing: 0.02 - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml index 30bd7900d..917e65def 100644 --- a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -9,5 +9,5 @@ Text { font.weight: 400 font.family: "PT Root UI VF" - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 0bad68e94..028f9fd35 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -413,6 +413,8 @@ PageType { BasicButtonType { Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml new file mode 100644 index 000000000..ffa428591 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -0,0 +1,280 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: SftpConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("SFTP settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Host") + descriptionText: ServersModel.getCurrentlyProcessedServerHostName() + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Port") + descriptionText: port + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Login") + descriptionText: username + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Password") + descriptionText: password + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Mount folder on device") + + onClicked: { + PageController.showBusyIndicator(true) + InstallController.mountSftpDrive(port, password, username) + PageController.showBusyIndicator(false) + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + readonly property string windowsFirstLink: "WinFsp" + readonly property string windowsSecondLink: "SSHFS-Win" + + readonly property string macosFirstLink: "macFUSE" + readonly property string macosSecondLink: "SSHFS" + + onLinkActivated: Qt.openUrlExternally(link) + textFormat: Text.RichText + text: { + var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") + if (Qt.platform.os === "windows") { + str += qsTr("
1. Install the latest version of ") + windowsFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + windowsSecondLink + "\n" + } else if (Qt.platform.os === "osx") { + str += qsTr("
1. Install the latest version of ") + macosFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + macosSecondLink + "\n" + } else if (Qt.platform.os === "linux") { + return "" + } else return "" + + return str + } + } + + BasicButtonType { + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Detailed instructions") + + onClicked: { +// Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove SFTP and all data stored there") + + onClicked: { + questionDrawer.headerText = qsTr("Some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml new file mode 100644 index 000000000..a47a95e3a --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -0,0 +1,169 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Tor website settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Website address") + descriptionText: { + var config = ContainersModel.getCurrentlyProcessedContainerConfig() + var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + return config[ContainerProps.containerTypeToString(containerIndex)]["site"] + } + + descriptionOnTop: true + textColor: "#FBB26A" + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + content.copyToClipBoard(descriptionText) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + onLinkActivated: Qt.openUrlExternally(link) + textFormat: Text.RichText + text: qsTr("Use Tor Browser to open this url.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("When configuring WordPress set the domain as this onion address.") + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove website") + + onClicked: { + questionDrawer.headerText = qsTr("Some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} From 0a1359ed16e16b9452e07528f07eaabfb295bb15 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Jul 2023 16:31:04 +0900 Subject: [PATCH 046/131] moved the platform-specific android code for the new ui --- client/CMakeLists.txt | 2 + client/amnezia_application.cpp | 35 ++- .../platforms/android/android_controller.cpp | 278 ++++++++--------- client/platforms/android/android_controller.h | 26 +- .../platforms/android/authResultReceiver.cpp | 16 + client/platforms/android/authResultReceiver.h | 32 ++ client/protocols/wireguardprotocol.cpp | 55 ++-- client/resources.qrc | 2 +- client/ui/controllers/exportController.cpp | 65 +++- client/ui/controllers/exportController.h | 12 + client/ui/controllers/importController.cpp | 54 +++- client/ui/controllers/importController.h | 12 +- client/ui/controllers/installController.cpp | 39 +++ client/ui/controllers/installController.h | 1 + client/ui/controllers/pageController.cpp | 27 +- client/ui/controllers/pageController.h | 13 +- client/ui/pages_logic/ServerSettingsLogic.cpp | 68 ++--- client/ui/pages_logic/StartPageLogic.cpp | 108 +++---- .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Controls2/CheckBoxType.qml | 46 +-- client/ui/qml/Controls2/PageType.qml | 1 - .../ui/qml/Controls2/VerticalRadioButton.qml | 62 ++-- client/ui/qml/Pages2/PageSettingsAbout.qml | 6 +- .../Pages2/PageSetupWizardConfigSource.qml | 20 +- .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 52 ++++ client/ui/qml/Pages2/PageSetupWizardStart.qml | 4 + client/ui/qml/Pages2/PageShare.qml | 39 ++- client/ui/qml/Pages2/PageStart.qml | 12 + client/ui/qml/Pages2/PageTest.qml | 282 ------------------ client/ui/qml/main2.qml | 14 +- client/ui/uilogic.cpp | 231 +++++++------- 31 files changed, 854 insertions(+), 764 deletions(-) create mode 100644 client/platforms/android/authResultReceiver.cpp create mode 100644 client/platforms/android/authResultReceiver.h create mode 100644 client/ui/qml/Pages2/PageSetupWizardQrReader.qml delete mode 100644 client/ui/qml/Pages2/PageTest.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 0ef72ed0c..936dcbf24 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -293,6 +293,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h ) @@ -301,6 +302,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 53d8fe307..2ee9af16d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -14,9 +15,12 @@ #include "version.h" #include "platforms/ios/QRCodeReaderBase.h" +#if defined(Q_OS_ANDROID) + #include "platforms/android/android_controller.h" +#endif -#include "ui/pages.h" #include "protocols/qml_register_protocols.h" +#include "ui/pages.h" #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" @@ -33,7 +37,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond setQuitOnLastWindowClosed(false); // Fix config file permissions -#ifdef Q_OS_LINUX && !defined(Q_OS_ANDROID) +#if defined Q_OS_LINUX && !defined(Q_OS_ANDROID) { QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); s.setValue("permFixed", true); @@ -87,16 +91,35 @@ void AmneziaApplication::init() initModels(); initControllers(); +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initialized, this, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); + if (!AndroidController::instance()->initialize()) { + qCritical() << QString("Init failed"); + if (m_vpnConnection) + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); + return; + } + + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_importController.get(), + &ImportController::extractConfigFromData); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), + &PageController::goToPageViewConfig); +#endif + m_notificationHandler.reset(NotificationHandler::create(nullptr)); connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), &NotificationHandler::setConnectionState); - void openConnection(); - void closeConnection(); - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), - &PageController::raise); + &PageController::raiseMainWindow); connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 18955532b..a56edcbfc 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -13,17 +13,16 @@ #include "android_controller.h" #include "private/qandroidextras_p.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "androidvpnactivity.h" #include "androidutils.h" +#include "androidvpnactivity.h" -namespace { -AndroidController* s_instance = nullptr; +namespace +{ + AndroidController *s_instance = nullptr; -constexpr auto PERMISSIONHELPER_CLASS = - "org/amnezia/vpn/qt/VPNPermissionHelper"; -} // namespace + constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper"; +} // namespace AndroidController::AndroidController() : QObject() { @@ -33,109 +32,127 @@ AndroidController::AndroidController() : QObject() auto activity = AndroidVPNActivity::instance(); - connect(activity, &AndroidVPNActivity::serviceConnected, this, []() { - qDebug() << "Transact: service connected"; - AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceConnected, this, + []() { + qDebug() << "Transact: service connected"; + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventInitialized, this, - [this](const QString& parcelBody) { - // We might get multiple Init events as widgets, or fragments - // might query this. - if (m_init) { - return; - } + connect( + activity, &AndroidVPNActivity::eventInitialized, this, + [this](const QString &parcelBody) { + // We might get multiple Init events as widgets, or fragments + // might query this. + if (m_init) { + return; + } - qDebug() << "Transact: init"; + qDebug() << "Transact: init"; - m_init = true; + m_init = true; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - qlonglong time = doc.object()["time"].toVariant().toLongLong(); + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + qlonglong time = doc.object()["time"].toVariant().toLongLong(); - isConnected = doc.object()["connected"].toBool(); + isConnected = doc.object()["connected"].toBool(); - if (isConnected) { - emit scheduleStatusCheckSignal(); - } + if (isConnected) { + emit scheduleStatusCheckSignal(); + } - emit initialized( - true, isConnected, - time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); + emit initialized(true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); - setFallbackConnectedNotification(); - }, Qt::QueuedConnection); + setFallbackConnectedNotification(); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConnected, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody); - qDebug() << "Transact: connected"; + connect( + activity, &AndroidVPNActivity::eventConnected, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody); + qDebug() << "Transact: connected"; - if (!isConnected) { - emit scheduleStatusCheckSignal(); - } + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } - isConnected = true; + isConnected = true; - emit connectionStateChanged(VpnProtocol::Connected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventDisconnected, this, - [this]() { - qDebug() << "Transact: disconnected"; + connect( + activity, &AndroidVPNActivity::eventDisconnected, this, + [this]() { + qDebug() << "Transact: disconnected"; - isConnected = false; + isConnected = false; - emit connectionStateChanged(VpnProtocol::Disconnected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, - [this](const QString& parcelBody) { - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventStatisticUpdate, this, + [this](const QString &parcelBody) { + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString rx = doc.object()["rx_bytes"].toString(); - QString tx = doc.object()["tx_bytes"].toString(); - QString endpoint = doc.object()["endpoint"].toString(); - QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); + QString rx = doc.object()["rx_bytes"].toString(); + QString tx = doc.object()["tx_bytes"].toString(); + QString endpoint = doc.object()["endpoint"].toString(); + QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); - emit statusUpdated(rx, tx, endpoint, deviceIPv4); - }, Qt::QueuedConnection); + emit statusUpdated(rx, tx, endpoint, deviceIPv4); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventBackendLogs, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: backend logs"; + connect( + activity, &AndroidVPNActivity::eventBackendLogs, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: backend logs"; - QString buffer = parcelBody.toUtf8(); - if (m_logCallback) { - m_logCallback(buffer); - } - }, Qt::QueuedConnection); + QString buffer = parcelBody.toUtf8(); + if (m_logCallback) { + m_logCallback(buffer); + } + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventActivationError, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody) - qDebug() << "Transact: error"; - emit connectionStateChanged(VpnProtocol::Error); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::eventActivationError, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody) + qDebug() << "Transact: error"; + emit connectionStateChanged(Vpn::ConnectionState::Error); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConfigImport, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: config import"; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventConfigImport, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: config import"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - importConfig(buffer); - }, Qt::QueuedConnection); + QString buffer = doc.object()["config"].toString(); + qDebug() << "Transact: config string" << buffer; + importConfigFromOutside(buffer); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::serviceDisconnected, this, - [this]() { - qDebug() << "Transact: service disconnected"; - m_serviceConnected = false; - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceDisconnected, this, + [this]() { + qDebug() << "Transact: service disconnected"; + m_serviceConnected = false; + }, + Qt::QueuedConnection); } -AndroidController* AndroidController::instance() { +AndroidController *AndroidController::instance() +{ if (!s_instance) { s_instance = new AndroidController(); } @@ -143,16 +160,13 @@ AndroidController* AndroidController::instance() { return s_instance; } -bool AndroidController::initialize(StartPageLogic *startPageLogic) +bool AndroidController::initialize() { qDebug() << "Initializing"; - m_startPageLogic = startPageLogic; - // Hook in the native implementation for startActivityForResult into the JNI - JNINativeMethod methods[]{{"startActivityForResult", - "(Landroid/content/Intent;)V", - reinterpret_cast(startActivityForResult)}}; + JNINativeMethod methods[] { { "startActivityForResult", "(Landroid/content/Intent;)V", + reinterpret_cast(startActivityForResult) } }; QJniObject javaClass(PERMISSIONHELPER_CLASS); QJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); @@ -168,11 +182,9 @@ ErrorCode AndroidController::start() { qDebug() << "Prompting for VPN permission"; QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QJniObject::callStaticMethod( - PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", - appContext.object()); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + QJniObject::callStaticMethod(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", + appContext.object()); QJsonDocument doc(m_vpnConfig); AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson()); @@ -180,7 +192,8 @@ ErrorCode AndroidController::start() return NoError; } -void AndroidController::stop() { +void AndroidController::stop() +{ qDebug() << "AndroidController::stop"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString()); @@ -188,16 +201,16 @@ void AndroidController::stop() { // Activates the tunnel that is currently set // in the VPN Service -void AndroidController::resumeStart() { +void AndroidController::resumeStart() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString()); } /* * Sets the current notification text that is shown */ -void AndroidController::setNotificationText(const QString& title, - const QString& message, - int timerSec) { +void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) +{ QJsonObject args; args["title"] = title; args["message"] = message; @@ -207,7 +220,8 @@ void AndroidController::setNotificationText(const QString& title, AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson()); } -void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { +void AndroidController::shareConfig(const QString &configContent, const QString &suggestedName) +{ AndroidVPNActivity::saveFileAs(configContent, suggestedName); } @@ -216,7 +230,8 @@ void AndroidController::shareConfig(const QString& configContent, const QString& * switches into the Connected state without the app open * e.g via always-on vpn */ -void AndroidController::setFallbackConnectedNotification() { +void AndroidController::setFallbackConnectedNotification() +{ QJsonObject args; args["title"] = tr("AmneziaVPN"); //% "Ready for you to connect" @@ -227,11 +242,13 @@ void AndroidController::setFallbackConnectedNotification() { AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); } -void AndroidController::checkStatus() { +void AndroidController::checkStatus() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString()); } -void AndroidController::getBackendLogs(std::function&& a_callback) { +void AndroidController::getBackendLogs(std::function &&a_callback) +{ qDebug() << "get logs"; m_logCallback = std::move(a_callback); @@ -239,16 +256,13 @@ void AndroidController::getBackendLogs(std::function&& a_c AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString()); } -void AndroidController::cleanupBackendLogs() { +void AndroidController::cleanupBackendLogs() +{ qDebug() << "cleanup logs"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString()); } -void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importAnyFile(data); -} - const QJsonObject &AndroidController::vpnConfig() const { return m_vpnConfig; @@ -285,31 +299,29 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int qDebug() << "start vpnPermissionHelper"; Q_UNUSED(env); - QtAndroidPrivate::startActivity(intent, 1337, - [](int receiverRequestCode, int resultCode, - const QJniObject& data) { - // Currently this function just used in - // VPNService.kt::checkPermissions. So the result - // we're getting is if the User gave us the - // Vpn.bind permission. In case of NO we should - // abort. - Q_UNUSED(receiverRequestCode); - Q_UNUSED(data); + QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) { + // Currently this function just used in + // VPNService.kt::checkPermissions. So the result + // we're getting is if the User gave us the + // Vpn.bind permission. In case of NO we should + // abort. + Q_UNUSED(receiverRequestCode); + Q_UNUSED(data); - AndroidController* controller = AndroidController::instance(); - if (!controller) { - return; - } + AndroidController *controller = AndroidController::instance(); + if (!controller) { + return; + } - if (resultCode == ACTIVITY_RESULT_OK) { - qDebug() << "VPN PROMPT RESULT - Accepted"; - controller->resumeStart(); - return; - } - // If the request got rejected abort the current - // connection. - qWarning() << "VPN PROMPT RESULT - Rejected"; - emit controller->connectionStateChanged(VpnProtocol::Disconnected); - }); + if (resultCode == ACTIVITY_RESULT_OK) { + qDebug() << "VPN PROMPT RESULT - Accepted"; + controller->resumeStart(); + return; + } + // If the request got rejected abort the current + // connection. + qWarning() << "VPN PROMPT RESULT - Rejected"; + emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); return; } diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 7e5b52c81..baa0bc80b 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -8,36 +8,32 @@ #include #include -#include "ui/pages_logic/StartPageLogic.h" - #include "protocols/vpnprotocol.h" using namespace amnezia; - class AndroidController : public QObject { Q_OBJECT public: explicit AndroidController(); - static AndroidController* instance(); + static AndroidController *instance(); virtual ~AndroidController() override = default; - bool initialize(StartPageLogic *startPageLogic); + bool initialize(); ErrorCode start(); void stop(); void resumeStart(); void checkStatus(); - void setNotificationText(const QString& title, const QString& message, int timerSec); - void shareConfig(const QString& data, const QString& suggestedName); + void setNotificationText(const QString &title, const QString &message, int timerSec); + void shareConfig(const QString &data, const QString &suggestedName); void setFallbackConnectedNotification(); - void getBackendLogs(std::function&& callback); + void getBackendLogs(std::function &&callback); void cleanupBackendLogs(); - void importConfig(const QString& data); const QJsonObject &vpnConfig() const; void setVpnConfig(const QJsonObject &newVpnConfig); @@ -45,18 +41,20 @@ public: void startQrReaderActivity(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); // This signal is emitted when the controller is initialized. Note that the // VPN tunnel can be already active. In this case, "connected" should be set // to true and the "connectionDate" should be set to the activation date if // known. // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, const QDateTime& connectionDate); + void initialized(bool status, bool connected, const QDateTime &connectionDate); void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); void scheduleStatusCheckSignal(); + void importConfigFromOutside(QString &data); + protected slots: void scheduleStatusCheckSlot(); @@ -65,12 +63,10 @@ private: QJsonObject m_vpnConfig; - StartPageLogic *m_startPageLogic; - bool m_serviceConnected = false; - std::function m_logCallback; + std::function m_logCallback; - static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); + static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent); bool isConnected = false; diff --git a/client/platforms/android/authResultReceiver.cpp b/client/platforms/android/authResultReceiver.cpp new file mode 100644 index 000000000..21e838a26 --- /dev/null +++ b/client/platforms/android/authResultReceiver.cpp @@ -0,0 +1,16 @@ +#include "authResultReceiver.h" + +AuthResultReceiver::AuthResultReceiver(QSharedPointer ¬ifier) : m_notifier(notifier) +{ +} + +void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) +{ + qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; + + if (resultCode == -1) { // ResultOK + emit m_notifier->authSuccessful(); + } else { + emit m_notifier->authFailed(); + } +} diff --git a/client/platforms/android/authResultReceiver.h b/client/platforms/android/authResultReceiver.h new file mode 100644 index 000000000..9a88dcf5b --- /dev/null +++ b/client/platforms/android/authResultReceiver.h @@ -0,0 +1,32 @@ +#ifndef AUTHRESULTRECEIVER_H +#define AUTHRESULTRECEIVER_H + +#include + +#include + +class AuthResultNotifier : public QObject +{ + Q_OBJECT + +public: + AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {}; + +signals: + void authFailed(); + void authSuccessful(); +}; + +/* Auth result handler for Android */ +class AuthResultReceiver final : public QAndroidActivityResultReceiver +{ +public: + AuthResultReceiver(QSharedPointer ¬ifier); + + void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; + +private: + QSharedPointer m_notifier; +}; + +#endif // AUTHRESULTRECEIVER_H diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index f48c7dfdf..66fec366e 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -5,27 +5,28 @@ #include #include "logger.h" -#include "wireguardprotocol.h" #include "utilities.h" +#include "wireguardprotocol.h" #include "mozilla/localsocketcontroller.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) + : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); // MZ #if defined(MZ_LINUX) - //m_impl.reset(new LinuxController()); + // m_impl.reset(new LinuxController()); #elif defined(MZ_MACOS) // || defined(MZ_WINDOWS) m_impl.reset(new LocalSocketController()); - connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) { - emit connectionStateChanged(VpnProtocol::Connected); - }); - connect(m_impl.get(), &ControllerImpl::disconnected, this, [this](){ - emit connectionStateChanged(VpnProtocol::Disconnected); - }); + connect(m_impl.get(), &ControllerImpl::connected, this, + [this](const QString &pubkey, const QDateTime &connectionTimestamp) { + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }); + connect(m_impl.get(), &ControllerImpl::disconnected, this, + [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); m_impl->initialize(nullptr, nullptr); #endif } @@ -74,9 +75,10 @@ void WireguardProtocol::stop() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; - }); + connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; + }); #ifdef Q_OS_LINUX if (IpcClient::Interface()) { @@ -143,8 +145,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura m_isConfigLoaded = true; qDebug().noquote() << QString("Set config data") << configPath(); - qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); - + qDebug().noquote() << QString("Set config data") + << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } QString WireguardProtocol::configPath() const @@ -156,7 +158,8 @@ void WireguardProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -216,13 +219,13 @@ ErrorCode WireguardProtocol::start() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; + }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(Vpn::ConnectionState::Connected); - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, + [this]() { setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAll(); @@ -250,7 +253,6 @@ ErrorCode WireguardProtocol::start() void WireguardProtocol::updateVpnGateway(const QString &line) { - } QString WireguardProtocol::serviceName() const @@ -261,9 +263,9 @@ QString WireguardProtocol::serviceName() const QStringList WireguardProtocol::stopArgs() { #ifdef Q_OS_WIN - return {"--remove", configPath()}; + return { "--remove", configPath() }; #elif defined Q_OS_LINUX - return {"down", "wg99"}; + return { "down", "wg99" }; #else return {}; #endif @@ -272,11 +274,10 @@ QStringList WireguardProtocol::stopArgs() QStringList WireguardProtocol::startArgs() { #ifdef Q_OS_WIN - return {"--add", configPath()}; + return { "--add", configPath() }; #elif defined Q_OS_LINUX - return {"up", "wg99"}; + return { "up", "wg99" }; #else return {}; #endif } - diff --git a/client/resources.qrc b/client/resources.qrc index e1afa2944..85ee838f2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -194,7 +194,6 @@ ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml ui/qml/Controls2/SwitcherType.qml - ui/qml/Pages2/PageTest.qml ui/qml/Controls2/TabButtonType.qml ui/qml/Pages2/PageSetupWizardProtocolSettings.qml ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -278,5 +277,6 @@ ui/qml/Pages2/PageServiceSftpSettings.qml images/controls/copy.svg ui/qml/Pages2/PageServiceTorWebsiteSettings.qml + ui/qml/Pages2/PageSetupWizardQrReader.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index c989422df..561222f26 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,9 +11,12 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" -#include "qrcodegen.hpp" - #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" + #include "platforms/android/androidutils.h" +#endif +#include "qrcodegen.hpp" ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -25,6 +28,14 @@ ExportController::ExportController(const QSharedPointer &serversMo m_settings(settings), m_configurator(configurator) { +#ifdef Q_OS_ANDROID + m_authResultNotifier.reset(new AuthResultNotifier); + m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier)); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, + [this]() { emit exportErrorOccurred(tr("Access error!")); }); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, + &ExportController::generateFullAccessConfig); +#endif } void ExportController::generateFullAccessConfig() @@ -44,6 +55,27 @@ void ExportController::generateFullAccessConfig() emit exportConfigChanged(); } +#if defined(Q_OS_ANDROID) +void ExportController::generateFullAccessConfigAndroid() +{ + /* We use builtin keyguard for ssh key export protection on Android */ + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + if (appContext.isValid()) { + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); + if (intent.isValid()) { + if (intent.object() != nullptr) { + QtAndroidPrivate::startActivity(intent.object(), 1, m_authResultReceiver.get()); + } + } else { + generateFullAccessConfig(); + } + } +} +#endif + void ExportController::generateConnectionConfig() { clearPreviousConfig(); @@ -194,6 +226,35 @@ void ExportController::saveFile() QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } +void ExportController::shareFile() +{ +#if defined Q_OS_IOS + ext.replace("*", ""); + QString fileName = QDir::tempPath() + "/" + suggestedName; + + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); + + QFile::remove(fileName); + + QFile save(fileName); + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QStringList filesToSend; + filesToSend.append(fileName); + MobileUtils::shareText(filesToSend); + return; +#endif +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(m_config, "amnezia_config"); + return; +#endif +} + QList ExportController::generateQrCodeImageSeries(const QByteArray &data) { double k = 850; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index e4a37a966..7af2cd850 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -6,6 +6,9 @@ #include "configurators/vpn_configurator.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/authResultReceiver.h" +#endif class ExportController : public QObject { @@ -22,6 +25,9 @@ public: public slots: void generateFullAccessConfig(); +#if defined(Q_OS_ANDROID) + void generateFullAccessConfigAndroid(); +#endif void generateConnectionConfig(); void generateOpenVpnConfig(); void generateWireGuardConfig(); @@ -30,6 +36,7 @@ public slots: QList getQrCodes(); void saveFile(); + void shareFile(); signals: void generateConfig(int type); @@ -52,6 +59,11 @@ private: QString m_config; QList m_qrCodes; + +#ifdef Q_OS_ANDROID + QSharedPointer m_authResultNotifier; + QSharedPointer m_authResultReceiver; +#endif }; #endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5b4b7a835..218f43cb4 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -2,8 +2,15 @@ #include #include +#include #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif +#include "utilities.h" namespace { @@ -41,33 +48,58 @@ ImportController::ImportController(const QSharedPointer &serversMo const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { +#ifdef Q_OS_ANDROID + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); +#endif } -void ImportController::extractConfigFromFile(const QUrl &fileUrl) +void ImportController::extractConfigFromFile() { - QFile file(fileUrl.toLocalFile()); + QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open config file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); - auto configFormat = checkConfigFormat(data); - if (configFormat == ConfigTypes::OpenVpn) { - m_config = extractOpenVpnConfig(data); - } else if (configFormat == ConfigTypes::WireGuard) { - m_config = extractWireGuardConfig(data); - } else { - m_config = extractAmneziaConfig(data); - } - + extractConfigFromData(data); m_configFileName = QFileInfo(file.fileName()).fileName(); } } +void ImportController::extractConfigFromData(QString &data) +{ + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + m_config = extractOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + m_config = extractWireGuardConfig(data); + } else { + m_config = extractAmneziaConfig(data); + } +} + void ImportController::extractConfigFromCode(QString code) { m_config = extractAmneziaConfig(code); m_configFileName = ""; } +void ImportController::extractConfigFromQr() +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); +#endif +} + QString ImportController::getConfig() { return QJsonDocument(m_config).toJson(QJsonDocument::Indented); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 561ea19c3..273b12a5b 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -3,10 +3,10 @@ #include -#include "core/defs.h" #include "containers/containers_defs.h" -#include "ui/models/servers_model.h" +#include "core/defs.h" #include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" class ImportController : public QObject { @@ -14,13 +14,14 @@ class ImportController : public QObject public: explicit ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); public slots: void importConfig(); - void extractConfigFromFile(const QUrl &fileUrl); + void extractConfigFromFile(); + void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); + void extractConfigFromQr(); QString getConfig(); QString getConfigFileName(); @@ -39,7 +40,6 @@ private: QJsonObject m_config; QString m_configFileName; - }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 84633b54b..68e47a891 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,6 +8,34 @@ #include "core/servercontroller.h" #include "utilities.h" +namespace +{ +#ifdef Q_OS_WINDOWS + QString getNextDriverLetter() + { + QProcess drivesProc; + drivesProc.start("wmic logicaldisk get caption"); + drivesProc.waitForFinished(); + QString drives = drivesProc.readAll(); + qDebug() << drives; + + QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; + QString letter; + for (int i = letters.size() - 1; i > 0; i--) { + letter = letters.at(i); + if (!drives.contains(letter + ":")) + break; + } + if (letter == "C:") { + // set err info + qDebug() << "Can't find free drive letter"; + return ""; + } + return letter; + } +#endif +} + InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent) @@ -15,6 +43,17 @@ InstallController::InstallController(const QSharedPointer &servers { } +InstallController::~InstallController() +{ +#ifdef Q_OS_WINDOWS + for (QSharedPointer process : m_sftpMountProcesses) { + Utils::signalCtrl(process->processId(), CTRL_C_EVENT); + process->kill(); + process->waitForFinished(); + } +#endif +} + void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 8458b46d7..fdd0ebedb 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -16,6 +16,7 @@ public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent = nullptr); + ~InstallController(); public slots: void install(DockerContainer container, int port, TransportProto transportProto); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 4ee9b3bf9..5abeb77fe 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,7 +1,9 @@ #include "pageController.h" -PageController::PageController(const QSharedPointer &serversModel, - QObject *parent) : QObject(parent), m_serversModel(serversModel) +#include + +PageController::PageController(const QSharedPointer &serversModel, QObject *parent) + : QObject(parent), m_serversModel(serversModel) { } @@ -24,3 +26,24 @@ QString PageController::getPagePath(PageLoader::PageEnum page) QString pageName = metaEnum.valueToKey(static_cast(page)); return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; } + +void PageController::closeWindow() +{ +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_serversModel->getServersCount() == 0) { + qApp->quit(); + } else { + emit hideMainWindow(); + } +#endif +} + +void PageController::keyPressEvent(Qt::Key key) +{ + switch (key) { + case Qt::Key_Back: emit closePage(); + default: return; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index d79b100a6..e8452b459 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -41,6 +41,7 @@ namespace PageLoader PageSetupWizardConfigSource, PageSetupWizardTextKey, PageSetupWizardViewConfig, + PageSetupWizardQrReader, PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, @@ -67,15 +68,25 @@ public slots: QString getInitialPage(); QString getPagePath(PageLoader::PageEnum page); + void closeWindow(); + void keyPressEvent(Qt::Key key); + signals: void goToPageHome(); void goToPageSettings(); + void goToPageViewConfig(); + void closePage(); + void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); + void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); + void showBusyIndicator(bool visible); - void raise(); + + void hideMainWindow(); + void raiseMainWindow(); private: QSharedPointer m_serversModel; diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp index a17f01599..4c68b5498 100644 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ b/client/ui/pages_logic/ServerSettingsLogic.cpp @@ -6,20 +6,21 @@ #include "VpnLogic.h" #include "core/errorstrings.h" -#include #include +#include #if defined(Q_OS_ANDROID) -#include "../../platforms/android/androidutils.h" + #include "../../platforms/android/androidutils.h" #endif -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearClientCacheVisible{true}, - m_pushButtonShareFullVisible{true}, - m_pushButtonClearClientCacheText{tr("Clear client cached profile")} -{ } +ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_labelWaitInfoVisible { true }, + m_pushButtonClearClientCacheVisible { true }, + m_pushButtonShareFullVisible { true }, + m_pushButtonClearClientCacheText { tr("Clear client cached profile") } +{ +} void ServerSettingsLogic::onUpdatePage() { @@ -33,11 +34,11 @@ void ServerSettingsLogic::onUpdatePage() const QString &userName = server.value(config_key::userName).toString(); const QString &hostName = server.value(config_key::hostName).toString(); QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); + .arg(userName) + .arg(userName.isEmpty() ? "" : "@") + .arg(hostName) + .arg(port.isEmpty() ? "" : ":") + .arg(port); set_labelServerText(name); set_lineEditDescriptionText(server.value(config_key::description).toString()); @@ -49,15 +50,15 @@ void ServerSettingsLogic::onUpdatePage() void ServerSettingsLogic::onPushButtonForgetServer() { - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex + && uiLogic()->m_vpnConnection->isConnected()) { uiLogic()->pageLogic()->onDisconnect(); } m_settings->removeServer(uiLogic()->m_selectedServerIndex); if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(0); - } - else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { + } else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); } @@ -65,14 +66,12 @@ void ServerSettingsLogic::onPushButtonForgetServer() m_settings->setDefaultServer(-1); } - uiLogic()->m_selectedServerIndex = -1; uiLogic()->onUpdateAllPages(); if (m_settings->serversCount() == 0) { uiLogic()->setStartPage(Page::Start); - } - else { + } else { uiLogic()->closePage(); } } @@ -86,9 +85,7 @@ void ServerSettingsLogic::onPushButtonClearClientCacheClicked() m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); } - QTimer::singleShot(3000, this, [this]() { - set_pushButtonClearClientCacheText(tr("Clear client cached profile")); - }); + QTimer::singleShot(3000, this, [this]() { set_pushButtonClearClientCacheText(tr("Clear client cached profile")); }); } void ServerSettingsLogic::onLineEditDescriptionEditingFinished() @@ -111,7 +108,7 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul { qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - if (resultCode == -1) { //ResultOK + if (resultCode == -1) { // ResultOK uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } @@ -121,26 +118,27 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul void ServerSettingsLogic::onPushButtonShareFullClicked() { #if defined(Q_OS_ANDROID) -/* We use builtin keyguard for ssh key export protection on Android */ + /* We use builtin keyguard for ssh key export protection on Android */ QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); if (appContext.isValid()) { QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod( - "org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); if (intent.isValid()) { if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + QtAndroidPrivate::startActivity(intent.object(), 1, receiver); + } + } else { + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } } #else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); #endif } diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 124908109..454b5fa32 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -1,68 +1,69 @@ #include "StartPageLogic.h" #include "ViewConfigLogic.h" -#include "core/errorstrings.h" +#include "../uilogic.h" #include "configurators/ssh_configurator.h" #include "configurators/vpn_configurator.h" -#include "../uilogic.h" -#include "utilities.h" +#include "core/errorstrings.h" #include "core/servercontroller.h" +#include "utilities.h" +#include #include #include -#include #ifdef Q_OS_ANDROID -#include -#include "../../platforms/android/androidutils.h" -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include #endif -namespace { -enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard -}; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - if (config.contains(openVpnConfigPatternCli) && - (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && - (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) && - config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; -} + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) + return ConfigTypes::WireGuard; + return ConfigTypes::Amnezia; + } } -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectEnabled{true}, - m_pushButtonConnectText{tr("Connect")}, - m_pushButtonConnectKeyChecked{false}, - m_labelWaitInfoVisible{true}, - m_pushButtonBackFromStartVisible{true}, - m_ipAddressPortRegex{Utils::ipAddressPortRegExp()} +StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_pushButtonConnectEnabled { true }, + m_pushButtonConnectText { tr("Connect") }, + m_pushButtonConnectKeyChecked { false }, + m_labelWaitInfoVisible { true }, + m_pushButtonBackFromStartVisible { true }, + m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } { #ifdef Q_OS_ANDROID // Set security screen for Android app AndroidUtils::runOnAndroidThreadSync([]() { QJniObject activity = AndroidUtils::getActivity(); QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()){ + if (window.isValid()) { const int FLAG_SECURE = 8192; window.callMethod("addFlags", "(I)V", FLAG_SECURE); } @@ -89,17 +90,13 @@ void StartPageLogic::onUpdatePage() void StartPageLogic::onPushButtonConnect() { - if (pushButtonConnectKeyChecked()){ - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - textEditSshKeyText().isEmpty() ) { + if (pushButtonConnectKeyChecked()) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } } else { - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - lineEditPasswordText().isEmpty() ) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } @@ -174,7 +171,8 @@ void StartPageLogic::onPushButtonConnect() set_pushButtonConnectText(tr("Connect")); uiLogic()->m_installCredentials = serverCredentials; - if (ok) emit uiLogic()->goToPage(Page::NewServer); + if (ok) + emit uiLogic()->goToPage(Page::NewServer); } void StartPageLogic::onPushButtonImport() @@ -185,8 +183,10 @@ void StartPageLogic::onPushButtonImport() void StartPageLogic::onPushButtonImportOpenFile() { QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) return; + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + if (fileName.isEmpty()) + return; QFile file(fileName); file.open(QIODevice::ReadOnly); @@ -226,8 +226,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile) // check config uiLogic()->pageLogic()->set_configJson(profile); emit uiLogic()->goToPage(Page::ViewConfig); - } - else { + } else { qDebug() << "Failed to import profile"; qDebug().noquote() << QJsonDocument(profile).toJson(); return false; @@ -298,7 +297,6 @@ bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-openvpn"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); if (dnsMatch.hasNext()) { @@ -345,7 +343,9 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-wireguard"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); if (dnsMatch.hasMatch()) { o[config_key::dns1] = dnsMatch.captured(1); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 05df413cc..692289a86 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -54,10 +54,10 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("Save connection code") + text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code") onClicked: { - ExportController.saveFile() + Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile() } } diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 6ce5ac4ef..e4b1703fe 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -26,7 +26,7 @@ CheckBox { hoverEnabled: true indicator: Rectangle { - id: checkBoxBackground + id: background anchors.verticalCenter: parent.verticalCenter @@ -57,7 +57,6 @@ CheckBox { radius: 4 Image { - id: indicator anchors.centerIn: parent source: root.pressed ? imageSource : root.checked ? imageSource : "" @@ -71,31 +70,38 @@ CheckBox { } } - contentItem: ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 8 + checkBoxBackground.width + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - spacing: 4 + anchors.fill: parent + anchors.leftMargin: 8 + background.width - ListItemTitleType { - Layout.fillWidth: true -// Layout.topMargin: 16 -// Layout.bottomMargin: description.visible ? 0 : 16 + ColumnLayout { + id: content - text: root.text - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - CaptionTextType { - id: description + spacing: 4 - Layout.fillWidth: true - Layout.bottomMargin: 16 + ListItemTitleType { + Layout.fillWidth: true - text: root.descriptionText - color: "#878b91" + text: root.text + } - visible: root.descriptionText !== "" + CaptionTextType { + id: description + + Layout.fillWidth: true + + text: root.descriptionText + color: "#878b91" + + visible: root.descriptionText !== "" + } } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 046201b7e..296fcc8c0 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -20,7 +20,6 @@ Item { if (root.stackView.depth <= 1) { return } - root.stackView.pop() } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index c833ca27d..f44fa751a 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -85,44 +85,50 @@ RadioButton { } } - contentItem: ColumnLayout { - id: content - anchors.left: parent.left - anchors.right: parent.right + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent anchors.leftMargin: 8 + background.width - spacing: 4 + ColumnLayout { + id: content - ListItemTitleType { - text: root.text + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - color: { - if (root.checked) { - return selectedTextColor + spacing: 4 + + ListItemTitleType { + text: root.text + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } + + Layout.fillWidth: true + + Behavior on color { + PropertyAnimation { duration: 200 } } - return textColor } - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 + CaptionTextType { + id: description - Behavior on color { - PropertyAnimation { duration: 200 } + color: "#878B91" + text: root.descriptionText + + visible: root.descriptionText !== "" + + Layout.fillWidth: true } } - - CaptionTextType { - id: description - - color: "#878B91" - text: root.descriptionText - - visible: root.descriptionText !== "" - - Layout.fillWidth: true - Layout.bottomMargin: 16 - } } MouseArea { diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 01b11bda9..1875f085e 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -43,8 +43,8 @@ PageType { Layout.topMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.preferredWidth: 344 - Layout.preferredHeight: 279 + Layout.preferredWidth: 291 + Layout.preferredHeight: 224 } Header2TextType { @@ -100,7 +100,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Show other methods on Github") - onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client#donate") } ParagraphTextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45e4c29ca..78bd74865 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -61,16 +61,18 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - onClicked: fileDialog.open() +// onClicked: fileDialog.open() + ImportController.extractConfigFromFile() + goToPage(PageEnum.PageSetupWizardViewConfig) } - FileDialog { - id: fileDialog - onAccepted: { - ImportController.extractConfigFromFile(selectedFile) - goToPage(PageEnum.PageSetupWizardViewConfig) - } - } +// FileDialog { +// id: fileDialog +// onAccepted: { +// ImportController.extractConfigFromFile(selectedFile) +// goToPage(PageEnum.PageSetupWizardViewConfig) +// } +// } } DividerType {} @@ -84,6 +86,8 @@ PageType { leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { + ImportController.extractConfigFromQr() +// goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml new file mode 100644 index 000000000..65d233eff --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import QRCodeReader 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + ColumnLayout { + anchors.fill: parent + + spacing: 0 + + BackButtonType { + Layout.topMargin: 20 + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("Point the camera at the QR code and hold for a couple of seconds.") + } + + ProgressBarType { + + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + QRCodeReader { + id: qrCodeReader + Component.onCompleted: { + qrCodeReader.setCameraSize(Qt.rect(parent.x, + parent.y, + parent.width, + parent.height)) + qrCodeReader.startReading() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 78ccfa950..750084c7b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -20,6 +20,10 @@ PageType { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() } + + function onGoToPageViewConfig() { + goToPage(PageEnum.PageSetupWizardViewConfig) + } } FlickableType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 903e56581..201c630a8 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -18,7 +18,7 @@ PageType { enum ConfigType { AmneziaConnection, - AmenziaFullAccess, + AmneziaFullAccess, OpenVpn, WireGuard } @@ -33,7 +33,14 @@ PageType { switch (type) { case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; - case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; + case PageShare.ConfigType.AmneziaFullAccess: { + if (Qt.platform.os === "android") { + ExportController.generateFullAccessConfigAndroid(); + } else { + ExportController.generateFullAccessConfig(); + } + break; + } case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; } @@ -48,6 +55,8 @@ PageType { } } + property string fullConfigServerSelectorText + property string connectionServerSelectorText property bool showContent: false property bool shareButtonEnabled: false property list connectionTypesModel: [ @@ -118,6 +127,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 0 + serverSelector.text = root.connectionServerSelectorText } } @@ -129,6 +139,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 + serverSelector.text = root.fullConfigServerSelectorText } } } @@ -176,14 +187,24 @@ PageType { currentIndex: 0 clickedFunction: function() { - serverSelector.text = selectedText - ServersModel.currentlyProcessedIndex = currentIndex - protocolSelector.visible = true - root.shareButtonEnabled = false + handler() + + if (accessTypeSelector.currentIndex === 0) { + protocolSelector.visible = true + root.shareButtonEnabled = false + } else { + serverSelector.menuVisible = false + } } Component.onCompleted: { + handler() + } + + function handler() { serverSelector.text = selectedText + root.fullConfigServerSelectorText = selectedText + root.connectionServerSelectorText = selectedText ServersModel.currentlyProcessedIndex = currentIndex } } @@ -260,7 +281,9 @@ PageType { } Component.onCompleted: { - handler() + if (accessTypeSelector.currentIndex === 0) { + handler() + } } function handler() { @@ -272,6 +295,8 @@ PageType { } serverSelector.text += ", " + selectedText + root.connectionServerSelectorText = serverSelector.text + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 09bebaa10..5d49abf70 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -25,6 +25,11 @@ PageType { tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) } + function onGoToPageViewConfig() { + var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } + function onShowErrorMessage(errorMessage) { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() @@ -35,6 +40,13 @@ PageType { tabBarStackView.enabled = !visible tabBar.enabled = !visible } + + function onClosePage() { + if (tabBarStackView.depth <= 1) { + return + } + tabBarStackView.pop() + } } StackViewType { diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml deleted file mode 100644 index d33608f87..000000000 --- a/client/ui/qml/Pages2/PageTest.qml +++ /dev/null @@ -1,282 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 - -import "./" -import "../Controls2" -import "../Config" -import "../Controls2/TextTypes" - -Item { - id: root - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - HeaderType { - id: header - - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 20 - Layout.bottomMargin: 32 - Layout.fillWidth: true - - backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Server 1" - descriptionText: "root 192.168.111.111" - } - - Item { - Layout.fillWidth: true - - TabBar { - id: tabBar - - anchors { - top: parent.top - right: parent.right - left: parent.left - } - - background: Rectangle { - color: "transparent" - } - - TabButtonType { - id: bb - isSelected: tabBar.currentIndex === 0 - text: qsTr("Протоколы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 1 - text: qsTr("Сервисы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 2 - text: qsTr("Данные") - } - } - - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex - - anchors.top: tabBar.bottom - anchors.topMargin: 16 - - width: parent.width - height: root.height - header.implicitHeight - tabBar.implicitHeight - 100 - - Item { - id: protocolsTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: protocolsTabContent.height - - ColumnLayout { - id: protocolsTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - text: qsTr("Forget this server") - } - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("Forget this server") - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - headerText: "Server IP adress [:port]" - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "IP, логин и пароль от сервера" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - LabelWithButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "QR-код, ключ или файл настроек" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - DropDownType { - Layout.fillWidth: true - - text: "IP, логин и пароль от сервера" - descriptionText: "IP, логин и пароль от сервера" - - menuModel: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - } - } - } - } - - Item { - id: servicesTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: servicesTabContent.height - - ColumnLayout { - id: servicesTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - } - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - descriptionText: qsTr("Auto-negotiate encryption") - } - - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight - - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: "#1C1D21" - radius: 16 - - RowLayout { - id: buttonGroup - - spacing: 0 - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "UDP" - } - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "TCP" - } - } - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - descriptionText: "Позволяет подключаться к одним сайтам через защищенное соединение, а к другим в обход него" - checked: true - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - SwitcherType { - text: "Auto-negotiate encryption" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - } - } - } - } - } - } -} diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 8ad71d0f8..d0f9880d6 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -18,10 +18,9 @@ Window { color: "#0E0E11" - // todo onClosing: function() { console.debug("QML onClosing signal") - UiLogic.onCloseWindow() + PageController.closeWindow() } title: "AmneziaVPN" @@ -36,6 +35,11 @@ Window { var pagePath = PageController.getInitialPage() rootStackView.push(pagePath, { "objectName" : pagePath }) } + + Keys.onPressed: function(event) { + PageController.keyPressEvent(event.key) + event.accepted = true + } } Connections { @@ -49,10 +53,14 @@ Window { rootStackView.replace(pagePath, { "objectName" : pagePath }) } - function onRaise() { + function onRaiseMainWindow() { root.show() root.raise() root.requestActivate() } + + function onHideMainWindow() { + root.hide() + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index cd02bf418..aefcc49c6 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -10,79 +10,76 @@ #include #include #include +#include +#include +#include #include #include #include -#include -#include -#include #include "amnezia_application.h" #include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" #include "configurators/openvpn_configurator.h" #include "configurators/shadowsocks_configurator.h" #include "configurators/ssh_configurator.h" +#include "configurators/vpn_configurator.h" -#include "core/servercontroller.h" -#include "core/server_defs.h" #include "core/errorstrings.h" +#include "core/server_defs.h" +#include "core/servercontroller.h" #include "containers/containers_defs.h" #include "ui/qautostart.h" #include "logger.h" -#include "version.h" #include "uilogic.h" #include "utilities.h" +#include "version.h" #include "vpnconnection.h" #include #if defined Q_OS_MAC || defined Q_OS_LINUX -#include "ui/macos_util.h" + #include "ui/macos_util.h" #endif #ifdef Q_OS_ANDROID -#include "platforms/android/android_controller.h" + #include "platforms/android/android_controller.h" #endif #include "platforms/ios/MobileUtils.h" +#include "pages_logic/AdvancedServerSettingsLogic.h" #include "pages_logic/AppSettingsLogic.h" +#include "pages_logic/ClientInfoLogic.h" +#include "pages_logic/ClientManagementLogic.h" #include "pages_logic/GeneralSettingsLogic.h" #include "pages_logic/NetworkSettingsLogic.h" #include "pages_logic/NewServerProtocolsLogic.h" #include "pages_logic/QrDecoderLogic.h" #include "pages_logic/ServerConfiguringProgressLogic.h" +#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ServerListLogic.h" #include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ShareConnectionLogic.h" #include "pages_logic/SitesLogic.h" #include "pages_logic/StartPageLogic.h" #include "pages_logic/ViewConfigLogic.h" #include "pages_logic/VpnLogic.h" #include "pages_logic/WizardLogic.h" -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/ClientInfoLogic.h" #include "pages_logic/protocols/CloakLogic.h" #include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/OtherProtocolsLogic.h" +#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/WireGuardLogic.h" using namespace amnezia; using namespace PageEnumNS; -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - QObject *parent) : - QObject(parent), - m_settings(settings), - m_configurator(configurator) +UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator) { m_protocolsModel = new ProtocolsModel(settings, this); m_clientManagementModel = new ClientManagementModel(this); @@ -90,7 +87,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptrmoveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); @@ -99,7 +95,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr()->onConnectionStateChanged(Vpn::ConnectionState::Connected); - if (m_vpnConnection) m_vpnConnection->restoreConnection(); - } - }); - if (!AndroidController::instance()->initialize(pageLogic())) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); - return; - } + connect(AndroidController::instance(), &AndroidController::initialized, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); +// if (!AndroidController::instance()->initialize(pageLogic())) { +// qCritical() << QString("Init failed"); +// if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); +// return; +// } #endif m_notificationHandler = NotificationHandler::create(qmlRoot()); - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState); + connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, + &NotificationHandler::setConnectionState); connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); + connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), + &VpnLogic::onDisconnect); -// if (m_settings->serversCount() > 0) { -// if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); -// emit goToPage(Page::PageStart, true, false); -// } -// else { -// emit goToPage(Page::PageSetupWizardStart, true, false); -// } + // if (m_settings->serversCount() > 0) { + // if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); + // emit goToPage(Page::PageStart, true, false); + // } + // else { + // emit goToPage(Page::PageSetupWizardStart, true, false); + // } m_selectedServerIndex = m_settings->defaultServerIndex(); @@ -165,14 +164,13 @@ void UiLogic::initializeUiLogic() void UiLogic::showOnStartup() { - if (! m_settings->isStartMinimized()) { + if (!m_settings->isStartMinimized()) { emit show(); - } - else { + } else { #ifdef Q_OS_WIN emit hide(); #elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); + // TODO: fix: setDockIconVisible(false); #endif } } @@ -180,7 +178,7 @@ void UiLogic::showOnStartup() void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic)) { + if (dynamic_cast(logic) || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); @@ -191,57 +189,50 @@ void UiLogic::keyPressEvent(Qt::Key key) { switch (key) { case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); - break; - case Qt::Key_L: Logger::openLogsFolder(); - break; - case Qt::Key_K: Logger::openServiceLogsFolder(); - break; + case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; + case Qt::Key_L: Logger::openLogsFolder(); break; + case Qt::Key_K: Logger::openServiceLogsFolder(); break; #ifdef QT_DEBUG - case Qt::Key_Q: - qApp->quit(); - break; + case Qt::Key_Q: qApp->quit(); break; case Qt::Key_H: m_selectedServerIndex = m_settings->defaultServerIndex(); m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); + // updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); emit goToPage(Page::ShareConnection); break; #endif case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() << m_settings->defaultContainerName(m_settings->defaultServerIndex()); + qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() + << m_settings->defaultContainerName(m_settings->defaultServerIndex()); qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); break; - case Qt::Key_A: - emit goToPage(Page::Start); - break; + case Qt::Key_A: emit goToPage(Page::Start); break; case Qt::Key_S: m_selectedServerIndex = m_settings->defaultServerIndex(); emit goToPage(Page::ServerSettings); break; - case Qt::Key_P: - onGotoCurrentProtocolsPage(); - break; + case Qt::Key_P: onGotoCurrentProtocolsPage(); break; case Qt::Key_T: m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: - if (currentPage() == Page::Vpn) break; - if (currentPage() == Page::ServerConfiguringProgress) break; + if (currentPage() == Page::Vpn) + break; + if (currentPage() == Page::ServerConfiguringProgress) + break; case Qt::Key_Back: -// if (currentPage() == Page::Start && pagesStack.size() < 2) break; -// if (currentPage() == Page::Sites && -// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { -// ui->tableView_sites->clearSelection(); -// break; -// } + // if (currentPage() == Page::Start && pagesStack.size() < 2) break; + // if (currentPage() == Page::Sites && + // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { + // ui->tableView_sites->clearSelection(); + // break; + // } - emit closePage(); + emit closePage(); //} - default: - ; + default:; } } @@ -250,8 +241,7 @@ void UiLogic::onCloseWindow() #ifdef Q_OS_ANDROID qApp->quit(); #else - if (m_settings->serversCount() == 0) - { + if (m_settings->serversCount() == 0) { qApp->quit(); } else { emit hide(); @@ -267,7 +257,6 @@ QString UiLogic::containerName(int container) QString UiLogic::containerDesc(int container) { return ContainerProps::containerDescriptions().value(static_cast(container)); - } void UiLogic::onGotoCurrentProtocolsPage() @@ -286,50 +275,50 @@ void UiLogic::installServer(QPair &container) qApp->processEvents(); ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { + pageFunc.setEnabledFunc = [this](bool enabled) -> void { pageLogic()->set_pageEnabled(enabled); }; ServerConfiguringProgressLogic::ButtonFunc noButton; ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { + waitInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelWaitInfoText(text); }; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + waitInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelWaitInfoVisible(visible); }; ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarVisible(visible); }; - progressBarFunc.setValueFunc = [this] (int value) -> void { + progressBarFunc.setValueFunc = [this](int value) -> void { pageLogic()->set_progressBarValue(value); }; - progressBarFunc.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this](void) -> int { return pageLogic()->progressBarValue(); }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { + progressBarFunc.getMaximumFunc = [this](void) -> int { return pageLogic()->progressBarMaximum(); }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setTextVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarTextVisible(visible); }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + progressBarFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_progressBarText(text); }; ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { + busyInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelServerBusyText(text); }; - busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + busyInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelServerBusyVisible(visible); }; ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + cancelButtonFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_pushButtonCancelVisible(visible); }; @@ -338,13 +327,12 @@ void UiLogic::installServer(QPair &container) if (errorCode == ErrorCode::NoError) { if (!isContainerAlreadyAddedToGui(container.first)) { progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&] () { + auto installAction = [&]() { ServerController serverController(m_settings); return serverController.setupContainer(m_installCredentials, container.first, container.second); }; - errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - noButton, waitInfoFunc, - busyInfoFunc, cancelButtonFunc); + errorCode = pageLogic()->doInstallAction( + installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); if (errorCode == ErrorCode::NoError) { if (!isServerCreated) { QJsonObject server; @@ -354,7 +342,7 @@ void UiLogic::installServer(QPair &container) server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); - server.insert(config_key::containers, QJsonArray{container.second}); + server.insert(config_key::containers, QJsonArray { container.second }); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); m_settings->addServer(server); @@ -371,22 +359,23 @@ void UiLogic::installServer(QPair &container) } } else { onUpdateAllPages(); - emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); + emit showWarningMessage( + "Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); emit setStartPage(Page::Vpn); return; } } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); + emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + + errorString(errorCode) + "\n" + tr("See logs for details.")); emit closePage(); } PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) { PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) return logic; + if (logic) + return logic; else { qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; return new PageProtocolLogicBase(this); @@ -418,7 +407,7 @@ PageEnumNS::Page UiLogic::currentPage() return static_cast(currentPageValue()); } -void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QString ext, const QString& data) +void UiLogic::saveTextFile(const QString &desc, const QString &suggestedName, QString ext, const QString &data) { #ifdef Q_OS_IOS shareTempFile(suggestedName, ext, data); @@ -429,17 +418,19 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; #ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, - QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext); + fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); + if (fileName.isEmpty()) + return; + if (!fileName.toString().endsWith(ext)) + fileName = QUrl(fileName.toString() + ext); #elif defined Q_OS_ANDROID qDebug() << "UiLogic::shareConfig" << data; AndroidController::instance()->shareConfig(data, suggestedName); return; #endif - if (fileName.isEmpty()) return; + if (fileName.isEmpty()) + return; #ifdef AMNEZIA_DESKTOP QFile save(fileName.toLocalFile()); @@ -458,11 +449,13 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) { ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName(nullptr, desc, - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); + QString fileName = QFileDialog::getSaveFileName( + nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile save(fileName); save.open(QIODevice::WriteOnly); @@ -478,12 +471,15 @@ void UiLogic::copyToClipboard(const QString &text) qApp->clipboard()->setText(text); } -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { +void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString &data) +{ ext.replace("*", ""); QString fileName = QDir::tempPath() + "/" + suggestedName; - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile::remove(fileName); @@ -497,14 +493,14 @@ void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QSt MobileUtils::shareText(filesToSend); } -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, QFileDialog::Options options) +QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) { QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); #ifdef Q_OS_ANDROID // patch for files containing spaces etc - const QString sep {"raw%3A%2F"}; + const QString sep { "raw%3A%2F" }; if (fileName.startsWith("content://") && fileName.contains(sep)) { QString contentUrl = fileName.split(sep).at(0); QString rawUrl = fileName.split(sep).at(1); @@ -590,7 +586,8 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) } if (createNewServer) { - server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); + server.insert(config_key::defaultContainer, + ContainerProps::containerToString(installedContainers.firstKey())); m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); isServerCreated = true; From b9a13d3a3243c4d00f69206a3e7958362c9ca3f0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Jul 2023 16:33:58 +0900 Subject: [PATCH 047/131] changed all text to english --- .../ConnectionTypeSelectionDrawer.qml | 6 ++--- client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 6 ++--- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 23 ++++++------------- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 ++-- .../PageSetupWizardProtocolSettings.qml | 6 ++--- .../qml/Pages2/PageSetupWizardProtocols.qml | 4 ++-- client/ui/qml/Pages2/PageSetupWizardStart.qml | 7 +++--- 10 files changed, 26 insertions(+), 36 deletions(-) diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 71299b1fc..8b377600a 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -28,7 +28,7 @@ DrawerType { Layout.bottomMargin: 32 Layout.alignment: Qt.AlignHCenter - text: "Данные для подключения" + text: qsTr("Connection data") wrapMode: Text.WordWrap } @@ -37,7 +37,7 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: "IP, логин и пароль от сервера" + text: qsTr("Server IP, login and password") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -51,7 +51,7 @@ DrawerType { LabelWithButtonType { Layout.fillWidth: true - text: "QR-код, ключ или файл настроек" + text: qsTr("QR code, key or configuration file") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 1dc542e05..49bcdb9af 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -81,7 +81,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 8 - text: "Обычно это занимает не больше 5 минут" + text: qsTr("Usually it takes no more than 5 minutes") } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b7d44c78c..76b9f94b4 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -163,7 +163,7 @@ PageType { text: root.currentContainerName textColor: "#0E0E11" - headerText: qsTr("Протокол подключения") + headerText: qsTr("Connection protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index c5af47e0e..20afecd26 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -22,7 +22,7 @@ PageType { if (isInstalledContainerFound) { message = qsTr("All installed containers have been added to the application") } else { - message = qsTr("Не найдено установленных контейнеров") + message = qsTr("No installed containers found") } PageController.showErrorMessage(message) @@ -84,8 +84,8 @@ PageType { visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") - descriptionText: qsTr("Добавим их в приложение, если они не отображались") + text: qsTr("Check the server for previously installed Amnesia services") + descriptionText: qsTr("Add them to the application if they were not displayed") clickedFunction: function() { PageController.showBusyIndicator(true) diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 4c6a98398..496ed3701 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -36,7 +36,7 @@ PageType { actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: "Серверы" + headerText: qsTr("Servers") actionButtonFunction: function() { connectionTypeSelection.visible = true diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 78bd74865..ae24c942b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -38,9 +38,9 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Подключение к серверу" - descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n -Всё в порядке, если код передал друг." + headerText: qsTr("Server connection") + descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n +It's okay if a friend passed the code.") } Header2TextType { @@ -49,30 +49,21 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: "Что у вас есть?" + text: qsTr("What do you have?") } LabelWithButtonType { Layout.fillWidth: true Layout.topMargin: 16 - text: "Файл с настройками подключения" + text: qsTr("File with connection settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { -// onClicked: fileDialog.open() ImportController.extractConfigFromFile() goToPage(PageEnum.PageSetupWizardViewConfig) } - -// FileDialog { -// id: fileDialog -// onAccepted: { -// ImportController.extractConfigFromFile(selectedFile) -// goToPage(PageEnum.PageSetupWizardViewConfig) -// } -// } } DividerType {} @@ -81,7 +72,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "QR-код" + text: qsTr("QR-code") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/qr-code.svg" @@ -96,7 +87,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "Ключ в виде текста" + text: qsTr("Key as text") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/text-cursor.svg" diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 2f07db186..a0fdf7bed 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -116,7 +116,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - headerText: "Установка" + headerText: qsTr("Installing") descriptionText: name } @@ -142,7 +142,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 8 - text: "Обычно это занимает не больше 5 минут" + text: qsTr("Usually it takes no more than 5 minutes") } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index e593393c9..29067cb28 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -193,7 +193,7 @@ PageType { Layout.topMargin: 16 - text: "Network protocol" + text: qsTr("Network protocol") } TransportProtoSelector { @@ -209,7 +209,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - headerText: "Port" + headerText: qsTr("Port") } Rectangle { @@ -223,7 +223,7 @@ PageType { Layout.fillWidth: true Layout.bottomMargin: 32 - text: qsTr("Установить") + text: qsTr("Install") onClicked: function() { goToPage(PageEnum.PageSetupWizardInstalling); diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 6a8e301f8..f3de85b61 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -70,8 +70,8 @@ PageType { width: parent.width - headerText: "Протокол подключения" - descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + headerText: qsTr("Connection protocol") + descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 750084c7b..73438e34c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -58,7 +58,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + text: qsTr("A free service to create a personal VPN on your server. We help you access blocked content without exposing your privacy even to VPN providers.") } BasicButtonType { @@ -67,7 +67,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("У меня есть данные для подключения") + text: qsTr("I have the data to connect") onClicked: { connectionTypeSelection.visible = true @@ -87,10 +87,9 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("У меня ничего нет") + text: qsTr("I have nothing") onClicked: { - goToPage(PageEnum.PageTest) } } } From 0411792ca5c2e614b151c497f7ab5685aa7467c8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 25 Jul 2023 16:56:10 +0900 Subject: [PATCH 048/131] added qr-code decoder for android - added color change for status and navigation bar for android --- client/ui/controllers/exportController.cpp | 47 +++--- client/ui/controllers/exportController.h | 1 - client/ui/controllers/importController.cpp | 138 +++++++++++++++--- client/ui/controllers/importController.h | 24 ++- client/ui/controllers/pageController.cpp | 41 ++++++ client/ui/controllers/pageController.h | 3 + .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Controls2/DrawerType.qml | 14 ++ .../Pages2/PageSetupWizardConfigSource.qml | 10 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 3 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 24 ++- client/ui/qml/main2.qml | 1 + 13 files changed, 255 insertions(+), 58 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 561222f26..6e4abb1f6 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -202,31 +202,6 @@ QList ExportController::getQrCodes() } void ExportController::saveFile() -{ - QString fileExtension = ".vpn"; - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileName; - fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"), - QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension); - if (fileName.isEmpty()) - return; - if (!fileName.toString().endsWith(fileExtension)) { - fileName = QUrl(fileName.toString() + fileExtension); - } - if (fileName.isEmpty()) - return; - - QFile save(fileName.toLocalFile()); - - save.open(QIODevice::WriteOnly); - save.write(m_config.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void ExportController::shareFile() { #if defined Q_OS_IOS ext.replace("*", ""); @@ -253,6 +228,28 @@ void ExportController::shareFile() AndroidController::instance()->shareConfig(m_config, "amnezia_config"); return; #endif + + QString fileExtension = ".vpn"; + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileName; + fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"), + QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension); + if (fileName.isEmpty()) + return; + if (!fileName.toString().endsWith(fileExtension)) { + fileName = QUrl(fileName.toString() + fileExtension); + } + if (fileName.isEmpty()) + return; + + QFile save(fileName.toLocalFile()); + + save.open(QIODevice::WriteOnly); + save.write(m_config.toUtf8()); + save.close(); + + QFileInfo fi(fileName.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 7af2cd850..84575079b 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -36,7 +36,6 @@ public slots: QList getQrCodes(); void saveFile(); - void shareFile(); signals: void generateConfig(int type); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 218f43cb4..5711c3bdb 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -41,6 +41,11 @@ namespace } return ConfigTypes::Amnezia; } + +#ifdef Q_OS_ANDROID + ImportController *mInstance = nullptr; + constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity"; +#endif } // namespace ImportController::ImportController(const QSharedPointer &serversModel, @@ -49,6 +54,7 @@ ImportController::ImportController(const QSharedPointer &serversMo : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { #ifdef Q_OS_ANDROID + mInstance = this; // Set security screen for Android app AndroidUtils::runOnAndroidThreadSync([]() { QJniObject activity = AndroidUtils::getActivity(); @@ -58,6 +64,18 @@ ImportController::ImportController(const QSharedPointer &serversMo window.callMethod("addFlags", "(I)V", FLAG_SECURE); } }); + + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[] { + { "passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewQrCodeDataChunk) }, + }; + + QJniObject javaClass(AndroidCameraActivity); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); #endif } @@ -93,11 +111,21 @@ void ImportController::extractConfigFromCode(QString code) m_configFileName = ""; } -void ImportController::extractConfigFromQr() +bool ImportController::extractConfigFromQr(const QByteArray &data) { -#ifdef Q_OS_ANDROID - AndroidController::instance()->startQrReaderActivity(); -#endif + QJsonObject dataObj = QJsonDocument::fromJson(data).object(); + if (!dataObj.isEmpty()) { + m_config = dataObj; + return true; + } + + QByteArray ba_uncompressed = qUncompress(data); + if (!ba_uncompressed.isEmpty()) { + m_config = QJsonDocument::fromJson(ba_uncompressed).object(); + return true; + } + + return false; } QString ImportController::getConfig() @@ -149,21 +177,6 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data) return QJsonDocument::fromJson(ba).object(); } -// bool ImportController::importConnectionFromQr(const QByteArray &data) -//{ -// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); -// if (!dataObj.isEmpty()) { -// return importConnection(dataObj); -// } - -// QByteArray ba_uncompressed = qUncompress(data); -// if (!ba_uncompressed.isEmpty()) { -// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); -// } - -// return false; -//} - QJsonObject ImportController::extractOpenVpnConfig(const QString &data) { QJsonObject openVpnConfig; @@ -251,3 +264,90 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) return config; } + +#ifdef Q_OS_ANDROID +void ImportController::startDecodingQr() +{ + AndroidController::instance()->startQrReaderActivity(); +} + +void ImportController::stopDecodingQr() +{ + QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); + emit qrDecodingFinished(); +} + +void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); + const char *buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(data, buffer); + + if (mInstance != nullptr) { + if (!mInstance->m_isQrCodeProcessed) { + mInstance->m_qrCodeChunks.clear(); + mInstance->m_isQrCodeProcessed = true; + mInstance->m_totalQrCodeChunksCount = 0; + mInstance->m_receivedQrCodeChunksCount = 0; + } + mInstance->parseQrCodeChunk(parcelBody); + } +} + +void ImportController::parseQrCodeChunk(const QString &code) +{ + // qDebug() << code; + if (!m_isQrCodeProcessed) + return; + + // check if chunk received + QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QDataStream s(&ba, QIODevice::ReadOnly); + qint16 magic; + s >> magic; + + if (magic == amnezia::qrMagicCode) { + quint8 chunksCount; + s >> chunksCount; + if (m_totalQrCodeChunksCount != chunksCount) { + m_qrCodeChunks.clear(); + } + + m_totalQrCodeChunksCount = chunksCount; + + quint8 chunkId; + s >> chunkId; + s >> m_qrCodeChunks[chunkId]; + m_receivedQrCodeChunksCount = m_qrCodeChunks.size(); + + if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) { + QByteArray data; + + for (int i = 0; i < m_totalQrCodeChunksCount; ++i) { + data.append(m_qrCodeChunks.value(i)); + } + + bool ok = extractConfigFromQr(data); + if (ok) { + m_isQrCodeProcessed = false; + stopDecodingQr(); + } else { + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + } + } + } else { + bool ok = extractConfigFromQr(ba); + if (ok) { + m_isQrCodeProcessed = false; + stopDecodingQr(); + } + } +} +#endif diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 273b12a5b..3bdb22527 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -7,6 +7,9 @@ #include "core/defs.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "jni.h" +#endif class ImportController : public QObject { @@ -21,25 +24,44 @@ public slots: void extractConfigFromFile(); void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); - void extractConfigFromQr(); + bool extractConfigFromQr(const QByteArray &data); QString getConfig(); QString getConfigFileName(); +#if defined Q_OS_ANDROID + void startDecodingQr(); +#endif + signals: void importFinished(); void importErrorOccurred(QString errorMessage); + void qrDecodingFinished(); + private: QJsonObject extractAmneziaConfig(QString &data); QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data); +#if defined Q_OS_ANDROID + void stopDecodingQr(); + static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data); + void parseQrCodeChunk(const QString &code); +#endif + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; QJsonObject m_config; QString m_configFileName; + +#if defined Q_OS_ANDROID + QMap m_qrCodeChunks; + bool m_isQrCodeProcessed; + int m_totalQrCodeChunksCount; + int m_receivedQrCodeChunksCount; +#endif }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 5abeb77fe..36d5ba7c9 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,10 +1,28 @@ #include "pageController.h" #include +#ifdef Q_OS_ANDROID + #include "../../platforms/android/androidutils.h" + #include +#endif PageController::PageController(const QSharedPointer &serversModel, QObject *parent) : QObject(parent), m_serversModel(serversModel) { +#ifdef Q_OS_ANDROID + // Change color of navigation and status bar's + auto initialPageNavigationBarColor = getInitialPageNavigationBarColor(); + AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("addFlags", "(I)V", 0x80000000); + window.callMethod("clearFlags", "(I)V", 0x04000000); + window.callMethod("setStatusBarColor", "(I)V", 0xFF0E0E11); + window.callMethod("setNavigationBarColor", "(I)V", initialPageNavigationBarColor); + } + }); +#endif } QString PageController::getInitialPage() @@ -47,3 +65,26 @@ void PageController::keyPressEvent(Qt::Key key) default: return; } } + +unsigned int PageController::getInitialPageNavigationBarColor() +{ + if (m_serversModel->getServersCount()) { + return 0xFF1C1D21; + } else { + return 0xFF0E0E11; + } +} + +void PageController::updateNavigationBarColor(const int color) +{ +#ifdef Q_OS_ANDROID + // Change color of navigation bar + AndroidUtils::runOnAndroidThreadSync([&color]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("setNavigationBarColor", "(I)V", color); + } + }); +#endif +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index e8452b459..8087d0fec 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -71,6 +71,9 @@ public slots: void closeWindow(); void keyPressEvent(Qt::Key key); + unsigned int getInitialPageNavigationBarColor(); + void updateNavigationBarColor(const int color); + signals: void goToPageHome(); void goToPageSettings(); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 692289a86..f133f27a1 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -54,10 +54,10 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code") + text: qsTr("Share") onClicked: { - Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile() + ExportController.saveFile() } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index e3c9d5881..97fbf0343 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -12,6 +12,7 @@ Drawer { velocity: 4 } } + exit: Transition { SmoothedAnimation { velocity: 4 @@ -31,4 +32,17 @@ Drawer { Overlay.modal: Rectangle { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } + + onAboutToShow: { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + onClosed: { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index ae24c942b..2d6b249d1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,6 +13,14 @@ import "../Config" PageType { id: root + Connections { + target: ImportController + + function onQrDecodingFinished() { + goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + FlickableType { id: fl anchors.top: parent.top @@ -77,7 +85,7 @@ It's okay if a friend passed the code.") leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { - ImportController.extractConfigFromQr() + ImportController.startDecodingQr() // goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index a0fdf7bed..92c95108b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -47,8 +47,7 @@ PageType { } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) } else { - var pagePath = PageController.getPagePath(PageEnum.PageStart) - stackView.replace(pagePath, { "objectName" : pagePath }) + PageController.replaceStartPage() } if (isInstalledContainerFound) { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index e1b933023..372a5cde5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -30,8 +30,7 @@ PageType { } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) } else { - var pagePath = PageController.getPagePath(PageEnum.PageStart) - stackView.replace(pagePath, { "objectName" : pagePath }) + PageController.replaceStartPage() } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5d49abf70..f522bacec 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Shapes import PageEnum 1.0 @@ -81,14 +82,27 @@ PageType { anchors.bottom: parent.bottom topPadding: 8 - bottomPadding: 8//34 + bottomPadding: 8 leftPadding: shareTabButton.visible ? 96 : 128 rightPadding: shareTabButton.visible ? 96 : 128 - background: Rectangle { - border.width: 1 - border.color: "#2C2D30" - color: "#1C1D21" + background: Shape { + width: parent.width + height: parent.height + + ShapePath { + startX: 0 + startY: 0 + + PathLine { x: width; y: 0 } + PathLine { x: width; y: height - 1 } + PathLine { x: 0; y: height - 1 } + PathLine { x: 0; y: 0 } + + strokeWidth: 1 + strokeColor: "#2C2D30" + fillColor: "#1C1D21" + } } TabImageButtonType { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index d0f9880d6..0b89d840a 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -50,6 +50,7 @@ Window { while (rootStackView.depth > 1) { rootStackView.pop() } + PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor()) rootStackView.replace(pagePath, { "objectName" : pagePath }) } From 1092abe776a89da2d482c5b6c929e196763c180e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 00:13:08 +0900 Subject: [PATCH 049/131] added output of notifications/errors after installation/import --- client/protocols/ios_vpnprotocol.mm | 50 ++++++------ client/ui/controllers/exportController.cpp | 38 ++++----- client/ui/controllers/importController.cpp | 4 +- client/ui/controllers/installController.cpp | 80 ++++++++++++++++--- client/ui/controllers/installController.h | 15 +++- client/ui/controllers/pageController.h | 2 +- .../protocolSettingsController.cpp | 15 ---- .../controllers/protocolSettingsController.h | 31 ------- client/ui/controllers/settingsController.cpp | 11 ++- client/ui/controllers/settingsController.h | 3 + client/ui/models/containers_model.cpp | 39 +++++---- client/ui/models/containers_model.h | 11 ++- client/ui/models/protocols_model.cpp | 2 + client/ui/models/protocols_model.h | 1 + client/ui/models/servers_model.cpp | 31 ++++++- client/ui/models/servers_model.h | 7 ++ .../qml/Components/HomeContainersListView.qml | 6 -- .../qml/Components/SelectLanguageDrawer.qml | 2 +- client/ui/qml/Controls2/CheckBoxType.qml | 28 ++++++- .../qml/Controls2/HorizontalRadioButton.qml | 53 ++++++------ client/ui/qml/Controls2/PopupType.qml | 54 ++++++++----- client/ui/qml/Controls2/SwitcherType.qml | 26 ++++-- client/ui/qml/Pages2/PageHome.qml | 34 ++------ .../Pages2/PageProtocolOpenVpnSettings.qml | 5 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 19 ++++- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 4 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 4 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 1 + client/ui/qml/Pages2/PageSettingsBackup.qml | 18 +++++ client/ui/qml/Pages2/PageSettingsDns.qml | 6 ++ .../ui/qml/Pages2/PageSettingsServerData.qml | 39 ++++++--- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 1 + .../qml/Pages2/PageSettingsServerProtocol.qml | 32 +++++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 15 +--- .../PageSetupWizardProtocolSettings.qml | 9 ++- client/ui/qml/Pages2/PageSetupWizardStart.qml | 17 ---- client/ui/qml/Pages2/PageShare.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 28 ++----- client/ui/qml/main2.qml | 47 +++++++++++ 39 files changed, 488 insertions(+), 303 deletions(-) delete mode 100644 client/ui/controllers/protocolSettingsController.cpp delete mode 100644 client/ui/controllers/protocolSettingsController.h diff --git a/client/protocols/ios_vpnprotocol.mm b/client/protocols/ios_vpnprotocol.mm index f71db34a6..2195dbdde 100644 --- a/client/protocols/ios_vpnprotocol.mm +++ b/client/protocols/ios_vpnprotocol.mm @@ -136,7 +136,7 @@ void IOSVpnProtocol::stop() [m_controller disconnect]; - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); [m_controller dealloc]; m_controller = nullptr; @@ -264,7 +264,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -272,7 +272,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -280,7 +280,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) case ConnectionStateDisconnected: [m_controller disconnect]; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -294,13 +294,13 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) qDebug() << "State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -325,7 +325,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -333,7 +333,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -341,7 +341,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -355,13 +355,13 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) qDebug() << "VPN State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -388,7 +388,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -396,7 +396,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -404,7 +404,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -418,13 +418,13 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) qDebug() << "VPN State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -452,7 +452,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -460,7 +460,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -468,7 +468,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -482,13 +482,13 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) qDebug() << "SS State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -539,7 +539,7 @@ void IOSVpnProtocol::launchWireguardTunnel(const QJsonObject &rawConfig) failureCallback:^() { qDebug() << "Wireguard Protocol - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -590,7 +590,7 @@ void IOSVpnProtocol::launchCloakTunnel(const QJsonObject &rawConfig) failureCallback:^{ qDebug() << "IOSVPNProtocol (OpenVPN Cloak) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -607,7 +607,7 @@ void IOSVpnProtocol::launchOpenVPNTunnel(const QJsonObject &rawConfig) failureCallback:^{ qDebug() << "IOSVPNProtocol (OpenVPN) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -624,7 +624,7 @@ void IOSVpnProtocol::launchShadowSocksTunnel(const QJsonObject &rawConfig) { failureCallback:^{ qDebug() << "IOSVPNProtocol (ShadowSocks) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 6e4abb1f6..8fbd69f1e 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -204,25 +204,25 @@ QList ExportController::getQrCodes() void ExportController::saveFile() { #if defined Q_OS_IOS - ext.replace("*", ""); - QString fileName = QDir::tempPath() + "/" + suggestedName; - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile::remove(fileName); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QStringList filesToSend; - filesToSend.append(fileName); - MobileUtils::shareText(filesToSend); - return; +// ext.replace("*", ""); +// QString fileName = QDir::tempPath() + "/" + suggestedName; +// +// if (fileName.isEmpty()) +// return; +// if (!fileName.endsWith(ext)) +// fileName.append(ext); +// +// QFile::remove(fileName); +// +// QFile save(fileName); +// save.open(QIODevice::WriteOnly); +// save.write(data.toUtf8()); +// save.close(); +// +// QStringList filesToSend; +// filesToSend.append(fileName); +// MobileUtils::shareText(filesToSend); +// return; #endif #if defined Q_OS_ANDROID AndroidController::instance()->shareConfig(m_config, "amnezia_config"); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5711c3bdb..8e2ad5d1c 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -149,9 +149,7 @@ void ImportController::importConfig() if (credentials.isValid() || m_config.contains(config_key::containers)) { m_serversModel->addServer(m_config); - if (!m_config.value(config_key::containers).toArray().isEmpty()) { - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - } + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit importFinished(); } else { diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 68e47a891..be235e423 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -88,14 +88,20 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); + + QString finishMessage = ""; + if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); installedContainers.insert(container, config); + finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); } - - bool isInstalledContainerFound = false; - if (!installedContainers.isEmpty()) { - isInstalledContainerFound = true; + if (installedContainers.size() > 1) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); } if (errorCode == ErrorCode::NoError) { @@ -117,7 +123,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co m_serversModel->addServer(server); m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit installServerFinished(false); // todo incorrect notification about found containers + emit installServerFinished(finishMessage); return; } @@ -135,16 +141,19 @@ void InstallController::installContainer(DockerContainer container, QJsonObject QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); - bool isInstalledContainerFound = false; - if (!installedContainers.isEmpty()) { - isInstalledContainerFound = true; - } + QString finishMessage = ""; if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(serverCredentials, container, config); installedContainers.insert(container, config); + finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); } + bool isInstalledContainerAddedToGui = false; + if (errorCode == ErrorCode::NoError) { for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { auto modelIndex = m_containersModel->index(iterator.key()); @@ -153,10 +162,17 @@ void InstallController::installContainer(DockerContainer container, QJsonObject if (containerConfig.isEmpty()) { m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), ContainersModel::Roles::ConfigRole); + if (container != iterator.key()) { // skip the newly installed container + isInstalledContainerAddedToGui = true; + } } } + if (isInstalledContainerAddedToGui) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } - emit installContainerFinished(false); // todo incorrect notification about found containers + emit installContainerFinished(finishMessage); return; } @@ -233,11 +249,55 @@ void InstallController::updateContainer(QJsonObject config) emit installationErrorOccurred(errorString(errorCode)); } +void InstallController::removeCurrentlyProcessedServer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + m_serversModel->removeServer(); + emit removeCurrentlyProcessedServerFinished(tr("Server '") + serverName + tr("' was removed")); +} + +void InstallController::removeAllContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeAllContainers(); + if (errorCode == ErrorCode::NoError) { + emit removeAllContainersFinished(tr("All containers from server '") + serverName + ("' have been removed")); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::removeCurrentlyProcessedContainer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + int container = m_containersModel->getCurrentlyProcessedContainerIndex(); + QString containerName = m_containersModel->data(container, ContainersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); + if (errorCode == ErrorCode::NoError) { + emit removeCurrentlyProcessedContainerFinished(containerName + tr(" has been removed from the server '") + + serverName + "'"); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + QRegularExpression InstallController::ipAddressPortRegExp() { return Utils::ipAddressPortRegExp(); } +QRegularExpression InstallController::ipAddressRegExp() +{ + return Utils::ipAddressRegExp(); +} + void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) { diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index fdd0ebedb..8bc04f399 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -28,18 +28,27 @@ public slots: void updateContainer(QJsonObject config); + void removeCurrentlyProcessedServer(); + void removeAllContainers(); + void removeCurrentlyProcessedContainer(); + QRegularExpression ipAddressPortRegExp(); + QRegularExpression ipAddressRegExp(); void mountSftpDrive(const QString &port, const QString &password, const QString &username); signals: - void installContainerFinished(bool isInstalledContainerFound); - void installServerFinished(bool isInstalledContainerFound); + void installContainerFinished(QString finishMessage); + void installServerFinished(QString finishMessage); void updateContainerFinished(); void scanServerFinished(bool isInstalledContainerFound); + void removeCurrentlyProcessedServerFinished(QString finishedMessage); + void removeAllContainersFinished(QString finishedMessage); + void removeCurrentlyProcessedContainerFinished(QString finishedMessage); + void installationErrorOccurred(QString errorMessage); void serverAlreadyExists(int serverIndex); @@ -57,7 +66,9 @@ private: bool m_shouldCreateServer; +#ifndef Q_OS_IOS QList> m_sftpMountProcesses; +#endif }; #endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8087d0fec..4273ed251 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -84,7 +84,7 @@ signals: void replaceStartPage(); void showErrorMessage(QString errorMessage); - void showInfoMessage(QString message); + void showNotificationMessage(QString message); void showBusyIndicator(bool visible); diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp deleted file mode 100644 index 11b6a904e..000000000 --- a/client/ui/controllers/protocolSettingsController.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "protocolSettingsController.h" - -ProtocolSettingsController::ProtocolSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{ -} - -QByteArray ProtocolSettingsController::getOpenVpnConfig() -{ - auto containerIndex = m_containersModel->index(m_containersModel->getCurrentlyProcessedContainerIndex()); - auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); - return QByteArray(); -} diff --git a/client/ui/controllers/protocolSettingsController.h b/client/ui/controllers/protocolSettingsController.h deleted file mode 100644 index 730cbda76..000000000 --- a/client/ui/controllers/protocolSettingsController.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef PROTOCOLSETTINGSCONTROLLER_H -#define PROTOCOLSETTINGSCONTROLLER_H - -#include - -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" - -class ProtocolSettingsController : public QObject -{ - Q_OBJECT -public: - explicit ProtocolSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); - -public slots: - QByteArray getOpenVpnConfig(); - -signals: - -private: - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - std::shared_ptr m_settings; -}; - -#endif // PROTOCOLSETTINGSCONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b501d0850..eda67919d 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -84,9 +84,10 @@ void SettingsController::restoreAppConfig() Utils::getFileName(Q_NULLPTR, tr("Open backup"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - // todo error processing - if (fileName.isEmpty()) + if (fileName.isEmpty()) { + emit changeSettingsErrorOccurred(tr("Backup file is empty")); return; + } QFile file(fileName); file.open(QIODevice::ReadOnly); @@ -94,7 +95,10 @@ void SettingsController::restoreAppConfig() bool ok = m_settings->restoreAppConfig(data); if (ok) { - // emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); + m_serversModel->resetModel(); + emit restoreBackupFinished(); + } else { + emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); } } @@ -106,4 +110,5 @@ QString SettingsController::getAppVersion() void SettingsController::clearSettings() { m_settings->clearSettings(); + m_serversModel->resetModel(); } diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 313f934d1..ac85d300e 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -47,6 +47,9 @@ signals: void secondaryDnsChanged(); void loggingStateChanged(); + void restoreBackupFinished(); + void changeSettingsErrorOccurred(QString errorMessage); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 922542dd4..ea571a227 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -83,6 +83,12 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } +QVariant ContainersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} + void ContainersModel::setCurrentlyProcessedServerIndex(const int index) { beginResetModel(); @@ -123,11 +129,12 @@ QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); } -void ContainersModel::removeAllContainers() +ErrorCode ContainersModel::removeAllContainers() { ServerController serverController(m_settings); - auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + ErrorCode errorCode = + serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); if (errorCode == ErrorCode::NoError) { beginResetModel(); @@ -138,30 +145,32 @@ void ContainersModel::removeAllContainers() setData(index(DockerContainer::None, 0), true, IsDefaultRole); endResetModel(); } - - // todo process errors + return errorCode; } -void ContainersModel::removeCurrentlyProcessedContainer() +ErrorCode ContainersModel::removeCurrentlyProcessedContainer() { ServerController serverController(m_settings); auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); auto dockerContainer = static_cast(m_currentlyProcessedContainerIndex); - ErrorCode e = serverController.removeContainer(credentials, dockerContainer); + ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); - beginResetModel(); // todo change to begin remove rows? - m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); - m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + if (errorCode == ErrorCode::NoError) { + beginResetModel(); // todo change to begin remove rows? + m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); - if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { - if (m_containers.isEmpty()) { - setData(index(DockerContainer::None, 0), true, IsDefaultRole); - } else { - setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { + if (m_containers.isEmpty()) { + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + } else { + setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + } } + endResetModel(); } - endResetModel(); + return errorCode; } void ContainersModel::clearCachedProfiles() diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 75ba91142..690eff413 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -36,9 +36,9 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const int index, int role) const; -signals: - void defaultContainerChanged(); + Q_PROPERTY(QString defaultContainerName READ getDefaultContainerName NOTIFY defaultContainerChanged) public slots: DockerContainer getDefaultContainer(); @@ -52,8 +52,8 @@ public slots: QString getCurrentlyProcessedContainerName(); QJsonObject getCurrentlyProcessedContainerConfig(); - void removeAllContainers(); - void removeCurrentlyProcessedContainer(); + ErrorCode removeAllContainers(); + ErrorCode removeCurrentlyProcessedContainer(); void clearCachedProfiles(); bool isAmneziaDnsContainerInstalled(); @@ -62,6 +62,9 @@ public slots: protected: QHash roleNames() const override; +signals: + void defaultContainerChanged(); + private: QMap m_containers; diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index da730f551..8c9994708 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -17,6 +17,7 @@ QHash ProtocolsModel::roleNames() const roles[ProtocolNameRole] = "protocolName"; roles[ProtocolPageRole] = "protocolPage"; + roles[ProtocolIndexRole] = "protocolIndex"; roles[RawConfigRole] = "rawConfig"; return roles; @@ -35,6 +36,7 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const } case ProtocolPageRole: return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); + case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row())); case RawConfigRole: { auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); auto lastConfigJsonDoc = diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index c4ad5c70c..5ee8a3dd9 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -14,6 +14,7 @@ public: enum Roles { ProtocolNameRole = Qt::UserRole + 1, ProtocolPageRole, + ProtocolIndexRole, RawConfigRole }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index fabbb8296..364fd71f9 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -78,6 +78,15 @@ QVariant ServersModel::data(const int index, int role) const return data(modelIndex, role); } +void ServersModel::resetModel() +{ + beginResetModel(); + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currentlyProcessedServerIndex = m_defaultServerIndex; + endResetModel(); +} + void ServersModel::setDefaultServerIndex(const int index) { m_settings->setDefaultServer(index); @@ -90,11 +99,31 @@ const int ServersModel::getDefaultServerIndex() return m_defaultServerIndex; } +const QString ServersModel::getDefaultServerName() +{ + return qvariant_cast(data(m_defaultServerIndex, NameRole)); +} + +const QString ServersModel::getDefaultServerHostName() +{ + return qvariant_cast(data(m_defaultServerIndex, HostNameRole)); +} + const int ServersModel::getServersCount() { return m_servers.count(); } +bool ServersModel::hasServerWithWriteAccess() +{ + for (size_t i = 0; i < getServersCount(); i++) { + if (qvariant_cast(data(i, HasWriteAccessRole))) { + return true; + } + } + return false; +} + void ServersModel::setCurrentlyProcessedServerIndex(const int index) { m_currentlyProcessedServerIndex = index; @@ -123,7 +152,7 @@ bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() bool ServersModel::isDefaultServerHasWriteAccess() { - return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_defaultServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index a31e3c044..60ec35b27 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -28,17 +28,24 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const int index, int role = Qt::DisplayRole) const; + void resetModel(); + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex NOTIFY currentlyProcessedServerIndexChanged) public slots: void setDefaultServerIndex(const int index); const int getDefaultServerIndex(); + const QString getDefaultServerName(); + const QString getDefaultServerHostName(); bool isDefaultServerCurrentlyProcessed(); bool isCurrentlyProcessedServerHasWriteAccess(); bool isDefaultServerHasWriteAccess(); + bool hasServerWithWriteAccess(); const int getServersCount(); diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 28f81b2e2..0c2408be0 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -78,11 +78,5 @@ ListView { Layout.fillWidth: true } } - - Component.onCompleted: { - if (isDefault) { - root.currentContainerName = name - } - } } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index f1ffa4163..d872a889e 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -67,7 +67,7 @@ DrawerType { delegate: Item { implicitWidth: root.width - implicitHeight: content.implicitHeight + implicitHeight: delegateContent.implicitHeight ColumnLayout { id: delegateContent diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index e4b1703fe..2724a30cd 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -9,6 +9,11 @@ CheckBox { id: root property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" @@ -16,14 +21,16 @@ CheckBox { property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" + property string checkedBorderDisabledColor: "#5A330C" property string checkedImageColor: "#FBB26A" property string pressedImageColor: "#A85809" property string defaultImageColor: "transparent" + property string checkedDisabledImageColor: "#84603D" property string imageSource: "qrc:/images/controls/check.svg" - hoverEnabled: true + hoverEnabled: enabled ? true : false indicator: Rectangle { id: background @@ -52,7 +59,7 @@ CheckBox { width: 24 height: 24 color: "transparent" - border.color: root.checked ? checkedBorderColor : defaultBorderColor + border.color: root.checked ? (root.enabled ? checkedBorderColor : checkedBorderDisabledColor) : defaultBorderColor border.width: 1 radius: 4 @@ -63,7 +70,19 @@ CheckBox { layer { enabled: true effect: ColorOverlay { - color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + color: { + if (root.pressed) { + return root.pressedImageColor + } else if (root.checked) { + if (root.enabled) { + return root.checkedImageColor + } else { + return root.checkedDisabledImageColor + } + } else { + return root.defaultImageColor + } + } } } } @@ -90,6 +109,7 @@ CheckBox { Layout.fillWidth: true text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor } CaptionTextType { @@ -98,7 +118,7 @@ CheckBox { Layout.fillWidth: true text: root.descriptionText - color: "#878b91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor visible: root.descriptionText !== "" } diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 88fc2531d..1ac5840ac 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -9,14 +9,16 @@ RadioButton { property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: Qt.rgba(1, 1, 1, 0) - property string disabledColor: Qt.rgba(1, 1, 1, 0) - property string selectedColor: Qt.rgba(1, 1, 1, 0) + property string checkedColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: "transparent" - property string textColor: "#0E0E11" + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string pressedBorderColor: "#494B50" - property string selectedBorderColor: "#FBB26A" + property string checkedBorderColor: "#FBB26A" property string defaultBodredColor: "transparent" + property string checkedDisabledBorderColor: "#84603D" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -27,39 +29,39 @@ RadioButton { radius: 16 color: { -// if (root.enabled) { + if (root.enabled) { if (root.hovered) { - return hoveredColor + return root.hoveredColor } else if (root.checked) { - return selectedColor + return root.checkedColor } - return defaultColor -// } else { -// return disabledColor -// } + return root.defaultColor + } else { + return root.disabledColor + } } border.color: { -// if (root.enabled) { + if (root.enabled) { if (root.pressed) { - return pressedBorderColor + return root.pressedBorderColor } else if (root.checked) { - return selectedBorderColor + return root.checkedBorderColor } - return defaultBodredColor -// } -// return defaultBodredColor + return root.defaultBodredColor + } else { + if (root.checked) { + return root.checkedDisabledBorderColor + } + return root.defaultBodredColor + } } border.width: { -// if (root.enabled) { - if(root.checked) { - return 1 - } - return root.pressed ? 1 : 0 -// } else { -// return 0 -// } + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 } Behavior on color { @@ -77,6 +79,7 @@ RadioButton { ButtonTextType { text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor Layout.fillWidth: true Layout.rightMargin: 16 diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 61bdfd18e..e7bb16f4b 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -7,7 +7,7 @@ import "TextTypes" Popup { id: root - property string popupErrorMessageText + property string text property bool closeButtonVisible: true leftMargin: 25 @@ -17,46 +17,56 @@ Popup { width: parent.width - leftMargin - rightMargin anchors.centerIn: parent - modal: true + modal: root.closeButtonVisible closePolicy: Popup.CloseOnEscape Overlay.modal: Rectangle { + visible: root.closeButtonVisible color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } background: Rectangle { anchors.fill: parent - color: "white"//Qt.rgba(215/255, 216/255, 219/255, 0.95) + color: "white" radius: 4 } - contentItem: RowLayout { + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + anchors.fill: parent - anchors.leftMargin: 16 - anchors.rightMargin: 16 - CaptionTextType { - horizontalAlignment: Text.AlignLeft - Layout.fillWidth: true + RowLayout { + id: content - text: root.popupErrorMessageText - } + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 - BasicButtonType { - visible: closeButtonVisible + CaptionTextType { + horizontalAlignment: Text.AlignLeft + Layout.fillWidth: true - defaultColor: "white"//"transparent"//Qt.rgba(215/255, 216/255, 219/255, 0.95) - hoveredColor: "#C1C2C5" - pressedColor: "#AEB0B7" - disabledColor: "#494B50" + text: root.text + } - textColor: "#0E0E11" - borderWidth: 0 + BasicButtonType { + visible: closeButtonVisible - text: qsTr("Close") - onClicked: { - root.close() + defaultColor: "white" + hoveredColor: "#C1C2C5" + pressedColor: "#AEB0B7" + disabledColor: "#494B50" + + textColor: "#0E0E11" + borderWidth: 0 + + text: qsTr("Close") + onClicked: { + root.close() + } } } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b63c64fbc..aca5ba0b5 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -8,14 +8,24 @@ Switch { id: root property alias descriptionText: description.text + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string checkedIndicatorColor: "#412102" property string defaultIndicatorColor: "transparent" + property string checkedDisabledIndicatorColor: "#5A330C" + property string checkedIndicatorBorderColor: "#412102" property string defaultIndicatorBorderColor: "#494B50" + property string checkedDisabledIndicatorBorderColor: "#5A330C" property string checkedInnerCircleColor: "#FBB26A" property string defaultInnerCircleColor: "#D7D8DB" + property string checkedDisabledInnerCircleColor: "#84603D" + property string defaultDisabledInnerCircleColor: "#494B50" property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" @@ -23,6 +33,8 @@ Switch { implicitWidth: content.implicitWidth + switcher.implicitWidth implicitHeight: content.implicitHeight + hoverEnabled: enabled ? true : false + indicator: Rectangle { id: switcher @@ -33,8 +45,10 @@ Switch { implicitHeight: 32 radius: 16 - color: root.checked ? checkedIndicatorColor : defaultIndicatorColor - border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor + color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor) + : root.defaultIndicatorColor + border.color: root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor) + : root.defaultIndicatorBorderColor Behavior on color { PropertyAnimation { duration: 200 } @@ -51,7 +65,8 @@ Switch { width: root.checked ? 24 : 16 height: root.checked ? 24 : 16 radius: 23 - color: root.checked ? checkedInnerCircleColor : defaultInnerCircleColor + color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor) + : (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor) Behavior on x { PropertyAnimation { duration: 200 } @@ -63,7 +78,7 @@ Switch { width: 40 height: 40 radius: 23 - color: hovered ? hoveredIndicatorBackgroundColor : defaultIndicatorBackgroundColor + color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor Behavior on color { PropertyAnimation { duration: 200 } @@ -81,6 +96,7 @@ Switch { Layout.fillWidth: true text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor } CaptionTextType { @@ -88,7 +104,7 @@ Switch { Layout.fillWidth: true - color: "#878B91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor visible: text !== "" } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 76b9f94b4..6a134e9b5 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -22,22 +22,14 @@ PageType { property string borderColor: "#2C2D30" - property string currentServerName - property string currentServerHostName - property string currentContainerName + property string defaultServerName: ServersModel.defaultServerName + property string defaultServerHostName: ServersModel.defaultServerHostName + property string defaultContainerName: ContainersModel.defaultContainerName ConnectButton { anchors.centerIn: parent } - Connections { - target: ContainersModel - - function onDefaultContainerChanged() { - root.currentContainerName = ContainersModel.getDefaultContainerName() - } - } - Connections { target: PageController @@ -79,7 +71,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Header1TextType { - text: root.currentServerName + text: root.defaultServerName } Image { @@ -107,7 +99,7 @@ PageType { } } - description += root.currentContainerName + " | " + root.currentServerHostName + description += root.defaultContainerName + " | " + root.defaultServerHostName return description } } @@ -139,14 +131,14 @@ PageType { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentServerName + text: root.defaultServerName } LabelTextType { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentServerHostName + text: root.defaultServerHostName } RowLayout { @@ -161,7 +153,7 @@ PageType { rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" - text: root.currentContainerName + text: root.defaultContainerName textColor: "#0E0E11" headerText: qsTr("Connection protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -299,9 +291,6 @@ PageType { ServersModel.currentlyProcessedIndex = index ServersModel.defaultIndex = index - - root.currentServerName = name - root.currentServerHostName = hostName } MouseArea { @@ -331,13 +320,6 @@ PageType { Layout.fillWidth: true } } - - Component.onCompleted: { - if (serversMenuContent.currentIndex === index) { - root.currentServerName = name - root.currentServerHostName = hostName - } - } } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 028f9fd35..65fcdc4b5 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -425,16 +425,13 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") -// questionDrawer.descriptionText = qsTr("") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 377d948e7..e82b0ff56 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -173,8 +173,19 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - ContainersModel.removeCurrentlyProcessedContainer() - closePage() + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } MouseArea { @@ -186,5 +197,9 @@ PageType { DividerType {} } + + QuestionDrawer { + id: questionDrawer + } } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index ffa428591..0eabb0765 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -258,9 +258,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index a47a95e3a..7cf2a81af 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -150,9 +150,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 54d315b03..2c7d86012 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -92,6 +92,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false SettingsController.clearSettings() + PageController.replaceStartPage() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 1c196aa3b..cd0b3ee84 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -12,6 +12,20 @@ import "../Controls2/TextTypes" PageType { id: root + Connections { + target: SettingsController + + function onChangeSettingsErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + goToStartPage() + PageController.goToPageHome() + } + } + BackButtonType { id: backButton @@ -66,7 +80,9 @@ PageType { text: qsTr("Make a backup") onClicked: { + PageController.showBusyIndicator(true) SettingsController.backupAppConfig() + PageController.showBusyIndicator(false) } } @@ -84,7 +100,9 @@ PageType { text: qsTr("Restore from backup") onClicked: { + PageController.showBusyIndicator(true) SettingsController.restoreAppConfig() + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index c51f9092a..2851e9b1a 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -55,6 +55,9 @@ PageType { headerText: "Primary DNS" textFieldText: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } } TextFieldWithHeaderType { @@ -64,6 +67,9 @@ PageType { headerText: "Secondary DNS" textFieldText: SettingsController.secondaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 20afecd26..b24411c74 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -27,6 +27,32 @@ PageType { PageController.showErrorMessage(message) } + + function onInstallationErrorOccurred(errorMessage) { + closePage() // close deInstalling page + PageController.showErrorMessage(errorMessage) + } + + function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + goToPage(PageEnum.PageSettingsServersList) + } + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveAllContainersFinished(finishedMessage) { + closePage() // close deInstalling page + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { + closePage() // close deInstalling page + closePage() // close page with remove button + PageController.showNotificationMessage(finishedMessage) + } } Connections { @@ -112,16 +138,12 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false + PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { ConnectionController.closeConnection() } - ServersModel.removeServer() - if (!ServersModel.getServersCount()) { - PageController.replaceStartPage() - } else { - goToStartPage() - goToPage(PageEnum.PageSettingsServersList) - } + InstallController.removeCurrentlyProcessedServer() + PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false @@ -151,8 +173,7 @@ PageType { if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { ConnectionController.closeVpnConnection() } - ContainersModel.removeAllContainers() - closePage() + InstallController.removeAllContainers() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 7b6dce682..cf748f54e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -93,6 +93,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server name") textFieldText: name + textField.maximumLength: 20 } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index ec2ca91ff..e417eea97 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -81,13 +81,12 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() - switch (containerIndex) { - case ContainerEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + switch (protocolIndex) { + case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } goToPage(protocolPage); } @@ -113,8 +112,19 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - ContainersModel.removeCurrentlyProcessedContainer() - closePage() + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } MouseArea { @@ -126,5 +136,9 @@ PageType { DividerType {} } + + QuestionDrawer { + id: questionDrawer + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 92c95108b..f254a4744 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -22,7 +22,7 @@ PageType { PageController.showErrorMessage(errorMessage) } - function onInstallContainerFinished(isInstalledContainerFound) { + function onInstallContainerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -33,14 +33,10 @@ PageType { goToPage(PageEnum.PageHome) } - if (isInstalledContainerFound) { - //todo change to info message - PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + - "All installed containers have been added to the application")) - } + PageController.showNotificationMessage(finishedMessage) } - function onInstallServerFinished(isInstalledContainerFound) { + function onInstallServerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() @@ -50,10 +46,7 @@ PageType { PageController.replaceStartPage() } - if (isInstalledContainerFound) { - PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + - "All installed containers have been added to the application")) - } + PageController.showNotificationMessage(finishedMessage) } function onServerAlreadyExists(serverIndex) { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 29067cb28..65e00da8c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -123,15 +123,16 @@ PageType { anchors.bottom: parent.bottom contentHeight: { var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin - - return (showDetailsDrawerContent.implicitHeight > emptySpaceHeight) ? - showDetailsDrawerContent.implicitHeight : emptySpaceHeight + return (showDetailsDrawerContent.height > emptySpaceHeight) ? + showDetailsDrawerContent.height : emptySpaceHeight } ColumnLayout { id: showDetailsDrawerContent - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 73438e34c..a2f42cc24 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -16,11 +16,6 @@ PageType { Connections { target: PageController - function onShowErrorMessage(errorMessage) { - popupErrorMessage.popupErrorMessageText = errorMessage - popupErrorMessage.open() - } - function onGoToPageViewConfig() { goToPage(PageEnum.PageSetupWizardViewConfig) } @@ -98,16 +93,4 @@ PageType { id: connectionTypeSelection } } - - Item { - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - implicitHeight: popupErrorMessage.height - - PopupType { - id: popupErrorMessage - } - } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 201c630a8..3d5851b14 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -58,7 +58,7 @@ PageType { property string fullConfigServerSelectorText property string connectionServerSelectorText property bool showContent: false - property bool shareButtonEnabled: false + property bool shareButtonEnabled: true property list connectionTypesModel: [ amneziaConnectionFormat ] @@ -140,6 +140,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 serverSelector.text = root.fullConfigServerSelectorText + root.shareButtonEnabled = true } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index f522bacec..811fc923c 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -31,11 +31,6 @@ PageType { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) } - function onShowErrorMessage(errorMessage) { - popupErrorMessage.popupErrorMessageText = errorMessage - popupErrorMessage.open() - } - function onShowBusyIndicator(visible) { busyIndicator.visible = visible tabBarStackView.enabled = !visible @@ -119,14 +114,15 @@ PageType { Connections { target: ServersModel - function onDefaultServerIndexChanged() { - shareTabButton.visible = ServersModel.isCurrentlyProcessedServerHasWriteAccess() - shareTabButton.width = ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + function onModelReset() { + var hasServerWithWriteAccess = ServersModel.hasServerWithWriteAccess() + shareTabButton.visible = hasServerWithWriteAccess + shareTabButton.width = hasServerWithWriteAccess ? undefined : 0 } } - visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() - width: ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + visible: ServersModel.hasServerWithWriteAccess() + width: ServersModel.hasServerWithWriteAccess() ? undefined : 0 isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" @@ -151,18 +147,6 @@ PageType { enabled: false } - Item { - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - implicitHeight: popupErrorMessage.height - - PopupType { - id: popupErrorMessage - } - } - BusyIndicatorType { id: busyIndicator anchors.centerIn: parent diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 0b89d840a..6b2bee2af 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -63,5 +63,52 @@ Window { function onHideMainWindow() { root.hide() } + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.text = errorMessage + popupErrorMessage.open() + } + + function onShowNotificationMessage(message) { + popupNotificationMessage.text = message + popupNotificationMessage.closeButtonVisible = false + popupNotificationMessage.open() + popupNotificationTimer.start() + } + } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupNotificationMessage.height + + PopupType { + id: popupNotificationMessage + } + + Timer { + id: popupNotificationTimer + + interval: 3000 + repeat: false + running: false + onTriggered: { + popupNotificationMessage.close() + } + } + } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } } } From 66f9a82f318de52159ef495dd1a3f87568f5500e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 12:54:59 +0900 Subject: [PATCH 050/131] added icons for buttons in the drop-down window of connections sharing. - corrections in texts --- client/containers/containers_defs.cpp | 180 +++++++++--------- .../qml/Components/ShareConnectionDrawer.qml | 2 + client/ui/qml/Controls2/BasicButtonType.qml | 33 +++- client/ui/qml/Pages2/PageHome.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 4 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 5 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageShare.qml | 6 +- 13 files changed, 136 insertions(+), 109 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 8b979296a..1e8046bf7 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -8,18 +8,23 @@ QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) return debug; } -amnezia::DockerContainer ContainerProps::containerFromString(const QString &container){ +amnezia::DockerContainer ContainerProps::containerFromString(const QString &container) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { DockerContainer c = static_cast(i); - if (container == containerToString(c)) return c; + if (container == containerToString(c)) + return c; } return DockerContainer::None; } -QString ContainerProps::containerToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Cloak) return "amnezia-openvpn-cloak"; +QString ContainerProps::containerToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Cloak) + return "amnezia-openvpn-cloak"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -27,9 +32,12 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c){ return "amnezia-" + containerKey.toLower(); } -QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Ipsec) return "ikev2"; +QString ContainerProps::containerTypeToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Ipsec) + return "ikev2"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -40,29 +48,21 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ QVector ContainerProps::protocolsForContainer(amnezia::DockerContainer container) { switch (container) { - case DockerContainer::None: - return { }; + case DockerContainer::None: return {}; - case DockerContainer::OpenVpn: - return { Proto::OpenVpn }; + case DockerContainer::OpenVpn: return { Proto::OpenVpn }; - case DockerContainer::ShadowSocks: - return { Proto::OpenVpn, Proto::ShadowSocks }; + case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks }; - case DockerContainer::Cloak: - return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; + case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; - case DockerContainer::Ipsec: - return { Proto::Ikev2 /*, Protocol::L2tp */}; + case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; - case DockerContainer::Dns: - return { }; + case DockerContainer::Dns: return {}; - case DockerContainer::Sftp: - return { Proto::Sftp}; + case DockerContainer::Sftp: return { Proto::Sftp }; - default: - return { defaultProtocol(container) }; + default: return { defaultProtocol(container) }; } } @@ -79,70 +79,67 @@ QList ContainerProps::allContainers() QMap ContainerProps::containerHumanNames() { - return { - {DockerContainer::None, "Not installed"}, - {DockerContainer::OpenVpn, "OpenVPN"}, - {DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"}, - {DockerContainer::Cloak, "OpenVpn over Cloak"}, - {DockerContainer::WireGuard, "WireGuard"}, - {DockerContainer::Ipsec, QObject::tr("IPsec")}, + return { { DockerContainer::None, "Not installed" }, + { DockerContainer::OpenVpn, "OpenVPN" }, + { DockerContainer::ShadowSocks, "OpenVPN over ShadowSocks" }, + { DockerContainer::Cloak, "OpenVPN over Cloak" }, + { DockerContainer::WireGuard, "WireGuard" }, + { DockerContainer::Ipsec, QObject::tr("IPsec") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service")} - }; + { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; } QMap ContainerProps::containerDescriptions() { - return { - {DockerContainer::OpenVpn, QObject::tr("OpenVPN container")}, - {DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks")}, - {DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols " - "configured with traffic masking by Cloak plugin")}, - {DockerContainer::WireGuard, QObject::tr("WireGuard container")}, - {DockerContainer::Ipsec, QObject::tr("IPsec container")}, + return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, + { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, + { DockerContainer::Cloak, + QObject::tr("Container with OpenVpn and ShadowSocks protocols " + "configured with traffic masking by Cloak plugin") }, + { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, + { DockerContainer::Ipsec, QObject::tr("IPsec container") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service")} - }; + { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::Dns, QObject::tr("DNS Service") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) { switch (c) { - case DockerContainer::None : return ServiceType::None; - case DockerContainer::OpenVpn : return ServiceType::Vpn; - case DockerContainer::Cloak : return ServiceType::Vpn; - case DockerContainer::ShadowSocks : return ServiceType::Vpn; - case DockerContainer::WireGuard : return ServiceType::Vpn; - case DockerContainer::Ipsec : return ServiceType::Vpn; - case DockerContainer::TorWebSite : return ServiceType::Other; - case DockerContainer::Dns : return ServiceType::Other; - //case DockerContainer::FileShare : return ServiceType::Other; - case DockerContainer::Sftp : return ServiceType::Other; - default: return ServiceType::Other; + case DockerContainer::None: return ServiceType::None; + case DockerContainer::OpenVpn: return ServiceType::Vpn; + case DockerContainer::Cloak: return ServiceType::Vpn; + case DockerContainer::ShadowSocks: return ServiceType::Vpn; + case DockerContainer::WireGuard: return ServiceType::Vpn; + case DockerContainer::Ipsec: return ServiceType::Vpn; + case DockerContainer::TorWebSite: return ServiceType::Other; + case DockerContainer::Dns: return ServiceType::Other; + // case DockerContainer::FileShare : return ServiceType::Other; + case DockerContainer::Sftp: return ServiceType::Other; + default: return ServiceType::Other; } } Proto ContainerProps::defaultProtocol(DockerContainer c) { switch (c) { - case DockerContainer::None : return Proto::Any; - case DockerContainer::OpenVpn : return Proto::OpenVpn; - case DockerContainer::Cloak : return Proto::Cloak; - case DockerContainer::ShadowSocks : return Proto::ShadowSocks; - case DockerContainer::WireGuard : return Proto::WireGuard; - case DockerContainer::Ipsec : return Proto::Ikev2; + case DockerContainer::None: return Proto::Any; + case DockerContainer::OpenVpn: return Proto::OpenVpn; + case DockerContainer::Cloak: return Proto::Cloak; + case DockerContainer::ShadowSocks: return Proto::ShadowSocks; + case DockerContainer::WireGuard: return Proto::WireGuard; + case DockerContainer::Ipsec: return Proto::Ikev2; - case DockerContainer::TorWebSite : return Proto::TorWebSite; - case DockerContainer::Dns : return Proto::Dns; - //case DockerContainer::FileShare : return Protocol::FileShare; - case DockerContainer::Sftp : return Proto::Sftp; - default: return Proto::Any; + case DockerContainer::TorWebSite: return Proto::TorWebSite; + case DockerContainer::Dns: return Proto::Dns; + // case DockerContainer::FileShare : return Protocol::FileShare; + case DockerContainer::Sftp: return Proto::Sftp; + default: return Proto::Any; } } @@ -151,22 +148,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #ifdef Q_OS_WINDOWS return true; -#elif defined (Q_OS_IOS) +#elif defined(Q_OS_IOS) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; - case DockerContainer::Cloak: return true; -// case DockerContainer::ShadowSocks: return true; + case DockerContainer::Cloak: + return true; + // case DockerContainer::ShadowSocks: return true; default: return false; } -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; default: return true; } -#elif defined (Q_OS_ANDROID) +#elif defined(Q_OS_ANDROID) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; @@ -175,7 +173,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) default: return false; } -#elif defined (Q_OS_LINUX) +#elif defined(Q_OS_LINUX) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; @@ -183,44 +181,44 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) } #else -return false; + return false; #endif } QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) { switch (c) { - case DockerContainer::Ipsec : return QStringList{"500", "4500"}; - default: return {}; + case DockerContainer::Ipsec: return QStringList { "500", "4500" }; + default: return {}; } } bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return true; - case DockerContainer::Cloak : return true; - case DockerContainer::ShadowSocks : return true; - default: return false; + case DockerContainer::OpenVpn: return true; + case DockerContainer::Cloak: return true; + case DockerContainer::ShadowSocks: return true; + default: return false; } } QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return tr("Low"); - case DockerContainer::Cloak : return tr("High"); - case DockerContainer::ShadowSocks : return tr("Medium"); - default: return ""; + case DockerContainer::OpenVpn: return tr("Low"); + case DockerContainer::Cloak: return tr("High"); + case DockerContainer::ShadowSocks: return tr("Medium"); + default: return ""; } } QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::Cloak : return tr("Some foreign sites are blocked, but VPN providers are not blocked"); - case DockerContainer::ShadowSocks : return tr("I just want to increase the level of privacy"); - default: return ""; + case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); + case DockerContainer::Cloak: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::ShadowSocks: return tr("Many foreign websites and VPN providers are blocked"); + default: return ""; } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index f133f27a1..3f27396bb 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -55,6 +55,7 @@ DrawerType { Layout.topMargin: 16 text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" onClicked: { ExportController.saveFile() @@ -73,6 +74,7 @@ DrawerType { borderWidth: 1 text: qsTr("Copy") + imageSource: "qrc:/images/controls/copy.svg" onClicked: { configText.selectAll() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index d91684668..aa05774e8 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -1,5 +1,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import "TextTypes" @@ -16,6 +18,8 @@ Button { property string borderColor: "#D7D8DB" property int borderWidth: 0 + property string imageSource + implicitHeight: 56 hoverEnabled: true @@ -48,11 +52,30 @@ Button { cursorShape: Qt.PointingHandCursor } - contentItem: ButtonTextType { + contentItem: Item { anchors.fill: background - color: textColor - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + RowLayout { + anchors.centerIn: parent + + Image { + source: root.imageSource + visible: root.imageSource === "" ? false : true + + layer { + enabled: true + effect: ColorOverlay { + color: textColor + } + } + } + + ButtonTextType { + color: textColor + text: root.text + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 6a134e9b5..ce5ddcd4a 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -155,7 +155,7 @@ PageType { text: root.defaultContainerName textColor: "#0E0E11" - headerText: qsTr("Connection protocol") + headerText: qsTr("VPN protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 33d231b5f..54c2ffb84 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -92,7 +92,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 - headerText: qsTr("Masquerading as traffic from") + headerText: qsTr("Disguised as traffic from") textFieldText: site textField.onEditingFinished: { @@ -161,7 +161,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 65fcdc4b5..26a9c4954 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -445,7 +445,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 730e39074..d873beacd 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -147,7 +147,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 1cf8a1a52..fd365edb2 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -48,7 +48,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Use AmnesiaDNS if installed on the server") + text: qsTr("Use AmneziaDNS if installed on the server") descriptionText: qsTr("Internal IP address 172.29.172.254") checked: SettingsController.isAmneziaDnsEnabled() diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b24411c74..3aec42428 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -110,7 +110,7 @@ PageType { visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: qsTr("Check the server for previously installed Amnesia services") + text: qsTr("Check the server for previously installed Amnezia services") descriptionText: qsTr("Add them to the application if they were not displayed") clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 487bdbde3..b72fe9889 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -49,7 +49,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server IP address [:port]") - textFieldPlaceholderText: qsTr("Enter the address in the format 255.255.255.255:88") + textFieldPlaceholderText: qsTr("255.255.255.255:88") textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } @@ -60,13 +60,14 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") + textFieldPlaceholderText: "root" } TextFieldWithHeaderType { id: secretData Layout.fillWidth: true - headerText: qsTr("Password / Private key") + headerText: qsTr("Password / SSH private key") textField.echoMode: TextInput.Password } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f3de85b61..8c7f5de05 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -70,7 +70,7 @@ PageType { width: parent.width - headerText: qsTr("Connection protocol") + headerText: qsTr("VPN protocol") descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index a2f42cc24..eff17ab56 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -53,7 +53,8 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("A free service to create a personal VPN on your server. We help you access blocked content without exposing your privacy even to VPN providers.") + text: qsTr("Free service for creating a personal VPN on your server.") + + qsTr(" Helps you access blocked content without revealing your privacy, even to VPN providers.") } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 3d5851b14..ca9dedd42 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -65,7 +65,7 @@ PageType { QtObject { id: amneziaConnectionFormat - property string name: qsTr("For the AmnesiaVPN app") + property string name: qsTr("For the AmneziaVPN app") property var type: PageShare.ConfigType.AmneziaConnection } QtObject { @@ -135,7 +135,7 @@ PageType { checked: root.currentIndex === 1 implicitWidth: (root.width - 32) / 2 - text: qsTr("Full") + text: qsTr("Full access") onClicked: { accessTypeSelector.currentIndex = 1 @@ -194,6 +194,8 @@ PageType { protocolSelector.visible = true root.shareButtonEnabled = false } else { + shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text serverSelector.menuVisible = false } } From aa66133813e375c9ec151967a80b04ae6f51bf25 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 14:29:49 +0900 Subject: [PATCH 051/131] added 'insert' button and 'show password' button for PageSetupWizardCredentials --- .gitignore | 1 + CMakeLists.txt | 4 ++-- client/images/controls/eye-off.svg | 6 ++++++ client/images/controls/eye.svg | 4 ++++ client/resources.qrc | 2 ++ client/ui/qml/Config/GlobalConfig.qml | 10 ++++----- client/ui/qml/Controls2/BasicButtonType.qml | 5 +++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 5 ++++- .../Pages2/PageSetupWizardConfigSource.qml | 6 ++++-- .../qml/Pages2/PageSetupWizardCredentials.qml | 21 ++++++++++++++++++- 10 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 client/images/controls/eye-off.svg create mode 100644 client/images/controls/eye.svg diff --git a/.gitignore b/.gitignore index 88a3b3977..7de64e4b2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ deploy/build/* deploy/build_32/* deploy/build_64/* winbuild*.bat +.cache/ # Qt-es diff --git a/CMakeLists.txt b/CMakeLists.txt index ad9866e0e..2ff60079b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.0.8.1 +project(${PROJECT} VERSION 4.0.0.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-07-15") +set(RELEASE_DATE "2023-07-31") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/images/controls/eye-off.svg b/client/images/controls/eye-off.svg new file mode 100644 index 000000000..d05e0b850 --- /dev/null +++ b/client/images/controls/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/eye.svg b/client/images/controls/eye.svg new file mode 100644 index 000000000..a01452af0 --- /dev/null +++ b/client/images/controls/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 85ee838f2..625292c2e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -278,5 +278,7 @@ images/controls/copy.svg ui/qml/Pages2/PageServiceTorWebsiteSettings.qml ui/qml/Pages2/PageSetupWizardQrReader.qml + images/controls/eye.svg + images/controls/eye-off.svg diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index 5bb71b6fe..a9edd543d 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -11,17 +11,17 @@ Item { readonly property int defaultMargin: 20 function isMobile() { - if (Qt.platform.os == "android" || - Qt.platform.os == "ios") { + if (Qt.platform.os === "android" || + Qt.platform.os === "ios") { return true } return false } function isDesktop() { - if (Qt.platform.os == "windows" || - Qt.platform.os == "linux" || - Qt.platform.os == "osx") { + if (Qt.platform.os === "windows" || + Qt.platform.os === "linux" || + Qt.platform.os === "osx") { return true } return false diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index aa05774e8..c69d51d7c 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -54,7 +54,11 @@ Button { contentItem: Item { anchors.fill: background + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight RowLayout { + id: content anchors.centerIn: parent Image { @@ -72,6 +76,7 @@ Button { ButtonTextType { color: textColor text: root.text + visible: root.text === "" ? false : true horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 13a7ea6e5..1414b5ecd 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -14,6 +14,7 @@ Item { property alias errorText: errorField.text property string buttonText + property string buttonImageSource property var clickedFunc property alias textField: textField @@ -101,7 +102,7 @@ Item { } BasicButtonType { - visible: root.buttonText !== "" + visible: (root.buttonText !== "") || (root.buttonImageSource !== "") defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -111,8 +112,10 @@ Item { borderWidth: 0 text: root.buttonText + imageSource: root.buttonImageSource Layout.rightMargin: 24 + Layout.preferredHeight: 32 onClicked: { if (root.clickedFunc && typeof root.clickedFunc === "function") { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 2d6b249d1..cd0c08fbc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -76,9 +76,9 @@ It's okay if a friend passed the code.") DividerType {} - //todo ifdef mobile platforms LabelWithButtonType { Layout.fillWidth: true + visible: GC.isMobile() text: qsTr("QR-code") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -90,7 +90,9 @@ It's okay if a friend passed the code.") } } - DividerType {} + DividerType { + visible: GC.isMobile() + } LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index b72fe9889..cc1197adf 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -53,6 +53,12 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } } TextFieldWithHeaderType { @@ -61,14 +67,27 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") textFieldPlaceholderText: "root" + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } } TextFieldWithHeaderType { id: secretData + property bool hidePassword: true + Layout.fillWidth: true headerText: qsTr("Password / SSH private key") - textField.echoMode: TextInput.Password + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunc: function() { + hidePassword = !hidePassword + } } BasicButtonType { From 0058edc24ef1c8c84cbf1866a08e704f461e7d3a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 20:38:13 +0900 Subject: [PATCH 052/131] added server availability check after entering credentials - moved the protocol self-selection button to the PageSetupWizardEasy page --- client/amnezia_application.cpp | 9 + client/amnezia_application.h | 3 + client/translations/amneziavpn_ru.ts | 1271 ++++++++++++----- client/ui/controllers/installController.cpp | 21 + client/ui/controllers/installController.h | 2 + client/ui/models/languageModel.cpp | 2 +- client/ui/models/languageModel.h | 6 +- .../qml/Components/SelectLanguageDrawer.qml | 3 +- client/ui/qml/Controls2/PopupType.qml | 2 + .../qml/Pages2/PageProtocolCloakSettings.qml | 6 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 6 +- .../PageProtocolShadowSocksSettings.qml | 6 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 6 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 6 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 5 - .../qml/Pages2/PageSetupWizardCredentials.qml | 47 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 34 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 5 - client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 20 + 21 files changed, 1002 insertions(+), 463 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2ee9af16d..7d7a0376f 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -221,6 +221,12 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) QResource::registerResource(":/translations.qrc"); if (!m_translator->isEmpty()) QCoreApplication::removeTranslator(m_translator); + + if (locale == QLocale::English) { + m_settings->setAppLanguage(locale); + m_engine->retranslate(); + } + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { if (QCoreApplication::installTranslator(m_translator)) { m_settings->setAppLanguage(locale); @@ -228,6 +234,8 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) m_engine->retranslate(); } + + emit translationsUpdated(); } bool AmneziaApplication::parseCommands() @@ -271,6 +279,7 @@ void AmneziaApplication::initModels() m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 3ba42e41a..2a10aa861 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -65,6 +65,9 @@ public: QQmlApplicationEngine *qmlEngine() const; +signals: + void translationsUpdated(); + private: void initModels(); void initControllers(); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 197c9d409..f338216b1 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -138,6 +138,24 @@ + + ConnectionTypeSelectionDrawer + + + Connection data + + + + + Server IP, login and password + + + + + QR code, key or configuration file + + + ContextMenu @@ -164,11 +182,71 @@ ExportController - + + Access error! + + + + Save AmneziaVPN config + + ImportController + + + Open config file + + + + + InstallController + + + + installed successfully. + + + + + + is already installed on the server. + + + + + + +Already installed containers were found on the server. All installed containers have been added to the application + + + + + Server ' + + + + + ' was removed + + + + + All containers from server ' + + + + + has been removed from the server ' + + + + + Please login as the user + + + NotificationHandler @@ -198,156 +276,6 @@ - - OpenVpnSettings - - - VPN Addresses Subnet - - - - - Network protocol - - - - - Port - - - - - Auto-negotiate encryption - - - - - - Hash - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - - Cipher - - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - TLS auth - - - - - Block DNS requests outside of VPN - - - - - Additional configuration commands - - - PageAbout @@ -545,6 +473,11 @@ Removing services from + + + Usually it takes no more than 5 minutes + + PageGeneralSettings @@ -587,12 +520,12 @@ PageHome - - Протокол подключения + + VPN protocol - + Servers @@ -1049,6 +982,304 @@ If AmneziaDNS service is not installed on the same server, or this option is unc + + PageProtocolCloakSettings + + + Settings updated successfully + + + + + Cloak settings + + + + + Disguised as traffic from + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageProtocolOpenVpnSettings + + + Settings updated successfully + + + + + OpenVPN settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + Port + + + + + Auto-negotiate encryption + + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client configuration commands + + + + + + Commands: + + + + + Additional server configuration commands + + + + + Remove OpenVPN + + + + + Remove OpenVpn from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + Save and Restart Amnezia + + + + + PageProtocolRaw + + + settings + + + + + Show connection options + + + + + Connection options + + + + + + Remove + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageProtocolShadowSocksSettings + + + Settings updated successfully + + + + + ShadowSocks settings + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + PageQrDecoderIos @@ -1175,6 +1406,139 @@ If AmneziaDNS service is not installed on the same server, or this option is unc + + PageServiceSftpSettings + + + Settings updated successfully + + + + + SFTP settings + + + + + Host + + + + + Port + + + + + Login + + + + + Password + + + + + Mount folder on device + + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + + + + + + <br>1. Install the latest version of + + + + + + <br>2. Install the latest version of + + + + + Detailed instructions + + + + + Remove SFTP and all data stored there + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + + + + + Tor website settings + + + + + Website address + + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + + + + + When configuring WordPress set the domain as this onion address. + + + + + Remove website + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + PageSettings @@ -1275,65 +1639,85 @@ And if you don't like the app, all the more support it - the donation will PageSettingsApplication - + Application - + Language - + + Logging + + + + + Enabled + + + + + Disabled + + + + Reset settings and remove all data from the application + + + Reset settings and remove all data from the application? + + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + + + + + Continue + Продолжить + + + + Cancel + + PageSettingsBackup - + Backup - - Save logs + + Settings restored from backup file - - Open folder with logs - - - - - Save logs to file - - - - - Clear logs - - - - + Configuration backup - + It will help you instantly restore connection settings at the next installation - + Make a backup - + Restore from backup @@ -1347,7 +1731,7 @@ And if you don't like the app, all the more support it - the donation will - Use AmnesiaDNS if installed on the server + Use AmneziaDNS if installed on the server @@ -1399,11 +1783,39 @@ And if you don't like the app, all the more support it - the donation will - + Save + + PageSettingsLogging + + + Logging + + + + + Save logs + + + + + Open folder with logs + + + + + Save logs to file + + + + + Clear logs + + + PageSettingsServerData @@ -1413,80 +1825,80 @@ And if you don't like the app, all the more support it - the donation will - Не найдено установленных контейнеров + No installed containers found - + Clear Amnezia cache - + May be needed when changing other settings - + Clear cached profiles? Очистить закешированные профили - + some description - - - + + + Continue Продолжить - - - + + + Cancel - - Проверить сервер на наличие ранее установленных сервисов Amnezia + + Check the server for previously installed Amnezia services - - Добавим их в приложение, если они не отображались + + Add them to the application if they were not displayed - + Remove server from application - + Remove server? - + All installed AmneziaVPN services will still remain on the server. - + Clear server from Amnezia software - + Clear server from Amnezia software? - + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. @@ -1499,26 +1911,63 @@ And if you don't like the app, all the more support it - the donation will - + Save - + Protocols - + Services - + Data + + PageSettingsServerProtocol + + + settings + + + + + + Remove + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageSettingsServersList + + + Servers + + + PageSetupWizard @@ -1568,6 +2017,41 @@ OpenVPN profile will be installed. + + PageSetupWizardConfigSource + + + Server connection + + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay if a friend passed the code. + + + + + What do you have? + + + + + File with connection settings + + + + + QR-code + + + + + Key as text + + + PageSetupWizardCredentials @@ -1582,42 +2066,47 @@ OpenVPN profile will be installed. - + 255.255.255.255:88 + + + + + + Insert + + + + + Password / SSH private key + + + + + Continue + Продолжить + + + Enter the address in the format 255.255.255.255:88 - + Login to connect via SSH - - Password / Private key - - - - - Set up a server the easy way - - - - - Select protocol to install - - - - + Ip address cannot be empty - + Login cannot be empty - + Password/private key cannot be empty @@ -1625,12 +2114,22 @@ OpenVPN profile will be installed. PageSetupWizardEasy - + What is the level of internet control in your region? + Set up a VPN yourself + + + + + I want to choose a VPN protocol + + + + Continue Продолжить @@ -1670,14 +2169,18 @@ This protocol support exporting connection profiles to mobile devices by exporti PageSetupWizardInstalling - - - The container you are trying to install is already installed on the server. All installed containers have been added to the application + + The server has already been added to the application - - The server has already been added to the application + + Installing + + + + + Usually it takes no more than 5 minutes @@ -1751,31 +2254,72 @@ This protocol supports exporting connection profiles to mobile devices by using - + detailed protocol description - + Close - - Установить + + Network protocol + + + + + Port + + + + + Install + + + + + PageSetupWizardProtocols + + + VPN protocol + + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. PageSetupWizardStart + + + Free service for creating a personal VPN on your server. + + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + + - У меня есть данные для подключения + I have the data to connect - У меня ничего нет + I have nothing @@ -1837,27 +2381,27 @@ Please note, you should add addresses to the list after VPN connection establish PageSetupWizardViewConfig - + New connection - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -1865,80 +2409,84 @@ Please note, you should add addresses to the list after VPN connection establish PageShare - - For the AmnesiaVPN app - - - - + OpenVpn native format - + WireGuard native format - + VPN Access - + Connection - - Full - - - - + VPN access without the ability to manage the server - + Full access to server - + Server and service - + Server - + + Accessing + + + + Protocols and services - - + Connection to - - + + File with connection settings to - - + + For the AmneziaVPN app + + + + + Full access + + + + + Connection format - + Share @@ -2298,87 +2846,6 @@ New encryption keys pair will be generated. - - PageTest - - - Протоколы - - - - - Сервисы - - - - - Данные - - - - - - Forget this server - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - - - Auto-negotiate encryption - - - PageVPN @@ -2470,7 +2937,7 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull PopupType - + Close @@ -2683,59 +3150,63 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - + IPsec - - + + Web site in Tor network - - + DNS Service - + Sftp file sharing service - + + Amnezia DNS + + + + OpenVPN container - + Container with OpenVpn and ShadowSocks - + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + WireGuard container - + IPsec container - + Sftp file sharing service - is secure FTP service - + Sftp service @@ -2788,17 +3259,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ServerContainersLogic - + Error occurred while configuring server. - + Error message: - + See logs for details. @@ -2807,17 +3278,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ServerSettingsLogic - + Clear client cached profile - + Service: - + Cache cleared @@ -2825,13 +3296,13 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull Settings - + Server #1 - - + + Server @@ -2844,20 +3315,30 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - + Save log - + Backup application config - + Open backup + + + Backup file is empty + + + + + Backup file is corrupted + + ShareConnectionButtonCopyType @@ -2875,22 +3356,22 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ShareConnectionDrawer - - Save connection code + + Share - + Copy - + Show content - + To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" @@ -2934,24 +3415,24 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull StartPageLogic - - + + Connect - - + + Please fill in all fields - + Connecting... - + Open config file @@ -2987,17 +3468,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull UiLogic - + Error occurred while configuring server. - + Error message: - + See logs for details. @@ -3081,27 +3562,27 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull amnezia::ContainerProps - + Low - + High - + Medium - + Many foreign websites and VPN providers are blocked - + Some foreign sites are blocked, but VPN providers are not blocked diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index be235e423..d6c32fac3 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -392,3 +392,24 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw #endif } + +bool InstallController::checkSshConnection() +{ + ServerController serverController(m_settings); + + ErrorCode errorCode = ErrorCode::NoError; + QString output; + output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); + + if (errorCode != ErrorCode::NoError) { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } else { + if (output.contains(tr("Please login as the user"))) { + output.replace("\n", ""); + emit installationErrorOccurred(output); + return false; + } + } + return true; +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 8bc04f399..b25f20823 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -37,6 +37,8 @@ public slots: void mountSftpDrive(const QString &port, const QString &password, const QString &username); + bool checkSshConnection(); + signals: void installContainerFinished(QString finishMessage); void installServerFinished(QString finishMessage); diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index adbbdaaa6..5135f3483 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -55,7 +55,7 @@ int LanguageModel::getCurrentLanguageIndex() } } -QString LanguageModel::getCurrentLanuageName() +QString LanguageModel::getCurrentLanguageName() { return m_availableLanguages[getCurrentLanguageIndex()].name; } diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index 4e8a90924..b64862dda 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -43,13 +43,17 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated) + Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated) + public slots: void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); int getCurrentLanguageIndex(); - QString getCurrentLanuageName(); + QString getCurrentLanguageName(); signals: void updateTranslations(const QLocale &locale); + void translationsUpdated(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index d872a889e..d318aab89 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -59,7 +59,7 @@ DrawerType { interactive: false model: LanguageModel - currentIndex: LanguageModel.getCurrentLanguageIndex() + currentIndex: LanguageModel.currentLanguageIndex ButtonGroup { id: buttonGroup @@ -127,6 +127,7 @@ DrawerType { onClicked: { listView.currentIndex = index LanguageModel.changeLanguage(languageIndex) + root.close() } } } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index e7bb16f4b..e4d2a4494 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -55,6 +55,8 @@ Popup { BasicButtonType { visible: closeButtonVisible + implicitHeight: 32 + defaultColor: "white" hoveredColor: "#C1C2C5" pressedColor: "#AEB0B7" diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 54c2ffb84..cc764451f 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -17,13 +17,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 26a9c4954..7d38a2b08 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -18,13 +18,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index d873beacd..643907907 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -17,13 +17,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 0eabb0765..d37562a17 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -19,13 +19,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 7cf2a81af..5ddb9ed6f 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -20,13 +20,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 2c7d86012..2001e8924 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -48,7 +48,7 @@ PageType { Layout.topMargin: 16 text: qsTr("Language") - descriptionText: LanguageModel.getCurrentLanuageName() + descriptionText: LanguageModel.currentLanguageName rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 3aec42428..12e662693 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -28,11 +28,6 @@ PageType { PageController.showErrorMessage(message) } - function onInstallationErrorOccurred(errorMessage) { - closePage() // close deInstalling page - PageController.showErrorMessage(errorMessage) - } - function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { if (!ServersModel.getServersCount()) { PageController.replaceStartPage() diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index cc1197adf..dab860c18 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -94,7 +94,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - text: qsTr("Set up a server the easy way") + text: qsTr("Continue") onClicked: function() { if (!isCredentialsFilled()) { @@ -104,34 +104,41 @@ PageType { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.showBusyIndicator(true) + var isConnectionOpened = InstallController.checkSshConnection() + PageController.showBusyIndicator(false) + if (!isConnectionOpened) { + return + } + goToPage(PageEnum.PageSetupWizardEasy) } } - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: -8 +// BasicButtonType { +// Layout.fillWidth: true +// Layout.topMargin: -8 - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 +// defaultColor: "transparent" +// hoveredColor: Qt.rgba(1, 1, 1, 0.08) +// pressedColor: Qt.rgba(1, 1, 1, 0.12) +// disabledColor: "#878B91" +// textColor: "#D7D8DB" +// borderWidth: 1 - text: qsTr("Select protocol to install") +// text: qsTr("Select protocol to install") - onClicked: function() { - if (!isCredentialsFilled()) { - return - } +// onClicked: function() { +// if (!isCredentialsFilled()) { +// return +// } - InstallController.setShouldCreateServer(true) - InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) +// InstallController.setShouldCreateServer(true) +// InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - goToPage(PageEnum.PageSetupWizardProtocols) - } - } +// goToPage(PageEnum.PageSetupWizardProtocols) +// } +// } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 2a6e1909e..ac1c3a445 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -15,6 +15,8 @@ import "../Config" PageType { id: root + property bool isEasySetup: true + SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel @@ -64,6 +66,10 @@ PageType { headerText: qsTr("What is the level of internet control in your region?") } + ButtonGroup { + id: buttonGroup + } + ListView { id: containers width: parent.width @@ -101,6 +107,7 @@ PageType { ButtonGroup.group: buttonGroup onClicked: function() { + isEasySetup = true var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) containers.dockerContainer = dockerContainer @@ -117,9 +124,18 @@ PageType { } } } + } - ButtonGroup { - id: buttonGroup + CardType { + implicitWidth: parent.width + + headerText: qsTr("Set up a VPN yourself") + bodyText: qsTr("I want to choose a VPN protocol") + + ButtonGroup.group: buttonGroup + + onClicked: function() { + isEasySetup = false } } @@ -132,11 +148,15 @@ PageType { text: qsTr("Continue") onClicked: function() { - ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(containers.dockerContainer, - containers.containerDefaultPort, - containers.containerDefaultTransportProto) + if (root.isEasySetup) { + ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) + goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(containers.dockerContainer, + containers.containerDefaultPort, + containers.containerDefaultTransportProto) + } else { + goToPage(PageEnum.PageSetupWizardProtocols) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f254a4744..6c6d1a675 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -17,11 +17,6 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - closePage() - PageController.showErrorMessage(errorMessage) - } - function onInstallContainerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index eff17ab56..11d7ba290 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -85,8 +85,7 @@ PageType { text: qsTr("I have nothing") - onClicked: { - } + onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions") } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 811fc923c..ec29b3146 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -45,6 +45,26 @@ PageType { } } + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var needCloseCurrentPage = false + var currentPageName = stackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + needCloseCurrentPage = true + } else if (currentPageName === PageController.getPagePath(PageEnum.PageDeinstalling)) { + needCloseCurrentPage = true + } + if (needCloseCurrentPage) { + PageController.closePage() + } + } + } + StackViewType { id: tabBarStackView From 925fd9f26805d03d53f2473993abc45d561734a8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 1 Aug 2023 11:06:46 +0900 Subject: [PATCH 053/131] added display of installed services on the page PageSettingsServersList --- client/ui/models/containers_model.cpp | 19 +++++++++++++++++++ client/ui/models/containers_model.h | 1 + .../ui/qml/Pages2/PageSettingsServersList.qml | 10 +++++++++- .../qml/Pages2/PageSetupWizardViewConfig.qml | 6 +++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index ea571a227..639cf9625 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -129,6 +129,25 @@ QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); } +QStringList ContainersModel::getAllInstalledServicesName(const int serverIndex) +{ + QStringList servicesName; + const auto &containers = m_settings->containers(serverIndex); + for (const DockerContainer &container : containers.keys()) { + if (ContainerProps::containerService(container) == ServiceType::Other && m_containers.contains(container)) { + if (container == DockerContainer::Dns) { + servicesName.append("DNS"); + } else if (container == DockerContainer::Sftp) { + servicesName.append("SFTP"); + } else if (container == DockerContainer::TorWebSite) { + servicesName.append("TOR"); + } + } + } + servicesName.sort(); + return servicesName; +} + ErrorCode ContainersModel::removeAllContainers() { diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 690eff413..a905890a1 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -51,6 +51,7 @@ public slots: QString getCurrentlyProcessedContainerName(); QJsonObject getCurrentlyProcessedContainerConfig(); + QStringList getAllInstalledServicesName(const int serverIndex); ErrorCode removeAllContainers(); ErrorCode removeCurrentlyProcessedContainer(); diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 496ed3701..40e51e9ec 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -85,7 +85,15 @@ PageType { Layout.fillWidth: true text: name - descriptionText: hostName + descriptionText: { + var servicesNameString = "" + var servicesName = ContainersModel.getAllInstalledServicesName(index) + for (var i = 0; i < servicesName.length; i++) { + servicesNameString += servicesName[i] + " · " + } + + return servicesNameString + hostName + } rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 372a5cde5..5f52a5bae 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -91,11 +91,15 @@ PageType { } BasicButtonType { + Layout.topMargin: 16 + Layout.leftMargin: -8 + implicitHeight: 32 + defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" - textColor: "#D7D8DB" + textColor: "#FBB26A" text: showContent ? qsTr("Collapse content") : qsTr("Show content") From ebcca0c3b8802c294b69065ee0a13c69bf2528ef Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 2 Aug 2023 20:37:43 +0900 Subject: [PATCH 054/131] added processing of private ssh keys --- client/amnezia_application.cpp | 4 + client/ui/controllers/installController.cpp | 32 +++++++- client/ui/controllers/installController.h | 7 ++ client/ui/controllers/pageController.h | 3 + client/ui/models/containers_model.cpp | 5 ++ client/ui/models/containers_model.h | 2 + .../Components/SettingsContainersListView.qml | 12 ++- .../ui/qml/Pages2/PageSettingsServerData.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 2 +- client/ui/qml/main2.qml | 74 +++++++++++++++++++ 10 files changed, 137 insertions(+), 8 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7d7a0376f..38126e433 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -315,6 +315,10 @@ void AmneziaApplication::initControllers() m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), + &PageController::showPassphraseRequestDrawer); + connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), + &InstallController::setEncryptedPassphrase); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index d6c32fac3..49c77708c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,7 @@ #include "installController.h" #include +#include #include #include @@ -396,8 +397,31 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw bool InstallController::checkSshConnection() { ServerController serverController(m_settings); - ErrorCode errorCode = ErrorCode::NoError; + m_privateKeyPassphrase = ""; + + if (m_currentlyInstalledServerCredentials.secretData.contains("BEGIN") + && m_currentlyInstalledServerCredentials.secretData.contains("PRIVATE KEY")) { + auto passphraseCallback = [this]() { + emit passphraseRequestStarted(); + QEventLoop loop; + QObject::connect(this, &InstallController::passphraseRequestFinished, &loop, &QEventLoop::quit); + loop.exec(); + + return m_privateKeyPassphrase; + }; + + QString decryptedPrivateKey; + errorCode = serverController.getDecryptedPrivateKey(m_currentlyInstalledServerCredentials, decryptedPrivateKey, + passphraseCallback); + if (errorCode == ErrorCode::NoError) { + m_currentlyInstalledServerCredentials.secretData = decryptedPrivateKey; + } else { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } + } + QString output; output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); @@ -413,3 +437,9 @@ bool InstallController::checkSshConnection() } return true; } + +void InstallController::setEncryptedPassphrase(QString passphrase) +{ + m_privateKeyPassphrase = passphrase; + emit passphraseRequestFinished(); +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index b25f20823..54bcda314 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -39,6 +39,8 @@ public slots: bool checkSshConnection(); + void setEncryptedPassphrase(QString passphrase); + signals: void installContainerFinished(QString finishMessage); void installServerFinished(QString finishMessage); @@ -55,6 +57,9 @@ signals: void serverAlreadyExists(int serverIndex); + void passphraseRequestStarted(); + void passphraseRequestFinished(); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); @@ -68,6 +73,8 @@ private: bool m_shouldCreateServer; + QString m_privateKeyPassphrase; + #ifndef Q_OS_IOS QList> m_sftpMountProcesses; #endif diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 4273ed251..8185b525a 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -91,6 +91,9 @@ signals: void hideMainWindow(); void raiseMainWindow(); + void showPassphraseRequestDrawer(); + void passphraseRequestDrawerClosed(QString passphrase); + private: QSharedPointer m_serversModel; }; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 639cf9625..8d24e0192 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -211,6 +211,11 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } +// bool ContainersModel::isOnlyServicesInstalled(const int serverIndex) +//{ + +//} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index a905890a1..8978315cf 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -60,6 +60,8 @@ public slots: bool isAmneziaDnsContainerInstalled(); bool isAmneziaDnsContainerInstalled(const int serverIndex); + // bool isOnlyServicesInstalled(const int serverIndex); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index eac473f46..bb5788993 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -106,13 +106,17 @@ ListView { break } case ContainerEnum.WireGuard: { - WireGuardConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolWireGuardSettings) + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) +// WireGuardConfigModel.updateModel(config) +// goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { - Ikev2ConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolIKev2Settings) + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) +// Ikev2ConfigModel.updateModel(config) +// goToPage(PageEnum.PageProtocolIKev2Settings) break } case ContainerEnum.Sftp: { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 12e662693..7be35cacc 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -134,7 +134,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false PageController.showBusyIndicator(true) - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } InstallController.removeCurrentlyProcessedServer() @@ -165,7 +165,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeVpnConnection() } InstallController.removeAllContainers() diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index ec29b3146..2cc64a911 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -52,7 +52,7 @@ PageType { PageController.showErrorMessage(errorMessage) var needCloseCurrentPage = false - var currentPageName = stackView.currentItem.objectName + var currentPageName = tabBarStackView.currentItem.objectName if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { needCloseCurrentPage = true diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6b2bee2af..f07bcf5d5 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -75,6 +75,10 @@ Window { popupNotificationMessage.open() popupNotificationTimer.start() } + + function onShowPassphraseRequestDrawer() { + privateKeyPassphraseDrawer.open() + } } Item { @@ -111,4 +115,74 @@ Window { id: popupErrorMessage } } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + DrawerType { + id: privateKeyPassphraseDrawer + + width: root.width + height: root.height * 0.35 + + onVisibleChanged: { + if (privateKeyPassphraseDrawer.visible) { + passphrase.textFieldText = "" + passphrase.textField.forceActiveFocus() + } + } + onAboutToHide: { + PageController.showBusyIndicator(true) + } + onAboutToShow: { + PageController.showBusyIndicator(false) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: passphrase + + property bool hidePassword: true + + Layout.fillWidth: true + headerText: qsTr("Private key passphrase") + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunc: function() { + hidePassword = !hidePassword + } + } + + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Save") + + onClicked: { + privateKeyPassphraseDrawer.close() + PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) + } + } + } + } + } } From 2c429fd406461abd211b531575269c191dc6d755 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 2 Aug 2023 21:46:02 +0900 Subject: [PATCH 055/131] added removal of spaces when inserting ip addresses - fixed server sharing when sharing a server available only for connection when choosing a server with full access - removed the notification about an empty backup file when the user closes the file dialog without selecting anything --- client/ui/controllers/settingsController.cpp | 1 - .../ui/qml/Pages2/PageSetupWizardCredentials.qml | 15 ++++----------- client/ui/qml/Pages2/PageShare.qml | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index eda67919d..6a83dcb1f 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -85,7 +85,6 @@ void SettingsController::restoreAppConfig() QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) { - emit changeSettingsErrorOccurred(tr("Backup file is empty")); return; } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index dab860c18..5187a6e37 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -53,11 +53,9 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } - buttonText: qsTr("Insert") - clickedFunc: function() { - textField.text = "" - textField.paste() + onTextFieldTextChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, ''); } } @@ -67,12 +65,6 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") textFieldPlaceholderText: "root" - buttonText: qsTr("Insert") - - clickedFunc: function() { - textField.text = "" - textField.paste() - } } TextFieldWithHeaderType { @@ -83,7 +75,8 @@ PageType { Layout.fillWidth: true headerText: qsTr("Password / SSH private key") textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal - buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") + : "" clickedFunc: function() { hidePassword = !hidePassword diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index ca9dedd42..6e3fecf25 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -208,7 +208,7 @@ PageType { serverSelector.text = selectedText root.fullConfigServerSelectorText = selectedText root.connectionServerSelectorText = selectedText - ServersModel.currentlyProcessedIndex = currentIndex + ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) } } From 90ae0b3e44689be23013ce63955170d18fc8bf9e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 8 Aug 2023 19:10:14 +0500 Subject: [PATCH 056/131] added PageSettingsSplitTunneling - added a call to the context menu when clicking the right mouse button for textInput --- client/amnezia_application.cpp | 25 +- client/amnezia_application.h | 4 + client/images/controls/more-vertical.svg | 5 + client/images/controls/trash.svg | 5 + client/resources.qrc | 5 + client/settings.cpp | 10 +- client/settings.h | 3 +- client/ui/Controls2 | 34 ++ client/ui/controllers/connectionController.h | 12 +- client/ui/controllers/exportController.h | 2 +- client/ui/controllers/importController.h | 2 +- client/ui/controllers/installController.h | 12 +- client/ui/controllers/pageController.h | 5 +- client/ui/controllers/settingsController.cpp | 14 +- client/ui/controllers/settingsController.h | 11 +- client/ui/controllers/sitesController.cpp | 149 +++++++ client/ui/controllers/sitesController.h | 36 ++ client/ui/models/sites_model.cpp | 137 ++++--- client/ui/models/sites_model.h | 31 +- client/ui/pages_logic/SitesLogic.cpp | 70 ++-- client/ui/qml/Controls2/BasicButtonType.qml | 28 ++ client/ui/qml/Controls2/ContextMenuType.qml | 33 ++ client/ui/qml/Controls2/DropDownType.qml | 14 +- client/ui/qml/Controls2/TextAreaType.qml | 70 ++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 42 +- client/ui/qml/Pages2/PageHome.qml | 5 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 88 +--- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 21 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 6 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 377 ++++++++++++++++++ 31 files changed, 1018 insertions(+), 240 deletions(-) create mode 100644 client/images/controls/more-vertical.svg create mode 100644 client/images/controls/trash.svg create mode 100644 client/ui/Controls2 create mode 100644 client/ui/controllers/sitesController.cpp create mode 100644 client/ui/controllers/sitesController.h create mode 100644 client/ui/qml/Controls2/ContextMenuType.qml create mode 100644 client/ui/qml/Controls2/TextAreaType.qml create mode 100644 client/ui/qml/Pages2/PageSettingsSplitTunneling.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 38126e433..933a6346e 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -146,16 +146,15 @@ void AmneziaApplication::init() // m_uiLogic->showOnStartup(); // #endif -// // TODO - fix -// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -// if (isPrimary()) { -// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ -// qDebug() << "Secondary instance started, showing this window instead"; -// emit m_uiLogic->show(); -// emit m_uiLogic->raise(); -// }); -// } -// #endif + // TODO - fix +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (isPrimary()) { + QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { + qDebug() << "Secondary instance started, showing this window instead"; + emit m_pageController->raiseMainWindow(); + }); + } +#endif // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 @@ -281,6 +280,9 @@ void AmneziaApplication::initModels() connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); + m_sitesModel.reset(new SitesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); @@ -328,4 +330,7 @@ void AmneziaApplication::initControllers() m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + + m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); + m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 2a10aa861..81897c83a 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/installController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" +#include "ui/controllers/sitesController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -32,6 +33,7 @@ #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" #include "ui/models/services/sftpConfigModel.h" +#include "ui/models/sites_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -86,6 +88,7 @@ private: QSharedPointer m_serversModel; QScopedPointer m_languageModel; QScopedPointer m_protocolsModel; + QSharedPointer m_sitesModel; QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; @@ -106,6 +109,7 @@ private: QScopedPointer m_importController; QScopedPointer m_exportController; QScopedPointer m_settingsController; + QScopedPointer m_sitesController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/more-vertical.svg b/client/images/controls/more-vertical.svg new file mode 100644 index 000000000..2110125d4 --- /dev/null +++ b/client/images/controls/more-vertical.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/trash.svg b/client/images/controls/trash.svg new file mode 100644 index 000000000..5f2f08bff --- /dev/null +++ b/client/images/controls/trash.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 625292c2e..2238f25dd 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -280,5 +280,10 @@ ui/qml/Pages2/PageSetupWizardQrReader.qml images/controls/eye.svg images/controls/eye-off.svg + ui/qml/Pages2/PageSettingsSplitTunneling.qml + ui/qml/Controls2/ContextMenuType.qml + ui/qml/Controls2/TextAreaType.qml + images/controls/trash.svg + images/controls/more-vertical.svg diff --git a/client/settings.cpp b/client/settings.cpp index fbdd63cee..af1fbf550 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -231,14 +231,15 @@ QString Settings::routeModeString(RouteMode mode) const } } -void Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) +bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); if (sites.contains(site) && ip.isEmpty()) - return; + return false; sites.insert(site, ip); setVpnSites(mode, sites); + return true; } void Settings::addVpnSites(RouteMode mode, const QMap &sites) @@ -308,6 +309,11 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } +void Settings::removeAllVpnSites(RouteMode mode) +{ + setVpnSites(mode, QVariantMap()); +} + QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); diff --git a/client/settings.h b/client/settings.h index 00b02c15b..d77373479 100644 --- a/client/settings.h +++ b/client/settings.h @@ -127,13 +127,14 @@ public: m_settings.setValue("Conf/" + routeModeString(mode), sites); m_settings.sync(); } - void addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); + bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); void addVpnSites(RouteMode mode, const QMap &sites); // map QStringList getVpnIps(RouteMode mode) const; void removeVpnSite(RouteMode mode, const QString &site); void addVpnIps(RouteMode mode, const QStringList &ip); void removeVpnSites(RouteMode mode, const QStringList &sites); + void removeAllVpnSites(RouteMode mode); bool useAmneziaDns() const { diff --git a/client/ui/Controls2 b/client/ui/Controls2 new file mode 100644 index 000000000..13f01bb7f --- /dev/null +++ b/client/ui/Controls2 @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +TextArea { + id: root + + width: parent.width + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + wrapMode: Text.Wrap + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } +} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 421ae84f2..a33d30c2f 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -1,9 +1,9 @@ #ifndef CONNECTIONCONTROLLER_H #define CONNECTIONCONTROLLER_H -#include "ui/models/servers_model.h" -#include "ui/models/containers_model.h" #include "protocols/vpnprotocol.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" #include "vpnconnection.h" class ConnectionController : public QObject @@ -17,8 +17,7 @@ public: explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, - QObject *parent = nullptr); + const QSharedPointer &vpnConnection, QObject *parent = nullptr); bool isConnected() const; bool isConnectionInProgress() const; @@ -32,11 +31,12 @@ public slots: void onConnectionStateChanged(Vpn::ConnectionState state); signals: - void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(); - void connectionErrorOccurred(QString errorMessage); + void connectionErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 84575079b..ce952096b 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -39,7 +39,7 @@ public slots: signals: void generateConfig(int type); - void exportErrorOccurred(QString errorMessage); + void exportErrorOccurred(const QString &errorMessage); void exportConfigChanged(); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 3bdb22527..9556bc6af 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -34,7 +34,7 @@ public slots: signals: void importFinished(); - void importErrorOccurred(QString errorMessage); + void importErrorOccurred(const QString &errorMessage); void qrDecodingFinished(); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 54bcda314..38e2e7802 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -42,18 +42,18 @@ public slots: void setEncryptedPassphrase(QString passphrase); signals: - void installContainerFinished(QString finishMessage); - void installServerFinished(QString finishMessage); + void installContainerFinished(const QString &finishMessage); + void installServerFinished(const QString &finishMessage); void updateContainerFinished(); void scanServerFinished(bool isInstalledContainerFound); - void removeCurrentlyProcessedServerFinished(QString finishedMessage); - void removeAllContainersFinished(QString finishedMessage); - void removeCurrentlyProcessedContainerFinished(QString finishedMessage); + void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); + void removeAllContainersFinished(const QString &finishedMessage); + void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); - void installationErrorOccurred(QString errorMessage); + void installationErrorOccurred(const QString &errorMessage); void serverAlreadyExists(int serverIndex); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8185b525a..fd748347b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -28,6 +28,7 @@ namespace PageLoader PageSettingsBackup, PageSettingsAbout, PageSettingsLogging, + PageSettingsSplitTunneling, PageServiceSftpSettings, PageServiceTorWebsiteSettings, @@ -83,8 +84,8 @@ signals: void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); - void showErrorMessage(QString errorMessage); - void showNotificationMessage(QString message); + void showErrorMessage(const QString &errorMessage); + void showNotificationMessage(const QString &message); void showBusyIndicator(bool visible); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 6a83dcb1f..571b03e89 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -14,7 +14,7 @@ SettingsController::SettingsController(const QSharedPointer &serve m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } -void SettingsController::setAmneziaDns(bool enable) +void SettingsController::toggleAmneziaDns(bool enable) { m_settings->setUseAmneziaDns(enable); } @@ -46,7 +46,7 @@ void SettingsController::setSecondaryDns(const QString &dns) emit secondaryDnsChanged(); } -bool SettingsController::isLoggingEnable() +bool SettingsController::isLoggingEnabled() { return m_settings->isSaveLogs(); } @@ -111,3 +111,13 @@ void SettingsController::clearSettings() m_settings->clearSettings(); m_serversModel->resetModel(); } + +bool SettingsController::isAutoConnectEnabled() +{ + return m_settings->isAutoConnect(); +} + +void SettingsController::toggleAutoConnect(bool enable) +{ + m_settings->setAutoConnect(enable); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index ac85d300e..ffd72f69d 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -16,10 +16,10 @@ public: Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) - Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) + Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged) public slots: - void setAmneziaDns(bool enable); + void toggleAmneziaDns(bool enable); bool isAmneziaDnsEnabled(); QString getPrimaryDns(); @@ -28,7 +28,7 @@ public slots: QString getSecondaryDns(); void setSecondaryDns(const QString &dns); - bool isLoggingEnable(); + bool isLoggingEnabled(); void toggleLogging(bool enable); void openLogsFolder(); @@ -42,13 +42,16 @@ public slots: void clearSettings(); + bool isAutoConnectEnabled(); + void toggleAutoConnect(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); void loggingStateChanged(); void restoreBackupFinished(); - void changeSettingsErrorOccurred(QString errorMessage); + void changeSettingsErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp new file mode 100644 index 000000000..7f3f93e1a --- /dev/null +++ b/client/ui/controllers/sitesController.cpp @@ -0,0 +1,149 @@ +#include "sitesController.h" + +#include + +#include "utilities.h" + +SitesController::SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent) + : QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel) +{ +} + +void SitesController::addSite(QString hostname) +{ + if (hostname.isEmpty()) { + return; + } + + if (!hostname.contains(".")) { + emit errorOccurred(tr("Hostname not look like ip adress or domain name")); + return; + } + + if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + // get domain name if it present + hostname.replace("https://", ""); + hostname.replace("http://", ""); + hostname.replace("ftp://", ""); + + hostname = hostname.split("/", Qt::SkipEmptyParts).first(); + } + + const auto &processSite = [this](const QString &hostname, const QString &ip) { + m_sitesModel->addSite(hostname, ip); + + if (!ip.isEmpty()) { + m_vpnConnection->addRoutes(QStringList() << ip); + m_vpnConnection->flushDns(); + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + m_vpnConnection->addRoutes(QStringList() << hostname); + m_vpnConnection->flushDns(); + } + }; + + const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + processSite(hostInfo.hostName(), addr.toString()); + break; + } + } + }; + + if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + processSite(hostname, ""); + } else { + processSite(hostname, ""); + QHostInfo::lookupHost(hostname, this, resolveCallback); + } + + emit finished(tr("New site added: ") + hostname); +} + +void SitesController::removeSite(int index) +{ + auto modelIndex = m_sitesModel->index(index); + auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); + m_sitesModel->removeSite(modelIndex); + + emit finished(tr("Site removed: ") + hostname); +} + +void SitesController::importSites(bool replaceExisting) +{ + QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open sites file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); + + if (fileName.isEmpty()) { + return; + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + emit errorOccurred(tr("Can't open file: ") + fileName); + return; + } + + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: ") + fileName); + return; + } + + auto jsonArray = jsonDocument.array(); + QMap sites; + QStringList ips; + + for (auto jsonValue : jsonArray) { + auto jsonObject = jsonValue.toObject(); + auto hostname = jsonObject.value("hostname").toString(""); + auto ip = jsonObject.value("ip").toString(""); + + if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + qDebug() << hostname << " not look like ip adress or domain name"; + continue; + } + + if (ip.isEmpty()) { + ips.append(hostname); + } else { + ips.append(ip); + } + sites.insert(hostname, ip); + } + + m_sitesModel->addSites(sites, replaceExisting); + + m_vpnConnection->addRoutes(QStringList() << ips); + m_vpnConnection->flushDns(); + + emit finished(tr("Import completed")); +} + +void SitesController::exportSites() +{ + auto sites = m_sitesModel->getCurrentSites(); + + QJsonArray jsonArray; + + for (const auto &site : sites) { + QJsonObject jsonObject { { "hostname", site.first }, { "ip", site.second } }; + jsonArray.append(jsonObject); + } + + QJsonDocument jsonDocument(jsonArray); + QByteArray jsonData = jsonDocument.toJson(); + + Utils::saveFile(".json", tr("Export sites file"), "sites", jsonData); + + emit finished(tr("Export completed")); +} diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h new file mode 100644 index 000000000..ff78c3de3 --- /dev/null +++ b/client/ui/controllers/sitesController.h @@ -0,0 +1,36 @@ +#ifndef SITESCONTROLLER_H +#define SITESCONTROLLER_H + +#include + +#include "settings.h" +#include "ui/models/sites_model.h" +#include "vpnconnection.h" + +class SitesController : public QObject +{ + Q_OBJECT +public: + explicit SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent = nullptr); + +public slots: + void addSite(QString hostname); + void removeSite(int index); + + void importSites(bool replaceExisting); + void exportSites(); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + +private: + std::shared_ptr m_settings; + + QSharedPointer m_vpnConnection; + QSharedPointer m_sitesModel; +}; + +#endif // SITESCONTROLLER_H diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index fe0f4ccf3..5fd9a38b3 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -1,87 +1,118 @@ #include "sites_model.h" -SitesModel::SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent) - : QAbstractListModel(parent), - m_settings(settings), - m_mode(mode) +SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) + : QAbstractListModel(parent), m_settings(settings) { -} - -void SitesModel::resetCache() -{ - beginResetModel(); - m_ipsCache.clear(); - m_cacheReady = false; - endResetModel(); + m_currentRouteMode = m_settings->routeMode(); + fillSites(); } int SitesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) - if (!m_cacheReady) genCache(); - return m_ipsCache.size(); + return m_sites.size(); } - QVariant SitesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) return QVariant(); - if (!m_cacheReady) genCache(); - - if (role == SitesModel::UrlRole || role == SitesModel::IpRole) { - if (m_ipsCache.isEmpty()) return QVariant(); - - if (role == SitesModel::UrlRole) { - return m_ipsCache.at(index.row()).first; - } - if (role == SitesModel::IpRole) { - return m_ipsCache.at(index.row()).second; - } + switch (role) { + case UrlRole: { + return m_sites.at(index.row()).first; + break; + } + case IpRole: { + return m_sites.at(index.row()).second; + break; + } + default: { + return true; + } } - - // if (role == Qt::TextAlignmentRole && index.column() == 1) { - // return Qt::AlignRight; - // } return QVariant(); } -QVariant SitesModel::data(int row, int column) +bool SitesModel::addSite(const QString &hostname, const QString &ip) { - if (row < 0 || row >= rowCount() || column < 0 || column >= 2) { - return QVariant(); + if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) { + return false; } - if (!m_cacheReady) genCache(); - - if (column == 0) { - return m_ipsCache.at(row).first; + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) { + m_sites[i].second = ip; + QModelIndex index = createIndex(i, i); + emit dataChanged(index, index); + return true; + } else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) { + return false; + } } - if (column == 1) { - return m_ipsCache.at(row).second; - } - return QVariant(); + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_sites.append(qMakePair(hostname, ip)); + endInsertRows(); + return true; } -void SitesModel::genCache() const +void SitesModel::addSites(const QMap &sites, bool replaceExisting) { - qDebug() << "SitesModel::genCache"; - m_ipsCache.clear(); + beginResetModel(); - const QVariantMap &sites = m_settings->vpnSites(m_mode); - auto i = sites.constBegin(); - while (i != sites.constEnd()) { - m_ipsCache.append(qMakePair(i.key(), i.value().toString())); - ++i; + if (replaceExisting) { + m_settings->removeAllVpnSites(m_currentRouteMode); } + m_settings->addVpnSites(m_currentRouteMode, sites); + fillSites(); - m_cacheReady= true; + endResetModel(); } -QHash SitesModel::roleNames() const { +void SitesModel::removeSite(QModelIndex index) +{ + auto hostname = m_sites.at(index.row()).first; + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_settings->removeVpnSite(m_currentRouteMode, hostname); + m_sites.removeAt(index.row()); + endRemoveRows(); +} + +int SitesModel::getRouteMode() +{ + return m_currentRouteMode; +} + +void SitesModel::setRouteMode(int routeMode) +{ + beginResetModel(); + m_settings->setRouteMode(static_cast(routeMode)); + m_currentRouteMode = m_settings->routeMode(); + fillSites(); + endResetModel(); + emit routeModeChanged(); +} + +QVector > SitesModel::getCurrentSites() +{ + return m_sites; +} + +QHash SitesModel::roleNames() const +{ QHash roles; - roles[UrlRole] = "url_path"; + roles[UrlRole] = "url"; roles[IpRole] = "ip"; return roles; } + +void SitesModel::fillSites() +{ + m_sites.clear(); + const QVariantMap &sites = m_settings->vpnSites(m_currentRouteMode); + auto i = sites.constBegin(); + while (i != sites.constEnd()) { + m_sites.append(qMakePair(i.key(), i.value().toString())); + ++i; + } +} diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index 7bf04b508..70def0ec5 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -10,32 +10,43 @@ class SitesModel : public QAbstractListModel Q_OBJECT public: - enum SiteRoles { + enum Roles { UrlRole = Qt::UserRole + 1, IpRole }; - explicit SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent = nullptr); - void resetCache(); + explicit SitesModel(std::shared_ptr settings, QObject *parent = nullptr); - // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant data(int row, int column); + + Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + +public slots: + bool addSite(const QString &hostname, const QString &ip); + void addSites(const QMap &sites, bool replaceExisting); + void removeSite(QModelIndex index); + + int getRouteMode(); + void setRouteMode(int routeMode); + + QVector> getCurrentSites(); + +signals: + void routeModeChanged(); protected: QHash roleNames() const override; private: - void genCache() const; + void fillSites(); -private: - Settings::RouteMode m_mode; std::shared_ptr m_settings; - mutable QVector> m_ipsCache; - mutable bool m_cacheReady = false; + Settings::RouteMode m_currentRouteMode; + + QVector> m_sites; }; #endif // SITESMODEL_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index bf926e569..7f653bfa8 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -9,23 +9,24 @@ #include "vpnconnection.h" #include -#include "../uilogic.h" #include "../models/sites_model.h" +#include "../uilogic.h" -SitesLogic::SitesLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelSitesAddCustomText{}, - m_tableViewSitesModel{nullptr}, - m_lineEditSitesAddCustomText{} +SitesLogic::SitesLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_labelSitesAddCustomText {}, + m_tableViewSitesModel { nullptr }, + m_lineEditSitesAddCustomText {} { - sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); - sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); + // sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); + // sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); } void SitesLogic::onUpdatePage() { Settings::RouteMode m = m_settings->routeMode(); - if (m == Settings::VpnAllSites) return; + if (m == Settings::VpnAllSites) + return; if (m == Settings::VpnOnlyForwardSites) { set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); @@ -35,7 +36,7 @@ void SitesLogic::onUpdatePage() } set_tableViewSitesModel(sitesModels.value(m)); - sitesModels.value(m)->resetCache(); + // sitesModels.value(m)->resetCache(); } void SitesLogic::onPushButtonAddCustomSitesClicked() @@ -47,8 +48,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() QString newSite = lineEditSitesAddCustomText(); - if (newSite.isEmpty()) return; - if (!newSite.contains(".")) return; + if (newSite.isEmpty()) + return; + if (!newSite.contains(".")) + return; if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { // get domain name if it present @@ -65,8 +68,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() if (!ip.isEmpty()) { uiLogic()->m_vpnConnection->addRoutes(QStringList() << ip); uiLogic()->m_vpnConnection->flushDns(); - } - else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { uiLogic()->m_vpnConnection->addRoutes(QStringList() << newSite); uiLogic()->m_vpnConnection->flushDns(); } @@ -74,10 +76,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() onUpdatePage(); }; - const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo){ + const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) { const QList &addresses = hostInfo.addresses(); QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { + for (const QHostAddress &addr : hostInfo.addresses()) { if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { cbProcess(hostInfo.hostName(), addr.toString()); break; @@ -90,8 +92,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { cbProcess(newSite, ""); return; - } - else { + } else { cbProcess(newSite, ""); onUpdatePage(); QHostInfo::lookupHost(newSite, this, cbResolv); @@ -102,7 +103,7 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) { Settings::RouteMode mode = m_settings->routeMode(); - auto siteModel = qobject_cast (tableViewSitesModel()); + auto siteModel = qobject_cast(tableViewSitesModel()); if (!siteModel || items.isEmpty()) { return; } @@ -110,14 +111,15 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) QStringList sites; QStringList ips; - for (const QString &s: items) { + for (const QString &s : items) { bool ok; int row = s.toInt(&ok); - if (!ok || row < 0 || row >= siteModel->rowCount()) return; - sites.append(siteModel->data(row, 0).toString()); + if (!ok || row < 0 || row >= siteModel->rowCount()) + return; + // sites.append(siteModel->data(row, 0).toString()); if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { - ips.append(siteModel->data(row, 1).toString()); + // ips.append(siteModel->data(row, 1).toString()); } } @@ -131,11 +133,11 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) onUpdatePage(); } -void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) +void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) { - QFile file(QUrl{fileName}.toLocalFile()); + QFile file(QUrl { fileName }.toLocalFile()); if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open file " << QUrl{fileName}.toLocalFile(); + qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile(); return; } @@ -164,27 +166,24 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) } // domain regex cover ip regex, so remove ips from sites - for (const QString& ip: line_ips) { + for (const QString &ip : line_ips) { line_sites.removeAll(ip); } if (line_sites.size() == 1 && line_ips.size() == 1) { sites.insert(line_sites.at(0), line_ips.at(0)); - } - else if (line_sites.size() > 0 && line_ips.size() == 0) { - for (const QString& site: line_sites) { + } else if (line_sites.size() > 0 && line_ips.size() == 0) { + for (const QString &site : line_sites) { sites.insert(site, ""); } - } - else { - for (const QString& site: line_sites) { + } else { + for (const QString &site : line_sites) { sites.insert(site, ""); } - for (const QString& ip: line_ips) { + for (const QString &ip : line_ips) { ips.append(ip); } } - } m_settings->addVpnIps(mode, ips); @@ -208,4 +207,3 @@ void SitesLogic::onPushButtonSitesExportClicked() } uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); } - diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index c69d51d7c..b0c39ddcb 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -20,6 +20,8 @@ Button { property string imageSource + property bool squareLeftSide: false + implicitHeight: 56 hoverEnabled: true @@ -44,6 +46,32 @@ Button { Behavior on color { PropertyAnimation { duration: 200 } } + + Rectangle { + visible: root.squareLeftSide + + z: 1 + + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } } MouseArea { diff --git a/client/ui/qml/Controls2/ContextMenuType.qml b/client/ui/qml/Controls2/ContextMenuType.qml new file mode 100644 index 000000000..867fcb106 --- /dev/null +++ b/client/ui/qml/Controls2/ContextMenuType.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform + +Menu { + property var textObj + + MenuItem { + text: qsTr("C&ut") + shortcut: StandardKey.Cut + enabled: textObj.selectedText + onTriggered: textObj.cut() + } + MenuItem { + text: qsTr("&Copy") + shortcut: StandardKey.Copy + enabled: textObj.selectedText + onTriggered: textObj.copy() + } + MenuItem { + text: qsTr("&Paste") + shortcut: StandardKey.Paste + enabled: textObj.canPaste + onTriggered: textObj.paste() + } + + MenuItem { + text: qsTr("&SelectAll") + shortcut: StandardKey.SelectAll + enabled: textObj.length > 0 + onTriggered: textObj.selectAll() + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 7a4538ba4..171516308 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,9 +24,11 @@ Item { property string rootButtonBackgroundColor: "#1C1D21" property string rootButtonHoveredBorderColor: "#494B50" - property string rootButtonDefaultBorderColor: "transparent" + property string rootButtonDefaultBorderColor: "#2C2D30" property string rootButtonPressedBorderColor: "#D7D8DB" + property int rootButtonTextMargins: 16 + property real drawerHeight: 0.9 property Component listView @@ -74,7 +76,9 @@ Item { spacing: 0 ColumnLayout { - Layout.leftMargin: 16 + Layout.leftMargin: rootButtonTextMargins + Layout.topMargin: rootButtonTextMargins + Layout.bottomMargin: rootButtonTextMargins LabelTextType { Layout.fillWidth: true @@ -96,16 +100,10 @@ Item { color: root.enabled ? root.textColor : root.textDisabledColor text: root.text - - wrapMode: Text.NoWrap - elide: Text.ElideRight } } ImageButtonType { - Layout.leftMargin: 4 - Layout.rightMargin: 16 - hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml new file mode 100644 index 000000000..9958f2e91 --- /dev/null +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + + property string placeholderText + property string text + property var onEditingFinished + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea + + width: parent.width + + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: root.placeholderText + text: root.text + + onEditingFinished: { + if (root.onEditingFinished && typeof root.onEditingFinished === "function") { + root.onEditingFinished() + } + } + + wrapMode: Text.Wrap + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + } + + //todo make whole background clickable, with code below we lose ability to select text by mouse +// MouseArea { +// anchors.fill: parent +// cursorShape: Qt.IBeamCursor +// onClicked: textArea.forceActiveFocus() +// } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 1414b5ecd..3f31c2243 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -41,7 +41,7 @@ Item { Rectangle { id: backgroud Layout.fillWidth: true - Layout.preferredHeight: 74 + Layout.preferredHeight: input.implicitHeight color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 border.color: textField.focus ? root.borderFocusedColor : root.borderColor @@ -52,16 +52,17 @@ Item { } RowLayout { + id: input anchors.fill: backgroud ColumnLayout { + Layout.margins: 16 LabelTextType { text: root.headerText color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor + visible: text !== "" + Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 } TextField { @@ -82,9 +83,7 @@ Item { height: 24 Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 + topPadding: 0 rightPadding: 0 leftPadding: 0 @@ -98,24 +97,37 @@ Item { onTextChanged: { root.errorText = "" } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } } } BasicButtonType { visible: (root.buttonText !== "") || (root.buttonImageSource !== "") - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 0 +// defaultColor: "transparent" +// hoveredColor: Qt.rgba(1, 1, 1, 0.08) +// pressedColor: Qt.rgba(1, 1, 1, 0.12) +// disabledColor: "#878B91" +// textColor: "#D7D8DB" +// borderWidth: 0 text: root.buttonText imageSource: root.buttonImageSource - Layout.rightMargin: 24 - Layout.preferredHeight: 32 +// Layout.rightMargin: 24 + Layout.preferredHeight: content.implicitHeight + Layout.preferredWidth: content.implicitHeight + squareLeftSide: true onClicked: { if (root.clickedFunc && typeof root.clickedFunc === "function") { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ce5ddcd4a..b756511b6 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -148,10 +148,11 @@ PageType { DropDownType { id: containersDropDown - implicitHeight: 40 - rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" + rootButtonHoveredBorderColor: "transparent" + rootButtonPressedBorderColor: "transparent" + rootButtonTextMargins: 8 text: root.defaultContainerName textColor: "#0E0E11" diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 7d38a2b08..d25fb6599 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -301,50 +301,18 @@ PageType { text: qsTr("Additional client configuration commands") } - Rectangle { + TextAreaType { Layout.fillWidth: true Layout.topMargin: 16 - height: 148 - color: "#1C1D21" - border.width: 1 - border.color: "#2C2D30" - radius: 16 visible: additionalClientCommandsSwitcher.checked - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: additionalClientCommandsTextArea.implicitHeight - TextArea { - id: additionalClientCommandsTextArea + text: additionalClientCommands + placeholderText: qsTr("Commands:") - width: parent.width - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - topPadding: 16 - leftPadding: 16 - - color: "#D7D8DB" - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: qsTr("Commands:") - text: additionalClientCommands - - wrapMode: Text.Wrap - - onEditingFinished: { - if (additionalClientCommands !== text) { - additionalClientCommands = text - } - } + onEditingFinished: { + if (additionalClientCommands !== text) { + additionalClientCommands = text } } } @@ -359,50 +327,18 @@ PageType { text: qsTr("Additional server configuration commands") } - Rectangle { + TextAreaType { Layout.fillWidth: true Layout.topMargin: 16 - height: 148 - color: "#1C1D21" - border.width: 1 - border.color: "#2C2D30" - radius: 16 visible: additionalServerCommandsSwitcher.checked - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: additionalServerCommandsTextArea.implicitHeight - TextArea { - id: additionalServerCommandsTextArea + text: additionalServerCommands + placeholderText: qsTr("Commands:") - width: parent.width - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - topPadding: 16 - leftPadding: 16 - - color: "#D7D8DB" - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: qsTr("Commands:") - text: additionalServerCommands - - wrapMode: Text.Wrap - - onEditingFinished: { - if (additionalServerCommands !== text) { - additionalServerCommands = text - } - } + onEditingFinished: { + if (additionalServerCommands !== text) { + additionalServerCommands = text } } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 2001e8924..9f2fe91a7 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -67,7 +67,7 @@ PageType { Layout.fillWidth: true text: qsTr("Logging") - descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") + descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index fd365edb2..6465e40ef 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -41,6 +41,24 @@ PageType { headerText: qsTr("Connection") } + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Auto connect") + descriptionText: qsTr("Connect to VPN on app start") + + checked: SettingsController.isAutoConnectEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoConnectEnabled()) { + SettingsController.toggleAutoConnect(checked) + } + } + } + SwitcherType { Layout.fillWidth: true Layout.topMargin: 16 @@ -54,7 +72,7 @@ PageType { checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { if (checked !== SettingsController.isAmneziaDnsEnabled()) { - SettingsController.setAmneziaDns(checked) + SettingsController.toggleAmneziaDns(checked) } } } @@ -83,6 +101,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsSplitTunneling) } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 998065e41..5138f9a39 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -50,10 +50,10 @@ PageType { text: qsTr("Save logs") - checked: SettingsController.isLoggingEnable + checked: SettingsController.isLoggingEnabled onCheckedChanged: { - if (checked !== SettingsController.isLoggingEnable) { - SettingsController.isLoggingEnable = checked + if (checked !== SettingsController.isLoggingEnabled) { + SettingsController.isLoggingEnabled = checked } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml new file mode 100644 index 000000000..a43e9e5e5 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -0,0 +1,377 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: SitesController + + function onFinished(message) { + PageController.showNotificationMessage(message) + } + + function onErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + + QtObject { + id: routeMode + property int allSites: 0 + property int onlyForwardSites: 1 + property int allExceptSites: 2 + } + + property list routeModesModel: [ + onlyForwardSites, + allExceptSites + ] + + QtObject { + id: onlyForwardSites + property string name: qsTr("Addresses from the list should always open via VPN") + property int type: routeMode.onlyForwardSites + } + QtObject { + id: allExceptSites + property string name: qsTr("Addresses from the list should never be opened via VPN") + property int type: routeMode.allExceptSites + } + + function getRouteModesModelIndex() { + var currentRouteMode = SitesModel.routeMode + if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { + return 0 + } else if (routeMode.allExceptSites === currentRouteMode) { + return 1 + } + } + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + RowLayout { + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("Split site tunneling") + } + + SwitcherType { + id: switcher + + property int lastActiveRouteMode: routeMode.onlyForwardSites + + Layout.fillWidth: true + Layout.rightMargin: 16 + + checked: SitesModel.routeMode !== routeMode.allSites + onToggled: { + if (checked) { + SitesModel.routeMode = lastActiveRouteMode + } else { + lastActiveRouteMode = SitesModel.routeMode + selector.text = root.routeModesModel[getRouteModesModelIndex()].name + SitesModel.routeMode = routeMode.allSites + } + } + } + } + + DropDownType { + id: selector + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + drawerHeight: 0.4375 + + enabled: switcher.checked + + headerText: qsTr("Mode") + + listView: ListViewType { + rootWidth: root.width + + model: root.routeModesModel + + currentIndex: getRouteModesModelIndex() + + clickedFunction: function() { + selector.text = selectedText + selector.menuVisible = false + if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { + SitesModel.routeMode = root.routeModesModel[currentIndex].type + } + } + + Component.onCompleted: { + if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) { + selector.text = selectedText + } else { + selector.text = root.routeModesModel[0].name + } + } + + Connections { + target: SitesModel + function onRouteModeChanged() { + currentIndex = getRouteModesModelIndex() + } + } + } + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin + + enabled: switcher.checked + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: sites + width: parent.width + height: sites.contentItem.height + + model: SitesModel + + clip: true + interactive: false + + delegate: Item { + implicitWidth: sites.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + Layout.fillWidth: true + + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + url + "?" + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SitesController.removeSite(index) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } + } + } + } + + RowLayout { + id: connectButton + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 24 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 24 + + TextFieldWithHeaderType { + Layout.fillWidth: true + + textFieldPlaceholderText: qsTr("Site or IP") + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + SitesController.addSite(textFieldText) + textFieldText = "" + } + } + + ImageButtonType { + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: "#D7D8DB" + + onClicked: function () { + moreActionsDrawer.open() + } + } + } + + DrawerType { + id: moreActionsDrawer + + width: parent.width + height: parent.height * 0.4375 + + FlickableType { + anchors.fill: parent + contentHeight: moreActionsDrawerContent.height + ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import/Export Sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.open() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Save site list") + + clickedFunction: function() { + SitesController.exportSites() + moreActionsDrawer.close() + } + } + + DividerType {} + } + } + } + + DrawerType { + id: importSitesDrawer + + width: parent.width + height: parent.height * 0.4375 + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.close() + } + } + + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import a list of sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Replace site list") + + clickedFunction: function() { + SitesController.importSites(true) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Add imported sites to existing ones") + + clickedFunction: function() { + SitesController.importSites(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } + + DividerType {} + } + } + } +} From 0c40a954fad64efc89092490601ef68698c3a450 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 8 Aug 2023 19:39:08 +0500 Subject: [PATCH 057/131] fixed include in sitesController for android build --- client/ui/controllers/sitesController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 7f3f93e1a..987906305 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -1,6 +1,7 @@ #include "sitesController.h" #include +#include #include "utilities.h" From 591d98d8b60aef386141ad6dc67559764fd64e02 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 9 Aug 2023 18:17:29 +0500 Subject: [PATCH 058/131] moved vpnConnection to separate thread - added tabbar blocking when installing/removing containers --- client/amnezia_application.cpp | 23 ++++ client/amnezia_application.h | 1 + client/main.cpp | 19 ++-- client/protocols/openvpnprotocol.cpp | 103 +++++++++--------- client/ui/controllers/pageController.h | 1 + client/ui/controllers/sitesController.cpp | 17 ++- client/ui/pages_logic/SitesLogic.cpp | 32 ++---- client/ui/qml/Pages2/PageDeinstalling.qml | 3 + .../Pages2/PageProtocolOpenVpnSettings.qml | 6 + client/ui/qml/Pages2/PageSettingsBackup.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 3 + client/ui/qml/Pages2/PageStart.qml | 8 +- client/ui/uilogic.cpp | 3 - 14 files changed, 128 insertions(+), 95 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 933a6346e..20b5ac3d8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -57,6 +57,27 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond AmneziaApplication::~AmneziaApplication() { + // emit hide(); + + // #ifdef AMNEZIA_DESKTOP + // if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { + // m_vpnConnection->disconnectFromVpn(); + // for (int i = 0; i < 50; i++) { + // qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + // QThread::msleep(100); + // if (m_vpnConnection->isDisconnected()) { + // break; + // } + // } + // } + // #endif + + // m_vpnConnection->deleteLater(); + // m_vpnConnectionThread.quit(); + // m_vpnConnectionThread.wait(3000); + + // qDebug() << "Application closed"; + if (m_engine) { QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; @@ -87,6 +108,8 @@ void AmneziaApplication::init() m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + m_vpnConnection->moveToThread(&m_vpnConnectionThread); + m_vpnConnectionThread.start(); initModels(); initControllers(); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 81897c83a..675ffd811 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -101,6 +101,7 @@ private: QScopedPointer m_sftpConfigModel; QSharedPointer m_vpnConnection; + QThread m_vpnConnectionThread; QScopedPointer m_notificationHandler; QScopedPointer m_connectionController; diff --git a/client/main.cpp b/client/main.cpp index 72ed6018e..e78a74ff5 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -2,20 +2,19 @@ #include #include "amnezia_application.h" -#include "version.h" #include "migrations.h" +#include "version.h" #include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif - int main(int argc, char *argv[]) { Migrations migrationsManager; @@ -27,16 +26,14 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #endif - #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); #else - AmneziaApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); + AmneziaApplication app(argc, argv, true, + SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); if (!app.isPrimary()) { - QTimer::singleShot(1000, &app, [&](){ - app.quit(); - }); + QTimer::singleShot(1000, &app, [&]() { app.quit(); }); return app.exec(); } #endif @@ -63,6 +60,10 @@ int main(int argc, char *argv[]) if (doExec) { app.init(); + + qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); + qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); + return app.exec(); } return 0; diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 7db642318..9551cb462 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -1,21 +1,20 @@ #include #include #include -#include -#include #include +#include +#include #include "logger.h" -#include "version.h" -#include "utilities.h" #include "openvpnprotocol.h" +#include "utilities.h" +#include "version.h" - -OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) +OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) { readOpenVpnConfiguration(configuration); - connect(&m_managementServer, &ManagementServer::readyRead, this, &OpenVpnProtocol::onReadyReadDataFromManagementServer); + connect(&m_managementServer, &ManagementServer::readyRead, this, + &OpenVpnProtocol::onReadyReadDataFromManagementServer); } OpenVpnProtocol::~OpenVpnProtocol() @@ -27,7 +26,7 @@ OpenVpnProtocol::~OpenVpnProtocol() QString OpenVpnProtocol::defaultConfigFileName() { - //qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); + // qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); } @@ -42,21 +41,20 @@ QString OpenVpnProtocol::defaultConfigPath() void OpenVpnProtocol::stop() { qDebug() << "OpenVpnProtocol::stop()"; - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); // TODO: need refactoring // sendTermSignal() will even return true while server connected ??? - if ((m_connectionState == Vpn::ConnectionState::Preparing) || - (m_connectionState == Vpn::ConnectionState::Connecting) || - (m_connectionState == Vpn::ConnectionState::Connected) || - (m_connectionState == Vpn::ConnectionState::Reconnecting)) { + if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting) + || (m_connectionState == Vpn::ConnectionState::Connected) + || (m_connectionState == Vpn::ConnectionState::Reconnecting)) { if (!sendTermSignal()) { killOpenVpnProcess(); } QThread::msleep(10); m_managementServer.stop(); } - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } ErrorCode OpenVpnProtocol::prepare() @@ -68,18 +66,19 @@ ErrorCode OpenVpnProtocol::prepare() QRemoteObjectPendingReply resultCheck = IpcClient::Interface()->getTapList(); resultCheck.waitForFinished(); - if (resultCheck.returnValue().isEmpty()){ + if (resultCheck.returnValue().isEmpty()) { QRemoteObjectPendingReply resultInstall = IpcClient::Interface()->checkAndInstallDriver(); resultInstall.waitForFinished(); - if (!resultInstall.returnValue()) return ErrorCode::OpenVpnTapAdapterError; + if (!resultInstall.returnValue()) + return ErrorCode::OpenVpnTapAdapterError; } return ErrorCode::NoError; } void OpenVpnProtocol::killOpenVpnProcess() { - if (m_openVpnProcess){ + if (m_openVpnProcess) { m_openVpnProcess->close(); } } @@ -113,9 +112,9 @@ QString OpenVpnProtocol::configPath() const return m_configFileName; } -void OpenVpnProtocol::sendManagementCommand(const QString& command) +void OpenVpnProtocol::sendManagementCommand(const QString &command) { - QIODevice *device = dynamic_cast(m_managementServer.socket().data()); + QIODevice *device = dynamic_cast(m_managementServer.socket().data()); if (device) { QTextStream stream(device); stream << command << Qt::endl; @@ -127,11 +126,12 @@ uint OpenVpnProtocol::selectMgmtPort() for (int i = 0; i < 100; ++i) { quint32 port = QRandomGenerator::global()->generate(); - port = (double)(65000-15001) * port / UINT32_MAX + 15001; + port = (double)(65000 - 15001) * port / UINT32_MAX + 15001; QTcpServer s; bool ok = s.listen(QHostAddress::LocalHost, port); - if (ok) return port; + if (ok) + return port; } return m_managementPort; @@ -141,7 +141,8 @@ void OpenVpnProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -149,7 +150,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line) ErrorCode OpenVpnProtocol::start() { - //qDebug() << "Start OpenVPN connection"; + // qDebug() << "Start OpenVPN connection"; OpenVpnProtocol::stop(); if (!QFileInfo::exists(Utils::openVpnExecPath())) { @@ -167,24 +168,25 @@ ErrorCode OpenVpnProtocol::start() QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); - p.start("route", QStringList() << "-n" << "get" << "default"); + p.start("route", + QStringList() << "-n" + << "get" + << "default"); p.waitForFinished(); - QString s = p.readAll(); + QString s = p.readAll(); QRegularExpression rx(R"(gateway:\s*(\d+\.\d+\.\d+\.\d+))"); QRegularExpressionMatch match = rx.match(s); if (match.hasMatch()) { m_routeGateway = match.captured(1); qDebug() << "Set VPN route gateway" << m_routeGateway; - } - else { + } else { qWarning() << "Unable to set VPN route gateway, output:\n" << s; } #endif - -// QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; -// Utils::createEmptyFile(vpnLogFileNamePath); + // QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; + // Utils::createEmptyFile(vpnLogFileNamePath); uint mgmtPort = selectMgmtPort(); qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort; @@ -199,7 +201,7 @@ ErrorCode OpenVpnProtocol::start() m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); if (!m_openVpnProcess) { - //qWarning() << "IpcProcess replica is not created!"; + // qWarning() << "IpcProcess replica is not created!"; setLastError(ErrorCode::AmneziaServiceConnectionFailed); return ErrorCode::AmneziaServiceConnectionFailed; } @@ -211,28 +213,25 @@ ErrorCode OpenVpnProtocol::start() return ErrorCode::AmneziaServiceConnectionFailed; } m_openVpnProcess->setProgram(PermittedProcess::OpenVPN); - QStringList arguments({"--config" , configPath(), - "--management", m_managementHost, QString::number(mgmtPort), - "--management-client"/*, "--log", vpnLogFileNamePath */ - }); + QStringList arguments({ + "--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort), + "--management-client" /*, "--log", vpnLogFileNamePath */ + }); m_openVpnProcess->setArguments(arguments); qDebug() << arguments.join(" "); - connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, [&](QProcess::ProcessError error) { - qDebug() << "PrivilegedProcess errorOccurred" << error; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, + [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, [&](QProcess::ProcessState newState) { - qDebug() << "PrivilegedProcess stateChanged" << newState; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, + [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(Vpn::ConnectionState::Disconnected); - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, + [&]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); m_openVpnProcess->start(); - //startTimeoutTimer(); + // startTimeoutTimer(); return ErrorCode::NoError; } @@ -255,7 +254,7 @@ void OpenVpnProtocol::sendInitialData() void OpenVpnProtocol::onReadyReadDataFromManagementServer() { - for (;;) { + for (;;) { QString line = m_managementServer.readLine().simplified(); if (line.isEmpty()) { @@ -268,14 +267,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains(">INFO:OpenVPN Management Interface")) { sendInitialData(); - } else if (line.startsWith(">STATE")) { + } else if (line.startsWith(">STATE")) { if (line.contains("CONNECTED,SUCCESS")) { sendByteCount(); stopTimeoutTimer(); setConnectionState(Vpn::ConnectionState::Connected); continue; } else if (line.contains("EXITING,SIGTER")) { - //openVpnStateSigTermHandler(); + // openVpnStateSigTermHandler(); setConnectionState(Vpn::ConnectionState::Disconnecting); continue; } else if (line.contains("RECONNECTING")) { @@ -295,8 +294,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains("FATAL")) { if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) { emit protocolError(ErrorCode::OpenVpnAdaptersInUseError); - } - else { + } else { emit protocolError(ErrorCode::OpenVpnUnknownError); } return; @@ -321,7 +319,8 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() void OpenVpnProtocol::updateVpnGateway(const QString &line) { // line looks like - // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' + // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart + // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' QStringList params = line.split(","); for (const QString &l : params) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index fd748347b..049201e46 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -88,6 +88,7 @@ signals: void showNotificationMessage(const QString &message); void showBusyIndicator(bool visible); + void enableTabBar(bool enabled); void hideMainWindow(); void raiseMainWindow(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 987906305..d8bc99c81 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -36,12 +36,13 @@ void SitesController::addSite(QString hostname) m_sitesModel->addSite(hostname, ip); if (!ip.isEmpty()) { - m_vpnConnection->addRoutes(QStringList() << ip); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << ip)); } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { - m_vpnConnection->addRoutes(QStringList() << hostname); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); } + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); }; const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { @@ -70,6 +71,10 @@ void SitesController::removeSite(int index) auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); m_sitesModel->removeSite(modelIndex); + QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); + emit finished(tr("Site removed: ") + hostname); } @@ -124,8 +129,8 @@ void SitesController::importSites(bool replaceExisting) m_sitesModel->addSites(sites, replaceExisting); - m_vpnConnection->addRoutes(QStringList() << ips); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); emit finished(tr("Import completed")); } diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index b5760dfb6..aaf8f73d9 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -66,18 +66,14 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() m_settings->addVpnSite(mode, newSite, ip); if (!ip.isEmpty()) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << ip)); - } - else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << newSite)); } - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); }; @@ -124,19 +120,16 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) return; // sites.append(siteModel->data(row, 0).toString()); - if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { - ips.append(siteModel->data(row, 1).toString()); - } + // if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + // ips.append(siteModel->data(row, 1).toString()); + // } } m_settings->removeVpnSites(mode, sites); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); } @@ -197,12 +190,9 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) m_settings->addVpnIps(mode, ips); m_settings->addVpnSites(mode, sites); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); } diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 49bcdb9af..eff580556 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -14,6 +14,9 @@ import "../Config" PageType { id: root + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + SortFilterProxyModel { id: proxyServersModel sourceModel: ServersModel diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index d25fb6599..bea239c78 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -299,6 +299,12 @@ PageType { checked: additionalClientCommands !== "" text: qsTr("Additional client configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalClientCommands = "" + } + } } TextAreaType { diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index cd0b3ee84..39cad7329 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -21,7 +21,7 @@ PageType { function onRestoreBackupFinished() { PageController.showNotificationMessage(qsTr("Settings restored from backup file")) - goToStartPage() + //goToStartPage() PageController.goToPageHome() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 7be35cacc..407194e19 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -166,7 +166,7 @@ PageType { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - ConnectionController.closeVpnConnection() + ConnectionController.closeConnection() } InstallController.removeAllContainers() } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 6c6d1a675..e194c21cb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -14,6 +14,9 @@ import "../Config" PageType { id: root + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + Connections { target: InstallController diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 2cc64a911..5c2a13df9 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -18,12 +18,12 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 - tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) + tabBarStackView.goToTabBarPage(PageEnum.PageHome) } function onGoToPageSettings() { tabBar.currentIndex = 2 - tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) } function onGoToPageViewConfig() { @@ -37,6 +37,10 @@ PageType { tabBar.enabled = !visible } + function onEnableTabBar(enabled) { + tabBar.enabled = enabled + } + function onClosePage() { if (tabBarStackView.depth <= 1) { return diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index aefcc49c6..27a4e9717 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -157,9 +157,6 @@ void UiLogic::initializeUiLogic() // } m_selectedServerIndex = m_settings->defaultServerIndex(); - - qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME).arg(APP_VERSION); - qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::currentCpuArchitecture()); } void UiLogic::showOnStartup() From c1c68cf72dfe5e6b45e50b5535562f730b9a2928 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 10 Aug 2023 10:02:13 +0500 Subject: [PATCH 059/131] fixed ability to share warguard in native format --- CMakeLists.txt | 4 ++-- client/ui/qml/Components/ConnectButton.qml | 1 + client/ui/qml/Pages2/PageShare.qml | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ff60079b..be5daeac6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.0.1 +project(${PROJECT} VERSION 4.0.1.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-07-31") +set(RELEASE_DATE "2023-08-10") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 85cc345af..ab28d0d05 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -41,6 +41,7 @@ Button { anchors.right: parent.right layer.enabled: true layer.samples: 4 + layer.smooth: true layer.effect: DropShadow { anchors.fill: backgroundCircle horizontalOffset: 0 diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6e3fecf25..e88372d67 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -310,10 +310,12 @@ PageType { function fillConnectionTypeModel() { root.connectionTypesModel = [amneziaConnectionFormat] - if (currentIndex === ContainerProps.containerFromString("OpenVpn")) { + var index = proxyContainersModel.mapToSource(currentIndex) + + if (index === ContainerProps.containerFromString("amnezia-openvpn")) { root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) { - root.connectionTypesModel.push(amneziaConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + root.connectionTypesModel.push(wireGuardConnectionFormat) } } } From 14fa0b4fd374ad80422309eac044c268221f7e20 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 13 Aug 2023 11:28:32 +0500 Subject: [PATCH 060/131] added qr code scanner for ios --- client/amnezia_application.h | 1 + client/containers/containers_defs.cpp | 4 +- client/platforms/ios/QRCodeReaderBase.mm | 7 +- client/ui/controllers/importController.cpp | 53 ++++++++--- client/ui/controllers/importController.h | 13 ++- client/ui/models/containers_model.cpp | 2 +- .../models/protocols/openvpnConfigModel.cpp | 3 +- client/ui/models/servers_model.cpp | 1 - .../qml/Pages2/PageProtocolCloakSettings.qml | 2 - .../Pages2/PageProtocolOpenVpnSettings.qml | 1 - .../PageProtocolShadowSocksSettings.qml | 2 - .../ui/qml/Pages2/PageServiceSftpSettings.qml | 2 - .../Pages2/PageServiceTorWebsiteSettings.qml | 2 - .../qml/Pages2/PageSettingsServerProtocol.qml | 7 +- .../Pages2/PageSetupWizardConfigSource.qml | 3 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 - .../PageSetupWizardProtocolSettings.qml | 1 - .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 87 +++++++++++++------ client/vpnconnection.cpp | 4 +- 19 files changed, 128 insertions(+), 69 deletions(-) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 675ffd811..02c600015 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "settings.h" #include "vpnconnection.h" diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 1e8046bf7..27f6f1dd4 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -217,8 +217,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); - case DockerContainer::Cloak: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); - case DockerContainer::ShadowSocks: return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::ShadowSocks: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); default: return ""; } } diff --git a/client/platforms/ios/QRCodeReaderBase.mm b/client/platforms/ios/QRCodeReaderBase.mm index bd0dbac38..af879e2f9 100644 --- a/client/platforms/ios/QRCodeReaderBase.mm +++ b/client/platforms/ios/QRCodeReaderBase.mm @@ -49,14 +49,15 @@ _videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession]; - CGFloat tabBarHeight = 20.0; + CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height; + QRect cameraRect = _qrCodeReader->cameraSize(); CGRect cameraCGRect = CGRectMake(cameraRect.x(), - cameraRect.y() + tabBarHeight, + cameraRect.y() + statusBarHeight, cameraRect.width(), cameraRect.height()); - [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspect]; + [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspectFill]; [_videoPreviewPlayer setFrame: cameraCGRect]; CALayer* layer = [UIApplication sharedApplication].keyWindow.layer; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 8e2ad5d1c..4ff972cce 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -42,8 +42,11 @@ namespace return ConfigTypes::Amnezia; } -#ifdef Q_OS_ANDROID +#if defined Q_OS_ANDROID ImportController *mInstance = nullptr; +#endif + +#ifdef Q_OS_ANDROID constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity"; #endif } // namespace @@ -264,17 +267,6 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) } #ifdef Q_OS_ANDROID -void ImportController::startDecodingQr() -{ - AndroidController::instance()->startQrReaderActivity(); -} - -void ImportController::stopDecodingQr() -{ - QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); - emit qrDecodingFinished(); -} - void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); @@ -296,6 +288,30 @@ void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring d mInstance->parseQrCodeChunk(parcelBody); } } +#endif + +#if defined Q_OS_ANDROID || defined Q_OS_IOS +void ImportController::startDecodingQr() +{ + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + + #if defined Q_OS_IOS + m_isQrCodeProcessed = true; + #endif + #if defined Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); + #endif +} + +void ImportController::stopDecodingQr() +{ + #if defined Q_OS_ANDROID + QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); + #endif + emit qrDecodingFinished(); +} void ImportController::parseQrCodeChunk(const QString &code) { @@ -333,8 +349,10 @@ void ImportController::parseQrCodeChunk(const QString &code) bool ok = extractConfigFromQr(data); if (ok) { m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; stopDecodingQr(); } else { + qDebug() << "error while extracting data from qr"; m_qrCodeChunks.clear(); m_totalQrCodeChunksCount = 0; m_receivedQrCodeChunksCount = 0; @@ -344,8 +362,19 @@ void ImportController::parseQrCodeChunk(const QString &code) bool ok = extractConfigFromQr(ba); if (ok) { m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; stopDecodingQr(); } } } + +double ImportController::getQrCodeScanProgressBarValue() +{ + return (1.0 / m_totalQrCodeChunksCount) * m_receivedQrCodeChunksCount; +} + +QString ImportController::getQrCodeScanProgressString() +{ + return tr("Scanned %1 of %2.").arg(m_receivedQrCodeChunksCount).arg(m_totalQrCodeChunksCount); +} #endif diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 9556bc6af..cccdf4b74 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -28,8 +28,12 @@ public slots: QString getConfig(); QString getConfigFileName(); -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS void startDecodingQr(); + void parseQrCodeChunk(const QString &code); + + double getQrCodeScanProgressBarValue(); + QString getQrCodeScanProgressString(); #endif signals: @@ -43,10 +47,11 @@ private: QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data); -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS void stopDecodingQr(); +#endif +#if defined Q_OS_ANDROID static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data); - void parseQrCodeChunk(const QString &code); #endif QSharedPointer m_serversModel; @@ -56,7 +61,7 @@ private: QJsonObject m_config; QString m_configFileName; -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS QMap m_qrCodeChunks; bool m_isQrCodeProcessed; int m_totalQrCodeChunksCount; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 8d24e0192..dd1d8d4e3 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -176,7 +176,7 @@ ErrorCode ContainersModel::removeCurrentlyProcessedContainer() ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); if (errorCode == ErrorCode::NoError) { - beginResetModel(); // todo change to begin remove rows? + beginResetModel(); m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); m_containers = m_settings->containers(m_currentlyProcessedServerIndex); diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp index 1b73c987f..7ef3af5b3 100644 --- a/client/ui/models/protocols/openvpnConfigModel.cpp +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -79,8 +79,7 @@ QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const void OpenVpnConfigModel::updateModel(const QJsonObject &config) { beginResetModel(); - m_container = - ContainerProps::containerFromString(config.value(config_key::container).toString()); // todo maybe unused + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); m_fullConfig = config; QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 364fd71f9..0a117e35a 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -157,7 +157,6 @@ bool ServersModel::isDefaultServerHasWriteAccess() void ServersModel::addServer(const QJsonObject &server) { - // todo beginInsertRows()? beginResetModel(); m_settings->addServer(server); m_servers = m_settings->serversArray(); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index cc764451f..8e9085edd 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,12 +13,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index bea239c78..ca13d7e50 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -19,7 +19,6 @@ PageType { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 643907907..1189290fc 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,12 +13,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index d37562a17..6783fecda 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -15,12 +15,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 5ddb9ed6f..40e1b3393 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -16,12 +16,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index e417eea97..db86f7b22 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -55,16 +55,15 @@ PageType { anchors.topMargin: 32 ListView { - // todo change id naming - id: container + id: protocols width: parent.width - height: container.contentItem.height + height: protocols.contentItem.height clip: true interactive: false model: ProtocolsModel delegate: Item { - implicitWidth: container.width + implicitWidth: protocols.width implicitHeight: delegateContent.implicitHeight ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index cd0c08fbc..f01556672 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -17,6 +17,7 @@ PageType { target: ImportController function onQrDecodingFinished() { + closePage() goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -86,7 +87,7 @@ It's okay if a friend passed the code.") clickedFunction: function() { ImportController.startDecodingQr() -// goToPage(PageEnum.PageSetupWizardQrReader) + goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index e194c21cb..3f135c155 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -68,7 +68,6 @@ PageType { } FlickableType { - id: fl anchors.fill: parent contentHeight: content.height @@ -82,7 +81,6 @@ PageType { spacing: 16 ListView { - // todo change id naming id: container width: parent.width height: container.contentItem.height diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 65e00da8c..f9d55ddfc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -233,7 +233,6 @@ PageType { } Component.onCompleted: { - //todo move to protocols model? var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml index 65d233eff..695175fde 100644 --- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -14,39 +14,76 @@ import "../Config" PageType { id: root - ColumnLayout { - anchors.fill: parent + BackButtonType { + id: backButton + anchors.left: parent.left + anchors.top: parent.top - spacing: 0 + anchors.topMargin: 20 + } - BackButtonType { - Layout.topMargin: 20 - } + ParagraphTextType { + id: header - ParagraphTextType { - Layout.fillWidth: true + property string progressString - text: qsTr("Point the camera at the QR code and hold for a couple of seconds.") - } + anchors.left: parent.left + anchors.top: backButton.bottom + anchors.right: parent.right - ProgressBarType { + anchors.leftMargin: 16 + anchors.rightMargin: 16 - } + text: qsTr("Point the camera at the QR code and hold for a couple of seconds. ") + progressString + } - Item { - Layout.fillHeight: true - Layout.fillWidth: true + ProgressBarType { + id: progressBar - QRCodeReader { - id: qrCodeReader - Component.onCompleted: { - qrCodeReader.setCameraSize(Qt.rect(parent.x, - parent.y, - parent.width, - parent.height)) - qrCodeReader.startReading() - } - } + anchors.left: parent.left + anchors.top: header.bottom + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + Rectangle { + id: qrCodeRectange + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: progressBar.bottom + + anchors.topMargin: 34 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.bottomMargin: 34 + + color: "transparent" + //radius: 16 + + QRCodeReader { + id: qrCodeReader + + onCodeReaded: function(code) { + ImportController.parseQrCodeChunk(code) + progressBar.value = ImportController.getQrCodeScanProgressBarValue() + header.progressString = ImportController.getQrCodeScanProgressString() + } + + Component.onCompleted: { + console.log(qrCodeRectange.x) + console.log(qrCodeRectange.y) + console.log(qrCodeRectange.width) + + qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x, + qrCodeRectange.y, + qrCodeRectange.width, + qrCodeRectange.height)) + qrCodeReader.startReading() + } + Component.onDestruction: qrCodeReader.stopReading() } } } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5f3511d4d..99ca700d4 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -96,7 +96,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #endif #ifdef Q_OS_IOS - if (state == VpnProtocol::Connected) { + if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); } else { @@ -337,7 +337,7 @@ void VpnConnection::connectToVpn(int serverIndex, if (!iosVpnProtocol->initialize()) { qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; iosVpnProtocol->deleteLater(); return; } From e157160337a43671342857c4b8495e95f4e4e2b6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 12:11:34 +0500 Subject: [PATCH 061/131] added disconnection from vpn when closing application for desktop - many small ui fixes --- client/amnezia_application.cpp | 51 ++++++------------- client/amnezia_application.h | 8 +-- .../ui/controllers/connectionController.cpp | 17 +++++++ client/ui/controllers/connectionController.h | 2 + client/ui/controllers/installController.cpp | 2 +- client/ui/controllers/installController.h | 2 +- client/ui/controllers/pageController.h | 2 + client/ui/controllers/settingsController.cpp | 18 ++++++- client/ui/controllers/settingsController.h | 5 ++ client/ui/models/servers_model.cpp | 5 ++ client/ui/models/servers_model.h | 3 +- .../qml/Components/ShareConnectionDrawer.qml | 2 + client/ui/qml/Controls2/ListViewType.qml | 6 +++ .../qml/Pages2/PageProtocolCloakSettings.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 5 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 7 +++ .../ui/qml/Pages2/PageSettingsServerInfo.qml | 15 +++--- .../qml/Pages2/PageSettingsServerProtocol.qml | 2 + .../qml/Pages2/PageSetupWizardInstalling.qml | 5 +- .../PageSetupWizardProtocolSettings.qml | 1 + .../qml/Pages2/PageSetupWizardViewConfig.qml | 1 + client/ui/qml/Pages2/PageShare.qml | 15 +++--- client/ui/qml/main2.qml | 7 +++ 24 files changed, 121 insertions(+), 64 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 20b5ac3d8..d9120042d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -57,36 +57,13 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond AmneziaApplication::~AmneziaApplication() { - // emit hide(); - - // #ifdef AMNEZIA_DESKTOP - // if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - // m_vpnConnection->disconnectFromVpn(); - // for (int i = 0; i < 50; i++) { - // qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - // QThread::msleep(100); - // if (m_vpnConnection->isDisconnected()) { - // break; - // } - // } - // } - // #endif - - // m_vpnConnection->deleteLater(); - // m_vpnConnectionThread.quit(); - // m_vpnConnectionThread.wait(3000); - - // qDebug() << "Application closed"; + m_vpnConnectionThread.quit(); + m_vpnConnectionThread.wait(3000); if (m_engine) { QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; } - - if (m_protocolProps) - delete m_protocolProps; - if (m_containerProps) - delete m_containerProps; } void AmneziaApplication::init() @@ -208,11 +185,11 @@ void AmneziaApplication::registerTypes() qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); - m_containerProps = new ContainerProps; - qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps); + m_containerProps.reset(new ContainerProps()); + qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); - m_protocolProps = new ProtocolProps; - qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + m_protocolProps.reset(new ProtocolProps()); + qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get()); qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, "ContainersModelFilters"); @@ -232,9 +209,13 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); - m_translator = new QTranslator; - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - installTranslator(m_translator); + if (locale != QLocale::English) { + m_translator.reset(new QTranslator()); + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } } } @@ -242,7 +223,7 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) { QResource::registerResource(":/translations.qrc"); if (!m_translator->isEmpty()) - QCoreApplication::removeTranslator(m_translator); + QCoreApplication::removeTranslator(m_translator.get()); if (locale == QLocale::English) { m_settings->setAppLanguage(locale); @@ -250,7 +231,7 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) } if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - if (QCoreApplication::installTranslator(m_translator)) { + if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); } @@ -351,7 +332,7 @@ void AmneziaApplication::initControllers() m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 02c600015..c4f337530 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -79,15 +79,15 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - ContainerProps *m_containerProps {}; - ProtocolProps *m_protocolProps {}; + QSharedPointer m_containerProps; + QSharedPointer m_protocolProps; - QTranslator *m_translator; + QSharedPointer m_translator; QCommandLineParser m_parser; QSharedPointer m_containersModel; QSharedPointer m_serversModel; - QScopedPointer m_languageModel; + QSharedPointer m_languageModel; QScopedPointer m_protocolsModel; QSharedPointer m_sitesModel; diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 7d1b6bed1..45f24d7fd 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -17,6 +17,23 @@ ConnectionController::ConnectionController(const QSharedPointer &s Qt::QueuedConnection); } +ConnectionController::~ConnectionController() +{ +// todo use ConnectionController instead of using m_vpnConnection directly +#ifdef AMNEZIA_DESKTOP + if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { + m_vpnConnection->disconnectFromVpn(); + for (int i = 0; i < 50; i++) { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + QThread::msleep(100); + if (m_vpnConnection->isDisconnected()) { + break; + } + } + } +#endif +} + void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index a33d30c2f..5a35f9d8a 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,6 +19,8 @@ public: const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); + ~ConnectionController(); + bool isConnected() const; bool isConnectionInProgress() const; QString connectionStateText() const; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 49c77708c..7e71aef8e 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -173,7 +173,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject "All installed containers have been added to the application"); } - emit installContainerFinished(finishMessage); + emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 38e2e7802..b74835d9f 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -42,7 +42,7 @@ public slots: void setEncryptedPassphrase(QString passphrase); signals: - void installContainerFinished(const QString &finishMessage); + void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); void updateContainerFinished(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 049201e46..1948ed11b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -79,6 +79,8 @@ signals: void goToPageHome(); void goToPageSettings(); void goToPageViewConfig(); + void goToPageSettingsServerServices(); + void closePage(); void restorePageHomeState(bool isContainerInstalled = false); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 571b03e89..531de14d7 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,8 +8,13 @@ SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &languageModel, const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_languageModel(languageModel), + m_settings(settings) { m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } @@ -95,6 +100,8 @@ void SettingsController::restoreAppConfig() bool ok = m_settings->restoreAppConfig(data); if (ok) { m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); emit restoreBackupFinished(); } else { emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); @@ -110,6 +117,15 @@ void SettingsController::clearSettings() { m_settings->clearSettings(); m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); + emit changeSettingsFinished(tr("All settings have been reset to default values")); +} + +void SettingsController::clearCachedProfiles() +{ + m_containersModel->clearCachedProfiles(); + emit changeSettingsFinished(tr("Cached profiles cleared")); } bool SettingsController::isAutoConnectEnabled() diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index ffd72f69d..a0a29ebfb 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -4,6 +4,7 @@ #include #include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" #include "ui/models/servers_model.h" class SettingsController : public QObject @@ -12,6 +13,7 @@ class SettingsController : public QObject public: explicit SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &languageModel, const std::shared_ptr &settings, QObject *parent = nullptr); Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) @@ -41,6 +43,7 @@ public slots: QString getAppVersion(); void clearSettings(); + void clearCachedProfiles(); bool isAutoConnectEnabled(); void toggleAutoConnect(bool enable); @@ -51,11 +54,13 @@ signals: void loggingStateChanged(); void restoreBackupFinished(); + void changeSettingsFinished(const QString &finishedMessage); void changeSettingsErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_languageModel; std::shared_ptr m_settings; QString m_appVersion; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 0a117e35a..7eea94e56 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -6,6 +6,8 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); m_currentlyProcessedServerIndex = m_defaultServerIndex; + + connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); } int ServersModel::rowCount(const QModelIndex &parent) const @@ -27,6 +29,9 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int server.insert(config_key::description, value.toString()); m_settings->editServer(index.row(), server); m_servers.replace(index.row(), server); + if (index.row() == m_defaultServerIndex) { + emit defaultServerNameChanged(); + } break; } default: { diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 60ec35b27..d7b158445 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,7 +31,7 @@ public: void resetModel(); Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged) Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex NOTIFY currentlyProcessedServerIndexChanged) @@ -65,6 +65,7 @@ protected: signals: void currentlyProcessedServerIndexChanged(const int index); void defaultServerIndexChanged(); + void defaultServerNameChanged(); private: ServerCredentials serverCredentials(int index) const; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 3f27396bb..7ad724ea2 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -80,6 +80,7 @@ DrawerType { configText.selectAll() configText.copy() configText.select(0, 0) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -92,6 +93,7 @@ DrawerType { pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" + borderWidth: 1 text: qsTr("Show content") diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index 4251f0fde..926ec0381 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -8,11 +8,17 @@ ListView { id: menuContent property var rootWidth + property var selectedText + property string imageSource + property var clickedFunction + property bool dividerVisible: false + currentIndex: 0 + width: rootWidth height: menuContent.contentItem.height diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 8e9085edd..c19118b52 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -102,6 +102,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -114,7 +115,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index ca13d7e50..a8f7bb776 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -133,6 +133,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -161,7 +162,6 @@ PageType { id: hashDropDown Layout.fillWidth: true Layout.topMargin: 20 - implicitHeight: 74 enabled: !autoNegotiateEncryprionSwitcher.checked @@ -208,7 +208,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 enabled: !autoNegotiateEncryprionSwitcher.checked @@ -393,7 +392,7 @@ PageType { } } } - } + } } QuestionDrawer { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 1189290fc..38a6be06f 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -88,6 +88,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -100,7 +101,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 20 - implicitHeight: 74 descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 407194e19..9ff991939 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -50,6 +50,13 @@ PageType { } } + Connections { + target: SettingsController + function onChangeSettingsFinished(finishedMessage) { + PageController.showNotificationMessage(finishedMessage) + } + } + Connections { target: ServersModel diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index cf748f54e..62a7a67ba 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -18,6 +18,14 @@ import "../Components" PageType { id: root + Connections { + target: PageController + + function onGoToPageSettingsServerServices() { + tabBar.currentIndex = 1 + } + } + SortFilterProxyModel { id: proxyServersModel sourceModel: ServersModel @@ -99,13 +107,6 @@ PageType { BasicButtonType { Layout.fillWidth: true - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - text: qsTr("Save") onClicked: { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index db86f7b22..f1f067c2f 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -107,6 +107,8 @@ PageType { width: parent.width + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 3f135c155..10daa0bec 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -20,13 +20,16 @@ PageType { Connections { target: InstallController - function onInstallContainerFinished(finishedMessage) { + function onInstallContainerFinished(finishedMessage, isServiceInstall) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) goToPage(PageEnum.PageSettingsServerInfo, false) + if (isServiceInstall) { + PageController.goToPageSettingsServerServices() + } } else { goToPage(PageEnum.PageHome) } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index f9d55ddfc..f5f076092 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -211,6 +211,7 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Port") + textField.maximumLength: 5 } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 5f52a5bae..222fbd11c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -79,6 +79,7 @@ PageType { Layout.fillWidth: true text: ImportController.getConfigFileName() + wrapMode: Text.Wrap } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index e88372d67..f2ed46079 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -161,11 +161,9 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - implicitHeight: 74 - drawerHeight: 0.4375 - descriptionText: qsTr("Server and service") + descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") headerText: qsTr("Server") listView: ListViewType { @@ -261,7 +259,7 @@ PageType { rootWidth: root.width dividerVisible: true - imageSource: "qrc:/images/controls/chevron-right.svg" + imageSource: "qrc:/images/controls/check.svg" model: SortFilterProxyModel { id: proxyContainersModel @@ -327,13 +325,11 @@ PageType { DropDownType { id: exportTypeSelector - property int currentIndex + property int currentIndex: 0 Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 - drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 @@ -343,13 +339,15 @@ PageType { headerText: qsTr("Connection format") listView: ListViewType { + id: exportTypeListView + rootWidth: root.width dividerVisible: true imageSource: "qrc:/images/controls/chevron-right.svg" model: root.connectionTypesModel - currentIndex: 0 + currentIndex: exportTypeSelector.currentIndex clickedFunction: function() { exportTypeSelector.text = selectedText @@ -375,6 +373,7 @@ PageType { enabled: shareButtonEnabled text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" onClicked: { if (accessTypeSelector.currentIndex === 0) { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index f07bcf5d5..6ae434d7e 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,6 +81,13 @@ Window { } } + Connections { + target: SettingsController + function onChangeSettingsFinished(finishedMessage) { + PageController.showNotificationMessage(finishedMessage) + } + } + Item { anchors.right: parent.right anchors.left: parent.left From a8deb3593be83eae01b17d5b8aba8936cb59f22f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 22:45:05 +0500 Subject: [PATCH 062/131] dropdown list fixes to match design layout --- client/resources.qrc | 3 +- .../Components/SettingsContainersListView.qml | 179 +++++++----------- .../qml/Controls2/ListViewWithLabelsType.qml | 70 +++++++ ...pe.qml => ListViewWithRadioButtonType.qml} | 21 +- client/ui/qml/Controls2/PageType.qml | 10 + .../qml/Pages2/PageProtocolCloakSettings.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 6 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 10 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 1 + .../qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 10 +- 12 files changed, 177 insertions(+), 139 deletions(-) create mode 100644 client/ui/qml/Controls2/ListViewWithLabelsType.qml rename client/ui/qml/Controls2/{ListViewType.qml => ListViewWithRadioButtonType.qml} (85%) diff --git a/client/resources.qrc b/client/resources.qrc index 2238f25dd..782b64462 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -250,7 +250,7 @@ ui/qml/Controls2/BackButtonType.qml ui/qml/Pages2/PageSettingsServerProtocol.qml ui/qml/Components/TransportProtoSelector.qml - ui/qml/Controls2/ListViewType.qml + ui/qml/Controls2/ListViewWithRadioButtonType.qml images/controls/radio-button.svg images/controls/radio-button-inner-circle.png images/controls/radio-button-pressed.svg @@ -285,5 +285,6 @@ ui/qml/Controls2/TextAreaType.qml images/controls/trash.svg images/controls/more-vertical.svg + ui/qml/Controls2/ListViewWithLabelsType.qml diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index bb5788993..2489323b0 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -22,133 +22,86 @@ ListView { clip: true interactive: false - ButtonGroup { - id: containersRadioButtonGroup - } - delegate: Item { implicitWidth: root.width - implicitHeight: containerRadioButton.implicitHeight + implicitHeight: delegateContent.implicitHeight - RadioButton { - id: containerRadioButton + ColumnLayout { + id: delegateContent - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight + anchors.fill: parent - hoverEnabled: true + LabelWithButtonType { + implicitWidth: parent.width - ButtonGroup.group: containersRadioButtonGroup + text: name + descriptionText: description + rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" + clickedFunction: function() { + if (isInstalled) { + var containerIndex = root.model.mapToSource(index) + ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - checkable: isInstalled - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - ColumnLayout { - Layout.topMargin: 20 - Layout.bottomMargin: 20 - - ListItemTitleType { - Layout.fillWidth: true - - text: name - } - - CaptionTextType { - Layout.fillWidth: true - - text: description - color: "#878B91" - } - } - - Image { - source: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - } - - onClicked: { - if (isInstalled) { - var containerIndex = root.model.mapToSource(index) - ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - - if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) - return - } - - switch (containerIndex) { - case ContainerEnum.OpenVpn: { - OpenVpnConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolOpenVpnSettings) - break - } - case ContainerEnum.WireGuard: { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) -// WireGuardConfigModel.updateModel(config) -// goToPage(PageEnum.PageProtocolWireGuardSettings) - break - } - case ContainerEnum.Ipsec: { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) -// Ikev2ConfigModel.updateModel(config) -// goToPage(PageEnum.PageProtocolIKev2Settings) - break - } - case ContainerEnum.Sftp: { - SftpConfigModel.updateModel(config) - goToPage(PageEnum.PageServiceSftpSettings) - break - } - case ContainerEnum.TorWebSite: { - goToPage(PageEnum.PageServiceTorWebsiteSettings) - break - } - - default: { - if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageSettingsServerProtocol) + goToPage(PageEnum.PageProtocolRaw) + return } - } - } - } else { - ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) - InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + switch (containerIndex) { + case ContainerEnum.OpenVpn: { + OpenVpnConfigModel.updateModel(config) + goToPage(PageEnum.PageProtocolOpenVpnSettings) + break + } + case ContainerEnum.WireGuard: { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + // WireGuardConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolWireGuardSettings) + break + } + case ContainerEnum.Ipsec: { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + // Ikev2ConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolIKev2Settings) + break + } + case ContainerEnum.Sftp: { + SftpConfigModel.updateModel(config) + goToPage(PageEnum.PageServiceSftpSettings) + break + } + case ContainerEnum.TorWebSite: { + goToPage(PageEnum.PageServiceTorWebsiteSettings) + break + } + + default: { + if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageSettingsServerProtocol) + } + } + } + + } else { + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false } } - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } + DividerType {} } } } diff --git a/client/ui/qml/Controls2/ListViewWithLabelsType.qml b/client/ui/qml/Controls2/ListViewWithLabelsType.qml new file mode 100644 index 000000000..5b614c43c --- /dev/null +++ b/client/ui/qml/Controls2/ListViewWithLabelsType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +ListView { + id: menuContent + + property var rootWidth + + property var selectedText + + property string imageSource: "qrc:/images/controls/check.svg" + + property var clickedFunction + + property bool dividerVisible: false + + currentIndex: 0 + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + interactive: false + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + LabelWithButtonType { + Layout.fillWidth: true + + text: name + rightImageSource: imageSource + + clickedFunction: function() { + menuContent.currentIndex = index + menuContent.selectedText = name + if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") { + menuContent.clickedFunction() + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.bottomMargin: 4 + + visible: dividerVisible + } + } + + Component.onCompleted: { + if (menuContent.currentIndex === index) { + menuContent.selectedText = name + } + } + } +} diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml similarity index 85% rename from client/ui/qml/Controls2/ListViewType.qml rename to client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 926ec0381..f7f99decc 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -11,12 +11,10 @@ ListView { property var selectedText - property string imageSource + property string imageSource: "qrc:/images/controls/check.svg" property var clickedFunction - property bool dividerVisible: false - currentIndex: 0 width: rootWidth @@ -53,6 +51,12 @@ ListView { Behavior on color { PropertyAnimation { duration: 200 } } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } } RowLayout { @@ -73,8 +77,8 @@ ListView { } Image { - source: imageSource ? imageSource : "qrc:/images/controls/check.svg" - visible: imageSource ? true : radioButton.checked + source: imageSource + visible: radioButton.checked width: 24 height: 24 @@ -94,13 +98,6 @@ ListView { } } } - - DividerType { - Layout.fillWidth: true - Layout.bottomMargin: 4 - - visible: dividerVisible - } } Component.onCompleted: { diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 296fcc8c0..cb6fa142b 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -28,4 +28,14 @@ Item { root.stackView.pop() } } + + MouseArea { + z: -1 + anchors.fill: parent + + onClicked: { + console.log("base mouse area pressed") + focus = true + } + } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index c19118b52..7273b86dd 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -119,7 +119,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index a8f7bb776..fb2258c5c 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -168,7 +168,7 @@ PageType { descriptionText: qsTr("Hash") headerText: qsTr("Hash") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: hashListView rootWidth: root.width @@ -214,7 +214,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width @@ -392,7 +392,7 @@ PageType { } } } - } + } } QuestionDrawer { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 38a6be06f..7b78bc07a 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -105,7 +105,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 6783fecda..32c0c170c 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -97,6 +97,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -113,6 +114,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -129,6 +131,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -145,6 +148,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -194,7 +198,9 @@ PageType { readonly property string macosFirstLink: "macFUSE" readonly property string macosSecondLink: "SSHFS" - onLinkActivated: Qt.openUrlExternally(link) + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } textFormat: Text.RichText text: { var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") @@ -210,6 +216,8 @@ PageType { return str } + + } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 40e1b3393..080f5ee6f 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -79,6 +79,7 @@ PageType { clickedFunction: function() { content.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index a43e9e5e5..5cd0d0956 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -116,7 +116,7 @@ PageType { headerText: qsTr("Mode") - listView: ListViewType { + listView: ListViewWithRadioButtonType { rootWidth: root.width model: root.routeModesModel diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index f2ed46079..065bda12e 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -166,7 +166,7 @@ PageType { descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") headerText: qsTr("Server") - listView: ListViewType { + listView: ListViewWithLabelsType { rootWidth: root.width dividerVisible: true @@ -255,9 +255,8 @@ PageType { wrapMode: Text.WordWrap } - ListViewType { + ListViewWithRadioButtonType { rootWidth: root.width - dividerVisible: true imageSource: "qrc:/images/controls/check.svg" @@ -274,7 +273,7 @@ PageType { currentIndex: 0 - clickedFunction: function () { + clickedFunction: function() { handler() protocolSelector.visible = false @@ -338,11 +337,10 @@ PageType { descriptionText: qsTr("Connection format") headerText: qsTr("Connection format") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: exportTypeListView rootWidth: root.width - dividerVisible: true imageSource: "qrc:/images/controls/chevron-right.svg" From a40f365a543d0545ab31217fe8fef44b223dea20 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 23:48:25 +0500 Subject: [PATCH 063/131] "added display of busy server package manager on the PageSetupWizardInstalling" --- client/ui/controllers/installController.cpp | 3 +++ client/ui/controllers/installController.h | 2 ++ client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 23 ++++++++++++++++--- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 7e71aef8e..06a5c5543 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -85,6 +85,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr void InstallController::installServer(DockerContainer container, QJsonObject &config) { ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); QMap installedContainers; ErrorCode errorCode = @@ -138,6 +139,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); @@ -239,6 +241,7 @@ void InstallController::updateContainer(QJsonObject config) qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index b74835d9f..63d4c714d 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -60,6 +60,8 @@ signals: void passphraseRequestStarted(); void passphraseRequestFinished(); + void serverIsBusy(const bool isBusy); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index eff580556..243b12054 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -75,7 +75,7 @@ PageType { repeat: true running: true onTriggered: { - progressBar.value += 0.001 + progressBar.value += 0.003 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 10daa0bec..20c589ee8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -17,6 +17,9 @@ PageType { Component.onCompleted: PageController.enableTabBar(false) Component.onDestruction: PageController.enableTabBar(true) + property bool isTimerRunning: true + property string progressBarText: qsTr("Usually it takes no more than 5 minutes") + Connections { target: InstallController @@ -57,6 +60,18 @@ PageType { PageController.showErrorMessage(qsTr("The server has already been added to the application")) } + + function onServerIsBusy(isBusy) { + if (isBusy) { + root.progressBarText = qsTr("Amnesia has detected that your server is currently ") + + qsTr("busy installing other software. Amnesia installation ") + + qsTr("will pause until the server finishes installing other software") + root.isTimerRunning = false + } else { + root.progressBarText = qsTr("Usually it takes no more than 5 minutes") + root.isTimerRunning = true + } + } } SortFilterProxyModel { @@ -122,18 +137,20 @@ PageType { interval: 300 repeat: true - running: true + running: root.isTimerRunning onTriggered: { - progressBar.value += 0.001 + progressBar.value += 0.003 } } } ParagraphTextType { + id: progressText + Layout.fillWidth: true Layout.topMargin: 8 - text: qsTr("Usually it takes no more than 5 minutes") + text: root.progressBarText } } } From ddaa5b784d89946f3b3b9523920c579c0220cd68 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 18 Aug 2023 14:14:45 +0500 Subject: [PATCH 064/131] minor ui fixes --- .../ui/qml/Controls2/LabelWithButtonType.qml | 27 +++++++++++++------ .../ui/qml/Controls2/VerticalRadioButton.qml | 1 + client/ui/qml/Pages2/PageSettingsAbout.qml | 8 +++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 3aca1d57b..82aef55a9 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,7 +17,9 @@ Item { property string textColor: "#d7d8db" property string descriptionColor: "#878B91" - property string rightImageColor: "#878B91" + property real textOpacity: 1.0 + + property string rightImageColor: "#d7d8db" property bool descriptionOnTop: false @@ -67,6 +69,8 @@ Item { text: root.text color: root.descriptionOnTop ? root.descriptionColor : root.textColor + opacity: root.textOpacity + Layout.fillWidth: true lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight @@ -75,14 +79,20 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } } CaptionTextType { id: description - color: root.descriptionOnTop ? root.textColor : root.descriptionColor text: root.descriptionText + color: root.descriptionOnTop ? root.textColor : root.descriptionColor + + opacity: root.textOpacity visible: root.descriptionText !== "" @@ -94,6 +104,10 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } } } @@ -141,9 +155,8 @@ Item { rightImageBackground.color = rightImage.hoveredColor } else if (leftImageSource) { leftImageBackground.color = rightImage.hoveredColor - } else { - background.color = rightImage.hoveredColor } + root.textOpacity = 0.8 } onExited: { @@ -151,9 +164,8 @@ Item { rightImageBackground.color = rightImage.defaultColor } else if (leftImageSource) { leftImageBackground.color = rightImage.defaultColor - } else { - background.color = rightImage.defaultColor } + root.textOpacity = 1 } onPressedChanged: { @@ -161,9 +173,8 @@ Item { rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } else if (leftImageSource) { leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } else { - background.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } + root.textOpacity = 0.7 } onClicked: { diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index f44fa751a..8229c959f 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -67,6 +67,7 @@ RadioButton { width: 24 height: 24 } + Image { source: { if (showImage) { diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 1875f085e..9fc23c15d 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -81,7 +81,8 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Card on Patreon") - onClicked: { + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) } } @@ -121,7 +122,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - Qt.openUrlExternally("https://t.me/amnezia_vpn_dev") + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_dev")) } } @@ -147,7 +148,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/github.svg" clickedFunction: function() { - Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) } } @@ -160,6 +161,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { + Qt.openUrlExternally(qsTr("amnezia.org/")) } } From 0060f57b634236aff626f708cb239ba82a97b6ab Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 19 Aug 2023 13:12:54 +0500 Subject: [PATCH 065/131] fixed selection of connection type on PageShare when changing protocol --- client/ui/qml/Pages2/PageShare.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 065bda12e..28f5ca066 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -338,14 +338,18 @@ PageType { headerText: qsTr("Connection format") listView: ListViewWithRadioButtonType { - id: exportTypeListView + onCurrentIndexChanged: { + console.log(currentIndex) + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.text = selectedText + } rootWidth: root.width - imageSource: "qrc:/images/controls/chevron-right.svg" + imageSource: "qrc:/images/controls/check.svg" model: root.connectionTypesModel - currentIndex: exportTypeSelector.currentIndex + currentIndex: 0 clickedFunction: function() { exportTypeSelector.text = selectedText From b5e1c78461a9cf52e8743a17e7e675a35a3831f7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 20 Aug 2023 13:36:54 +0500 Subject: [PATCH 066/131] minor ui fixes --- client/containers/containers_defs.cpp | 29 ++++- client/containers/containers_defs.h | 105 +++++++++--------- client/ui/controllers/installController.cpp | 15 +++ client/ui/controllers/installController.h | 2 + client/ui/models/containers_model.cpp | 8 +- client/ui/models/containers_model.h | 3 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 2 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 26 ++++- .../PageSetupWizardProtocolSettings.qml | 4 +- 11 files changed, 132 insertions(+), 66 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 27f6f1dd4..fc4570f43 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -81,7 +81,7 @@ QMap ContainerProps::containerHumanNames() { return { { DockerContainer::None, "Not installed" }, { DockerContainer::OpenVpn, "OpenVPN" }, - { DockerContainer::ShadowSocks, "OpenVPN over ShadowSocks" }, + { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, @@ -93,6 +93,33 @@ QMap ContainerProps::containerHumanNames() } QMap ContainerProps::containerDescriptions() +{ + return { { DockerContainer::OpenVpn, + QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " + "own security protocol with SSL/TLS for key exchange.") }, + { DockerContainer::ShadowSocks, + QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is " + "recognised by analysis systems in some highly censored regions.") }, + { DockerContainer::Cloak, + QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " + "active-probbing detection. Ideal for bypassing blocking in regions with the highest levels " + "of censorship.") }, + { DockerContainer::WireGuard, + QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " + "consumption. Recommended for regions with low levels of censorship.") }, + { DockerContainer::Ipsec, + QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " + "signal loss. It has native support on the latest versions of Android and iOS.") }, + + { DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") }, + { DockerContainer::Dns, + QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { DockerContainer::Sftp, + QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; +} + +QMap ContainerProps::containerDetailedDescriptions() { return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index d4a35d265..a84d997cf 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -8,72 +8,69 @@ using namespace amnezia; -namespace amnezia { - -namespace ContainerEnumNS { -Q_NAMESPACE -enum DockerContainer { - None = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ipsec, - - //non-vpn - TorWebSite, - Dns, - //FileShare, - Sftp -}; -Q_ENUM_NS(DockerContainer) -} // namespace ContainerEnumNS - -using namespace ContainerEnumNS; -using namespace ProtocolEnumNS; - -class ContainerProps : public QObject +namespace amnezia { - Q_OBJECT -public: - Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); - Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); - Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); + namespace ContainerEnumNS + { + Q_NAMESPACE + enum DockerContainer { + None = 0, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Ipsec, - Q_INVOKABLE static QList allContainers(); + // non-vpn + TorWebSite, + Dns, + // FileShare, + Sftp + }; + Q_ENUM_NS(DockerContainer) + } // namespace ContainerEnumNS - Q_INVOKABLE static QMap containerHumanNames(); - Q_INVOKABLE static QMap containerDescriptions(); + using namespace ContainerEnumNS; + using namespace ProtocolEnumNS; - // these protocols will be displayed in container settings - Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + class ContainerProps : public QObject + { + Q_OBJECT - Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); + public: + Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); + Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); + Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); - // binding between Docker container and main protocol of given container - // it may be changed fot future containers :) - Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); + Q_INVOKABLE static QList allContainers(); - Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); - Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + Q_INVOKABLE static QMap containerHumanNames(); + Q_INVOKABLE static QMap containerDescriptions(); + Q_INVOKABLE static QMap containerDetailedDescriptions(); - static bool isEasySetupContainer(amnezia::DockerContainer container); - static QString easySetupHeader(amnezia::DockerContainer container); - static QString easySetupDescription(amnezia::DockerContainer container); -}; + // these protocols will be displayed in container settings + Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); + // binding between Docker container and main protocol of given container + // it may be changed fot future containers :) + Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); -static void declareQmlContainerEnum() { - qmlRegisterUncreatableMetaObject( - ContainerEnumNS::staticMetaObject, - "ContainerEnum", - 1, 0, - "ContainerEnum", - "Error: only enums" - ); -} + Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); + Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + + static bool isEasySetupContainer(amnezia::DockerContainer container); + static QString easySetupHeader(amnezia::DockerContainer container); + static QString easySetupDescription(amnezia::DockerContainer container); + }; + + static void declareQmlContainerEnum() + { + qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum", + "Error: only enums"); + } } // namespace amnezia diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 06a5c5543..18588aefd 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -446,3 +446,18 @@ void InstallController::setEncryptedPassphrase(QString passphrase) m_privateKeyPassphrase = passphrase; emit passphraseRequestFinished(); } + +void InstallController::addEmptyServer() +{ + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + m_serversModel->addServer(server); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + emit installServerFinished(tr("Server added successfully")); +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 63d4c714d..4060c97c4 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -41,6 +41,8 @@ public slots: void setEncryptedPassphrase(QString passphrase); + void addEmptyServer(); + signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index dd1d8d4e3..0a07895c8 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -24,7 +24,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case NameRole: // return ContainerProps::containerHumanNames().value(container); - case DescRole: + case DescriptionRole: // return ContainerProps::containerDescriptions().value(container); case ConfigRole: { m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); @@ -62,7 +62,8 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const switch (role) { case NameRole: return ContainerProps::containerHumanNames().value(container); - case DescRole: return ContainerProps::containerDescriptions().value(container); + case DescriptionRole: return ContainerProps::containerDescriptions().value(container); + case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container); case ConfigRole: { if (container == DockerContainer::None) { return QJsonObject(); @@ -220,7 +221,8 @@ QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; - roles[DescRole] = "description"; + roles[DescriptionRole] = "description"; + roles[DetailedDescriptionRole] = "detailedDescription"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; roles[ConfigRole] = "config"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 8978315cf..cde775260 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -17,7 +17,8 @@ public: enum Roles { NameRole = Qt::UserRole + 1, - DescRole, + DescriptionRole, + DetailedDescriptionRole, ServiceTypeRole, ConfigRole, DockerContainerRole, diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 080f5ee6f..384903752 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -138,7 +138,7 @@ PageType { text: qsTr("Remove website") onClicked: { - questionDrawer.headerText = qsTr("Some description") + questionDrawer.headerText = qsTr("The site with all data will be removed from the tor network.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 9fc23c15d..67dada439 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -81,7 +81,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Card on Patreon") - clickedFunction: function() { + onClicked: function() { Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 5cd0d0956..71f2ba297 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -43,7 +43,7 @@ PageType { QtObject { id: onlyForwardSites - property string name: qsTr("Addresses from the list should always open via VPN") + property string name: qsTr("Only the addresses in the list must be opened via VPN") property int type: routeMode.onlyForwardSites } QtObject { diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index ac1c3a445..f2600f65d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -126,6 +126,8 @@ PageType { } } + DividerType {} + CardType { implicitWidth: parent.width @@ -143,14 +145,14 @@ PageType { id: continueButton implicitWidth: parent.width - anchors.bottomMargin: 24 + anchors.topMargin: 24 text: qsTr("Continue") onClicked: function() { if (root.isEasySetup) { ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) @@ -159,6 +161,26 @@ PageType { } } } + + BasicButtonType { + implicitWidth: parent.width + anchors.topMargin: 8 + anchors.bottomMargin: 24 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Set up later") + + onClicked: function() { + goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.addEmptyServer() + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index f5f076092..2f5ed5699 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -73,7 +73,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Installing ") + name - descriptionText: qsTr("protocol description") + descriptionText: description } BasicButtonType { @@ -159,7 +159,7 @@ PageType { font.weight: Font.Medium font.family: "PT Root UI VF" - text: qsTr("detailed protocol description") + text: detailedDescription wrapMode: Text.WordWrap From 420c616e9d875890449f07731cc7d8e007c6374c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 20 Aug 2023 13:43:27 +0500 Subject: [PATCH 067/131] added authResultReceiver to android.cmake --- client/cmake/android.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index dd37e0a61..9440ad10b 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -10,6 +10,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ) @@ -18,6 +19,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ) From f7926847ac9139bb23bf77a185c73d99cf1764c4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 22 Aug 2023 14:37:29 +0500 Subject: [PATCH 068/131] minor ui fixes --- client/containers/containers_defs.cpp | 10 ++++++++ client/containers/containers_defs.h | 2 ++ client/ui/controllers/exportController.cpp | 25 +++---------------- client/ui/controllers/exportController.h | 2 +- client/ui/models/containers_model.cpp | 2 ++ client/ui/models/containers_model.h | 3 ++- .../qml/Components/ShareConnectionDrawer.qml | 12 ++++++++- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 1 - .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 4 --- client/ui/qml/Pages2/PageShare.qml | 21 +++++++++++++--- 11 files changed, 50 insertions(+), 34 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index fc4570f43..98b2d0da8 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -249,3 +249,13 @@ QString ContainerProps::easySetupDescription(DockerContainer container) default: return ""; } } + +bool ContainerProps::isShareable(DockerContainer container) +{ + switch (container) { + case DockerContainer::TorWebSite: return false; + case DockerContainer::Dns: return false; + case DockerContainer::Sftp: return false; + default: return true; + } +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index a84d997cf..247824071 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -64,6 +64,8 @@ namespace amnezia static bool isEasySetupContainer(amnezia::DockerContainer container); static QString easySetupHeader(amnezia::DockerContainer container); static QString easySetupDescription(amnezia::DockerContainer container); + + static bool isShareable(amnezia::DockerContainer container); }; static void declareQmlContainerEnum() diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 8fbd69f1e..abe9fda24 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -12,6 +12,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" +#include "utilities.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" @@ -201,7 +202,7 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile() +void ExportController::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName) { #if defined Q_OS_IOS // ext.replace("*", ""); @@ -229,27 +230,7 @@ void ExportController::saveFile() return; #endif - QString fileExtension = ".vpn"; - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileName; - fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"), - QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension); - if (fileName.isEmpty()) - return; - if (!fileName.toString().endsWith(fileExtension)) { - fileName = QUrl(fileName.toString() + fileExtension); - } - if (fileName.isEmpty()) - return; - - QFile save(fileName.toLocalFile()); - - save.open(QIODevice::WriteOnly); - save.write(m_config.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); + Utils::saveFile(fileExtension, caption, fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index ce952096b..913dfc3a1 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(); + void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName); signals: void generateConfig(int type); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 0a07895c8..67847727d 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -79,6 +79,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); + case IsShareableRole: return ContainerProps::isShareable(container); } return QVariant(); @@ -235,5 +236,6 @@ QHash ContainersModel::roleNames() const roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsDefaultRole] = "isDefault"; roles[IsSupportedRole] = "isSupported"; + roles[IsShareableRole] = "isShareable"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index cde775260..547eea831 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -30,7 +30,8 @@ public: IsInstalledRole, IsCurrentlyProcessedRole, IsDefaultRole, - IsSupportedRole + IsSupportedRole, + IsShareableRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 7ad724ea2..368d503ab 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -19,9 +19,19 @@ DrawerType { property alias configContentHeaderText: configContentHeader.headerText property alias contentVisible: content.visible + property string configExtension: ".vpn" + property string configCaption: qsTr("Save AmneziaVPN config") + property string configFileName: "amnezia_config.vpn" + width: parent.width height: parent.height * 0.9 + onClosed: { + configExtension = ".vpn" + configCaption = qsTr("Save AmneziaVPN config") + configFileName = "amnezia_config.vpn" + } + Item { anchors.fill: parent @@ -58,7 +68,7 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { - ExportController.saveFile() + ExportController.saveFile(configExtension, configCaption, configFileName) } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 67dada439..1a6f7e806 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -161,7 +161,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - Qt.openUrlExternally(qsTr("amnezia.org/")) + Qt.openUrlExternally(qsTr("https://amnezia.org")) } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 6465e40ef..0765f7d29 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -67,7 +67,6 @@ PageType { Layout.rightMargin: 16 text: qsTr("Use AmneziaDNS if installed on the server") - descriptionText: qsTr("Internal IP address 172.29.172.254") checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml index 695175fde..1fa71592f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -73,10 +73,6 @@ PageType { } Component.onCompleted: { - console.log(qrCodeRectange.x) - console.log(qrCodeRectange.y) - console.log(qrCodeRectange.width) - qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x, qrCodeRectange.y, qrCodeRectange.width, diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 28f5ca066..120e04fc1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -41,8 +41,20 @@ PageType { } break; } - case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; - case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; + case PageShare.ConfigType.OpenVpn: { + ExportController.generateOpenVpnConfig(); + shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config") + shareConnectionDrawer.configExtension = ".ovpn" + shareConnectionDrawer.configFileName = "amnezia_for_openvpn" + break; + } + case PageShare.ConfigType.WireGuard: { + ExportController.generateWireGuardConfig(); + shareConnectionDrawer.configCaption = qsTr("Save WireGuard config") + shareConnectionDrawer.configExtension = ".conf" + shareConnectionDrawer.configFileName = "amnezia_for_wireguard" + break; + } } PageController.showBusyIndicator(false) @@ -267,6 +279,10 @@ PageType { ValueFilter { roleName: "isInstalled" value: true + }, + ValueFilter { + roleName: "isShareable" + value: true } ] } @@ -339,7 +355,6 @@ PageType { listView: ListViewWithRadioButtonType { onCurrentIndexChanged: { - console.log(currentIndex) exportTypeSelector.currentIndex = currentIndex exportTypeSelector.text = selectedText } From 23ad006187b1bf6f3e073f6ae0e3f1f022f9af0c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 23 Aug 2023 00:20:59 +0500 Subject: [PATCH 069/131] removed Widgets from service part --- client/amnezia_application.cpp | 10 -- client/fileUtilites.cpp | 47 ++++++ client/fileUtilites.h | 19 +++ client/ui/controllers/exportController.cpp | 5 +- client/ui/controllers/importController.cpp | 8 +- client/ui/controllers/installController.cpp | 1 + client/ui/controllers/settingsController.cpp | 10 +- client/ui/controllers/sitesController.cpp | 9 +- client/utilities.cpp | 154 +++++++------------ client/utilities.h | 73 +++++---- service/server/CMakeLists.txt | 4 +- 11 files changed, 178 insertions(+), 162 deletions(-) create mode 100644 client/fileUtilites.cpp create mode 100644 client/fileUtilites.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d6d346abf..ba4499a1a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -10,7 +10,6 @@ #include #include -#include "core/servercontroller.h" #include "logger.h" #include "version.h" @@ -20,7 +19,6 @@ #endif #include "protocols/qml_register_protocols.h" -#include "ui/pages.h" #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" @@ -81,8 +79,6 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); - // - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_vpnConnection->moveToThread(&m_vpnConnectionThread); @@ -125,14 +121,8 @@ void AmneziaApplication::init() connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), &ConnectionController::closeConnection); - // - m_engine->load(url); - // if (m_engine->rootObjects().size() > 0) { - // m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); - // } - if (m_settings->isSaveLogs()) { if (!Logger::init()) { qWarning() << "Initialization of debug subsystem failed"; diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp new file mode 100644 index 000000000..87fa7aed9 --- /dev/null +++ b/client/fileUtilites.cpp @@ -0,0 +1,47 @@ +#include "fileUtilites.h" + +#include +#include + +void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, + const QString &data) +{ + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), + "*" + fileExtension); + if (fileUrl.isEmpty()) + return; + if (!fileUrl.toString().endsWith(fileExtension)) { + fileUrl = QUrl(fileUrl.toString() + fileExtension); + } + if (fileUrl.isEmpty()) + return; + + QFile save(fileUrl.toLocalFile()); + + // todo check if save successful + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +} + +QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) +{ + QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep { "raw%3A%2F" }; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} diff --git a/client/fileUtilites.h b/client/fileUtilites.h new file mode 100644 index 000000000..8cf4807c5 --- /dev/null +++ b/client/fileUtilites.h @@ -0,0 +1,19 @@ +#ifndef FILEUTILITES_H +#define FILEUTILITES_H + +#include + +class FileUtilites : public QObject +{ + Q_OBJECT + +public: + static void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, + const QString &data); + + static QString getFileName(QWidget *parent = nullptr, const QString &caption = QString(), + const QString &dir = QString(), const QString &filter = QString(), + QString *selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); +}; + +#endif // FILEUTILITES_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index abe9fda24..4cee1dc91 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -12,7 +11,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" -#include "utilities.h" +#include "fileUtilites.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" @@ -230,7 +229,7 @@ void ExportController::saveFile(const QString &fileExtension, const QString &cap return; #endif - Utils::saveFile(fileExtension, caption, fileName, m_config); + FileUtilites::saveFile(fileExtension, caption, fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 4ff972cce..b5d10abf4 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -10,7 +10,7 @@ #include "../../platforms/android/androidutils.h" #include #endif -#include "utilities.h" +#include "fileUtilites.h" namespace { @@ -84,9 +84,9 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile() { - QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); + QString fileName = FileUtilites::getFileName(Q_NULLPTR, tr("Open config file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 18588aefd..7d6ba5904 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -7,6 +7,7 @@ #include "core/errorstrings.h" #include "core/servercontroller.h" +#include "fileUtilites.h" #include "utilities.h" namespace diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531de14d7..b56e48ad2 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,8 +2,8 @@ #include +#include "fileUtilites.h" #include "logger.h" -#include "utilities.h" #include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, @@ -69,7 +69,7 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile() { - Utils::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); + FileUtilites::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); } void SettingsController::clearLogs() @@ -80,14 +80,14 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig() { - Utils::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); + FileUtilites::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig() { QString fileName = - Utils::getFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); + FileUtilites::getFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) { return; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index d8bc99c81..d19f57581 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -3,7 +3,7 @@ #include #include -#include "utilities.h" +#include "fileUtilites.h" SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &vpnConnection, @@ -80,8 +80,9 @@ void SitesController::removeSite(int index) void SitesController::importSites(bool replaceExisting) { - QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open sites file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); + QString fileName = + FileUtilites::getFileName(Q_NULLPTR, tr("Open sites file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); if (fileName.isEmpty()) { return; @@ -149,7 +150,7 @@ void SitesController::exportSites() QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - Utils::saveFile(".json", tr("Export sites file"), "sites", jsonData); + FileUtilites::saveFile(".json", tr("Export sites file"), "sites", jsonData); emit finished(tr("Export completed")); } diff --git a/client/utilities.cpp b/client/utilities.cpp index f984e9a8a..158bce930 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -10,15 +9,15 @@ #include #include -#include "version.h" #include "utilities.h" +#include "version.h" QString Utils::getRandomString(int len) { const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString randomString; - for(int i=0; igenerate() % possibleCharacters.length(); QChar nextChar = possibleCharacters.at(index); randomString.append(nextChar); @@ -31,7 +30,7 @@ QString Utils::systemLogPath() #ifdef Q_OS_WIN QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QString primaryLocation = "ProgramData"; - foreach (const QString& location, locationList) { + foreach (const QString &location, locationList) { if (location.contains(primaryLocation)) { return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME); } @@ -42,7 +41,7 @@ QString Utils::systemLogPath() #endif } -bool Utils::initializePath(const QString& path) +bool Utils::initializePath(const QString &path) { QDir dir; if (!dir.mkpath(path)) { @@ -52,13 +51,13 @@ bool Utils::initializePath(const QString& path) return true; } -bool Utils::createEmptyFile(const QString& path) +bool Utils::createEmptyFile(const QString &path) { QFile f(path); return f.open(QIODevice::WriteOnly | QIODevice::Truncate); } -QString Utils::executable(const QString& baseName, bool absPath) +QString Utils::executable(const QString &baseName, bool absPath) { QString ext; #ifdef Q_OS_WIN @@ -71,7 +70,7 @@ QString Utils::executable(const QString& baseName, bool absPath) return QCoreApplication::applicationDirPath() + "/" + fileName; } -QString Utils::usrExecutable(const QString& baseName) +QString Utils::usrExecutable(const QString &baseName) { if (QFileInfo::exists("/usr/sbin/" + baseName)) return ("/usr/sbin/" + baseName); @@ -79,18 +78,22 @@ QString Utils::usrExecutable(const QString& baseName) return ("/usr/bin/" + baseName); } -bool Utils::processIsRunning(const QString& fileName) +bool Utils::processIsRunning(const QString &fileName) { #ifdef Q_OS_WIN QProcess process; process.setReadChannel(QProcess::StandardOutput); process.setProcessChannelMode(QProcess::MergedChannels); - process.start("wmic.exe", QStringList() << "/OUTPUT:STDOUT" << "PROCESS" << "get" << "Caption"); + process.start("wmic.exe", + QStringList() << "/OUTPUT:STDOUT" + << "PROCESS" + << "get" + << "Caption"); process.waitForStarted(); process.waitForFinished(); QString processData(process.readAll()); - QStringList processList = processData.split(QRegularExpression("[\r\n]"),Qt::SkipEmptyParts); - foreach (const QString& rawLine, processList) { + QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); + foreach (const QString &rawLine, processList) { const QString line = rawLine.simplified(); if (line.isEmpty()) { continue; @@ -99,7 +102,6 @@ bool Utils::processIsRunning(const QString& fileName) if (line == fileName) { return true; } - } return false; #elif defined(Q_OS_IOS) @@ -107,7 +109,7 @@ bool Utils::processIsRunning(const QString& fileName) #else QProcess process; process.setProcessChannelMode(QProcess::MergedChannels); - process.start("pgrep", QStringList({fileName})); + process.start("pgrep", QStringList({ fileName })); process.waitForFinished(); if (process.exitStatus() == QProcess::NormalExit) { return (process.readAll().toUInt() > 0); @@ -116,7 +118,7 @@ bool Utils::processIsRunning(const QString& fileName) #endif } -QString Utils::getIPAddress(const QString& host) +QString Utils::getIPAddress(const QString &host) { if (ipAddressRegExp().match(host).hasMatch()) { return host; @@ -130,42 +132,48 @@ QString Utils::getIPAddress(const QString& host) return ""; } -QString Utils::getStringBetween(const QString& s, const QString& a, const QString& b) +QString Utils::getStringBetween(const QString &s, const QString &a, const QString &b) { int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length()); - if(ap < 0 || bp < 0) + if (ap < 0 || bp < 0) return QString(); ap += a.length(); - if(bp - ap <= 0) + if (bp - ap <= 0) return QString(); return s.mid(ap, bp - ap).trimmed(); } -bool Utils::checkIPv4Format(const QString& ip) +bool Utils::checkIPv4Format(const QString &ip) { - if (ip.isEmpty()) return false; + if (ip.isEmpty()) + return false; int count = ip.count("."); - if(count != 3) return false; + if (count != 3) + return false; QHostAddress addr(ip); - return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); + return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); } bool Utils::checkIpSubnetFormat(const QString &ip) { - if (!ip.contains("/")) return checkIPv4Format(ip); + if (!ip.contains("/")) + return checkIPv4Format(ip); QStringList parts = ip.split("/"); - if (parts.size() != 2) return false; + if (parts.size() != 2) + return false; bool ok; int subnet = parts.at(1).toInt(&ok); - if (subnet >= 0 && subnet <= 32 && ok) return checkIPv4Format(parts.at(0)); - else return false; + if (subnet >= 0 && subnet <= 32 && ok) + return checkIPv4Format(parts.at(0)); + else + return false; } void Utils::killProcessByName(const QString &name) -{ +{ qDebug().noquote() << "Kill process" << name; #ifdef Q_OS_WIN QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F"); @@ -178,40 +186,39 @@ void Utils::killProcessByName(const QString &name) QString Utils::netMaskFromIpWithSubnet(const QString ip) { - if (!ip.contains("/")) return "255.255.255.255"; + if (!ip.contains("/")) + return "255.255.255.255"; bool ok; int prefix = ip.split("/").at(1).toInt(&ok); - if (!ok) return "255.255.255.255"; + if (!ok) + return "255.255.255.255"; unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF; - return QString("%1.%2.%3.%4") - .arg(mask >> 24) - .arg((mask >> 16) & 0xFF) - .arg((mask >> 8) & 0xFF) - .arg( mask & 0xFF); + return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF); } QString Utils::ipAddressFromIpWithSubnet(const QString ip) { - if (ip.count(".") != 3) return ""; + if (ip.count(".") != 3) + return ""; return ip.split("/").first(); } QStringList Utils::summarizeRoutes(const QStringList &ips, const QString cidr) { -// QMap -// QHostAddress + // QMap + // QHostAddress -// QMap subnets; // <"a.b", > + // QMap subnets; // <"a.b", > -// for (const QString &ip : ips) { -// if (ip.count(".") != 3) continue; + // for (const QString &ip : ips) { + // if (ip.count(".") != 3) continue; -// const QStringList &parts = ip.split("."); -// subnets[parts.at(0) + "." + parts.at(1)].append(ip); -// } + // const QStringList &parts = ip.split("."); + // subnets[parts.at(0) + "." + parts.at(1)].append(ip); + // } return QStringList(); } @@ -252,58 +259,6 @@ QString Utils::certUtilPath() #endif } -void Utils::saveFile(const QString &fileExtension, - const QString &caption, - const QString &fileName, - const QString &data) -{ - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, - caption, - QUrl::fromLocalFile(docDir + "/" + fileName), - "*" + fileExtension); - if (fileUrl.isEmpty()) - return; - if (!fileUrl.toString().endsWith(fileExtension)) { - fileUrl = QUrl(fileUrl.toString() + fileExtension); - } - if (fileUrl.isEmpty()) - return; - - QFile save(fileUrl.toLocalFile()); - - //todo check if save successful - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QFileInfo fi(fileUrl.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -QString Utils::getFileName(QWidget *parent, - const QString &caption, - const QString &dir, - const QString &filter, - QString *selectedFilter, - QFileDialog::Options options) -{ - QString fileName - = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep{"raw%3A%2F"}; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } -#endif - return fileName; -} - #ifdef Q_OS_WIN // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 @@ -315,8 +270,7 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) bool consoleDetached = (FreeConsole() != FALSE); - if (AttachConsole(dwProcessId) != FALSE) - { + if (AttachConsole(dwProcessId) != FALSE) { // Add a fake Ctrl-C handler for avoid instant kill is this console // WARNING: do not revert it or current program will be also killed SetConsoleCtrlHandler(nullptr, true); @@ -324,11 +278,9 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) FreeConsole(); } - if (consoleDetached) - { + if (consoleDetached) { // Create a new console if previous was deleted by OS - if (AttachConsole(thisConsoleId) == FALSE) - { + if (AttachConsole(thisConsoleId) == FALSE) { int errorCode = GetLastError(); if (errorCode == 31) // 31=ERROR_GEN_FAILURE { diff --git a/client/utilities.h b/client/utilities.h index 191fa5c18..7ef0cd3f5 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -1,45 +1,64 @@ #ifndef UTILITIES_H #define UTILITIES_H -#include #include #include #include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif -class Utils : public QObject { +class Utils : public QObject +{ Q_OBJECT public: static QString getRandomString(int len); - static QString executable(const QString& baseName, bool absPath); - static QString usrExecutable(const QString& baseName); + static QString executable(const QString &baseName, bool absPath); + static QString usrExecutable(const QString &baseName); static QString systemLogPath(); - static bool createEmptyFile(const QString& path); - static bool initializePath(const QString& path); + static bool createEmptyFile(const QString &path); + static bool initializePath(const QString &path); - static QString getIPAddress(const QString& host); - static QString getStringBetween(const QString& s, const QString& a, const QString& b); - static bool checkIPv4Format(const QString& ip); - static bool checkIpSubnetFormat(const QString& ip); - static QRegularExpression ipAddressRegExp() { return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } - static QRegularExpression ipAddressPortRegExp() { return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); } + static QString getIPAddress(const QString &host); + static QString getStringBetween(const QString &s, const QString &a, const QString &b); + static bool checkIPv4Format(const QString &ip); + static bool checkIpSubnetFormat(const QString &ip); + static QRegularExpression ipAddressRegExp() + { + return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); + } + static QRegularExpression ipAddressPortRegExp() + { + return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); + } - static QRegExp ipAddressWithSubnetRegExp() { return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); } + static QRegExp ipAddressWithSubnetRegExp() + { + return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); + } - static QRegExp ipNetwork24RegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "0$"); } + static QRegExp ipNetwork24RegExp() + { + return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "0$"); + } - static QRegExp ipPortRegExp() { return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); } + static QRegExp ipPortRegExp() + { + return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); + } - static QRegExp domainRegExp() { return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-9\\-]{1,30})\\.[a-z]{2,}"); } - static bool processIsRunning(const QString& fileName); + static QRegExp domainRegExp() + { + return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-" + "9\\-]{1,30})\\.[a-z]{2,}"); + } + static bool processIsRunning(const QString &fileName); static void killProcessByName(const QString &name); static QString netMaskFromIpWithSubnet(const QString ip); @@ -51,18 +70,6 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); - static void saveFile(const QString &fileExtension, - const QString &caption, - const QString &fileName, - const QString &data); - - static QString getFileName(QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &dir = QString(), - const QString &filter = QString(), - QString *selectedFilter = nullptr, - QFileDialog::Options options = QFileDialog::Options()); - #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 9046f687b..5d4217638 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat Widgets) +find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat) qt_standard_project_setup() configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) @@ -188,7 +188,7 @@ include_directories( ) add_executable(${PROJECT} ${SOURCES} ${HEADERS}) -target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::Widgets ${LIBS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) target_compile_definitions(${PROJECT} PRIVATE "MZ_$") if(CMAKE_BUILD_TYPE STREQUAL "Debug") From 4c79905f5bd56f21b0e9778f5599e9a8504cd56f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 14:53:52 +0500 Subject: [PATCH 070/131] added autostart and start minimized options - added disabling split tunneling when selecting the wireguard protocol - if for macos the application is minimized to tray, then now it is not displayed in the dock --- client/amnezia_application.cpp | 26 +++++-- client/ui/controllers/pageController.cpp | 24 +++++- client/ui/controllers/pageController.h | 7 +- client/ui/controllers/settingsController.cpp | 21 ++++++ client/ui/controllers/settingsController.h | 6 ++ client/ui/macos_util.mm | 74 +++++++++++++------ .../ui/qml/Pages2/PageSettingsApplication.qml | 43 ++++++++++- .../ui/qml/Pages2/PageSettingsConnection.qml | 16 ++-- 8 files changed, 173 insertions(+), 44 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index ba4499a1a..9ecc5df44 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -129,14 +129,16 @@ void AmneziaApplication::init() } } - // #ifdef Q_OS_WIN - // if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); - // else emit m_uiLogic->show(); - // #else - // m_uiLogic->showOnStartup(); - // #endif +#ifdef Q_OS_WIN + if (m_parser.isSet("a")) + m_pageController->showOnStartup(); + else + emit m_pageController->raiseMainWindow(); +#else + m_pageController->showOnStartup(); +#endif - // TODO - fix + // TODO - fix #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (isPrimary()) { QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { @@ -276,6 +278,14 @@ void AmneziaApplication::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { + if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + && m_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) { + m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); + emit m_pageController->showNotificationMessage( + tr("Split tunneling for WireGuard is not implemented, the option was disabled")); + } + }); m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); @@ -306,7 +316,7 @@ void AmneziaApplication::initControllers() m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - m_pageController.reset(new PageController(m_serversModel)); + m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 36d5ba7c9..d0c219c1d 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -5,9 +5,13 @@ #include "../../platforms/android/androidutils.h" #include #endif +#if defined Q_OS_MAC + #include "ui/macos_util.h" +#endif -PageController::PageController(const QSharedPointer &serversModel, QObject *parent) - : QObject(parent), m_serversModel(serversModel) +PageController::PageController(const QSharedPointer &serversModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) { #ifdef Q_OS_ANDROID // Change color of navigation and status bar's @@ -23,6 +27,9 @@ PageController::PageController(const QSharedPointer &serversModel, } }); #endif + + connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); + connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); } QString PageController::getInitialPage() @@ -88,3 +95,16 @@ void PageController::updateNavigationBarColor(const int color) }); #endif } + +void PageController::showOnStartup() +{ + if (!m_settings->isStartMinimized()) { + emit raiseMainWindow(); + } else { +#ifdef Q_OS_WIN + emit hideMainWindow(); +#elif defined Q_OS_MACX + setDockIconVisible(false); +#endif + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1948ed11b..1dd16090b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -63,7 +63,8 @@ class PageController : public QObject { Q_OBJECT public: - explicit PageController(const QSharedPointer &serversModel, QObject *parent = nullptr); + explicit PageController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); public slots: QString getInitialPage(); @@ -75,6 +76,8 @@ public slots: unsigned int getInitialPageNavigationBarColor(); void updateNavigationBarColor(const int color); + void showOnStartup(); + signals: void goToPageHome(); void goToPageSettings(); @@ -100,6 +103,8 @@ signals: private: QSharedPointer m_serversModel; + + std::shared_ptr m_settings; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b56e48ad2..f09ebc218 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -4,6 +4,7 @@ #include "fileUtilites.h" #include "logger.h" +#include "ui/qautostart.h" #include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, @@ -137,3 +138,23 @@ void SettingsController::toggleAutoConnect(bool enable) { m_settings->setAutoConnect(enable); } + +bool SettingsController::isAutoStartEnabled() +{ + return Autostart::isAutostart(); +} + +void SettingsController::toggleAutoStart(bool enable) +{ + Autostart::setAutostart(enable); +} + +bool SettingsController::isStartMinimizedEnabled() +{ + return m_settings->isStartMinimized(); +} + +void SettingsController::toggleStartMinimized(bool enable) +{ + m_settings->setStartMinimized(enable); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index a0a29ebfb..3ad602b70 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -48,6 +48,12 @@ public slots: bool isAutoConnectEnabled(); void toggleAutoConnect(bool enable); + bool isAutoStartEnabled(); + void toggleAutoStart(bool enable); + + bool isStartMinimizedEnabled(); + void toggleStartMinimized(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); diff --git a/client/ui/macos_util.mm b/client/ui/macos_util.mm index 8adda58aa..3947b89bd 100644 --- a/client/ui/macos_util.mm +++ b/client/ui/macos_util.mm @@ -1,60 +1,86 @@ -#include -#include #include "macos_util.h" +#include +#include + +#include -#import #import +#import + +// void setDockIconVisible(bool visible) +//{ +// QProcess process; +// process.start( +// "osascript", +// { "-e tell application \"System Events\" to get properties of (get application process \"AmneziaVPN\")" }); +// process.waitForFinished(3000); +// const auto output = QString::fromLocal8Bit(process.readAllStandardOutput()); + +// qDebug() << output; + +// if (visible) { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; +// } else { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; +// } +//} void setDockIconVisible(bool visible) { - if (!visible) { - [NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory]; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + if (visible) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); } else { - [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); } } -//this Objective-c class is used to override the action of system close button and zoom button -//https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results -@interface ButtonPasser : NSObject{ +// this Objective-c class is used to override the action of system close button and zoom button +// https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results +@interface ButtonPasser : NSObject { } -@property(readwrite) QMainWindow* window; +@property (readwrite) QMainWindow *window; + (void)closeButtonAction:(id)sender; - (void)zoomButtonAction:(id)sender; @end -@implementation ButtonPasser{ +@implementation ButtonPasser { } + (void)closeButtonAction:(id)sender { Q_UNUSED(sender); ProcessSerialNumber pn; - GetFrontProcess (&pn); - ShowHideProcess(&pn,false); + GetFrontProcess(&pn); + ShowHideProcess(&pn, false); } - (void)zoomButtonAction:(id)sender { Q_UNUSED(sender); - if (0 == self.window) return; - if (self.window->isMaximized()) self.window->showNormal(); - else self.window->showMaximized(); + if (0 == self.window) + return; + if (self.window->isMaximized()) + self.window->showNormal(); + else + self.window->showMaximized(); } @end void fixWidget(QWidget *widget) { NSView *view = (NSView *)widget->winId(); - if (0 == view) return; + if (0 == view) + return; NSWindow *window = view.window; - if (0 == window) return; + if (0 == window) + return; - //override the action of close button - //https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results - //https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html -// NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; -// [closeButton setTarget:[ButtonPasser class]]; -// [closeButton setAction:@selector(closeButtonAction:)]; + // override the action of close button + // https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results + // https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html + // NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; + // [closeButton setTarget:[ButtonPasser class]]; + // [closeButton setAction:@selector(closeButtonAction:)]; [[window standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 9f2fe91a7..7b628037b 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -43,9 +43,50 @@ PageType { headerText: qsTr("Application") } + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Auto start") + descriptionText: qsTr("Launch the application every time ") + Qt.platform.os + qsTr(" starts") + + checked: SettingsController.isAutoStartEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoStartEnabled()) { + SettingsController.toggleAutoStart(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Start minimized") + descriptionText: qsTr("Launch application minimized") + + checked: SettingsController.isStartMinimizedEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isStartMinimizedEnabled()) { + SettingsController.toggleStartMinimized(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + LabelWithButtonType { Layout.fillWidth: true - Layout.topMargin: 16 text: qsTr("Language") descriptionText: LanguageModel.currentLanguageName diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 0765f7d29..0692abda6 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -42,11 +42,10 @@ PageType { } SwitcherType { + visible: !GC.isMobile() + Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + Layout.margins: 16 text: qsTr("Auto connect") descriptionText: qsTr("Connect to VPN on app start") @@ -59,12 +58,13 @@ PageType { } } + DividerType { + visible: !GC.isMobile() + } + SwitcherType { Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + Layout.margins: 16 text: qsTr("Use AmneziaDNS if installed on the server") From c271235d169c9edb4dfe9d556ecd5f28ef2a71ba Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 16:22:55 +0500 Subject: [PATCH 071/131] added confirmation dialog when clearing logs and notification after clearing --- client/ui/qml/Pages2/PageSettingsLogging.qml | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 5138f9a39..e14be439d 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -4,9 +4,9 @@ import QtQuick.Layouts import PageEnum 1.0 -import "./" import "../Controls2" import "../Config" +import "../Components" import "../Controls2/TextTypes" PageType { @@ -121,7 +121,23 @@ PageType { image: "qrc:/images/controls/delete.svg" - onClicked: SettingsController.clearLogs() + onClicked: function() { + questionDrawer.headerText = qsTr("Clear logs?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.clearLogs() + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } } CaptionTextType { @@ -133,6 +149,10 @@ PageType { } } } + + QuestionDrawer { + id: questionDrawer + } } } } From 259eff3fea891b674df4feb031fd5cddc8e814f1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 16:25:51 +0500 Subject: [PATCH 072/131] upgraded app version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eac7f0a9e..e7231c569 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.1.1 +project(${PROJECT} VERSION 4.0.2.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From 3f7e7f260113b65db04b7d595be921212041bec2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 25 Aug 2023 09:20:42 +0500 Subject: [PATCH 073/131] fixed native wireguard config import if there is no port in the Endpoint field --- client/ui/controllers/importController.cpp | 9 +++++++-- client/ui/controllers/pageController.cpp | 2 ++ client/ui/controllers/sitesController.cpp | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index b5d10abf4..81ab313bd 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -225,12 +225,17 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) QJsonObject lastConfig; lastConfig[config_key::config] = data; - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*)(?::([0-9]*))?"); QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); QString hostName; QString port; - if (hostNameAndPortMatch.hasMatch()) { + if (hostNameAndPortMatch.hasCaptured(1)) { hostName = hostNameAndPortMatch.captured(1); + } /*else { + qDebug() << "send error?" + }*/ + + if (hostNameAndPortMatch.hasCaptured(2)) { port = hostNameAndPortMatch.captured(2); } diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index d0c219c1d..84d2ebf29 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -28,8 +28,10 @@ PageController::PageController(const QSharedPointer &serversModel, }); #endif +#if defined Q_OS_MACX connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); +#endif } QString PageController::getInitialPage() diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index d19f57581..80cac698f 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -4,6 +4,7 @@ #include #include "fileUtilites.h" +#include "utilities.h" SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &vpnConnection, From 29bef052c7bf3fead0afa76a1e2c0af890c329d7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 26 Aug 2023 10:08:50 +0300 Subject: [PATCH 074/131] minor ui fixes --- CMakeLists.txt | 2 +- client/ui/qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Controls2/CheckBoxType.qml | 2 +- client/ui/qml/Controls2/DropDownType.qml | 12 ++++++++---- client/ui/qml/Controls2/HorizontalRadioButton.qml | 11 ++++++----- client/ui/qml/Controls2/ProgressBarType.qml | 2 +- client/ui/qml/Controls2/SwitcherType.qml | 9 +++++---- client/ui/qml/Controls2/TabButtonType.qml | 8 +++++++- client/ui/qml/Controls2/TabImageButtonType.qml | 9 ++++++++- client/ui/qml/Controls2/TextAreaType.qml | 2 +- client/ui/qml/Controls2/TextFieldWithHeaderType.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 8 +++++++- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- client/ui/qml/Pages2/PageSettings.qml | 4 +++- client/ui/qml/Pages2/PageShare.qml | 9 ++++++--- client/ui/qml/Pages2/PageStart.qml | 8 -------- 16 files changed, 57 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7231c569..4560bdd09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT} VERSION 4.0.2.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-16") +set(RELEASE_DATE "2023-08-25") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 368d503ab..05684ba81 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -163,7 +163,7 @@ DrawerType { height: 24 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 2724a30cd..1ad3b4122 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -21,7 +21,7 @@ CheckBox { property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" - property string checkedBorderDisabledColor: "#5A330C" + property string checkedBorderDisabledColor: "#402102" property string checkedImageColor: "#FBB26A" property string pressedImageColor: "#A85809" diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 171516308..c21fa48f0 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -27,7 +27,9 @@ Item { property string rootButtonDefaultBorderColor: "#2C2D30" property string rootButtonPressedBorderColor: "#D7D8DB" - property int rootButtonTextMargins: 16 + property int rootButtonTextLeftMargins: 16 + property int rootButtonTextTopMargin: 16 + property int rootButtonTextBottomMargin: 16 property real drawerHeight: 0.9 property Component listView @@ -76,9 +78,9 @@ Item { spacing: 0 ColumnLayout { - Layout.leftMargin: rootButtonTextMargins - Layout.topMargin: rootButtonTextMargins - Layout.bottomMargin: rootButtonTextMargins + Layout.leftMargin: rootButtonTextLeftMargins + Layout.topMargin: rootButtonTextTopMargin + Layout.bottomMargin: rootButtonTextBottomMargin LabelTextType { Layout.fillWidth: true @@ -104,6 +106,8 @@ Item { } ImageButtonType { + Layout.rightMargin: 16 + hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 1ac5840ac..81cc8ec0a 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -75,19 +75,20 @@ RadioButton { ColumnLayout { id: content anchors.fill: parent - spacing: 16 + spacing: 0 ButtonTextType { text: root.text color: root.enabled ? root.textColor : root.textDisabledColor Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: 16 + Layout.rightMargin: 24 + Layout.leftMargin: 24 + Layout.topMargin: 12 + Layout.bottomMargin: 12 horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter } } diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml index 183c3736b..e642c3eb0 100644 --- a/client/ui/qml/Controls2/ProgressBarType.qml +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -8,7 +8,7 @@ ProgressBar { implicitHeight: 4 background: Rectangle { - color: "#412102" + color: "#633303" } contentItem: Item { diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index aca5ba0b5..ee7372f51 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -14,13 +14,13 @@ Switch { property string textColor: "#D7D8DB" property string textDisabledColor: "#878B91" - property string checkedIndicatorColor: "#412102" + property string checkedIndicatorColor: "#633303" property string defaultIndicatorColor: "transparent" - property string checkedDisabledIndicatorColor: "#5A330C" + property string checkedDisabledIndicatorColor: "#402102" - property string checkedIndicatorBorderColor: "#412102" + property string checkedIndicatorBorderColor: "#633303" property string defaultIndicatorBorderColor: "#494B50" - property string checkedDisabledIndicatorBorderColor: "#5A330C" + property string checkedDisabledIndicatorBorderColor: "#402102" property string checkedInnerCircleColor: "#FBB26A" property string defaultInnerCircleColor: "#D7D8DB" @@ -40,6 +40,7 @@ Switch { anchors.left: content.right anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 4 implicitWidth: 52 implicitHeight: 32 diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index 4699dfcd2..f8011f0df 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -4,7 +4,7 @@ import QtQuick.Controls TabButton { id: root - property string hoveredColor: "#412102" + property string hoveredColor: "#633303" property string defaultColor: "#2C2D30" property string selectedColor: "#FBB26A" @@ -39,6 +39,12 @@ TabButton { } } + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } + contentItem: Text { anchors.fill: background height: 24 diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml index 06b77e6ae..4d745a0b8 100644 --- a/client/ui/qml/Controls2/TabImageButtonType.qml +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -4,7 +4,7 @@ import QtQuick.Controls TabButton { id: root - property string hoveredColor: "#412102" + property string hoveredColor: "#633303" property string defaultColor: "#D7D8DB" property string selectedColor: "#FBB26A" @@ -18,7 +18,14 @@ TabButton { icon.color: isSelected ? selectedColor : defaultColor background: Rectangle { + id: background anchors.fill: parent color: "transparent" } + + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 9958f2e91..a75ea55d3 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -29,7 +29,7 @@ Rectangle { anchors.bottomMargin: 16 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" placeholderTextColor: "#878B91" diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3f31c2243..048a564db 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -74,7 +74,7 @@ Item { placeholderText: root.textFieldPlaceholderText placeholderTextColor: "#494B50" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b756511b6..c5add5d8b 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -70,6 +70,8 @@ PageType { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 0 + Header1TextType { text: root.defaultServerName } @@ -78,6 +80,8 @@ PageType { Layout.preferredWidth: 18 Layout.preferredHeight: 18 + Layout.leftMargin: 12 + source: "qrc:/images/controls/chevron-down.svg" } } @@ -152,7 +156,8 @@ PageType { rootButtonBackgroundColor: "#D7D8DB" rootButtonHoveredBorderColor: "transparent" rootButtonPressedBorderColor: "transparent" - rootButtonTextMargins: 8 + rootButtonTextTopMargin: 8 + rootButtonTextBottomMargin: 8 text: root.defaultContainerName textColor: "#0E0E11" @@ -303,6 +308,7 @@ PageType { ImageButtonType { image: "qrc:/images/controls/settings.svg" + imageColor: "#D7D8DB" implicitWidth: 56 implicitHeight: 56 diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index e82b0ff56..5c70b1c00 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -142,7 +142,7 @@ PageType { height: 24 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index f430a0049..6c088a703 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -26,9 +26,11 @@ PageType { anchors.left: parent.left anchors.right: parent.right + spacing: 0 + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 120e04fc1..135c7fbc6 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -106,9 +106,11 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 + spacing: 0 + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 24 headerText: qsTr("VPN Access") } @@ -161,6 +163,7 @@ PageType { ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 24 + Layout.bottomMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : qsTr("Full access to server") @@ -171,7 +174,7 @@ PageType { id: serverSelector Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 16 drawerHeight: 0.4375 @@ -385,7 +388,7 @@ PageType { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 40 enabled: shareButtonEnabled diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5c2a13df9..e4cc02eec 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -163,14 +163,6 @@ PageType { } } - MouseArea { - anchors.fill: tabBar - anchors.leftMargin: shareTabButton.visible ? 96 : 128 - anchors.rightMargin: shareTabButton.visible ? 96 : 128 - cursorShape: Qt.PointingHandCursor - enabled: false - } - BusyIndicatorType { id: busyIndicator anchors.centerIn: parent From fe08fd3f0af9e7a7385229db8c4687055260a3d8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 11:06:58 +0300 Subject: [PATCH 075/131] moved the connect button to the center of the screen --- CMakeLists.txt | 4 ++-- client/ui/controllers/connectionController.cpp | 2 +- client/ui/qml/Pages2/PageHome.qml | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4560bdd09..aec2a9745 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.2.1 +project(${PROJECT} VERSION 4.0.3.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-25") +set(RELEASE_DATE "2023-08-26") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 45f24d7fd..d38130868 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -72,7 +72,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) case Vpn::ConnectionState::Connected: { m_isConnectionInProgress = false; m_isConnected = true; - m_connectionStateText = tr("Disconnect"); + m_connectionStateText = tr("Connected"); break; } case Vpn::ConnectionState::Connecting: { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index c5add5d8b..d40f8748d 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -27,7 +27,10 @@ PageType { property string defaultContainerName: ContainersModel.defaultContainerName ConnectButton { - anchors.centerIn: parent + anchors.top: parent.top + anchors.bottom: buttonBackground.top + anchors.right: parent.right + anchors.left: parent.left } Connections { From 639c18395b32081c422eab99cf5b01c7f7bbfedd Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 14:18:41 +0300 Subject: [PATCH 076/131] fixed display of notification about successful clearing of cached profiles - limited the input for the Port field to only numeric values, in the range 1-65535 --- .../qml/Components/ShareConnectionDrawer.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 1 + .../Pages2/PageProtocolOpenVpnSettings.qml | 1 + .../PageProtocolShadowSocksSettings.qml | 1 + .../ui/qml/Pages2/PageSettingsServerData.qml | 8 +++--- .../Pages2/PageSetupWizardConfigSource.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 25 ------------------- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 13 ++++++++-- .../PageSetupWizardProtocolSettings.qml | 1 + 9 files changed, 22 insertions(+), 32 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 05684ba81..c369af423 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -227,7 +227,7 @@ DrawerType { visible: ExportController.qrCodesCount > 0 horizontalAlignment: Text.AlignHCenter - text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"") + text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"") } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 7273b86dd..b53fdfdf2 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -103,6 +103,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index fb2258c5c..659193cab 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -134,6 +134,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 7b78bc07a..fe0ef8c3b 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -89,6 +89,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 9ff991939..15c5e5317 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -89,13 +89,15 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Clear cached profiles?") - questionDrawer.descriptionText = qsTr("some description") + questionDrawer.descriptionText = qsTr("") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - ContainersModel.clearCachedProfiles() + PageController.showBusyIndicator(true) + SettingsController.clearCachedProfiles() + PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false @@ -165,7 +167,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Clear server from Amnezia software?") - questionDrawer.descriptionText = qsTr(" All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index f01556672..45aad9d09 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -49,7 +49,7 @@ PageType { headerText: qsTr("Server connection") descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n -It's okay if a friend passed the code.") +It's okay as long as it's from someone you trust.") } Header2TextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 5187a6e37..d089a70d8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -107,31 +107,6 @@ PageType { goToPage(PageEnum.PageSetupWizardEasy) } } - -// BasicButtonType { -// Layout.fillWidth: true -// Layout.topMargin: -8 - -// defaultColor: "transparent" -// hoveredColor: Qt.rgba(1, 1, 1, 0.08) -// pressedColor: Qt.rgba(1, 1, 1, 0.12) -// disabledColor: "#878B91" -// textColor: "#D7D8DB" -// borderWidth: 1 - -// text: qsTr("Select protocol to install") - -// onClicked: function() { -// if (!isCredentialsFilled()) { -// return -// } - -// InstallController.setShouldCreateServer(true) -// InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - -// goToPage(PageEnum.PageSetupWizardProtocols) -// } -// } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index f2600f65d..375a83327 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -45,7 +45,7 @@ PageType { id: fl anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin + contentHeight: content.implicitHeight + setupLaterButton.anchors.bottomMargin Column { id: content @@ -126,7 +126,9 @@ PageType { } } - DividerType {} + DividerType { + implicitWidth: parent.width + } CardType { implicitWidth: parent.width @@ -141,6 +143,11 @@ PageType { } } + Item { + implicitWidth: 1 + implicitHeight: 1 + } + BasicButtonType { id: continueButton @@ -163,6 +170,8 @@ PageType { } BasicButtonType { + id: setupLaterButton + implicitWidth: parent.width anchors.topMargin: 8 anchors.bottomMargin: 24 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 2f5ed5699..d64fa0975 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -212,6 +212,7 @@ PageType { headerText: qsTr("Port") textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } } Rectangle { From 8f6aa950cd4a7c34cf8f9e9d855505f63120e3e5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 22:03:28 +0300 Subject: [PATCH 077/131] fixed conflicts after merge --- client/amnezia_application.cpp | 7 +- client/ui/controllers/importController.cpp | 15 ++ client/ui/controllers/settingsController.cpp | 12 ++ client/ui/controllers/sitesController.cpp | 12 ++ client/ui/pages_logic/StartPageLogic.cpp | 23 +-- client/vpnconnection.cpp | 175 ++++++++++--------- 6 files changed, 140 insertions(+), 104 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 93f73cd6f..c45867484 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -21,8 +21,8 @@ #include "protocols/qml_register_protocols.h" #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/ios_controller.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -108,6 +108,9 @@ void AmneziaApplication::init() &ImportController::extractConfigFromData); connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), &PageController::goToPageViewConfig); +#endif + +#ifdef Q_OS_IOS IosController::Instance()->initialize(); #endif diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 81ab313bd..7f8b82a9e 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -10,6 +10,9 @@ #include "../../platforms/android/androidutils.h" #include #endif +#ifdef Q_OS_IOS + #include +#endif #include "fileUtilites.h" namespace @@ -88,6 +91,18 @@ void ImportController::extractConfigFromFile() QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f09ebc218..531d3c505 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -95,6 +95,18 @@ void SettingsController::restoreAppConfig() } QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 80cac698f..891ddb6f7 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -90,6 +90,18 @@ void SitesController::importSites(bool replaceExisting) } QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); return; diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 9b89781fe..891d67fbf 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -19,17 +19,10 @@ #endif #ifdef Q_OS_IOS -#include + #include #endif -namespace { -enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard -}; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { enum class ConfigTypes { Amnezia, @@ -200,18 +193,18 @@ void StartPageLogic::onPushButtonImportOpenFile() return; QFile file(fileName); - + #ifdef Q_OS_IOS CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), - fileName.length()), - kCFURLPOSIXPathStyle, 0); - + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + if (!CFURLStartAccessingSecurityScopedResource(url)) { qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); } #endif - + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5c9e4c3e6..1e8fd60ab 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -5,41 +5,40 @@ #include #include -#include #include +#include #include -#include #include +#include #include #ifdef AMNEZIA_DESKTOP -#include "ipc.h" -#include "core/ipcclient.h" -#include + #include "core/ipcclient.h" + #include "ipc.h" + #include #endif #ifdef Q_OS_ANDROID -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" #endif #ifdef Q_OS_IOS -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/ios_controller.h" #endif #include "utilities.h" #include "vpnconnection.h" -VpnConnection::VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, QObject* parent) : QObject(parent), - m_settings(settings), - m_configurator(configurator), - m_checkTimer(new QTimer(this)) +VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, + QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator), m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); #ifdef Q_OS_IOS - connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); + connect(IosController::Instance(), &IosController::connectionStateChanged, this, + &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); - + #endif } @@ -60,27 +59,23 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #ifdef AMNEZIA_DESKTOP if (IpcClient::Interface()) { - if (state == Vpn::ConnectionState::Connected){ + if (state == Vpn::ConnectionState::Connected) { IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); if (m_settings->routeMode() != Settings::VpnAllSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - //qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); + // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); } QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString(); - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), - QStringList() << dns1 << dns2); - + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - QTimer::singleShot(1000, m_vpnProtocol.data(), [this](){ - addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); - }); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + QTimer::singleShot(1000, m_vpnProtocol.data(), + [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); @@ -88,9 +83,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); } - - } - else if (state == Vpn::ConnectionState::Error) { + } else if (state == Vpn::ConnectionState::Error) { IpcClient::Interface()->flushDns(); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { @@ -103,8 +96,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #ifdef Q_OS_IOS if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); - } - else { + } else { m_checkTimer.stop(); } #endif @@ -125,8 +117,7 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else { + } else { if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } @@ -139,24 +130,24 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) IpcClient::Interface()->routeAddList(gw, ips); // re-resolve domains - for (const QString &site: sites) { - const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo){ - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - const QString &ip = addr.toString(); - //qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; - if (!ips.contains(ip)) { - IpcClient::Interface()->routeAddList(gw, QStringList() << ip); - m_settings->addVpnSite(mode, site, ip); - } - flushDns(); - break; + for (const QString &site : sites) { + const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + QString ipv4Addr; + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + const QString &ip = addr.toString(); + // qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; + if (!ips.contains(ip)) { + IpcClient::Interface()->routeAddList(gw, QStringList() << ip); + m_settings->addVpnSite(mode, site, ip); } + flushDns(); + break; } - }; - QHostInfo::lookupHost(site, this, cbResolv); + } + }; + QHostInfo::lookupHost(site, this, cbResolv); } #endif } @@ -172,8 +163,7 @@ void VpnConnection::addRoutes(const QStringList &ips) if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), ips); } } @@ -186,8 +176,7 @@ void VpnConnection::deleteRoutes(const QStringList &ips) if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeDeleteList(vpnProtocol()->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->routeGateway(), ips); } } @@ -197,7 +186,8 @@ void VpnConnection::deleteRoutes(const QStringList &ips) void VpnConnection::flushDns() { #ifdef AMNEZIA_DESKTOP - if (IpcClient::Interface()) IpcClient::Interface()->flushDns(); + if (IpcClient::Interface()) + IpcClient::Interface()->flushDns(); #endif } @@ -213,18 +203,22 @@ ErrorCode VpnConnection::lastError() const QMap VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) { QMap configs; - for (Proto proto: ProtocolProps::allProtocols()) { + for (Proto proto : ProtocolProps::allProtocols()) { - QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); + QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)) + .toObject() + .value(config_key::last_config) + .toString(); - if (!cfg.isEmpty()) configs.insert(proto, cfg); + if (!cfg.isEmpty()) + configs.insert(proto, cfg); } return configs; } -QString VpnConnection::createVpnConfigurationForProto(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, - ErrorCode *errorCode) +QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + Proto proto, ErrorCode *errorCode) { QMap lastVpnConfig = getLastVpnConfig(containerConfig); @@ -232,10 +226,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, if (lastVpnConfig.contains(proto)) { configData = lastVpnConfig.value(proto); configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); - } - else { - configData = m_configurator->genVpnProtocolConfig(credentials, - container, containerConfig, proto, errorCode); + } else { + configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode); if (errorCode && *errorCode) { return ""; @@ -246,7 +238,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); if (serverIndex >= 0) { - qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container << proto; + qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container + << proto; QJsonObject protoObject = m_settings->protocolConfig(serverIndex, container, proto); protoObject.insert(config_key::last_config, configDataBeforeLocalProcessing); m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); @@ -256,17 +249,18 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, return configData; } -QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode) +QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + ErrorCode *errorCode) { QJsonObject vpnConfiguration; for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { - QJsonObject vpnConfigData = QJsonDocument::fromJson( - createVpnConfigurationForProto( - serverIndex, credentials, container, containerConfig, proto, errorCode).toUtf8()). - object(); + QJsonObject vpnConfigData = + QJsonDocument::fromJson(createVpnConfigurationForProto(serverIndex, credentials, container, + containerConfig, proto, errorCode) + .toUtf8()) + .object(); if (errorCode && *errorCode) { return {}; @@ -293,12 +287,14 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, return vpnConfiguration; } -void VpnConnection::connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) +void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig) { qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") - .arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode(); -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + .arg(serverIndex) + .arg(ContainerProps::containerToString(container)) + << m_settings->routeMode(); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); } @@ -331,8 +327,8 @@ void VpnConnection::connectToVpn(int serverIndex, emit connectionStateChanged(Vpn::ConnectionState::Error); return; } - -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { emit connectionStateChanged(Vpn::ConnectionState::Error); @@ -354,17 +350,21 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit connectionStateChanged(Vpn::ConnectionState::Error); + if (e) + emit connectionStateChanged(Vpn::ConnectionState::Error); } -void VpnConnection::createProtocolConnections() { +void VpnConnection::createProtocolConnections() +{ connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, SLOT(onConnectionStateChanged(Vpn::ConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, + SLOT(onConnectionStateChanged(Vpn::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } #ifdef Q_OS_ANDROID -void VpnConnection::restoreConnection() { +void VpnConnection::restoreConnection() +{ createAndroidConnections(); m_vpnProtocol.reset(androidVpnProtocol); @@ -384,11 +384,13 @@ void VpnConnection::createAndroidConnections(DockerContainer container) { androidVpnProtocol = createDefaultAndroidVpnProtocol(container); - connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); - connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, + &AndroidVpnProtocol::setConnectionState); + connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, + &AndroidVpnProtocol::connectionDataUpdated); } -AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) +AndroidVpnProtocol *VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) { Proto proto = ContainerProps::defaultProtocol(container); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); @@ -415,8 +417,6 @@ void VpnConnection::disconnectFromVpn() } #endif - if (!m_vpnProtocol.data()) { - emit connectionStateChanged(Vpn::ConnectionState::Disconnected); #ifdef Q_OS_ANDROID AndroidController::instance()->stop(); #endif @@ -427,7 +427,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); return; } @@ -439,7 +439,8 @@ void VpnConnection::disconnectFromVpn() Vpn::ConnectionState VpnConnection::connectionState() { - if (!m_vpnProtocol) return Vpn::ConnectionState::Disconnected; + if (!m_vpnProtocol) + return Vpn::ConnectionState::Disconnected; return m_vpnProtocol->connectionState(); } From 810da0db615bf457ac817af8bc843962d1e399c7 Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 28 Aug 2023 14:14:10 -0700 Subject: [PATCH 078/131] Fixes for iOS --- client/platforms/ios/ios_controller.h | 2 +- client/platforms/ios/ios_controller.mm | 28 ++++++++++---------- client/ui/controllers/settingsController.cpp | 4 +++ client/ui/controllers/sitesController.cpp | 4 +++ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 0750f7cd4..4d1122b29 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -47,7 +47,7 @@ public: void getBackendLogs(std::function &&callback); void checkStatus(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); protected slots: diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index c8e272525..6f23ac81c 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -30,22 +30,22 @@ const char* MessageKey::host = "host"; const char* MessageKey::port = "port"; const char* MessageKey::isOnDemand = "is-on-demand"; -VpnProtocol::VpnConnectionState iosStatusToState(NEVPNStatus status) { +Vpn::ConnectionState iosStatusToState(NEVPNStatus status) { switch (status) { case NEVPNStatusInvalid: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; case NEVPNStatusDisconnected: - return VpnProtocol::VpnConnectionState::Disconnected; + return Vpn::ConnectionState::Disconnected; case NEVPNStatusConnecting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusConnected: - return VpnProtocol::VpnConnectionState::Connected; + return Vpn::ConnectionState::Connected; case NEVPNStatusReasserting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusDisconnecting: - return VpnProtocol::VpnConnectionState::Disconnecting; + return Vpn::ConnectionState::Disconnecting; default: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; } } @@ -85,7 +85,7 @@ bool IosController::initialize() @try { if (error) { qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -147,7 +147,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur @try { if (error) { qDebug() << "IosController::connectVpn : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -161,7 +161,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur m_currentTunnel = manager; qDebug() << "IosController::connectVpn : Using existing tunnel"; if (manager.connection.status == NEVPNStatusConnected) { - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); return; } @@ -356,7 +356,7 @@ void IosController::startTunnel() if (saveError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Save Error" << saveError.localizedDescription.UTF8String; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } @@ -365,7 +365,7 @@ void IosController::startTunnel() if (loadError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } @@ -389,7 +389,7 @@ void IosController::startTunnel() if (!started || startError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error" << (startError ? startError.localizedDescription.UTF8String : ""); - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); } else { qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded"; } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531d3c505..7d1bf643b 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,6 +2,10 @@ #include +#ifdef Q_OS_IOS + #include +#endif + #include "fileUtilites.h" #include "logger.h" #include "ui/qautostart.h" diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 891ddb6f7..00704bcae 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -3,6 +3,10 @@ #include #include +#ifdef Q_OS_IOS + #include +#endif + #include "fileUtilites.h" #include "utilities.h" From 0eda42f29fa9d49540fea3dd93da7935cef575f9 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 30 Aug 2023 01:17:14 +0300 Subject: [PATCH 079/131] Savefile for iOS --- client/fileUtilites.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 87fa7aed9..94ef077e4 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -1,4 +1,5 @@ #include "fileUtilites.h" +#include "platforms/ios/MobileUtils.h" #include #include @@ -6,9 +7,15 @@ void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, const QString &data) { + +#ifdef Q_OS_IOS + QUrl fileUrl = QDir::tempPath() + "/" + fileName; +#else QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), "*" + fileExtension); +#endif + if (fileUrl.isEmpty()) return; if (!fileUrl.toString().endsWith(fileExtension)) { @@ -17,13 +24,24 @@ void FileUtilites::saveFile(const QString &fileExtension, const QString &caption if (fileUrl.isEmpty()) return; +#ifdef Q_OS_IOS + QFile save(fileUrl.toString()); +#else QFile save(fileUrl.toLocalFile()); +#endif // todo check if save successful save.open(QIODevice::WriteOnly); save.write(data.toUtf8()); save.close(); +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileUrl.toString()); + MobileUtils::shareText(filesToSend); + return; +#endif + QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } From 1c1a82696b52d0f5a8d86923f7137a1d6e629998 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 30 Aug 2023 17:23:55 -0400 Subject: [PATCH 080/131] Fix qrDecoder for Android --- client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45aad9d09..685444974 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -87,7 +87,9 @@ It's okay as long as it's from someone you trust.") clickedFunction: function() { ImportController.startDecodingQr() - goToPage(PageEnum.PageSetupWizardQrReader) + if (Qt.platform.os === "ios") { + goToPage(PageEnum.PageSetupWizardQrReader) + } } } From e8862a3811d7b0de79881f391bebb5ddbd617a4d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 30 Aug 2023 15:10:44 +0500 Subject: [PATCH 081/131] removed the use of QFileDialog --- client/CMakeLists.txt | 23 ++++--- client/amnezia_application.h | 10 +-- client/core/servercontroller.cpp | 1 - client/fileUtilites.cpp | 69 +++++++++++++------ client/fileUtilites.h | 11 ++- client/platforms/ios/ios_controller.h | 2 +- client/ui/controllers/exportController.cpp | 31 +-------- client/ui/controllers/exportController.h | 2 +- client/ui/controllers/importController.cpp | 18 +---- client/ui/controllers/importController.h | 2 +- client/ui/controllers/settingsController.cpp | 32 ++------- client/ui/controllers/settingsController.h | 6 +- client/ui/controllers/sitesController.cpp | 27 ++------ client/ui/controllers/sitesController.h | 4 +- .../qml/Components/ShareConnectionDrawer.qml | 17 ++++- .../qml/Controls2/TextFieldWithHeaderType.qml | 10 +++ client/ui/qml/Pages2/PageSettingsBackup.qml | 42 +++++++++-- client/ui/qml/Pages2/PageSettingsLogging.qml | 18 ++++- .../qml/Pages2/PageSettingsSplitTunneling.qml | 44 +++++++++--- .../Pages2/PageSetupWizardConfigSource.qml | 13 +++- client/ui/uilogic.cpp | 4 +- client/vpnconnection.cpp | 2 +- 22 files changed, 224 insertions(+), 164 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 713a9165c..47264fe60 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,31 +10,34 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml + Core Gui Network Xml RemoteObjects Quick Svg QuickControls2 Core5Compat Concurrent LinguistTools ) + if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia - ) + set(PACKAGES ${PACKAGES} Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(PACKAGES ${PACKAGES} Widgets) endif() find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES}) set(LIBS ${LIBS} - Qt6::Widgets Qt6::Core Qt6::Gui + Qt6::Core Qt6::Gui Qt6::Network Qt6::Xml Qt6::RemoteObjects Qt6::Quick Qt6::Svg Qt6::QuickControls2 Qt6::Core5Compat Qt6::Concurrent ) if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS ${LIBS} Qt6::Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(LIBS ${LIBS} Qt6::Widgets) endif() qt_standard_project_setup() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index c4f337530..e18fb70ce 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -1,13 +1,15 @@ #ifndef AMNEZIA_APPLICATION_H #define AMNEZIA_APPLICATION_H -#include -#include - #include #include #include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "settings.h" #include "vpnconnection.h" @@ -39,7 +41,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QApplication + #define AMNEZIA_BASE_CLASS QGuiApp #else #define AMNEZIA_BASE_CLASS SingleApplication #define QAPPLICATION_CLASS QApplication diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index cbb87f118..0dad30200 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,6 +1,5 @@ #include "servercontroller.h" -#include #include #include #include diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 87fa7aed9..659243aa8 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -3,35 +3,59 @@ #include #include -void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, - const QString &data) -{ - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), - "*" + fileExtension); - if (fileUrl.isEmpty()) - return; - if (!fileUrl.toString().endsWith(fileExtension)) { - fileUrl = QUrl(fileUrl.toString() + fileExtension); - } - if (fileUrl.isEmpty()) - return; +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif - QFile save(fileUrl.toLocalFile()); +#ifdef Q_OS_IOS + #include "platforms/ios/MobileUtils.h" + #include +#endif + +void FileUtilites::saveFile(QString fileName, const QString &data) +{ +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(data, fileName); + return; +#endif + +#ifdef Q_OS_IOS + QFile file(fileName); +#else + QUrl fileUrl = QUrl(fileName); + QFile file(fileUrl.toLocalFile()); +#endif // todo check if save successful - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); + file.open(QIODevice::WriteOnly); + file.write(data.toUtf8()); + file.close(); + +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileName); + MobileUtils::shareText(filesToSend); + return; +#endif QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } -QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, - QString *selectedFilter, QFileDialog::Options options) +QString FileUtilites::getFileName(QString fileName) { - QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } + + return fileName; +#endif #ifdef Q_OS_ANDROID // patch for files containing spaces etc @@ -42,6 +66,9 @@ QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const rawUrl.replace(" ", "%20"); fileName = contentUrl + sep + rawUrl; } -#endif + return fileName; +#endif + + return QUrl(FileUtilites::getFileName(fileName)).toLocalFile(); } diff --git a/client/fileUtilites.h b/client/fileUtilites.h index 8cf4807c5..21cb03a9b 100644 --- a/client/fileUtilites.h +++ b/client/fileUtilites.h @@ -1,19 +1,16 @@ #ifndef FILEUTILITES_H #define FILEUTILITES_H -#include +#include +#include class FileUtilites : public QObject { Q_OBJECT public: - static void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, - const QString &data); - - static QString getFileName(QWidget *parent = nullptr, const QString &caption = QString(), - const QString &dir = QString(), const QString &filter = QString(), - QString *selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); + static void saveFile(QString fileName, const QString &data); + static QString getFileName(QString fileName); }; #endif // FILEUTILITES_H diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 0750f7cd4..4d1122b29 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -47,7 +47,7 @@ public: void getBackendLogs(std::function &&callback); void checkStatus(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); protected slots: diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 4cee1dc91..ddc976cc7 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -13,7 +13,6 @@ #include "core/errorstrings.h" #include "fileUtilites.h" #ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" #endif #include "qrcodegen.hpp" @@ -201,35 +200,9 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName) +void ExportController::saveFile(const QString &fileName) { -#if defined Q_OS_IOS -// ext.replace("*", ""); -// QString fileName = QDir::tempPath() + "/" + suggestedName; -// -// if (fileName.isEmpty()) -// return; -// if (!fileName.endsWith(ext)) -// fileName.append(ext); -// -// QFile::remove(fileName); -// -// QFile save(fileName); -// save.open(QIODevice::WriteOnly); -// save.write(data.toUtf8()); -// save.close(); -// -// QStringList filesToSend; -// filesToSend.append(fileName); -// MobileUtils::shareText(filesToSend); -// return; -#endif -#if defined Q_OS_ANDROID - AndroidController::instance()->shareConfig(m_config, "amnezia_config"); - return; -#endif - - FileUtilites::saveFile(fileExtension, caption, fileName, m_config); + FileUtilites::saveFile(fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 913dfc3a1..b526521ef 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName); + void saveFile(const QString &fileName); signals: void generateConfig(int type); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 7f8b82a9e..9a374aa19 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -85,23 +85,9 @@ ImportController::ImportController(const QSharedPointer &serversMo #endif } -void ImportController::extractConfigFromFile() +void ImportController::extractConfigFromFile(const QString &fileName) { - QString fileName = FileUtilites::getFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index cccdf4b74..7def7733e 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -21,7 +21,7 @@ public: public slots: void importConfig(); - void extractConfigFromFile(); + void extractConfigFromFile(const QString &fileName); void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); bool extractConfigFromQr(const QByteArray &data); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531d3c505..7c7402e02 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -68,9 +68,9 @@ void SettingsController::openLogsFolder() Logger::openLogsFolder(); } -void SettingsController::exportLogsFile() +void SettingsController::exportLogsFile(const QString &fileName) { - FileUtilites::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); + FileUtilites::saveFile(fileName, Logger::getLogFile()); } void SettingsController::clearLogs() @@ -79,35 +79,17 @@ void SettingsController::clearLogs() Logger::clearServiceLogs(); } -void SettingsController::backupAppConfig() +void SettingsController::backupAppConfig(const QString &fileName) { - FileUtilites::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); + FileUtilites::saveFile(fileName, m_settings->backupAppConfig()); } -void SettingsController::restoreAppConfig() +void SettingsController::restoreAppConfig(const QString &fileName) { - QString fileName = - FileUtilites::getFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - - if (fileName.isEmpty()) { - return; - } - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); bool ok = m_settings->restoreAppConfig(data); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 3ad602b70..af816d465 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -34,11 +34,11 @@ public slots: void toggleLogging(bool enable); void openLogsFolder(); - void exportLogsFile(); + void exportLogsFile(const QString &fileName); void clearLogs(); - void backupAppConfig(); - void restoreAppConfig(); + void backupAppConfig(const QString &fileName); + void restoreAppConfig(const QString &fileName); QString getAppVersion(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 891ddb6f7..9eafe6ba4 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -79,28 +79,9 @@ void SitesController::removeSite(int index) emit finished(tr("Site removed: ") + hostname); } -void SitesController::importSites(bool replaceExisting) +void SitesController::importSites(const QString &fileName, bool replaceExisting) { - QString fileName = - FileUtilites::getFileName(Q_NULLPTR, tr("Open sites file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); - - if (fileName.isEmpty()) { - return; - } - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); @@ -149,7 +130,7 @@ void SitesController::importSites(bool replaceExisting) emit finished(tr("Import completed")); } -void SitesController::exportSites() +void SitesController::exportSites(const QString &fileName) { auto sites = m_sitesModel->getCurrentSites(); @@ -163,7 +144,7 @@ void SitesController::exportSites() QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - FileUtilites::saveFile(".json", tr("Export sites file"), "sites", jsonData); + FileUtilites::saveFile(fileName, jsonData); emit finished(tr("Export completed")); } diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h index ff78c3de3..2171e7b42 100644 --- a/client/ui/controllers/sitesController.h +++ b/client/ui/controllers/sitesController.h @@ -19,8 +19,8 @@ public slots: void addSite(QString hostname); void removeSite(int index); - void importSites(bool replaceExisting); - void exportSites(); + void importSites(const QString &fileName, bool replaceExisting); + void exportSites(const QString &fileName); signals: void errorOccurred(const QString &errorMessage); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index c369af423..41dd2ab2d 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs +import QtCore + import SortFilterProxyModel 0.2 import PageEnum 1.0 @@ -67,8 +69,19 @@ DrawerType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: { - ExportController.saveFile(configExtension, configCaption, configFileName) + onClicked: fileDialog.open() + + FileDialog { + id: fileDialog + acceptLabel: configCaption + nameFilters: [ "Config files (*" + configExtension + ")" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName + defaultSuffix: configExtension + onAccepted: { + ExportController.saveFile(fileDialog.currentFile.toString()) + } } } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 048a564db..3f80428ea 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -146,4 +146,14 @@ Item { color: "#EB5757" } } + + MouseArea { + anchors.fill: root + cursorShape: Qt.PointingHandCursor + + onPressed: function(mouse) { + textField.forceActiveFocus() + mouse.accepted = false + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 39cad7329..363bc66f0 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import PageEnum 1.0 @@ -74,14 +77,36 @@ PageType { } BasicButtonType { + id: makeBackupButton Layout.fillWidth: true Layout.topMargin: 14 text: qsTr("Make a backup") onClicked: { + if (GC.isMobile()) { + backupAppConfig("AmneziaVPN.backup") + } else { + saveFileDialog.open() + } + } + + FileDialog { + id: saveFileDialog + acceptLabel: qsTr("Save backup file") + nameFilters: [ "Backup files (*.backup)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" + defaultSuffix: ".backup" + onAccepted: { + makeBackupButton.backupAppConfig(saveFileDialog.currentFile.toString()) + } + } + + function backupAppConfig(fileName) { PageController.showBusyIndicator(true) - SettingsController.backupAppConfig() + SettingsController.backupAppConfig(fileName) PageController.showBusyIndicator(false) } } @@ -100,9 +125,18 @@ PageType { text: qsTr("Restore from backup") onClicked: { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig() - PageController.showBusyIndicator(false) + openFileDialog.open() + } + + FileDialog { + id: openFileDialog + acceptLabel: qsTr("Open backup file") + nameFilters: [ "Backup files (*.backup)" ] + onAccepted: { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(openFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index e14be439d..cfe7d7c99 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import PageEnum 1.0 @@ -97,7 +100,20 @@ PageType { image: "qrc:/images/controls/save.svg" - onClicked: SettingsController.exportLogsFile() + onClicked: fileDialog.open() + + FileDialog { + id: fileDialog + acceptLabel: qsTr("Save logs") + nameFilters: [ "Logs files (*.log)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" + defaultSuffix: ".log" + onAccepted: { + ExportController.saveFile(fileDialog.currentFile.toString()) + } + } } CaptionTextType { diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 71f2ba297..535ab18cd 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import SortFilterProxyModel 0.2 @@ -41,6 +44,8 @@ PageType { allExceptSites ] + property bool replaceExistingSites + QtObject { id: onlyForwardSites property string name: qsTr("Only the addresses in the list must be opened via VPN") @@ -295,8 +300,21 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - SitesController.exportSites() - moreActionsDrawer.close() + saveFileDialog.open() + } + + FileDialog { + id: saveFileDialog + acceptLabel: qsTr("Save sites") + nameFilters: [ "Sites files (*.json)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" + defaultSuffix: ".json" + onAccepted: { + SitesController.exportSites(saveFileDialog.currentFile.toString()) + moreActionsDrawer.close() + } } } @@ -331,6 +349,7 @@ PageType { anchors.bottom: parent.bottom contentHeight: importSitesDrawerContent.height + ColumnLayout { id: importSitesDrawerContent @@ -351,9 +370,8 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - SitesController.importSites(true) - importSitesDrawer.close() - moreActionsDrawer.close() + root.replaceExistingSites = true + openFileDialog.open() } } @@ -364,13 +382,23 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - SitesController.importSites(false) - importSitesDrawer.close() - moreActionsDrawer.close() + root.replaceExistingSites = false + openFileDialog.open() } } DividerType {} + + FileDialog { + id: openFileDialog + acceptLabel: qsTr("Open sites file") + nameFilters: [ "Sites files (*.json)" ] + onAccepted: { + SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45aad9d09..6986509de 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -70,8 +70,17 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - ImportController.extractConfigFromFile() - goToPage(PageEnum.PageSetupWizardViewConfig) + fileDialog.open() + } + + FileDialog { + id: fileDialog + acceptLabel: qsTr("Open config file") + nameFilters: [ "Config files (*.vpn *.ovpn *.conf)" ] + onAccepted: { + ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) + goToPage(PageEnum.PageSetupWizardViewConfig) + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 546b18cb4..e7d90f852 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -175,7 +174,8 @@ void UiLogic::showOnStartup() void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic) || dynamic_cast(logic)) { + if (dynamic_cast(logic) || dynamic_cast(logic) + || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 1e8fd60ab..1cff01e6b 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -1,5 +1,5 @@ #include "qtimer.h" -#include + #include #include #include From 4baa003c0dc4995ba7dfacb898ff11b1dfe34280 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 31 Aug 2023 16:00:41 +0500 Subject: [PATCH 082/131] removed old ui files --- client/CMakeLists.txt | 2 - client/amnezia_application.cpp | 2 +- client/amnezia_application.h | 2 +- client/configurators/ikev2_configurator.cpp | 48 +- client/configurators/openvpn_configurator.cpp | 128 ++-- client/configurators/ssh_configurator.cpp | 59 +- .../configurators/wireguard_configurator.cpp | 98 +-- client/fileUtilites.cpp | 4 +- client/platforms/android/androidutils.cpp | 81 +-- .../platforms/android/androidvpnactivity.cpp | 112 ++-- client/resources.qrc | 73 --- .../ui/controllers/connectionController.cpp | 6 +- client/ui/controllers/pageController.cpp | 7 +- client/ui/controllers/settingsController.cpp | 4 - client/ui/controllers/sitesController.cpp | 5 +- .../AdvancedServerSettingsLogic.cpp | 88 --- .../pages_logic/AdvancedServerSettingsLogic.h | 31 - client/ui/pages_logic/AppSettingsLogic.cpp | 119 ---- client/ui/pages_logic/AppSettingsLogic.h | 37 -- client/ui/pages_logic/ClientInfoLogic.cpp | 213 ------ client/ui/pages_logic/ClientInfoLogic.h | 42 -- .../ui/pages_logic/ClientManagementLogic.cpp | 143 ---- client/ui/pages_logic/ClientManagementLogic.h | 33 - .../ui/pages_logic/GeneralSettingsLogic.cpp | 40 -- client/ui/pages_logic/GeneralSettingsLogic.h | 25 - .../ui/pages_logic/NetworkSettingsLogic.cpp | 52 -- client/ui/pages_logic/NetworkSettingsLogic.h | 35 - .../pages_logic/NewServerProtocolsLogic.cpp | 34 - .../ui/pages_logic/NewServerProtocolsLogic.h | 25 - client/ui/pages_logic/PageLogicBase.cpp | 16 - client/ui/pages_logic/PageLogicBase.h | 35 - client/ui/pages_logic/QrDecoderLogic.cpp | 125 ---- client/ui/pages_logic/QrDecoderLogic.h | 41 -- .../ServerConfiguringProgressLogic.cpp | 187 ------ .../ServerConfiguringProgressLogic.h | 73 --- .../ui/pages_logic/ServerContainersLogic.cpp | 128 ---- client/ui/pages_logic/ServerContainersLogic.h | 29 - client/ui/pages_logic/ServerListLogic.cpp | 47 -- client/ui/pages_logic/ServerListLogic.h | 30 - client/ui/pages_logic/ServerSettingsLogic.cpp | 144 ----- client/ui/pages_logic/ServerSettingsLogic.h | 62 -- .../ui/pages_logic/ShareConnectionLogic.cpp | 293 --------- client/ui/pages_logic/ShareConnectionLogic.h | 54 -- client/ui/pages_logic/SitesLogic.cpp | 211 ------ client/ui/pages_logic/SitesLogic.h | 33 - client/ui/pages_logic/StartPageLogic.cpp | 374 ----------- client/ui/pages_logic/StartPageLogic.h | 56 -- client/ui/pages_logic/ViewConfigLogic.cpp | 93 --- client/ui/pages_logic/ViewConfigLogic.h | 47 -- client/ui/pages_logic/VpnLogic.cpp | 245 ------- client/ui/pages_logic/VpnLogic.h | 70 -- client/ui/pages_logic/WizardLogic.cpp | 70 -- client/ui/pages_logic/WizardLogic.h | 31 - .../ui/pages_logic/protocols/CloakLogic.cpp | 137 ---- client/ui/pages_logic/protocols/CloakLogic.h | 46 -- .../ui/pages_logic/protocols/OpenVpnLogic.cpp | 203 ------ .../ui/pages_logic/protocols/OpenVpnLogic.h | 63 -- .../protocols/OtherProtocolsLogic.cpp | 169 ----- .../protocols/OtherProtocolsLogic.h | 45 -- .../protocols/PageProtocolLogicBase.cpp | 8 - .../protocols/PageProtocolLogicBase.h | 24 - .../protocols/ShadowSocksLogic.cpp | 127 ---- .../pages_logic/protocols/ShadowSocksLogic.h | 44 -- .../pages_logic/protocols/WireGuardLogic.cpp | 30 - .../ui/pages_logic/protocols/WireGuardLogic.h | 26 - client/ui/qml/Controls/BackButton.qml | 33 - client/ui/qml/Controls/BasicButtonType.qml | 17 - client/ui/qml/Controls/BlueButtonType.qml | 28 - client/ui/qml/Controls/Caption.qml | 17 - client/ui/qml/Controls/CheckBoxType.qml | 27 - client/ui/qml/Controls/ComboBoxType.qml | 11 - client/ui/qml/Controls/ContextMenu.qml | 33 - client/ui/qml/Controls/FadeBehavior.qml | 35 - client/ui/qml/Controls/FlickableType.qml | 26 - client/ui/qml/Controls/ImageButtonType.qml | 17 - client/ui/qml/Controls/LabelType.qml | 17 - client/ui/qml/Controls/Logo.qml | 8 - client/ui/qml/Controls/PopupWarning.qml | 34 - client/ui/qml/Controls/PopupWithQuestion.qml | 62 -- client/ui/qml/Controls/PopupWithTextField.qml | 62 -- client/ui/qml/Controls/RadioButtonType.qml | 36 -- client/ui/qml/Controls/RichLabelType.qml | 17 - client/ui/qml/Controls/SettingButtonType.qml | 33 - .../ShareConnectionButtonCopyType.qml | 26 - .../Controls/ShareConnectionButtonType.qml | 27 - .../qml/Controls/ShareConnectionContent.qml | 65 -- client/ui/qml/Controls/SvgButtonType.qml | 16 - client/ui/qml/Controls/SvgImageType.qml | 23 - client/ui/qml/Controls/TextAreaType.qml | 63 -- client/ui/qml/Controls/TextFieldType.qml | 52 -- client/ui/qml/Controls/UrlButtonType.qml | 25 - client/ui/qml/Controls/VisibleBehavior.qml | 6 - .../Pages/ClientInfo/PageClientInfoBase.qml | 15 - .../ClientInfo/PageClientInfoOpenVPN.qml | 115 ---- .../ClientInfo/PageClientInfoWireGuard.qml | 100 --- .../InstallSettings/InstallSettingsBase.qml | 75 --- .../Pages/InstallSettings/SelectContainer.qml | 200 ------ client/ui/qml/Pages/PageAbout.qml | 90 --- .../qml/Pages/PageAdvancedServerSettings.qml | 118 ---- client/ui/qml/Pages/PageAppSetting.qml | 152 ----- client/ui/qml/Pages/PageBase.qml | 20 - client/ui/qml/Pages/PageClientManagement.qml | 119 ---- client/ui/qml/Pages/PageGeneralSettings.qml | 166 ----- client/ui/qml/Pages/PageNetworkSetting.qml | 113 ---- client/ui/qml/Pages/PageNewServer.qml | 61 -- .../ui/qml/Pages/PageNewServerProtocols.qml | 154 ----- client/ui/qml/Pages/PageQrDecoderIos.qml | 94 --- .../Pages/PageServerConfiguringProgress.qml | 121 ---- client/ui/qml/Pages/PageServerContainers.qml | 434 ------------- client/ui/qml/Pages/PageServerList.qml | 185 ------ client/ui/qml/Pages/PageServerSettings.qml | 140 ---- client/ui/qml/Pages/PageSetupWizard.qml | 108 ---- .../ui/qml/Pages/PageSetupWizardHighLevel.qml | 96 --- .../ui/qml/Pages/PageSetupWizardLowLevel.qml | 65 -- .../qml/Pages/PageSetupWizardMediumLevel.qml | 60 -- .../ui/qml/Pages/PageSetupWizardVPNMode.qml | 65 -- client/ui/qml/Pages/PageShareConnection.qml | 87 --- client/ui/qml/Pages/PageSites.qml | 315 --------- client/ui/qml/Pages/PageStart.qml | 356 ---------- client/ui/qml/Pages/PageVPN.qml | 387 ----------- client/ui/qml/Pages/PageViewConfig.qml | 138 ---- .../ui/qml/Pages/Protocols/PageProtoCloak.qml | 194 ------ .../qml/Pages/Protocols/PageProtoOpenVPN.qml | 454 ------------- .../ui/qml/Pages/Protocols/PageProtoSftp.qml | 140 ---- .../Pages/Protocols/PageProtoShadowSocks.qml | 175 ----- .../Pages/Protocols/PageProtoTorWebSite.qml | 69 -- .../Pages/Protocols/PageProtoWireGuard.qml | 60 -- .../qml/Pages/Protocols/PageProtocolBase.qml | 13 - .../qml/Pages/Share/PageShareProtoAmnezia.qml | 145 ----- .../qml/Pages/Share/PageShareProtoCloak.qml | 99 --- .../qml/Pages/Share/PageShareProtoIkev2.qml | 131 ---- .../qml/Pages/Share/PageShareProtoOpenVPN.qml | 96 --- .../ui/qml/Pages/Share/PageShareProtoSftp.qml | 21 - .../Pages/Share/PageShareProtoShadowSocks.qml | 115 ---- .../Pages/Share/PageShareProtoTorWebSite.qml | 20 - .../Pages/Share/PageShareProtoWireGuard.qml | 103 --- .../qml/Pages/Share/PageShareProtocolBase.qml | 19 - client/ui/qml/main.qml | 387 ----------- client/ui/uilogic.cpp | 609 ------------------ client/ui/uilogic.h | 201 ------ 140 files changed, 285 insertions(+), 12695 deletions(-) delete mode 100644 client/ui/pages_logic/AdvancedServerSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/AdvancedServerSettingsLogic.h delete mode 100644 client/ui/pages_logic/AppSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/AppSettingsLogic.h delete mode 100644 client/ui/pages_logic/ClientInfoLogic.cpp delete mode 100644 client/ui/pages_logic/ClientInfoLogic.h delete mode 100644 client/ui/pages_logic/ClientManagementLogic.cpp delete mode 100644 client/ui/pages_logic/ClientManagementLogic.h delete mode 100644 client/ui/pages_logic/GeneralSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/GeneralSettingsLogic.h delete mode 100644 client/ui/pages_logic/NetworkSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/NetworkSettingsLogic.h delete mode 100644 client/ui/pages_logic/NewServerProtocolsLogic.cpp delete mode 100644 client/ui/pages_logic/NewServerProtocolsLogic.h delete mode 100644 client/ui/pages_logic/PageLogicBase.cpp delete mode 100644 client/ui/pages_logic/PageLogicBase.h delete mode 100644 client/ui/pages_logic/QrDecoderLogic.cpp delete mode 100644 client/ui/pages_logic/QrDecoderLogic.h delete mode 100644 client/ui/pages_logic/ServerConfiguringProgressLogic.cpp delete mode 100644 client/ui/pages_logic/ServerConfiguringProgressLogic.h delete mode 100644 client/ui/pages_logic/ServerContainersLogic.cpp delete mode 100644 client/ui/pages_logic/ServerContainersLogic.h delete mode 100644 client/ui/pages_logic/ServerListLogic.cpp delete mode 100644 client/ui/pages_logic/ServerListLogic.h delete mode 100644 client/ui/pages_logic/ServerSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/ServerSettingsLogic.h delete mode 100644 client/ui/pages_logic/ShareConnectionLogic.cpp delete mode 100644 client/ui/pages_logic/ShareConnectionLogic.h delete mode 100644 client/ui/pages_logic/SitesLogic.cpp delete mode 100644 client/ui/pages_logic/SitesLogic.h delete mode 100644 client/ui/pages_logic/StartPageLogic.cpp delete mode 100644 client/ui/pages_logic/StartPageLogic.h delete mode 100644 client/ui/pages_logic/ViewConfigLogic.cpp delete mode 100644 client/ui/pages_logic/ViewConfigLogic.h delete mode 100644 client/ui/pages_logic/VpnLogic.cpp delete mode 100644 client/ui/pages_logic/VpnLogic.h delete mode 100644 client/ui/pages_logic/WizardLogic.cpp delete mode 100644 client/ui/pages_logic/WizardLogic.h delete mode 100644 client/ui/pages_logic/protocols/CloakLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/CloakLogic.h delete mode 100644 client/ui/pages_logic/protocols/OpenVpnLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/OpenVpnLogic.h delete mode 100644 client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/OtherProtocolsLogic.h delete mode 100644 client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp delete mode 100644 client/ui/pages_logic/protocols/PageProtocolLogicBase.h delete mode 100644 client/ui/pages_logic/protocols/ShadowSocksLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/ShadowSocksLogic.h delete mode 100644 client/ui/pages_logic/protocols/WireGuardLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/WireGuardLogic.h delete mode 100644 client/ui/qml/Controls/BackButton.qml delete mode 100644 client/ui/qml/Controls/BasicButtonType.qml delete mode 100644 client/ui/qml/Controls/BlueButtonType.qml delete mode 100644 client/ui/qml/Controls/Caption.qml delete mode 100644 client/ui/qml/Controls/CheckBoxType.qml delete mode 100644 client/ui/qml/Controls/ComboBoxType.qml delete mode 100644 client/ui/qml/Controls/ContextMenu.qml delete mode 100644 client/ui/qml/Controls/FadeBehavior.qml delete mode 100644 client/ui/qml/Controls/FlickableType.qml delete mode 100644 client/ui/qml/Controls/ImageButtonType.qml delete mode 100644 client/ui/qml/Controls/LabelType.qml delete mode 100644 client/ui/qml/Controls/Logo.qml delete mode 100644 client/ui/qml/Controls/PopupWarning.qml delete mode 100644 client/ui/qml/Controls/PopupWithQuestion.qml delete mode 100644 client/ui/qml/Controls/PopupWithTextField.qml delete mode 100644 client/ui/qml/Controls/RadioButtonType.qml delete mode 100644 client/ui/qml/Controls/RichLabelType.qml delete mode 100644 client/ui/qml/Controls/SettingButtonType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionButtonCopyType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionButtonType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionContent.qml delete mode 100644 client/ui/qml/Controls/SvgButtonType.qml delete mode 100644 client/ui/qml/Controls/SvgImageType.qml delete mode 100644 client/ui/qml/Controls/TextAreaType.qml delete mode 100644 client/ui/qml/Controls/TextFieldType.qml delete mode 100644 client/ui/qml/Controls/UrlButtonType.qml delete mode 100644 client/ui/qml/Controls/VisibleBehavior.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml delete mode 100644 client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml delete mode 100644 client/ui/qml/Pages/InstallSettings/SelectContainer.qml delete mode 100644 client/ui/qml/Pages/PageAbout.qml delete mode 100644 client/ui/qml/Pages/PageAdvancedServerSettings.qml delete mode 100644 client/ui/qml/Pages/PageAppSetting.qml delete mode 100644 client/ui/qml/Pages/PageBase.qml delete mode 100644 client/ui/qml/Pages/PageClientManagement.qml delete mode 100644 client/ui/qml/Pages/PageGeneralSettings.qml delete mode 100644 client/ui/qml/Pages/PageNetworkSetting.qml delete mode 100644 client/ui/qml/Pages/PageNewServer.qml delete mode 100644 client/ui/qml/Pages/PageNewServerProtocols.qml delete mode 100644 client/ui/qml/Pages/PageQrDecoderIos.qml delete mode 100644 client/ui/qml/Pages/PageServerConfiguringProgress.qml delete mode 100644 client/ui/qml/Pages/PageServerContainers.qml delete mode 100644 client/ui/qml/Pages/PageServerList.qml delete mode 100644 client/ui/qml/Pages/PageServerSettings.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizard.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardHighLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardLowLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardMediumLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardVPNMode.qml delete mode 100644 client/ui/qml/Pages/PageShareConnection.qml delete mode 100644 client/ui/qml/Pages/PageSites.qml delete mode 100644 client/ui/qml/Pages/PageStart.qml delete mode 100644 client/ui/qml/Pages/PageVPN.qml delete mode 100644 client/ui/qml/Pages/PageViewConfig.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoCloak.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoSftp.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtocolBase.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoCloak.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoIkev2.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoSftp.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtocolBase.qml delete mode 100644 client/ui/qml/main.qml delete mode 100644 client/ui/uilogic.cpp delete mode 100644 client/ui/uilogic.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 47264fe60..ca5161cff 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -95,7 +95,6 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_BINARY_DIR}/version.h @@ -132,7 +131,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index c45867484..d14917f01 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -156,7 +156,7 @@ void AmneziaApplication::init() // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 #ifdef Q_OS_ANDROID - QObject::connect(qApp, &QApplication::applicationStateChanged, [](Qt::ApplicationState state) { + QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) { if (state == Qt::ApplicationActive) { if (qApp->clipboard()->mimeData()->formats().contains("text/html")) { QTextDocument doc; diff --git a/client/amnezia_application.h b/client/amnezia_application.h index e18fb70ce..40ea81b49 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -41,7 +41,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QGuiApp + #define AMNEZIA_BASE_CLASS QGuiApplication #else #define AMNEZIA_BASE_CLASS SingleApplication #define QAPPLICATION_CLASS QApplication diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 7ed83da10..4ca0e5dac 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -1,28 +1,26 @@ #include "ikev2_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include #include #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" +#include "utilities.h" - -Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, ErrorCode *errorCode) { Ikev2Configurator::ConnectionData connData; connData.host = credentials.hostName; @@ -32,26 +30,27 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12"; - QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\ - "-S -c \"IKEv2 VPN CA\" -n \"%1\" "\ - "-s \"O=IKEv2 VPN,CN=%1\" "\ - "-k rsa -g 3072 -v 120 "\ - "-d sql:/etc/ipsec.d -t \",,\" "\ - "--keyUsage digitalSignature,keyEncipherment "\ - "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") - .arg(connData.clientId); + QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) " + "-S -c \"IKEv2 VPN CA\" -n \"%1\" " + "-s \"O=IKEv2 VPN,CN=%1\" " + "-k rsa -g 3072 -v 120 " + "-d sql:/etc/ipsec.d -t \",,\" " + "--keyUsage digitalSignature,keyEncipherment " + "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") + .arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") - .arg(connData.password) - .arg(connData.clientId) - .arg(certFileName); + .arg(connData.password) + .arg(connData.clientId) + .arg(certFileName); e = serverController.runContainerScript(credentials, container, scriptExportCert); connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); - connData.caCert = serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); + connData.caCert = + serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); @@ -59,8 +58,8 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se return connData; } -QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { Q_UNUSED(containerConfig) @@ -120,4 +119,3 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData) return config; } - diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 3bc6676a4..bfde4a913 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -1,82 +1,89 @@ #include "openvpn_configurator.h" -#include + +#include +#include +#include #include #include #include -#include #include -#include -#include #include "containers/containers_defs.h" +#include "core/scripts_registry.h" #include "core/server_defs.h" #include "core/servercontroller.h" -#include "core/scripts_registry.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" +#include #include #include -#include -OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, + ErrorCode *errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::OpenSslFailed; + if (errorCode) + *errorCode = ErrorCode::OpenSslFailed; return connData; } - QString reqFileName = QString("%1/%2.req"). - arg(amnezia::protocols::openvpn::clientsDirPath). - arg(connData.clientId); + QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } e = signCert(container, credentials, connData.clientId); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); - connData.clientCert = serverController.getTextFileFromContainer(container, credentials, - QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); + connData.caCert = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::caCertPath, &e); + connData.clientCert = serverController.getTextFileFromContainer( + container, credentials, + QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.taKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); + connData.taKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::taKeyPath, &e); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::SshSftpFailureError; + if (errorCode) + *errorCode = ErrorCode::SshSftpFailureError; } return connData; } -QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { @@ -89,8 +96,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia if (config.contains("$OPENVPN_TA_KEY")) { config.replace("$OPENVPN_TA_KEY", connData.taKey); - } - else { + } else { config.replace("", ""); config.replace("", ""); } @@ -133,12 +139,11 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.replace("block-outside-dns", ""); #endif -#if (defined (MZ_MACOS) || defined(MZ_LINUX)) - QString dnsConf = QString( - "\nscript-security 2\n" - "up %1/update-resolv-conf.sh\n" - "down %1/update-resolv-conf.sh\n"). - arg(qApp->applicationDirPath()); +#if (defined(MZ_MACOS) || defined(MZ_LINUX)) + QString dnsConf = QString("\nscript-security 2\n" + "up %1/update-resolv-conf.sh\n" + "down %1/update-resolv-conf.sh\n") + .arg(qApp->applicationDirPath()); config.append(dnsConf); #endif @@ -168,23 +173,23 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) return QJsonDocument(json).toJson(); } -ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, - const ServerCredentials &credentials, QString clientId) +ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId) { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " - "easyrsa import-req %2/%3.req %3\"") - .arg(ContainerProps::containerToString(container)) - .arg(amnezia::protocols::openvpn::clientsDirPath) - .arg(clientId); + "easyrsa import-req %2/%3.req %3\"") + .arg(ContainerProps::containerToString(container)) + .arg(amnezia::protocols::openvpn::clientsDirPath) + .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " - "easyrsa sign-req client %2\"") - .arg(ContainerProps::containerToString(container)) - .arg(clientId); + "easyrsa sign-req client %2\"") + .arg(ContainerProps::containerToString(container)) + .arg(clientId); ServerController serverController(m_settings); - QStringList scriptList {script_import, script_sign}; - QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container)); + QStringList scriptList { script_import, script_sign }; + QString script = serverController.replaceVars(scriptList.join("\n"), + serverController.genVarsForScript(credentials, container)); return serverController.runScript(credentials, script); } @@ -194,18 +199,17 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() ConnectionData connData; connData.clientId = Utils::getRandomString(32); - int ret = 0; - int nVersion = 1; + int ret = 0; + int nVersion = 1; QByteArray clientIdUtf8 = connData.clientId.toUtf8(); - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); - RSA * rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); + RSA *rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); q_check_ptr(rsa); EVP_PKEY_assign_RSA(pKey, rsa); - // 2. set version of x509 req X509_REQ *x509_req = X509_REQ_new(); ret = X509_REQ_set_version(x509_req, nVersion); @@ -219,16 +223,14 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() // 3. set subject of x509 req X509_NAME *x509_name = X509_REQ_get_subject_name(x509_req); - X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, - (unsigned char *)"ORG", -1, -1, 0); - X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, - (unsigned char *)"", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0); X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0); // 4. set public key of x509 req ret = X509_REQ_set_pubkey(x509_req, pKey); - if (ret != 1){ + if (ret != 1) { qWarning() << "Could not set pubkey!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -236,8 +238,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // 5. set sign key of x509 req - ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length - if (ret <= 0){ + ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length + if (ret <= 0) { qWarning() << "Could not sign request!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -245,10 +247,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // save private key - BIO * bp_private = BIO_new(BIO_s_mem()); + BIO *bp_private = BIO_new(BIO_s_mem()); q_check_ptr(bp_private); - if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) - { + if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) { qFatal("PEM_write_bio_PrivateKey"); EVP_PKEY_free(pKey); BIO_free_all(bp_private); @@ -256,7 +257,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() return connData; } - const char * buffer = nullptr; + const char *buffer = nullptr; size_t size = BIO_get_mem_data(bp_private, &buffer); q_check_ptr(buffer); connData.privKey = QByteArray(buffer, size); @@ -270,7 +271,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() BIO_free_all(bp_private); // save req - BIO * bio_req = BIO_new(BIO_s_mem()); + BIO *bio_req = BIO_new(BIO_s_mem()); PEM_write_bio_X509_REQ(bio_req, x509_req); BUF_MEM *bio_buf; @@ -278,7 +279,6 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() connData.request = QByteArray(bio_buf->data, bio_buf->length); BIO_free(bio_req); - EVP_PKEY_free(pKey); // this will also free the rsa key return connData; diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index d94e44684..42e7eb476 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -1,24 +1,25 @@ #include "ssh_configurator.h" -#include + +#include +#include #include #include #include -#include #include #include -#include -#include -#include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "core/server_defs.h" #include "utilities.h" - -SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } QString SshConfigurator::convertOpenSShKey(const QString &key) @@ -28,23 +29,30 @@ QString SshConfigurator::convertOpenSShKey(const QString &key) p.setProcessChannelMode(QProcess::MergedChannels); QTemporaryFile tmp; -#ifdef QT_DEBUG + #ifdef QT_DEBUG tmp.setAutoRemove(false); -#endif + #endif tmp.open(); tmp.write(key.toUtf8()); tmp.close(); // ssh-keygen -p -P "" -N "" -m pem -f id_ssh -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName())); -#else + #else p.setProgram("ssh-keygen"); - p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName()); -#endif + p.setArguments(QStringList() << "-p" + << "-P" + << "" + << "-N" + << "" + << "-m" + << "pem" + << "-f" << tmp.fileName()); + #endif p.start(); p.waitForFinished(); @@ -65,22 +73,21 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) QProcess *p = new QProcess(); p->setProcessChannelMode(QProcess::SeparateChannels); -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p->setProcessEnvironment(prepareEnv()); p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); if (credentials.secretData.contains("PRIVATE KEY")) { // todo: connect by key -// p->setNativeArguments(QString("%1@%2") -// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); + // p->setNativeArguments(QString("%1@%2") + // .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); + } else { + p->setNativeArguments( + QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } - else { - p->setNativeArguments(QString("%1@%2 -pw %3") - .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); - } -#else + #else p->setProgram("/bin/bash"); -#endif + #endif p->startDetached(); #endif @@ -95,11 +102,11 @@ QProcessEnvironment SshConfigurator::prepareEnv() pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#else +#elif defined(Q_OS_MACX) pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); #endif env.insert("PATH", pathEnvVar); - //qDebug().noquote() << "ENV PATH" << pathEnvVar; + // qDebug().noquote() << "ENV PATH" << pathEnvVar; return env; } diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 54ee320cd..14059977b 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -1,30 +1,27 @@ #include "wireguard_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include - +#include #include #include #include -#include - #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" #include "settings.h" +#include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -36,37 +33,40 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() unsigned char buff[EDDSA_KEY_LENGTH]; int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH); - if (ret <=0) return connData; + if (ret <= 0) + return connData; - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH); - size_t keySize = EDDSA_KEY_LENGTH; // save private key unsigned char priv[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_private_key(pKey, priv, &keySize); - connData.clientPrivKey = QByteArray::fromRawData((char*)priv, keySize).toBase64(); + connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64(); // save public key unsigned char pub[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_public_key(pKey, pub, &keySize); - connData.clientPubKey = QByteArray::fromRawData((char*)pub, keySize).toBase64(); + connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64(); return connData; } WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) + DockerContainer container, + const QJsonObject &containerConfig, + ErrorCode *errorCode) { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::InternalError; + if (errorCode) + *errorCode = ErrorCode::InternalError; return connData; } @@ -96,22 +96,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Calc next IP address if (ips.isEmpty()) { nextIpNumber = "2"; - } - else { + } else { int next = ips.last().split(".").last().toInt() + 1; if (next > 254) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } nextIpNumber = QString::number(next); } } - QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); + QString subnetIp = + containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); { QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); if (l.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } l.removeLast(); @@ -121,52 +123,60 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer( + container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::wireguard::serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } // Add client to config - QString configPart = QString( - "[Peer]\n" - "PublicKey = %1\n" - "PresharedKey = %2\n" - "AllowedIPs = %3/32\n\n"). - arg(connData.clientPubKey). - arg(connData.pskKey). - arg(connData.clientIP); + QString configPart = QString("[Peer]\n" + "PublicKey = %1\n" + "PresharedKey = %2\n" + "AllowedIPs = %3/32\n\n") + .arg(connData.clientPubKey) + .arg(connData.pskKey) + .arg(connData.clientIP); e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); + protocols::wireguard::serverConfigPath, + libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - e = serverController.runScript(credentials, - serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", - serverController.genVarsForScript(credentials, container))); + e = serverController.runScript( + credentials, + serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick " + "strip /opt/amnezia/wireguard/wg0.conf)'", + serverController.genVarsForScript(credentials, container))); return connData; } -QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index c1b60966f..b3c65216c 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -1,7 +1,9 @@ #include "fileUtilites.h" -#include "platforms/ios/MobileUtils.h" #include +#include +#include +#include #include #ifdef Q_OS_ANDROID diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp index 5e9f094cf..7cc39824f 100644 --- a/client/platforms/android/androidutils.cpp +++ b/client/platforms/android/androidutils.cpp @@ -4,23 +4,25 @@ #include "androidutils.h" -#include +#include #include #include #include #include #include -#include #include +#include #include "jni.h" -namespace { - AndroidUtils* s_instance = nullptr; -} // namespace +namespace +{ + AndroidUtils *s_instance = nullptr; +} // namespace // static -QString AndroidUtils::GetDeviceName() { +QString AndroidUtils::GetDeviceName() +{ QJniEnvironment env; jclass BUILD = env->FindClass("android/os/Build"); jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); @@ -30,7 +32,7 @@ QString AndroidUtils::GetDeviceName() { return QString("Android Device"); } - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { return QString("Android Device"); } @@ -42,7 +44,8 @@ QString AndroidUtils::GetDeviceName() { }; // static -AndroidUtils* AndroidUtils::instance() { +AndroidUtils *AndroidUtils::instance() +{ if (!s_instance) { Q_ASSERT(qApp); s_instance = new AndroidUtils(qApp); @@ -51,19 +54,22 @@ AndroidUtils* AndroidUtils::instance() { return s_instance; } -AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) { +AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent) +{ Q_ASSERT(!s_instance); s_instance = this; } -AndroidUtils::~AndroidUtils() { +AndroidUtils::~AndroidUtils() +{ Q_ASSERT(s_instance == this); s_instance = nullptr; } // static -void AndroidUtils::dispatchToMainThread(std::function callback) { - QTimer* timer = new QTimer(); +void AndroidUtils::dispatchToMainThread(std::function callback) +{ + QTimer *timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=]() { @@ -74,8 +80,9 @@ void AndroidUtils::dispatchToMainThread(std::function callback) { } // static -QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQByteArrayFromJString - failed to parse data."; return QByteArray(); @@ -87,8 +94,9 @@ QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { } // static -QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQStringFromJString - failed to parse data."; return QString(); @@ -100,15 +108,14 @@ QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { } // static -QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { +QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data) +{ QByteArray raw(getQByteArrayFromJString(env, data)); QJsonParseError jsonError; QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError); if (QJsonParseError::NoError != jsonError.error) { - qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " - << jsonError.error << "Offset: " << jsonError.offset - << "Message: " << jsonError.errorString() - << "Data: " << raw; + qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error + << "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw; return QJsonObject(); } @@ -120,11 +127,13 @@ QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { return json.object(); } -QJniObject AndroidUtils::getActivity() { +QJniObject AndroidUtils::getActivity() +{ return QNativeInterface::QAndroidApplication::context(); } -int AndroidUtils::GetSDKVersion() { +int AndroidUtils::GetSDKVersion() +{ QJniEnvironment env; jclass versionClass = env->FindClass("android/os/Build$VERSION"); jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); @@ -133,15 +142,14 @@ int AndroidUtils::GetSDKVersion() { return sdk; } -QString AndroidUtils::GetManufacturer() { +QString AndroidUtils::GetManufacturer() +{ QJniEnvironment env; jclass buildClass = env->FindClass("android/os/Build"); - jfieldID manuFacturerField = - env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); - jstring value = - (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); + jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); + jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { qDebug() << "Failed to fetch MANUFACTURER"; @@ -154,21 +162,22 @@ QString AndroidUtils::GetManufacturer() { return res; } -void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) { - QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable) - .waitForFinished(); +void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) +{ + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished(); } -void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) { +void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) +{ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); } // Static // Creates a copy of the passed QByteArray in the JVM and passes back a ref -jbyteArray AndroidUtils::tojByteArray(const QByteArray& data) { +jbyteArray AndroidUtils::tojByteArray(const QByteArray &data) +{ QJniEnvironment env; jbyteArray out = env->NewByteArray(data.size()); - env->SetByteArrayRegion(out, 0, data.size(), - reinterpret_cast(data.constData())); + env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast(data.constData())); return out; } diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp index 2076280d7..e81e694b3 100644 --- a/client/platforms/android/androidvpnactivity.cpp +++ b/client/platforms/android/androidvpnactivity.cpp @@ -4,7 +4,6 @@ #include "androidvpnactivity.h" -#include #include #include #include @@ -13,19 +12,21 @@ #include "androidutils.h" #include "jni.h" -namespace { - AndroidVPNActivity* s_instance = nullptr; +namespace +{ + AndroidVPNActivity *s_instance = nullptr; constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity"; } -AndroidVPNActivity::AndroidVPNActivity() { +AndroidVPNActivity::AndroidVPNActivity() +{ AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"handleBackButton", "()Z", reinterpret_cast(handleBackButton)}, - {"onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage)}, - {"qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected)}, - {"qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, - {"onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage)} + JNINativeMethod methods[] { + { "handleBackButton", "()Z", reinterpret_cast(handleBackButton) }, + { "onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage) }, + { "qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected) }, + { "qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected) }, + { "onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage) } }; QJniObject javaClass(CLASSNAME); @@ -36,19 +37,22 @@ AndroidVPNActivity::AndroidVPNActivity() { }); } -void AndroidVPNActivity::maybeInit() { +void AndroidVPNActivity::maybeInit() +{ if (s_instance == nullptr) { s_instance = new AndroidVPNActivity(); } } // static -bool AndroidVPNActivity::handleBackButton(JNIEnv* env, jobject thiz) { +bool AndroidVPNActivity::handleBackButton(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); } -void AndroidVPNActivity::connectService() { +void AndroidVPNActivity::connectService() +{ QJniObject::callStaticMethod(CLASSNAME, "connectService", "()V"); } @@ -57,16 +61,16 @@ void AndroidVPNActivity::startQrCodeReader() QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); } -void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) { - QJniObject::callStaticMethod( - CLASSNAME, - "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", - QJniObject::fromString(fileContent).object(), - QJniObject::fromString(suggestedFilename).object()); +void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) +{ + QJniObject::callStaticMethod(CLASSNAME, "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", + QJniObject::fromString(fileContent).object(), + QJniObject::fromString(suggestedFilename).object()); } // static -AndroidVPNActivity* AndroidVPNActivity::instance() { +AndroidVPNActivity *AndroidVPNActivity::instance() +{ if (s_instance == nullptr) { AndroidVPNActivity::maybeInit(); } @@ -75,21 +79,19 @@ AndroidVPNActivity* AndroidVPNActivity::instance() { } // static -void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) { +void AndroidVPNActivity::sendToService(ServiceAction type, const QString &data) +{ int messageType = (int)type; - QJniObject::callStaticMethod( - CLASSNAME, - "sendToService", "(ILjava/lang/String;)V", - static_cast(messageType), - QJniObject::fromString(data).object()); + QJniObject::callStaticMethod(CLASSNAME, "sendToService", "(ILjava/lang/String;)V", + static_cast(messageType), QJniObject::fromString(data).object()); } // static -void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, - jint messageType, jstring body) { +void AndroidVPNActivity::onServiceMessage(JNIEnv *env, jobject thiz, jint messageType, jstring body) +{ Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(body, nullptr); + const char *buffer = env->GetStringUTFChars(body, nullptr); if (!buffer) { return; } @@ -97,38 +99,23 @@ void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, QString parcelBody(buffer); env->ReleaseStringUTFChars(body, buffer); AndroidUtils::dispatchToMainThread([messageType, parcelBody] { - AndroidVPNActivity::instance()->handleServiceMessage(messageType, - parcelBody); + AndroidVPNActivity::instance()->handleServiceMessage(messageType, parcelBody); }); } -void AndroidVPNActivity::handleServiceMessage(int code, const QString& data) { +void AndroidVPNActivity::handleServiceMessage(int code, const QString &data) +{ auto mode = (ServiceEvents)code; switch (mode) { - case ServiceEvents::EVENT_INIT: - emit eventInitialized(data); - break; - case ServiceEvents::EVENT_CONNECTED: - emit eventConnected(data); - break; - case ServiceEvents::EVENT_DISCONNECTED: - emit eventDisconnected(data); - break; - case ServiceEvents::EVENT_STATISTIC_UPDATE: - emit eventStatisticUpdate(data); - break; - case ServiceEvents::EVENT_BACKEND_LOGS: - emit eventBackendLogs(data); - break; - case ServiceEvents::EVENT_ACTIVATION_ERROR: - emit eventActivationError(data); - break; - case ServiceEvents::EVENT_CONFIG_IMPORT: - emit eventConfigImport(data); - break; - default: - Q_ASSERT(false); + case ServiceEvents::EVENT_INIT: emit eventInitialized(data); break; + case ServiceEvents::EVENT_CONNECTED: emit eventConnected(data); break; + case ServiceEvents::EVENT_DISCONNECTED: emit eventDisconnected(data); break; + case ServiceEvents::EVENT_STATISTIC_UPDATE: emit eventStatisticUpdate(data); break; + case ServiceEvents::EVENT_BACKEND_LOGS: emit eventBackendLogs(data); break; + case ServiceEvents::EVENT_ACTIVATION_ERROR: emit eventActivationError(data); break; + case ServiceEvents::EVENT_CONFIG_IMPORT: emit eventConfigImport(data); break; + default: Q_ASSERT(false); } } @@ -137,22 +124,21 @@ void AndroidVPNActivity::handleActivityMessage(int code, const QString &data) auto mode = (UIEvents)code; switch (mode) { - case UIEvents::QR_CODED_DECODED: - emit eventQrCodeReceived(data); - break; - default: - Q_ASSERT(false); + case UIEvents::QR_CODED_DECODED: emit eventQrCodeReceived(data); break; + default: Q_ASSERT(false); } } -void AndroidVPNActivity::onServiceConnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceConnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidVPNActivity::instance()->serviceConnected(); } -void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceDisconnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); @@ -162,7 +148,7 @@ void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message) { Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(message, nullptr); + const char *buffer = env->GetStringUTFChars(message, nullptr); if (!buffer) { return; } diff --git a/client/resources.qrc b/client/resources.qrc index 782b64462..d0ff176f7 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -56,75 +56,6 @@ server_scripts/wireguard/template.conf server_scripts/website_tor/configure_container.sh server_scripts/website_tor/run_container.sh - ui/qml/main.qml - ui/qml/Pages/PageBase.qml - ui/qml/Pages/PageAppSetting.qml - ui/qml/Pages/PageGeneralSettings.qml - ui/qml/Pages/PageNetworkSetting.qml - ui/qml/Pages/PageNewServer.qml - ui/qml/Pages/PageServerConfiguringProgress.qml - ui/qml/Pages/PageNewServerProtocols.qml - ui/qml/Pages/PageServerList.qml - ui/qml/Pages/PageServerContainers.qml - ui/qml/Pages/PageServerSettings.qml - ui/qml/Pages/PageSetupWizard.qml - ui/qml/Pages/PageSetupWizardHighLevel.qml - ui/qml/Pages/PageSetupWizardLowLevel.qml - ui/qml/Pages/PageSetupWizardMediumLevel.qml - ui/qml/Pages/PageSetupWizardVPNMode.qml - ui/qml/Pages/PageShareConnection.qml - ui/qml/Pages/PageSites.qml - ui/qml/Pages/PageStart.qml - ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageAbout.qml - ui/qml/Pages/PageQrDecoderIos.qml - ui/qml/Pages/PageViewConfig.qml - ui/qml/Pages/PageClientManagement.qml - ui/qml/Pages/ClientInfo/PageClientInfoBase.qml - ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml - ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml - ui/qml/Pages/Protocols/PageProtoCloak.qml - ui/qml/Pages/Protocols/PageProtoOpenVPN.qml - ui/qml/Pages/Protocols/PageProtoShadowSocks.qml - ui/qml/Pages/Protocols/PageProtoSftp.qml - ui/qml/Pages/Protocols/PageProtoTorWebSite.qml - ui/qml/Pages/Protocols/PageProtocolBase.qml - ui/qml/Pages/Protocols/PageProtoWireGuard.qml - ui/qml/Pages/InstallSettings/InstallSettingsBase.qml - ui/qml/Pages/InstallSettings/SelectContainer.qml - ui/qml/Pages/Share/PageShareProtoCloak.qml - ui/qml/Pages/Share/PageShareProtocolBase.qml - ui/qml/Pages/Share/PageShareProtoOpenVPN.qml - ui/qml/Pages/Share/PageShareProtoSftp.qml - ui/qml/Pages/Share/PageShareProtoShadowSocks.qml - ui/qml/Pages/Share/PageShareProtoTorWebSite.qml - ui/qml/Pages/Share/PageShareProtoAmnezia.qml - ui/qml/Pages/Share/PageShareProtoWireGuard.qml - ui/qml/Pages/Share/PageShareProtoIkev2.qml - ui/qml/Controls/BasicButtonType.qml - ui/qml/Controls/BlueButtonType.qml - ui/qml/Controls/CheckBoxType.qml - ui/qml/Controls/ComboBoxType.qml - ui/qml/Controls/ImageButtonType.qml - ui/qml/Controls/LabelType.qml - ui/qml/Controls/RadioButtonType.qml - ui/qml/Controls/SettingButtonType.qml - ui/qml/Controls/ShareConnectionButtonType.qml - ui/qml/Controls/ShareConnectionContent.qml - ui/qml/Controls/TextFieldType.qml - ui/qml/Controls/RichLabelType.qml - ui/qml/Controls/SvgImageType.qml - ui/qml/Controls/FlickableType.qml - ui/qml/Controls/UrlButtonType.qml - ui/qml/Controls/TextAreaType.qml - ui/qml/Controls/ContextMenu.qml - ui/qml/Controls/FadeBehavior.qml - ui/qml/Controls/VisibleBehavior.qml - ui/qml/Controls/Caption.qml - ui/qml/Controls/Logo.qml - ui/qml/Controls/BackButton.qml - ui/qml/Controls/ShareConnectionButtonCopyType.qml - ui/qml/Controls/SvgButtonType.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir server_scripts/check_server_is_busy.sh @@ -161,10 +92,6 @@ images/svg/control_point_black_24dp.svg images/svg/settings_suggest_black_24dp.svg server_scripts/website_tor/Dockerfile - ui/qml/Controls/PopupWithQuestion.qml - ui/qml/Pages/PageAdvancedServerSettings.qml - ui/qml/Controls/PopupWarning.qml - ui/qml/Controls/PopupWithTextField.qml server_scripts/check_user_in_sudo.sh ui/qml/Controls2/BasicButtonType.qml ui/qml/Controls2/TextFieldWithHeaderType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index d38130868..0754b024e 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -1,6 +1,10 @@ #include "connectionController.h" -#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "core/errorstrings.h" diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 84d2ebf29..46a1b1fdd 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,6 +1,11 @@ #include "pageController.h" -#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif + #ifdef Q_OS_ANDROID #include "../../platforms/android/androidutils.h" #include diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 203d53bdb..7c7402e02 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,10 +2,6 @@ #include -#ifdef Q_OS_IOS - #include -#endif - #include "fileUtilites.h" #include "logger.h" #include "ui/qautostart.h" diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 16225aa88..5821b3719 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -1,12 +1,9 @@ #include "sitesController.h" +#include #include #include -#ifdef Q_OS_IOS - #include -#endif - #include "fileUtilites.h" #include "utilities.h" diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp deleted file mode 100644 index a96827b67..000000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "AdvancedServerSettingsLogic.h" - -#include "VpnLogic.h" -#include "ui/uilogic.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" - -AdvancedServerSettingsLogic::AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent): PageLogicBase(uiLogic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearVisible{true}, - m_pushButtonClearText{tr("Clear server from Amnezia software")} -{ -} - -void AdvancedServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5").arg(userName, - userName.isEmpty() ? "" : "@", - hostName, - port.isEmpty() ? "" : ":", - port); - - set_labelServerText(name); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void AdvancedServerSettingsLogic::onPushButtonClearServerClicked() -{ - set_pageEnabled(false); - set_pushButtonClearText(tr("Uninstalling Amnezia software...")); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - uiLogic()->pageLogic()->onDisconnect(); - } - - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); - if (errorCode) { - emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } else { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(tr("Amnezia server successfully uninstalled")); - } - - m_settings->setContainers(uiLogic()->m_selectedServerIndex, {}); - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - - set_pageEnabled(true); - set_pushButtonClearText(tr("Clear server from Amnezia software")); -} - -void AdvancedServerSettingsLogic::onPushButtonScanServerClicked() -{ - set_labelWaitInfoVisible(false); - set_pageEnabled(false); - - bool isServerCreated; - auto containersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while scanning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } - auto newContainersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - if (containersCount != newContainersCount) { - emit uiLogic()->showWarningMessage(tr("All containers installed on the server are added to the GUI")); - } else { - emit uiLogic()->showWarningMessage(tr("No installed containers found on the server")); - } - - - onUpdatePage(); - set_pageEnabled(true); -} diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.h b/client/ui/pages_logic/AdvancedServerSettingsLogic.h deleted file mode 100644 index 692968f1b..000000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ADVANCEDSERVERSETTINGSLOGIC_H -#define ADVANCEDSERVERSETTINGSLOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AdvancedServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - - AUTO_PROPERTY(QString, pushButtonClearText) - AUTO_PROPERTY(bool, pushButtonClearVisible) - - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - explicit AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AdvancedServerSettingsLogic() = default; - - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonClearServerClicked(); - Q_INVOKABLE void onPushButtonScanServerClicked(); -}; - -#endif // ADVANCEDSERVERSETTINGSLOGIC_H diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp deleted file mode 100644 index 044b97b80..000000000 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "AppSettingsLogic.h" - -#include "logger.h" -#include "version.h" -#include "ui/qautostart.h" -#include "ui/uilogic.h" - -#include -#include -#include -#include - -#ifdef Q_OS_IOS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -AppSettingsLogic::AppSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxAutostartChecked{false}, - m_checkBoxAutoConnectChecked{false}, - m_checkBoxStartMinimizedChecked{false}, - m_checkBoxSaveLogsChecked{false} -{ - -} - -void AppSettingsLogic::onUpdatePage() -{ - set_checkBoxAutostartChecked(Autostart::isAutostart()); - set_checkBoxAutoConnectChecked(m_settings->isAutoConnect()); - set_checkBoxStartMinimizedChecked(m_settings->isStartMinimized()); - set_checkBoxSaveLogsChecked(m_settings->isSaveLogs()); - - QString ver = QString("%1: %2 (%3)") - .arg(tr("Software version")) - .arg(QString(APP_MAJOR_VERSION)) - .arg(__DATE__); - set_labelVersionText(ver); -} - -void AppSettingsLogic::onCheckBoxAutostartToggled(bool checked) -{ - if (!checked) { - set_checkBoxAutoConnectChecked(false); - } - Autostart::setAutostart(checked); -} - -void AppSettingsLogic::onCheckBoxAutoconnectToggled(bool checked) -{ - m_settings->setAutoConnect(checked); -} - -void AppSettingsLogic::onCheckBoxStartMinimizedToggled(bool checked) -{ - m_settings->setStartMinimized(checked); -} - -void AppSettingsLogic::onCheckBoxSaveLogsCheckedToggled(bool checked) -{ - m_settings->setSaveLogs(checked); -} - -void AppSettingsLogic::onPushButtonOpenLogsClicked() -{ - Logger::openLogsFolder(); -} - -void AppSettingsLogic::onPushButtonExportLogsClicked() -{ - uiLogic()->saveTextFile(tr("Save log"), "AmneziaVPN.log", ".log", Logger::getLogFile()); -} - -void AppSettingsLogic::onPushButtonClearLogsClicked() -{ - Logger::clearLogs(); - Logger::clearServiceLogs(); -} - -void AppSettingsLogic::onPushButtonBackupAppConfigClicked() -{ - uiLogic()->saveTextFile("Backup application config", "AmneziaVPN.backup", ".backup", m_settings->backupAppConfig()); -} - -void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - - if (fileName.isEmpty()) return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), - fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - bool ok = m_settings->restoreAppConfig(data); - if (ok) { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } else { - emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); - } -} - diff --git a/client/ui/pages_logic/AppSettingsLogic.h b/client/ui/pages_logic/AppSettingsLogic.h deleted file mode 100644 index fc9f0da7e..000000000 --- a/client/ui/pages_logic/AppSettingsLogic.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef APP_SETTINGS_LOGIC_H -#define APP_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AppSettingsLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, checkBoxAutostartChecked) - AUTO_PROPERTY(bool, checkBoxAutoConnectChecked) - AUTO_PROPERTY(bool, checkBoxStartMinimizedChecked) - AUTO_PROPERTY(bool, checkBoxSaveLogsChecked) - AUTO_PROPERTY(QString, labelVersionText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onCheckBoxAutostartToggled(bool checked); - Q_INVOKABLE void onCheckBoxAutoconnectToggled(bool checked); - Q_INVOKABLE void onCheckBoxStartMinimizedToggled(bool checked); - Q_INVOKABLE void onCheckBoxSaveLogsCheckedToggled(bool checked); - Q_INVOKABLE void onPushButtonOpenLogsClicked(); - Q_INVOKABLE void onPushButtonExportLogsClicked(); - Q_INVOKABLE void onPushButtonClearLogsClicked(); - - Q_INVOKABLE void onPushButtonBackupAppConfigClicked(); - Q_INVOKABLE void onPushButtonRestoreAppConfigClicked(); - - -public: - explicit AppSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AppSettingsLogic() = default; - -}; -#endif // APP_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ClientInfoLogic.cpp b/client/ui/pages_logic/ClientInfoLogic.cpp deleted file mode 100644 index dca0b9fdd..000000000 --- a/client/ui/pages_logic/ClientInfoLogic.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "ClientInfoLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -namespace { - bool isErrorOccured(ErrorCode error) { - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error)); - return true; - } - return false; - } -} - -ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientInfoLogic::setCurrentClientId(int index) -{ - m_currentClientIndex = index; -} - -void ClientInfoLogic::onUpdatePage() -{ - set_pageContentVisible(false); - set_busyIndicatorIsRunning(true); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const QString containerNameString = ContainerProps::containerHumanNames().value(container); - set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString); - - const QVector protocols = ContainerProps::protocolsForContainer(container); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - - set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString()); - if (currentMainProtocol == Proto::OpenVpn) { - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString(); - - if (certData.isEmpty() && !certId.isEmpty()) { - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'") - .arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container)); - ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut); - certData = stdOut; - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->closePage(); - return; - } - } - set_labelOpenVpnCertId(certId); - set_textAreaOpenVpnCertData(certData); - } else if (currentMainProtocol == Proto::WireGuard) { - set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString()); - } - } - set_pageContentVisible(true); - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onLineEditNameAliasEditingFinished() -{ - set_busyIndicatorIsRunning(true); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole); - - const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const QVector protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - const QJsonObject clientsTable = model->getContent(currentMainProtocol); - ErrorCode error = setClientsList(credentials, - selectedContainer, - currentMainProtocol, - clientsTable); - isErrorOccured(error); - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeOpenVpnCertificateClicked() -{ - set_busyIndicatorIsRunning(true); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '" - "cd /opt/amnezia/openvpn ;\\" - "easyrsa revoke %1 ;\\" - "easyrsa gen-crl ;\\" - "cp pki/crl.pem .'").arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, - serverController.genVarsForScript(credentials, container)); - auto error = serverController.runScript(credentials, script); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->goToPage(Page::ServerSettings); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::OpenVpn); - error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container); - error = serverController.startupContainerWorker(credentials, container, containerConfig); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeWireGuardKeyClicked() -{ - set_busyIndicatorIsRunning(true); - ErrorCode error; - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ServerController serverController(m_settings); - - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString(); - - auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); - for (auto §ion : configSections) { - if (section.contains(key)) { - configSections.removeOne(section); - } - } - QString newWireGuardConfig = configSections.join("["); - newWireGuardConfig.insert(0, "["); - error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, - protocols::wireguard::serverConfigPath, - libssh::SftpOverwriteMode::SftpOverwriteExisting); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::WireGuard); - error = setClientsList(credentials, container, Proto::WireGuard, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'"; - error = serverController.runScript(credentials, - serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns) -{ - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString); - ServerController serverController(m_settings); - ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile); - return error; -} diff --git a/client/ui/pages_logic/ClientInfoLogic.h b/client/ui/pages_logic/ClientInfoLogic.h deleted file mode 100644 index 5ba19887d..000000000 --- a/client/ui/pages_logic/ClientInfoLogic.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CLIENTINFOLOGIC_H -#define CLIENTINFOLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientInfoLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditNameAliasText) - AUTO_PROPERTY(QString, labelOpenVpnCertId) - AUTO_PROPERTY(QString, textAreaOpenVpnCertData) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(QString, textAreaWireGuardKeyData) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - AUTO_PROPERTY(bool, pageContentVisible); - -public: - ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientInfoLogic() = default; - - void setCurrentClientId(int index); - -public slots: - void onUpdatePage() override; - void onLineEditNameAliasEditingFinished(); - void onRevokeOpenVpnCertificateClicked(); - void onRevokeWireGuardKeyClicked(); - -private: - ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns); - - int m_currentClientIndex; -}; - -#endif // CLIENTINFOLOGIC_H diff --git a/client/ui/pages_logic/ClientManagementLogic.cpp b/client/ui/pages_logic/ClientManagementLogic.cpp deleted file mode 100644 index 673ee9aba..000000000 --- a/client/ui/pages_logic/ClientManagementLogic.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "ClientManagementLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/pages_logic/ClientInfoLogic.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientManagementLogic::onUpdatePage() -{ - set_busyIndicatorIsRunning(true); - - qobject_cast(uiLogic()->clientManagementModel())->clearData(); - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); - - QJsonObject clients; - - auto protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - m_currentMainProtocol = protocols.front(); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients); - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("An error occurred while getting the list of clients.") + "\n" + errorString(error)); - set_busyIndicatorIsRunning(false); - return; - } - } - QVector clientsArray; - for (auto &clientId : clients.keys()) { - clientsArray.push_back(clients[clientId].toObject()); - } - qobject_cast(uiLogic()->clientManagementModel())->setContent(clientsArray); - - set_busyIndicatorIsRunning(false); -} - -void ClientManagementLogic::onClientItemClicked(int index) -{ - uiLogic()->pageLogic()->setCurrentClientId(index); - emit uiLogic()->goToClientInfoPage(m_currentMainProtocol); -} - -ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns) -{ - ErrorCode error = ErrorCode::NoError; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - - ServerController serverController(m_settings); - - const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString); - const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object(); - int count = 0; - - if (mainProtocol == Proto::OpenVpn) { - const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; - QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container)); - error = serverController.runScript(credentials, script, cbReadStdOut); - if (error != ErrorCode::NoError) { - return error; - } - - if (!stdOut.isEmpty()) { - QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); - certsIds.removeAll("AmneziaReq.crt"); - - for (auto &openvpnCertId : certsIds) { - openvpnCertId.replace(".crt", ""); - if (!clientsTable.contains(openvpnCertId)) { - - QJsonObject client; - client["openvpnCertId"] = openvpnCertId; - client["clientName"] = QString("Client %1").arg(count); - client["openvpnCertData"] = ""; - clientsTable[openvpnCertId] = client; - count++; - } - } - } - } else if (mainProtocol == Proto::WireGuard) { - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - - auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts); - QStringList wireguardKeys; - for (const auto &line : configLines) { - auto configPair = line.split(" = ", Qt::SkipEmptyParts); - if (configPair.front() == "PublicKey") { - wireguardKeys.push_back(configPair.back()); - } - } - - for (auto &wireguardKey : wireguardKeys) { - if (!clientsTable.contains(wireguardKey)) { - QJsonObject client; - client["clientName"] = QString("Client %1").arg(count); - client["wireguardPublicKey"] = wireguardKey; - clientsTable[wireguardKey] = client; - count++; - } - } - } - - const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson(); - if (clientsTableString != newClientsTableString) { - error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); - } - - if (error != ErrorCode::NoError) { - return error; - } - - clietns = clientsTable; - - return error; -} diff --git a/client/ui/pages_logic/ClientManagementLogic.h b/client/ui/pages_logic/ClientManagementLogic.h deleted file mode 100644 index 9c1817160..000000000 --- a/client/ui/pages_logic/ClientManagementLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CLIENTMANAGMENTLOGIC_H -#define CLIENTMANAGMENTLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientManagementLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - -public: - ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientManagementLogic() = default; - -public slots: - void onUpdatePage() override; - void onClientItemClicked(int index); - -private: - ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns); - - amnezia::Proto m_currentMainProtocol; -}; - -#endif // CLIENTMANAGMENTLOGIC_H diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp deleted file mode 100644 index 141308f40..000000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "GeneralSettingsLogic.h" -#include "ShareConnectionLogic.h" - -#include "../models/protocols_model.h" -#include "../uilogic.h" - -GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) -{ -} - -void GeneralSettingsLogic::onUpdatePage() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - set_existsAnyServer(m_settings->serversCount() > 0); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex())); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsServerSettingsClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - emit uiLogic()->goToPage(Page::ServerSettings); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - - // qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - uiLogic()->m_selectedDockerContainer); - emit uiLogic()->goToPage(Page::ShareConnection); -} diff --git a/client/ui/pages_logic/GeneralSettingsLogic.h b/client/ui/pages_logic/GeneralSettingsLogic.h deleted file mode 100644 index a0cff3337..000000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef GENERAL_SETTINGS_LOGIC_H -#define GENERAL_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class GeneralSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonGeneralSettingsShareConnectionEnable) - AUTO_PROPERTY(bool, existsAnyServer) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonGeneralSettingsServerSettingsClicked(); - Q_INVOKABLE void onPushButtonGeneralSettingsShareConnectionClicked(); - -public: - explicit GeneralSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~GeneralSettingsLogic() = default; - -}; -#endif // GENERAL_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NetworkSettingsLogic.cpp b/client/ui/pages_logic/NetworkSettingsLogic.cpp deleted file mode 100644 index 1315aa108..000000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "NetworkSettingsLogic.h" - -#include "version.h" -#include "utilities.h" -#include "settings.h" - -NetworkSettingsLogic::NetworkSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxUseAmneziaDnsChecked{false}, - m_ipAddressRegex{Utils::ipAddressRegExp()} -{ - -} - -void NetworkSettingsLogic::onUpdatePage() -{ - set_checkBoxUseAmneziaDnsChecked(m_settings->useAmneziaDns()); - - set_lineEditDns1Text(m_settings->primaryDns()); - set_lineEditDns2Text(m_settings->secondaryDns()); -} - -void NetworkSettingsLogic::onLineEditDns1EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setPrimaryDns(text); - } -} - -void NetworkSettingsLogic::onLineEditDns2EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setSecondaryDns(text); - } -} - -void NetworkSettingsLogic::onPushButtonResetDns1Clicked() -{ - m_settings->setPrimaryDns(m_settings->cloudFlareNs1); - onUpdatePage(); -} - -void NetworkSettingsLogic::onPushButtonResetDns2Clicked() -{ - m_settings->setSecondaryDns(m_settings->cloudFlareNs2); - onUpdatePage(); -} - -void NetworkSettingsLogic::onCheckBoxUseAmneziaDnsToggled(bool checked) -{ - m_settings->setUseAmneziaDns(checked); -} diff --git a/client/ui/pages_logic/NetworkSettingsLogic.h b/client/ui/pages_logic/NetworkSettingsLogic.h deleted file mode 100644 index 237706eb3..000000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef NETWORK_SETTINGS_LOGIC_H -#define NETWORK_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class NetworkSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, checkBoxUseAmneziaDnsChecked) - - AUTO_PROPERTY(QString, lineEditDns1Text) - AUTO_PROPERTY(QString, lineEditDns2Text) - READONLY_PROPERTY(QRegularExpression, ipAddressRegex) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onLineEditDns1EditFinished(const QString& text); - Q_INVOKABLE void onLineEditDns2EditFinished(const QString& text); - Q_INVOKABLE void onPushButtonResetDns1Clicked(); - Q_INVOKABLE void onPushButtonResetDns2Clicked(); - - Q_INVOKABLE void onCheckBoxUseAmneziaDnsToggled(bool checked); - -public: - explicit NetworkSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NetworkSettingsLogic() = default; - -}; -#endif // NETWORK_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.cpp b/client/ui/pages_logic/NewServerProtocolsLogic.cpp deleted file mode 100644 index a1db75655..000000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "NewServerProtocolsLogic.h" -#include "../uilogic.h" - -NewServerProtocolsLogic::NewServerProtocolsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarConnectionMinimum{0}, - m_progressBarConnectionMaximum{100} -{ -} - - -void NewServerProtocolsLogic::onUpdatePage() -{ - set_progressBarConnectionMinimum(0); - set_progressBarConnectionMaximum(300); -} - -void NewServerProtocolsLogic::onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(c); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(c) }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, QString::number(port) }, - { config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto) }} - } - }; - - QPair container(c, config); - - uiLogic()->installServer(container); -} - diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.h b/client/ui/pages_logic/NewServerProtocolsLogic.h deleted file mode 100644 index abf3d1029..000000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef NEW_SERVER_PROTOCOLS_LOGIC_H -#define NEW_SERVER_PROTOCOLS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class NewServerProtocolsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarConnectionMinimum) - AUTO_PROPERTY(double, progressBarConnectionMaximum) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp); - -public: - explicit NewServerProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NewServerProtocolsLogic() = default; - -}; -#endif // NEW_SERVER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/PageLogicBase.cpp b/client/ui/pages_logic/PageLogicBase.cpp deleted file mode 100644 index 05c4e3a1b..000000000 --- a/client/ui/pages_logic/PageLogicBase.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "PageLogicBase.h" - -#include "ui/uilogic.h" -#include "settings.h" -#include "configurators/vpn_configurator.h" - -PageLogicBase::PageLogicBase(UiLogic *logic, QObject *parent): - QObject(parent), - m_pageEnabled{true}, - m_uiLogic(logic) -{ - m_settings = logic->m_settings; - m_configurator = logic->m_configurator; -} - - diff --git a/client/ui/pages_logic/PageLogicBase.h b/client/ui/pages_logic/PageLogicBase.h deleted file mode 100644 index 6616de9d9..000000000 --- a/client/ui/pages_logic/PageLogicBase.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef PAGE_LOGIC_BASE_H -#define PAGE_LOGIC_BASE_H - -#include "../pages.h" -#include "../property_helper.h" - -using namespace PageEnumNS; - -class UiLogic; -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase : public QObject -{ - Q_OBJECT - AUTO_PROPERTY(bool, pageEnabled) - -public: - explicit PageLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageLogicBase() = default; - - Q_INVOKABLE virtual void onUpdatePage() {} - -protected: - UiLogic *m_uiLogic; - UiLogic *uiLogic() const { return m_uiLogic; } - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - -signals: - void updatePage(); -}; -#endif // PAGE_LOGIC_BASE_H diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp deleted file mode 100644 index e1845c77b..000000000 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "QrDecoderLogic.h" - -#include "ui/uilogic.h" -#include "ui/pages_logic/StartPageLogic.h" - -#ifdef Q_OS_ANDROID -#include -#include -#include "../../platforms/android/androidutils.h" -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -namespace { - QrDecoderLogic* mInstance = nullptr; - constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity"; -} - -QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - mInstance = this; - - #if (defined(Q_OS_ANDROID)) - AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewDataChunk)}, - }; - - QJniObject javaClass(CLASSNAME); - QJniEnvironment env; - jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); - env->DeleteLocalRef(objectClass); - }); - #endif -} - -void QrDecoderLogic::stopDecodingQr() -{ - #if (defined(Q_OS_ANDROID)) - QJniObject::callStaticMethod(CLASSNAME, "stopQrCodeReader", "()V"); - #endif - - emit stopDecode(); -} - -#ifdef Q_OS_ANDROID -void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data) -{ - Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - return; - } - - QString parcelBody(buffer); - env->ReleaseStringUTFChars(data, buffer); - - if (mInstance != nullptr) { - if (!mInstance->m_detectingEnabled) { - mInstance->onUpdatePage(); - } - mInstance->onDetectedQrCode(parcelBody); - } -} -#endif - -void QrDecoderLogic::onUpdatePage() -{ - m_chunks.clear(); - set_detectingEnabled(true); - set_totalChunksCount(0); - set_receivedChunksCount(0); - emit startDecode(); -} - -void QrDecoderLogic::onDetectedQrCode(const QString &code) -{ - //qDebug() << code; - if (!detectingEnabled()) return; - - // check if chunk received - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QDataStream s(&ba, QIODevice::ReadOnly); - qint16 magic; s >> magic; - - if (magic == amnezia::qrMagicCode) { - quint8 chunksCount; s >> chunksCount; - if (totalChunksCount() != chunksCount) { - m_chunks.clear(); - } - - set_totalChunksCount(chunksCount); - - quint8 chunkId; s >> chunkId; - s >> m_chunks[chunkId]; - set_receivedChunksCount(m_chunks.size()); - - if (m_chunks.size() == totalChunksCount()) { - QByteArray data; - - for (int i = 0; i < totalChunksCount(); ++i) { - data.append(m_chunks.value(i)); - } - - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(data); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } else { - m_chunks.clear(); - set_totalChunksCount(0); - set_receivedChunksCount(0); - } - } - } else { - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(ba); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } - } -} diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h deleted file mode 100644 index 2b24bf27e..000000000 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef QR_DECODER_LOGIC_H -#define QR_DECODER_LOGIC_H - -#include "PageLogicBase.h" - -#ifdef Q_OS_ANDROID -#include "jni.h" -#endif - -class UiLogic; - -class QrDecoderLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, detectingEnabled) - AUTO_PROPERTY(int, totalChunksCount) - AUTO_PROPERTY(int, receivedChunksCount) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onDetectedQrCode(const QString &code); - -#ifdef Q_OS_ANDROID - static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data); -#endif - -public: - explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~QrDecoderLogic() = default; - -private: - void stopDecodingQr(); - -signals: - void startDecode(); - void stopDecode(); - -private: - QMap m_chunks; -}; -#endif // QR_DECODER_LOGIC_H diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp deleted file mode 100644 index 2b42fac97..000000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "ServerConfiguringProgressLogic.h" -#include "version.h" -#include "core/errorstrings.h" -#include -#include - -#include "core/servercontroller.h" - -ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarValue{0}, - m_labelWaitInfoVisible{true}, - m_labelWaitInfoText{tr("Please wait, configuring process may take up to 5 minutes")}, - m_progressBarVisible{true}, - m_progressBarMaximum{100}, - m_progressBarTextVisible{true}, - m_progressBarText{tr("Configuring...")}, - m_labelServerBusyVisible{false}, - m_labelServerBusyText{""} -{ - -} - -void ServerConfiguringProgressLogic::onUpdatePage() -{ - set_progressBarValue(0); -} - - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action) -{ - PageFunc page; - page.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ButtonFunc noButton; - LabelFunc noWaitInfo; - ProgressFunc progress; - progress.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarVisible(visible); - }; - - progress.setValueFunc = [this] (int value) -> void { - set_progressBarValue(value); - }; - progress.getValueFunc = [this] (void) -> int { - return progressBarValue(); - }; - progress.getMaximumFunc = [this] (void) -> int { - return progressBarMaximum(); - }; - - LabelFunc busyInfo; - busyInfo.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfo.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ButtonFunc cancelButton; - cancelButton.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - return doInstallAction(action, page, progress, noButton, noWaitInfo, busyInfo, cancelButton); -} - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton) -{ - progress.setVisibleFunc(true); - if (page.setEnabledFunc) { - page.setEnabledFunc(false); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(false); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); - } - - QTimer timer; - connect(&timer, &QTimer::timeout, [progress](){ - progress.setValueFunc(progress.getValueFunc() + 1); - }); - - progress.setValueFunc(0); - timer.start(1000); - - ServerController serverController(m_settings); - - QMetaObject::Connection cancelDoInstallActionConnection; - if (cancelButton.setVisibleFunc) { - cancelDoInstallActionConnection = connect(this, &ServerConfiguringProgressLogic::cancelDoInstallAction, - &serverController, &ServerController::setCancelInstallation); - } - - - QMetaObject::Connection serverBusyConnection; - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - auto onServerIsBusy = [&serverBusyInfo, &timer, &cancelButton](const bool isBusy) { - isBusy ? timer.stop() : timer.start(1000); - serverBusyInfo.setVisibleFunc(isBusy); - serverBusyInfo.setTextFunc(isBusy ? "Amnesia has detected that your server is currently " - "busy installing other software. Amnesia installation " - "will pause until the server finishes installing other software" - : ""); - if (cancelButton.setVisibleFunc) { - cancelButton.setVisibleFunc(isBusy ? true : false); - } - }; - - serverBusyConnection = connect(&serverController, &ServerController::serverIsBusy, this, onServerIsBusy); - } - - ErrorCode e = action(); - qDebug() << "doInstallAction finished with code" << e; - if (cancelButton.setVisibleFunc) { - disconnect(cancelDoInstallActionConnection); - } - - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - disconnect(serverBusyConnection); - } - - if (e) { - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(false); - } - - progress.setVisibleFunc(false); - return e; - } - - // just ui progressbar tweak - timer.stop(); - - int remainingVal = progress.getMaximumFunc() - progress.getValueFunc(); - - if (remainingVal > 0) { - QTimer timer1; - QEventLoop loop1; - - connect(&timer1, &QTimer::timeout, [&](){ - progress.setValueFunc(progress.getValueFunc() + 1); - if (progress.getValueFunc() >= progress.getMaximumFunc()) { - loop1.quit(); - } - }); - - timer1.start(5); - loop1.exec(); - } - - - progress.setVisibleFunc(false); - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Operation finished")); - } - return ErrorCode::NoError; -} - -void ServerConfiguringProgressLogic::onPushButtonCancelClicked() -{ - emit cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.h b/client/ui/pages_logic/ServerConfiguringProgressLogic.h deleted file mode 100644 index 9f10bc877..000000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SERVER_CONFIGURING_PROGRESS_LOGIC_H -#define SERVER_CONFIGURING_PROGRESS_LOGIC_H - -#include -#include "PageLogicBase.h" -#include "core/defs.h" - -using namespace amnezia; - -class UiLogic; - -class ServerConfiguringProgressLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarValue) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, progressBarVisible) - AUTO_PROPERTY(int, progressBarMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonCancelClicked(); - -private: - struct ProgressFunc { - std::function setVisibleFunc; - std::function setValueFunc; - std::function getValueFunc; - std::function getMaximumFunc; - std::function setTextVisibleFunc; - std::function setTextFunc; - }; - struct PageFunc { - std::function setEnabledFunc; - }; - struct ButtonFunc { - std::function setVisibleFunc; - }; - struct LabelFunc { - std::function setVisibleFunc; - std::function setTextFunc; - }; - -public: - explicit ServerConfiguringProgressLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerConfiguringProgressLogic() = default; - - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - friend class UiLogic; - - void onUpdatePage() override; - ErrorCode doInstallAction(const std::function &action); - ErrorCode doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton); - -signals: - void cancelDoInstallAction(const bool cancel); - -}; -#endif // SERVER_CONFIGURING_PROGRESS_LOGIC_H diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp deleted file mode 100644 index 89eeb6c80..000000000 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "ServerContainersLogic.h" -#include "ServerConfiguringProgressLogic.h" -#include "ShareConnectionLogic.h" - -#include - -#include "protocols/PageProtocolLogicBase.h" - -#include "core/servercontroller.h" -#include - -#include "../pages_logic/VpnLogic.h" -#include "../uilogic.h" -#include "core/errorstrings.h" -#include "vpnconnection.h" - -ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) -{ -} - -void ServerContainersLogic::onUpdatePage() -{ - // ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - // c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - // p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - emit updatePage(); -} - -void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) -{ - qDebug() << "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; - uiLogic()->m_selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage( - m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), - uiLogic()->m_selectedDockerContainer, m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - - emit uiLogic()->goToProtocolPage(p); -} - -void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) -{ - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) - return; - - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - uiLogic()->onUpdateAllPages(); - - if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) - return; - if (!uiLogic()->m_vpnConnection) - return; - if (!uiLogic()->m_vpnConnection->isConnected()) - return; - - uiLogic()->pageLogic()->onDisconnect(); - uiLogic()->pageLogic()->onConnect(); -} - -void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) -{ - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, c); - emit uiLogic()->goToPage(Page::ShareConnection); -} - -void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) -{ - // buttonSetEnabledFunc(false); - ServerController serverController(m_settings); - ErrorCode e = - serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); - m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); - // buttonSetEnabledFunc(true); - - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { - const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); - if (c.isEmpty()) - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - else - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); - } - uiLogic()->onUpdateAllPages(); -} - -void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) -{ - ServerController serverController(m_settings); - QJsonObject config; // = serverController.createContainerInitialConfig(c, port, tp); - - emit uiLogic()->goToPage(Page::ServerConfiguringProgress); - qApp->processEvents(); - - bool isServerCreated = false; - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - - if (errorCode == ErrorCode::NoError) { - if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { - auto installAction = [this, c, &config]() { - ServerController serverController(m_settings); - return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - c, config); - }; - errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); - - if (errorCode == ErrorCode::NoError) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, c, config); - if (ContainerProps::containerService(c) == ServiceType::Vpn) { - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - } - } - } else { - emit uiLogic()->showWarningMessage( - "Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - } - - uiLogic()->onUpdateAllPages(); - } - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") - + errorString(errorCode) + "\n" + tr("See logs for details.")); - } - emit uiLogic()->closePage(); -} diff --git a/client/ui/pages_logic/ServerContainersLogic.h b/client/ui/pages_logic/ServerContainersLogic.h deleted file mode 100644 index d0081516c..000000000 --- a/client/ui/pages_logic/ServerContainersLogic.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SERVER_CONTAINERS_LOGIC_H -#define SERVER_CONTAINERS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ServerContainersLogic : public PageLogicBase -{ - Q_OBJECT - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonProtoSettingsClicked(DockerContainer c, Proto p); - Q_INVOKABLE void onPushButtonDefaultClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonShareClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonRemoveClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp); - - AUTO_PROPERTY(bool, isManagedServer) - -public: - explicit ServerContainersLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerContainersLogic() = default; - -}; -#endif // SERVER_CONTAINERS_LOGIC_H diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp deleted file mode 100644 index 16775bc0d..000000000 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ServerListLogic.h" - -#include "../models/servers_model.h" -#include "../uilogic.h" -#include "vpnconnection.h" - -ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), m_serverListModel { new ServersModel(m_settings, this) } -{ -} - -void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) -{ - m_settings->setDefaultServer(index); - uiLogic()->onUpdateAllPages(); - emit currServerIdxChanged(); -} - -void ServerListLogic::onServerListPushbuttonSettingsClicked(int index) -{ - uiLogic()->m_selectedServerIndex = index; - uiLogic()->goToPage(Page::ServerSettings); -} - -int ServerListLogic::currServerIdx() const -{ - return m_settings->defaultServerIndex(); -} - -void ServerListLogic::onUpdatePage() -{ - // const QJsonArray &servers = m_settings->serversArray(); - // int defaultServer = m_settings->defaultServerIndex(); - // QVector serverListContent; - // for(int i = 0; i < servers.size(); i++) { - // ServerModelContent c; - // auto server = servers.at(i).toObject(); - // c.desc = server.value(config_key::description).toString(); - // c.address = server.value(config_key::hostName).toString(); - // if (c.desc.isEmpty()) { - // c.desc = c.address; - // } - // c.isDefault = (i == defaultServer); - // serverListContent.push_back(c); - // } - // qobject_cast(m_serverListModel)->setContent(serverListContent); -} diff --git a/client/ui/pages_logic/ServerListLogic.h b/client/ui/pages_logic/ServerListLogic.h deleted file mode 100644 index b4f475471..000000000 --- a/client/ui/pages_logic/ServerListLogic.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SERVER_LIST_LOGIC_H -#define SERVER_LIST_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class ServerListLogic : public PageLogicBase -{ - Q_OBJECT - - READONLY_PROPERTY(QObject *, serverListModel) - Q_PROPERTY(int currServerIdx READ currServerIdx NOTIFY currServerIdxChanged) - -public: - int currServerIdx() const; - - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onServerListPushbuttonDefaultClicked(int index); - Q_INVOKABLE void onServerListPushbuttonSettingsClicked(int index); - -public: - explicit ServerListLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerListLogic() = default; - -signals: - void currServerIdxChanged(); - -}; -#endif // SERVER_LIST_LOGIC_H diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp deleted file mode 100644 index 4c68b5498..000000000 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "ServerSettingsLogic.h" -#include "vpnconnection.h" - -#include "../uilogic.h" -#include "ShareConnectionLogic.h" -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include -#include - -#if defined(Q_OS_ANDROID) - #include "../../platforms/android/androidutils.h" -#endif - -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_labelWaitInfoVisible { true }, - m_pushButtonClearClientCacheVisible { true }, - m_pushButtonShareFullVisible { true }, - m_pushButtonClearClientCacheText { tr("Clear client cached profile") } -{ -} - -void ServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearClientCacheVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - set_pushButtonShareFullVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); - - set_labelServerText(name); - set_lineEditDescriptionText(server.value(config_key::description).toString()); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void ServerSettingsLogic::onPushButtonForgetServer() -{ - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex - && uiLogic()->m_vpnConnection->isConnected()) { - uiLogic()->pageLogic()->onDisconnect(); - } - m_settings->removeServer(uiLogic()->m_selectedServerIndex); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(0); - } else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); - } - - if (m_settings->serversCount() == 0) { - m_settings->setDefaultServer(-1); - } - - uiLogic()->m_selectedServerIndex = -1; - uiLogic()->onUpdateAllPages(); - - if (m_settings->serversCount() == 0) { - uiLogic()->setStartPage(Page::Start); - } else { - uiLogic()->closePage(); - } -} - -void ServerSettingsLogic::onPushButtonClearClientCacheClicked() -{ - set_pushButtonClearClientCacheText(tr("Cache cleared")); - - const auto &containers = m_settings->containers(uiLogic()->m_selectedServerIndex); - for (DockerContainer container : containers.keys()) { - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); - } - - QTimer::singleShot(3000, this, [this]() { set_pushButtonClearClientCacheText(tr("Clear client cached profile")); }); -} - -void ServerSettingsLogic::onLineEditDescriptionEditingFinished() -{ - const QString &newText = lineEditDescriptionText(); - QJsonObject server = m_settings->server(uiLogic()->m_selectedServerIndex); - server.insert(config_key::description, newText); - m_settings->editServer(uiLogic()->m_selectedServerIndex, server); - uiLogic()->onUpdateAllPages(); -} - -bool ServerSettingsLogic::isCurrentServerHasCredentials() -{ - return m_settings->haveAuthData(uiLogic()->m_selectedServerIndex); -} - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) -{ - qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - - if (resultCode == -1) { // ResultOK - uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } -} -#endif - -void ServerSettingsLogic::onPushButtonShareFullClicked() -{ -#if defined(Q_OS_ANDROID) - /* We use builtin keyguard for ssh key export protection on Android */ - QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); - if (appContext.isValid()) { - QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", - appContext.object()); - if (intent.isValid()) { - if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } - } -#else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); -#endif -} diff --git a/client/ui/pages_logic/ServerSettingsLogic.h b/client/ui/pages_logic/ServerSettingsLogic.h deleted file mode 100644 index 3ce26164b..000000000 --- a/client/ui/pages_logic/ServerSettingsLogic.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef SERVER_SETTINGS_LOGIC_H -#define SERVER_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#if defined(Q_OS_ANDROID) -#include -#include -#endif - -class UiLogic; - -class ServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(QString, pushButtonClearClientCacheText) - AUTO_PROPERTY(bool, pushButtonClearClientCacheVisible) - AUTO_PROPERTY(bool, pushButtonShareFullVisible) - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, lineEditDescriptionText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonForgetServer(); - Q_INVOKABLE void onPushButtonShareFullClicked(); - Q_INVOKABLE void onPushButtonClearClientCacheClicked(); - Q_INVOKABLE void onLineEditDescriptionEditingFinished(); - - Q_INVOKABLE bool isCurrentServerHasCredentials(); - -public: - explicit ServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerSettingsLogic() = default; - -}; - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -class authResultReceiver final : public PageLogicBase, public QAndroidActivityResultReceiver -{ -Q_OBJECT - -public: - authResultReceiver(UiLogic *uiLogic, int serverIndex , QObject *parent = nullptr) : PageLogicBase(uiLogic, parent) { - m_serverIndex = serverIndex; - } - ~authResultReceiver() {} - -public: - void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; - -private: - int m_serverIndex; -}; -#endif - -#endif // SERVER_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ShareConnectionLogic.cpp b/client/ui/pages_logic/ShareConnectionLogic.cpp deleted file mode 100644 index 911c872c2..000000000 --- a/client/ui/pages_logic/ShareConnectionLogic.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include -#include -#include - -#include "qrcodegen.hpp" - -#include "ShareConnectionLogic.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/wireguard_configurator.h" -#include "configurators/ikev2_configurator.h" -#include "configurators/ssh_configurator.h" - -#include "version.h" -#include "core/defs.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include - -#include "../uilogic.h" - -#ifdef __linux__ - #include -#endif - -using namespace qrcodegen; - -ShareConnectionLogic::ShareConnectionLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_textEditShareOpenVpnCodeText{}, - m_lineEditShareShadowSocksStringText{}, - m_shareShadowSocksQrCodeText{}, - m_textEditShareCloakText{}, - m_textEditShareAmneziaCodeText{} -{ -} - -void ShareConnectionLogic::onUpdatePage() -{ - set_textEditShareAmneziaCodeText(tr("")); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - set_textEditShareOpenVpnCodeText(""); - - set_shareShadowSocksQrCodeText(""); - set_textEditShareShadowSocksText(""); - set_lineEditShareShadowSocksStringText(""); - - set_textEditShareCloakText(""); - - set_textEditShareWireGuardCodeText(""); - set_shareWireGuardQrCodeText(""); - - set_textEditShareIkev2CertText(""); - set_textEditShareIkev2MobileConfigText(""); - set_textEditShareIkev2StrongSwanConfigText(""); -} - -void ShareConnectionLogic::onPushButtonShareAmneziaGenerateClicked() -{ - set_textEditShareAmneziaCodeText(""); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - QJsonObject serverConfig; - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - - // Full access - if (shareFullAccess()) { - serverConfig = m_settings->server(serverIndex); - } - // Container share - else { - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - QJsonObject containerConfig = m_settings->containerConfig(serverIndex, container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - ErrorCode e = ErrorCode::NoError; - for (Proto p: ContainerProps::protocolsForContainer(container)) { - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, p); - - QString cfg = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, p, &e); - if (e) { - cfg = "Error generating config"; - break; - } - protoConfig.insert(config_key::last_config, cfg); - containerConfig.insert(ProtocolProps::protoToString(p), protoConfig); - } - - QByteArray ba; - if (!e) { - serverConfig = m_settings->server(serverIndex); - serverConfig.remove(config_key::userName); - serverConfig.remove(config_key::password); - serverConfig.remove(config_key::port); - serverConfig.insert(config_key::containers, QJsonArray {containerConfig}); - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - auto dns = m_configurator->getDnsForConfig(serverIndex); - serverConfig.insert(config_key::dns1, dns.first); - serverConfig.insert(config_key::dns2, dns.second); - - } - else { - set_textEditShareAmneziaCodeText(tr("Error while generating connection profile")); - return; - } - } - - QByteArray ba = QJsonDocument(serverConfig).toJson(); - ba = qCompress(ba, 8); - QString code = QString("vpn://%1").arg(QString(ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - set_textEditShareAmneziaCodeText(code); - - - QList qrChunks = genQrCodeImageSeries(ba); - set_shareAmneziaQrCodeTextSeries(qrChunks); - set_shareAmneziaQrCodeTextSeriesLength(qrChunks.size()); -} - -void ShareConnectionLogic::onPushButtonShareOpenVpnGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &e); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, cfg); - - set_textEditShareOpenVpnCodeText(QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString()); -} - -void ShareConnectionLogic::onPushButtonShareShadowSocksGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::ShadowSocks); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, &e); - } - - QJsonObject ssConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - - QString ssString = QString("%1:%2@%3:%4") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - ssString = "ss://" + ssString.toUtf8().toBase64(); - set_lineEditShareShadowSocksStringText(ssString); - - QrCode qr = QrCode::encodeText(ssString.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareShadowSocksQrCodeText(svgToBase64(svg)); - - QString humanString = QString("Server: %3\n" - "Port: %4\n" - "Encryption: %1\n" - "Password: %2") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - set_textEditShareShadowSocksText(humanString); -} - -void ShareConnectionLogic::onPushButtonShareCloakGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::Cloak); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &e); - } - - QJsonObject cloakConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - cloakConfig.remove(config_key::transport_proto); - cloakConfig.insert("ProxyMethod", "shadowsocks"); - - set_textEditShareCloakText(QJsonDocument(cloakConfig).toJson()); -} - -void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, &e); - if (e) { - emit uiLogic()->showWarningMessage(tr("Error occurred while generating the config.") + "\n" + - tr("Error message: ") + errorString(e) + "\n" + - tr("See logs for details.")); - return; - } - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString(); - - set_textEditShareWireGuardCodeText(cfg); - - QrCode qr = QrCode::encodeText(cfg.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareWireGuardQrCodeText(svgToBase64(svg)); -} - -void ShareConnectionLogic::onPushButtonShareIkev2GenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - Ikev2Configurator::ConnectionData connData = m_configurator->ikev2Configurator->prepareIkev2Config(credentials, container); - - QString cfg = m_configurator->ikev2Configurator->genIkev2Config(connData); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Ikev2, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::cert].toString(); - - set_textEditShareIkev2CertText(cfg); - - QString mobileCfg = m_configurator->ikev2Configurator->genMobileConfig(connData); - set_textEditShareIkev2MobileConfigText(mobileCfg); - - QString strongSwanCfg = m_configurator->ikev2Configurator->genStrongSwanConfig(connData); - set_textEditShareIkev2StrongSwanConfigText(strongSwanCfg); - -} - - -void ShareConnectionLogic::updateSharingPage(int serverIndex, DockerContainer container) -{ - uiLogic()->m_selectedDockerContainer = container; - uiLogic()->m_selectedServerIndex = serverIndex; - set_shareFullAccess(container == DockerContainer::None); - - m_shareAmneziaQrCodeTextSeries.clear(); - set_shareAmneziaQrCodeTextSeriesLength(0); -} - -QList ShareConnectionLogic::genQrCodeImageSeries(const QByteArray &data) -{ - double k = 850; - - quint8 chunksCount = std::ceil(data.size() / k); - QList chunks; - for (int i = 0; i < data.size(); i = i + k) { - QByteArray chunk; - QDataStream s(&chunk, QIODevice::WriteOnly); - s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i/k) << data.mid(i, k); - - QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - QrCode qr = QrCode::encodeText(ba, QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - chunks.append(svgToBase64(svg)); - } - - return chunks; -} - -QString ShareConnectionLogic::svgToBase64(const QString &image) -{ - return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); -} diff --git a/client/ui/pages_logic/ShareConnectionLogic.h b/client/ui/pages_logic/ShareConnectionLogic.h deleted file mode 100644 index 3b9655aab..000000000 --- a/client/ui/pages_logic/ShareConnectionLogic.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SHARE_CONNECTION_LOGIC_H -#define SHARE_CONNECTION_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ShareConnectionLogic: public PageLogicBase -{ - Q_OBJECT - -public: - AUTO_PROPERTY(bool, shareFullAccess) - - AUTO_PROPERTY(QString, textEditShareAmneziaCodeText) - AUTO_PROPERTY(QStringList, shareAmneziaQrCodeTextSeries) - AUTO_PROPERTY(int, shareAmneziaQrCodeTextSeriesLength) - - AUTO_PROPERTY(QString, textEditShareOpenVpnCodeText) - - AUTO_PROPERTY(QString, textEditShareShadowSocksText) - AUTO_PROPERTY(QString, lineEditShareShadowSocksStringText) - AUTO_PROPERTY(QString, shareShadowSocksQrCodeText) - - AUTO_PROPERTY(QString, textEditShareCloakText) - - AUTO_PROPERTY(QString, textEditShareWireGuardCodeText) - AUTO_PROPERTY(QString, shareWireGuardQrCodeText) - - AUTO_PROPERTY(QString, textEditShareIkev2CertText) - AUTO_PROPERTY(QString, textEditShareIkev2MobileConfigText) - AUTO_PROPERTY(QString, textEditShareIkev2StrongSwanConfigText) - -public: - Q_INVOKABLE void onPushButtonShareAmneziaGenerateClicked(); - Q_INVOKABLE void onPushButtonShareOpenVpnGenerateClicked(); - Q_INVOKABLE void onPushButtonShareShadowSocksGenerateClicked(); - Q_INVOKABLE void onPushButtonShareCloakGenerateClicked(); - Q_INVOKABLE void onPushButtonShareWireGuardGenerateClicked(); - Q_INVOKABLE void onPushButtonShareIkev2GenerateClicked(); - - Q_INVOKABLE virtual void onUpdatePage() override; - -public: - explicit ShareConnectionLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShareConnectionLogic() = default; - - void updateSharingPage(int serverIndex, DockerContainer container); - QList genQrCodeImageSeries(const QByteArray &data); - - QString svgToBase64(const QString &image); -}; -#endif // SHARE_CONNECTION_LOGIC_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp deleted file mode 100644 index aaf8f73d9..000000000 --- a/client/ui/pages_logic/SitesLogic.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include - -#include "SitesLogic.h" -#include "VpnLogic.h" -#include "utilities.h" -#include "vpnconnection.h" -#include - -#include "../models/sites_model.h" -#include "../uilogic.h" - -SitesLogic::SitesLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_labelSitesAddCustomText {}, - m_tableViewSitesModel { nullptr }, - m_lineEditSitesAddCustomText {} -{ - // sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); - // sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); -} - -void SitesLogic::onUpdatePage() -{ - Settings::RouteMode m = m_settings->routeMode(); - if (m == Settings::VpnAllSites) - return; - - if (m == Settings::VpnOnlyForwardSites) { - set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); - } - if (m == Settings::VpnAllExceptSites) { - set_labelSitesAddCustomText(tr("These sites will be excepted from VPN")); - } - - set_tableViewSitesModel(sitesModels.value(m)); - // sitesModels.value(m)->resetCache(); -} - -void SitesLogic::onPushButtonAddCustomSitesClicked() -{ - if (uiLogic()->pageLogic()->radioButtonVpnModeAllSitesChecked()) { - return; - } - Settings::RouteMode mode = m_settings->routeMode(); - - QString newSite = lineEditSitesAddCustomText(); - - if (newSite.isEmpty()) - return; - if (!newSite.contains(".")) - return; - - if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - // get domain name if it present - newSite.replace("https://", ""); - newSite.replace("http://", ""); - newSite.replace("ftp://", ""); - - newSite = newSite.split("/", Qt::SkipEmptyParts).first(); - } - - const auto &cbProcess = [this, mode](const QString &newSite, const QString &ip) { - m_settings->addVpnSite(mode, newSite, ip); - - if (!ip.isEmpty()) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << ip)); - } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << newSite)); - } - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); - }; - - const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) { - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr : hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - cbProcess(hostInfo.hostName(), addr.toString()); - break; - } - } - }; - - set_lineEditSitesAddCustomText(""); - - if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - cbProcess(newSite, ""); - return; - } else { - cbProcess(newSite, ""); - onUpdatePage(); - QHostInfo::lookupHost(newSite, this, cbResolv); - } -} - -void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) -{ - Settings::RouteMode mode = m_settings->routeMode(); - - auto siteModel = qobject_cast(tableViewSitesModel()); - if (!siteModel || items.isEmpty()) { - return; - } - - QStringList sites; - QStringList ips; - - for (const QString &s : items) { - bool ok; - int row = s.toInt(&ok); - if (!ok || row < 0 || row >= siteModel->rowCount()) - return; - // sites.append(siteModel->data(row, 0).toString()); - - // if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { - // ips.append(siteModel->data(row, 1).toString()); - // } - } - - m_settings->removeVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) -{ - QFile file(QUrl { fileName }.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile(); - return; - } - - Settings::RouteMode mode = m_settings->routeMode(); - - QStringList ips; - QMap sites; - - while (!file.atEnd()) { - QString line = file.readLine(); - QStringList line_ips; - QStringList line_sites; - - int posDomain = 0; - QRegExp domainRx = Utils::domainRegExp(); - while ((posDomain = domainRx.indexIn(line, posDomain)) != -1) { - line_sites.append(domainRx.cap(0)); - posDomain += domainRx.matchedLength(); - } - - int posIp = 0; - QRegExp ipRx = Utils::ipAddressWithSubnetRegExp(); - while ((posIp = ipRx.indexIn(line, posIp)) != -1) { - line_ips.append(ipRx.cap(0)); - posIp += ipRx.matchedLength(); - } - - // domain regex cover ip regex, so remove ips from sites - for (const QString &ip : line_ips) { - line_sites.removeAll(ip); - } - - if (line_sites.size() == 1 && line_ips.size() == 1) { - sites.insert(line_sites.at(0), line_ips.at(0)); - } else if (line_sites.size() > 0 && line_ips.size() == 0) { - for (const QString &site : line_sites) { - sites.insert(site, ""); - } - } else { - for (const QString &site : line_sites) { - sites.insert(site, ""); - } - for (const QString &ip : line_ips) { - ips.append(ip); - } - } - } - - m_settings->addVpnIps(mode, ips); - m_settings->addVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesExportClicked() -{ - Settings::RouteMode mode = m_settings->routeMode(); - - QVariantMap sites = m_settings->vpnSites(mode); - - QString data; - for (auto s : sites.keys()) { - data += s + "\t" + sites.value(s).toString() + "\n"; - } - uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); -} diff --git a/client/ui/pages_logic/SitesLogic.h b/client/ui/pages_logic/SitesLogic.h deleted file mode 100644 index 35bf1f90c..000000000 --- a/client/ui/pages_logic/SitesLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SITES_LOGIC_H -#define SITES_LOGIC_H - -#include "PageLogicBase.h" -#include "settings.h" - -class UiLogic; -class SitesModel; - -class SitesLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelSitesAddCustomText) - AUTO_PROPERTY(QObject*, tableViewSitesModel) - AUTO_PROPERTY(QString, lineEditSitesAddCustomText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonAddCustomSitesClicked(); - Q_INVOKABLE void onPushButtonSitesDeleteClicked(QStringList items); - Q_INVOKABLE void onPushButtonSitesImportClicked(const QString &fileName); - Q_INVOKABLE void onPushButtonSitesExportClicked(); - - -public: - explicit SitesLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~SitesLogic() = default; - - QMap sitesModels; -}; -#endif // SITES_LOGIC_H diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp deleted file mode 100644 index 891d67fbf..000000000 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ /dev/null @@ -1,374 +0,0 @@ -#include "StartPageLogic.h" -#include "ViewConfigLogic.h" - -#include "../uilogic.h" -#include "configurators/ssh_configurator.h" -#include "configurators/vpn_configurator.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "utilities.h" - -#include -#include -#include - -#ifdef Q_OS_ANDROID - #include "../../platforms/android/android_controller.h" - #include "../../platforms/android/androidutils.h" - #include -#endif - -#ifdef Q_OS_IOS - #include -#endif - -namespace -{ - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard - }; - - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; - - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; - - if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) - && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) - && config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; - } - -} - -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_pushButtonConnectEnabled { true }, - m_pushButtonConnectText { tr("Connect") }, - m_pushButtonConnectKeyChecked { false }, - m_labelWaitInfoVisible { true }, - m_pushButtonBackFromStartVisible { true }, - m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } -{ -#ifdef Q_OS_ANDROID - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); -#endif -} - -void StartPageLogic::onUpdatePage() -{ - set_lineEditStartExistingCodeText(""); - set_textEditSshKeyText(""); - set_lineEditIpText(""); - set_lineEditPasswordText(""); - set_textEditSshKeyText(""); - set_lineEditLoginText(""); - - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - - set_pushButtonConnectKeyChecked(false); - - set_pushButtonBackFromStartVisible(uiLogic()->pagesStackDepth() > 0); -} - -void StartPageLogic::onPushButtonConnect() -{ - if (pushButtonConnectKeyChecked()) { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } else { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } - - ServerCredentials serverCredentials; - serverCredentials.hostName = lineEditIpText(); - if (serverCredentials.hostName.contains(":")) { - serverCredentials.port = serverCredentials.hostName.split(":").at(1).toInt(); - serverCredentials.hostName = serverCredentials.hostName.split(":").at(0); - } - serverCredentials.userName = lineEditLoginText(); - if (pushButtonConnectKeyChecked()) { - QString key = textEditSshKeyText(); - if (key.startsWith("ssh-rsa")) { - emit uiLogic()->showPublicKeyWarning(); - return; - } - - if (key.contains("OPENSSH") && key.contains("BEGIN") && key.contains("PRIVATE KEY")) { - key = m_configurator->sshConfigurator->convertOpenSShKey(key); - } - - serverCredentials.secretData = key; - } else { - serverCredentials.secretData = lineEditPasswordText(); - } - - set_pushButtonConnectEnabled(false); - set_pushButtonConnectText(tr("Connecting...")); - - ServerController serverController(m_settings); - ErrorCode errorCode = ErrorCode::NoError; - - if (pushButtonConnectKeyChecked()) { - auto passphraseCallback = [this, &serverController]() { - emit showPassphraseRequestMessage(); - QEventLoop loop; - QObject::connect(this, &StartPageLogic::passphraseDialogClosed, &loop, &QEventLoop::quit); - loop.exec(); - - return m_privateKeyPassphrase; - }; - - QString decryptedPrivateKey; - errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); - if (errorCode == ErrorCode::NoError) { - serverCredentials.secretData = decryptedPrivateKey; - } - } - - QString output; - if (errorCode == ErrorCode::NoError) { - output = serverController.checkSshConnection(serverCredentials, &errorCode); - } - - bool ok = true; - if (errorCode) { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(errorString(errorCode)); - ok = false; - } else { - if (output.contains("Please login as the user")) { - output.replace("\n", ""); - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(output); - ok = false; - } - } - - set_pushButtonConnectEnabled(true); - set_pushButtonConnectText(tr("Connect")); - - uiLogic()->m_installCredentials = serverCredentials; - if (ok) - emit uiLogic()->goToPage(Page::NewServer); -} - -void StartPageLogic::onPushButtonImport() -{ - importConnectionFromCode(lineEditStartExistingCodeText()); -} - -void StartPageLogic::onPushButtonImportOpenFile() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) - return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - importAnyFile(QString(data)); -} - -#ifdef Q_OS_ANDROID -void StartPageLogic::startQrDecoder() -{ - AndroidController::instance()->startQrReaderActivity(); -} -#endif - -void StartPageLogic::importAnyFile(const QString &configData) -{ - auto configFormat = checkConfigFormat(configData); - if (configFormat == ConfigTypes::OpenVpn) { - importConnectionFromOpenVpnConfig(configData); - } else if (configFormat == ConfigTypes::WireGuard) { - importConnectionFromWireguardConfig(configData); - } else { - importConnectionFromCode(configData); - } -} - -bool StartPageLogic::importConnection(const QJsonObject &profile) -{ - ServerCredentials credentials; - credentials.hostName = profile.value(config_key::hostName).toString(); - credentials.port = profile.value(config_key::port).toInt(); - credentials.userName = profile.value(config_key::userName).toString(); - credentials.secretData = profile.value(config_key::password).toString(); - - if (credentials.isValid() || profile.contains(config_key::containers)) { - // check config - uiLogic()->pageLogic()->set_configJson(profile); - emit uiLogic()->goToPage(Page::ViewConfig); - } else { - qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(profile).toJson(); - return false; - } - - return true; -} - -bool StartPageLogic::importConnectionFromCode(QString code) -{ - code.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QJsonObject o; - o = QJsonDocument::fromJson(ba).object(); - if (!o.isEmpty()) { - return importConnection(o); - } - - return false; -} - -bool StartPageLogic::importConnectionFromQr(const QByteArray &data) -{ - QJsonObject dataObj = QJsonDocument::fromJson(data).object(); - if (!dataObj.isEmpty()) { - return importConnection(dataObj); - } - - QByteArray ba_uncompressed = qUncompress(data); - if (!ba_uncompressed.isEmpty()) { - return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); - } - - return false; -} - -bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) -{ - QJsonObject openVpnConfig; - openVpnConfig[config_key::config] = config; - - QJsonObject lastConfig; - lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); - lastConfig[config_key::isThirdPartyConfig] = true; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); - containers.insert(config_key::openvpn, QJsonValue(lastConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QString hostName; - const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); - QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(config); - if (hostNameMatch.hasMatch()) { - hostName = hostNameMatch.captured(1); - } - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-openvpn"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); - if (dnsMatch.hasNext()) { - o[config_key::dns1] = dnsMatch.next().captured(1); - } - if (dnsMatch.hasNext()) { - o[config_key::dns2] = dnsMatch.next().captured(1); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} - -bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) -{ - QJsonObject lastConfig; - lastConfig[config_key::config] = config; - - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); - QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config); - QString hostName; - QString port; - if (hostNameAndPortMatch.hasMatch()) { - hostName = hostNameAndPortMatch.captured(1); - port = hostNameAndPortMatch.captured(2); - } - - QJsonObject wireguardConfig; - wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); - wireguardConfig[config_key::isThirdPartyConfig] = true; - wireguardConfig[config_key::port] = port; - wireguardConfig[config_key::transport_proto] = "udp"; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); - containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-wireguard"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp( - "DNS = " - "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); - if (dnsMatch.hasMatch()) { - o[config_key::dns1] = dnsMatch.captured(1); - o[config_key::dns2] = dnsMatch.captured(2); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h deleted file mode 100644 index 9025a052c..000000000 --- a/client/ui/pages_logic/StartPageLogic.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef START_PAGE_LOGIC_H -#define START_PAGE_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class StartPageLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectKeyChecked) - AUTO_PROPERTY(QString, pushButtonConnectText) - AUTO_PROPERTY(QString, lineEditStartExistingCodeText) - AUTO_PROPERTY(QString, textEditSshKeyText) - AUTO_PROPERTY(QString, lineEditIpText) - AUTO_PROPERTY(QString, lineEditPasswordText) - AUTO_PROPERTY(QString, lineEditLoginText) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, pushButtonBackFromStartVisible) - - AUTO_PROPERTY(QString, privateKeyPassphrase) - - READONLY_PROPERTY(QRegularExpression, ipAddressPortRegex) -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonConnect(); - Q_INVOKABLE void onPushButtonImport(); - Q_INVOKABLE void onPushButtonImportOpenFile(); - -#ifdef Q_OS_ANDROID - Q_INVOKABLE void startQrDecoder(); -#endif - - void importAnyFile(const QString &configData); - - bool importConnection(const QJsonObject &profile); - bool importConnectionFromCode(QString code); - bool importConnectionFromQr(const QByteArray &data); - bool importConnectionFromOpenVpnConfig(const QString &config); - bool importConnectionFromWireguardConfig(const QString &config); - -public: - explicit StartPageLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~StartPageLogic() = default; - -signals: - void showPassphraseRequestMessage(); - void passphraseDialogClosed(); -}; -#endif // START_PAGE_LOGIC_H diff --git a/client/ui/pages_logic/ViewConfigLogic.cpp b/client/ui/pages_logic/ViewConfigLogic.cpp deleted file mode 100644 index 5f7114985..000000000 --- a/client/ui/pages_logic/ViewConfigLogic.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "ViewConfigLogic.h" -#include "core/errorstrings.h" -#include "../uilogic.h" - - -ViewConfigLogic::ViewConfigLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ViewConfigLogic::onUpdatePage() -{ - set_configText(QJsonDocument(configJson()).toJson()); - - auto s = configJson()[config_key::isThirdPartyConfig].toBool(); - - m_openVpnLastConfigs = m_openVpnMalStrings = - "
"; - - m_warningStringNumber = 3; - m_warningActive = false; - - const QJsonArray &containers = configJson()[config_key::containers].toArray(); - int i = 0; - for (const QJsonValue &v: containers) { - auto containerName = v.toObject()[config_key::container].toString(); - QJsonObject containerConfig = v.toObject()[containerName.replace("amnezia-", "")].toObject(); - if (containerConfig[config_key::isThirdPartyConfig].toBool()) { - auto lastConfig = containerConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString lastConfigText; - for (const QString &l: lines) { - lastConfigText.append(l + "\n"); - } - set_configText(lastConfigText); - } - - - if (v.toObject()[config_key::container].toString() == "amnezia-openvpn") { - QString lastConfig = v.toObject()[ProtocolProps::protoToString(Proto::OpenVpn)] - .toObject()[config_key::last_config].toString(); - - QString lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object()[config_key::config] - .toString(); - - QStringList lines = lastConfigJson.replace("\r", "").split("\n"); - for (const QString &l: lines) { - i++; - QRegularExpressionMatch match = m_re.match(l); - if (dangerousTags.contains(match.captured(0))) { - QString t = QString("

%1").arg(l); - m_openVpnLastConfigs.append(t + "\n"); - m_openVpnMalStrings.append(t); - if (m_warningStringNumber == 3) m_warningStringNumber = i - 3; - m_warningActive = true; - qDebug() << "ViewConfigLogic : malicious scripts warning:" << l; - } - else { - m_openVpnLastConfigs.append("

" + l + " \n"); - } - } - } - } - - emit openVpnLastConfigsChanged(m_openVpnLastConfigs); - emit openVpnMalStringsChanged(m_openVpnMalStrings); - emit warningStringNumberChanged(m_warningStringNumber); - emit warningActiveChanged(m_warningActive); -} - -void ViewConfigLogic::importConfig() -{ - m_settings->addServer(configJson()); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - - - if (!configJson().contains(config_key::containers) || configJson().value(config_key::containers).toArray().isEmpty()) { - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - uiLogic()->onUpdateAllPages(); - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - emit uiLogic()->goToPage(Page::ServerContainers); - } else { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } -} - diff --git a/client/ui/pages_logic/ViewConfigLogic.h b/client/ui/pages_logic/ViewConfigLogic.h deleted file mode 100644 index 4713158ec..000000000 --- a/client/ui/pages_logic/ViewConfigLogic.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef VIEW_CONFIG_LOGIC_H -#define VIEW_CONFIG_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class ViewConfigLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, configText) - AUTO_PROPERTY(QString, openVpnLastConfigs) - AUTO_PROPERTY(QString, openVpnMalStrings) - AUTO_PROPERTY(QJsonObject, configJson) - AUTO_PROPERTY(int, warningStringNumber) - AUTO_PROPERTY(bool, warningActive) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void importConfig(); - - -public: - explicit ViewConfigLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ViewConfigLogic() = default; - -private: - QRegularExpression m_re {"(\\w+-\\w+|\\w+)"}; - - // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst - QStringList dangerousTags { - "up", - "tls-verify", - "ipchange", - "client-connect", - "route-up", - "route-pre-down", - "client-disconnect", - "down", - "learn-address", - "auth-user-pass-verify" - }; -}; -#endif // VIEW_CONFIG_LOGIC_H diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp deleted file mode 100644 index 2f5e7b650..000000000 --- a/client/ui/pages_logic/VpnLogic.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include - -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include "vpnconnection.h" -#include -#include -#include "../uilogic.h" -#include "version.h" -#include - - -VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectChecked{false}, - - m_radioButtonVpnModeAllSitesChecked{true}, - m_radioButtonVpnModeForwardSitesChecked{false}, - m_radioButtonVpnModeExceptSitesChecked{false}, - - m_labelSpeedReceivedText{tr("0 Mbps")}, - m_labelSpeedSentText{tr("0 Mbps")}, - m_labelStateText{}, - m_isContainerHaveAuthData{false}, - m_isContainerSupportedByCurrentPlatform{false}, - m_widgetVpnModeEnabled{false} -{ - connect(uiLogic()->m_vpnConnection, &VpnConnection::bytesChanged, this, &VpnLogic::onBytesChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::connectionStateChanged, this, &VpnLogic::onConnectionStateChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::vpnProtocolError, this, &VpnLogic::onVpnProtocolError); - - connect(this, &VpnLogic::connectToVpn, uiLogic()->m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, &VpnLogic::disconnectFromVpn, uiLogic()->m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); - - connect(m_settings.get(), &Settings::saveLogsChanged, this, &VpnLogic::onUpdatePage); - - if (m_settings->isAutoConnect() && m_settings->defaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this](){ - set_pushButtonConnectEnabled(false); - onConnect(); - }); - } - else { - onConnectionStateChanged(Vpn::ConnectionState::Disconnected); - } -} - - -void VpnLogic::onUpdatePage() -{ - Settings::RouteMode mode = m_settings->routeMode(); - DockerContainer selectedContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_isCustomRoutesSupported (selectedContainer == DockerContainer::OpenVpn || - selectedContainer == DockerContainer::ShadowSocks|| - selectedContainer == DockerContainer::Cloak); - - set_isContainerHaveAuthData(m_settings->haveAuthData(m_settings->defaultServerIndex())); - - set_radioButtonVpnModeAllSitesChecked(mode == Settings::VpnAllSites || !isCustomRoutesSupported()); - set_radioButtonVpnModeForwardSitesChecked(mode == Settings::VpnOnlyForwardSites && isCustomRoutesSupported()); - set_radioButtonVpnModeExceptSitesChecked(mode == Settings::VpnAllExceptSites && isCustomRoutesSupported()); - - const QJsonObject &server = uiLogic()->m_settings->defaultServer(); - QString serverString = QString("%2 (%3)") - .arg(server.value(config_key::description).toString()) - .arg(server.value(config_key::hostName).toString()); - set_labelCurrentServer(serverString); - - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentService(selectedContainerName); - - auto dns = m_configurator->getDnsForConfig(m_settings->defaultServerIndex()); - set_amneziaDnsEnabled(dns.first == protocols::dns::amneziaDnsIp); - if (dns.first == protocols::dns::amneziaDnsIp) { - set_labelCurrentDns("On your server"); - } - else { - set_labelCurrentDns(dns.first + ", " + dns.second); - } - - set_isContainerSupportedByCurrentPlatform(ContainerProps::isSupportedByCurrentPlatform(selectedContainer)); - if (!isContainerSupportedByCurrentPlatform()) { - set_labelErrorText(tr("AmneziaVPN not supporting selected protocol on this device. Select another protocol.")); - } - else { - set_labelErrorText(""); - } - QString ver = QString("v. %2").arg(QString(APP_MAJOR_VERSION)); - set_labelVersionText(ver); - - set_labelLogEnabledVisible(m_settings->isSaveLogs()); -} - - -void VpnLogic::onRadioButtonVpnModeAllSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeForwardSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeExceptSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllExceptSites); - onUpdatePage(); -} - -void VpnLogic::onBytesChanged(quint64 receivedData, quint64 sentData) -{ - set_labelSpeedReceivedText(VpnConnection::bytesPerSecToText(receivedData)); - set_labelSpeedSentText(VpnConnection::bytesPerSecToText(sentData)); -} - -void VpnLogic::onConnectionStateChanged(Vpn::ConnectionState state) -{ - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); - if (uiLogic()->m_vpnConnection == NULL) { - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state) << "невозможно, соединение отсутствует (уничтожено ранее)"; - return; - } - bool pbConnectEnabled = false; - bool pbConnectChecked = false; - - bool rbModeEnabled = false; - bool pbConnectVisible = false; - set_labelStateText(VpnProtocol::textConnectionState(state)); - - switch (state) { - case Vpn::ConnectionState::Disconnected: - onBytesChanged(0,0); - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case Vpn::ConnectionState::Preparing: - pbConnectChecked = true; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Connecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Connected: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Disconnecting: - pbConnectChecked = false; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Reconnecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Error: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case Vpn::ConnectionState::Unknown: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - } - - set_pushButtonConnectEnabled(pbConnectEnabled); - set_pushButtonConnectChecked(pbConnectChecked); - - set_pushButtonConnectVisible(pbConnectVisible); - set_widgetVpnModeEnabled(rbModeEnabled); -} - -void VpnLogic::onVpnProtocolError(ErrorCode errorCode) -{ - set_labelErrorText(errorString(errorCode)); -} - -void VpnLogic::onPushButtonConnectClicked() -{ - if (! pushButtonConnectChecked()) { - onConnect(); - } else { - onDisconnect(); - } -} - -void VpnLogic::onConnect() -{ - int serverIndex = m_settings->defaultServerIndex(); - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - DockerContainer container = m_settings->defaultContainer(serverIndex); - - if (m_settings->containers(serverIndex).isEmpty()) { - set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); - set_pushButtonConnectChecked(false); - return; - } - - if (container == DockerContainer::None) { - set_labelErrorText(tr("VPN Protocol not chosen")); - set_pushButtonConnectChecked(false); - return; - } - - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - onConnectWorker(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) -{ - set_labelErrorText(""); - set_pushButtonConnectChecked(true); - set_pushButtonConnectEnabled(false); - - qApp->processEvents(); - - emit connectToVpn(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onDisconnect() -{ - onConnectionStateChanged(Vpn::ConnectionState::Disconnected); - emit disconnectFromVpn(); -} diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h deleted file mode 100644 index a0f7763b2..000000000 --- a/client/ui/pages_logic/VpnLogic.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef VPN_LOGIC_H -#define VPN_LOGIC_H - -#include "PageLogicBase.h" -#include "protocols/vpnprotocol.h" - -class UiLogic; - -class VpnLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectChecked) - AUTO_PROPERTY(QString, labelSpeedReceivedText) - AUTO_PROPERTY(QString, labelSpeedSentText) - AUTO_PROPERTY(QString, labelStateText) - AUTO_PROPERTY(QString, labelCurrentServer) - AUTO_PROPERTY(QString, labelCurrentService) - AUTO_PROPERTY(QString, labelCurrentDns) - AUTO_PROPERTY(bool, amneziaDnsEnabled) - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectVisible) - AUTO_PROPERTY(bool, widgetVpnModeEnabled) - AUTO_PROPERTY(bool, isContainerSupportedByCurrentPlatform) - AUTO_PROPERTY(bool, isContainerHaveAuthData) - - AUTO_PROPERTY(QString, labelErrorText) - AUTO_PROPERTY(QString, labelVersionText) - - AUTO_PROPERTY(bool, isCustomRoutesSupported) - - AUTO_PROPERTY(bool, radioButtonVpnModeAllSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeForwardSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeExceptSitesChecked) - - AUTO_PROPERTY(bool, labelLogEnabledVisible) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onRadioButtonVpnModeAllSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeForwardSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeExceptSitesClicked(); - - Q_INVOKABLE void onPushButtonConnectClicked(); - -public: - explicit VpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~VpnLogic() = default; - - bool getPushButtonConnectChecked() const; - void setPushButtonConnectChecked(bool pushButtonConnectChecked); - -public slots: - void onConnect(); - void onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - void onDisconnect(); - - void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(Vpn::ConnectionState state); - void onVpnProtocolError(amnezia::ErrorCode errorCode); - -signals: - void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - - void disconnectFromVpn(); -}; -#endif // VPN_LOGIC_H diff --git a/client/ui/pages_logic/WizardLogic.cpp b/client/ui/pages_logic/WizardLogic.cpp deleted file mode 100644 index 23a20aed0..000000000 --- a/client/ui/pages_logic/WizardLogic.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "WizardLogic.h" -#include "../uilogic.h" - -WizardLogic::WizardLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_radioButtonHighChecked{false}, - m_radioButtonMediumChecked{true}, - m_radioButtonLowChecked{false}, - m_lineEditHighWebsiteMaskingText{}, - m_checkBoxVpnModeChecked{false} -{ - -} - -void WizardLogic::onUpdatePage() -{ - set_lineEditHighWebsiteMaskingText(protocols::cloak::defaultRedirSite); - set_radioButtonMediumChecked(true); -} - -QPair WizardLogic::getInstallConfigsFromWizardPage() const -{ - QJsonObject cloakConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::Cloak) }, - { ProtocolProps::protoToString(Proto::Cloak), QJsonObject { - { config_key::site, lineEditHighWebsiteMaskingText() }} - } - }; - QJsonObject ssConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::ShadowSocks) } - }; - QJsonObject openVpnConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::OpenVpn) } - }; - - QPair container; - - DockerContainer dockerContainer; - - if (radioButtonHighChecked()) { - container = {DockerContainer::Cloak, cloakConfig}; - } - - if (radioButtonMediumChecked()) { - container = {DockerContainer::ShadowSocks, ssConfig}; - } - - if (radioButtonLowChecked()) { - container = {DockerContainer::OpenVpn, openVpnConfig}; - } - - return container; -} - -void WizardLogic::onPushButtonVpnModeFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); - if (checkBoxVpnModeChecked()) { - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - } else { - m_settings->setRouteMode(Settings::VpnAllSites); - } -} - -void WizardLogic::onPushButtonLowFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); -} diff --git a/client/ui/pages_logic/WizardLogic.h b/client/ui/pages_logic/WizardLogic.h deleted file mode 100644 index a2e45af7d..000000000 --- a/client/ui/pages_logic/WizardLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef WIZARD_LOGIC_H -#define WIZARD_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class WizardLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, radioButtonHighChecked) - AUTO_PROPERTY(bool, radioButtonMediumChecked) - AUTO_PROPERTY(bool, radioButtonLowChecked) - AUTO_PROPERTY(bool, checkBoxVpnModeChecked) - AUTO_PROPERTY(QString, lineEditHighWebsiteMaskingText) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonVpnModeFinishClicked(); - Q_INVOKABLE void onPushButtonLowFinishClicked(); - -public: - explicit WizardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WizardLogic() = default; - - QPair getInstallConfigsFromWizardPage() const; - -}; -#endif // WIZARD_LOGIC_H diff --git a/client/ui/pages_logic/protocols/CloakLogic.cpp b/client/ui/pages_logic/protocols/CloakLogic.cpp deleted file mode 100644 index 0062d12d1..000000000 --- a/client/ui/pages_logic/protocols/CloakLogic.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "CloakLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -CloakLogic::CloakLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditSiteText{"tile.openstreetmap.org"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_pageEnabled{true}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void CloakLogic::updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ckConfig.value(config_key::cipher). - toString(protocols::cloak::defaultCipher)); - - set_lineEditSiteText(ckConfig.value(config_key::site). - toString(protocols::cloak::defaultRedirSite)); - - set_lineEditPortText(ckConfig.value(config_key::port). - toString(protocols::cloak::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::Cloak); -} - -QJsonObject CloakLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - oldConfig.insert(config_key::site, lineEditSiteText()); - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void CloakLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::Cloak); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::Cloak), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void CloakLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/CloakLogic.h b/client/ui/pages_logic/protocols/CloakLogic.h deleted file mode 100644 index c135a6218..000000000 --- a/client/ui/pages_logic/protocols/CloakLogic.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef CLOAK_LOGIC_H -#define CLOAK_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class CloakLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditSiteText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit CloakLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~CloakLogic() = default; - - void updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // CLOAK_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp deleted file mode 100644 index 2eb68ed99..000000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "OpenVpnLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -OpenVpnLogic::OpenVpnLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_lineEditSubnetText{""}, - - m_radioButtonTcpEnabled{true}, - m_radioButtonTcpChecked{false}, - m_radioButtonUdpEnabled{true}, - m_radioButtonUdpChecked{false}, - - m_checkBoxAutoEncryptionChecked{}, - m_comboBoxVpnCipherText{"AES-256-GCM"}, - m_comboBoxVpnHashText{"SHA512"}, - m_checkBoxBlockDnsChecked{false}, - m_lineEditPortText{}, - m_checkBoxTlsAuthChecked{false}, - m_textAreaAdditionalClientConfig{""}, - m_textAreaAdditionalServerConfig{""}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - - m_lineEditPortEnabled{false}, - m_labelProtoOpenVpnInfoVisible{true}, - m_labelProtoOpenVpnInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "OpenVpnLogic::updateProtocolPage"; - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_radioButtonUdpEnabled(true); - set_radioButtonTcpEnabled(true); - - set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address). - toString(protocols::openvpn::defaultSubnetAddress)); - - QString transport; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - transport = "tcp"; - set_radioButtonUdpEnabled(false); - set_radioButtonTcpEnabled(false); - } else { - transport = openvpnConfig.value(config_key::transport_proto). - toString(protocols::openvpn::defaultTransportProto); - } - set_radioButtonUdpChecked(transport == protocols::openvpn::defaultTransportProto); - set_radioButtonTcpChecked(transport != protocols::openvpn::defaultTransportProto); - - set_comboBoxVpnCipherText(openvpnConfig.value(config_key::cipher). - toString(protocols::openvpn::defaultCipher)); - - set_comboBoxVpnHashText(openvpnConfig.value(config_key::hash). - toString(protocols::openvpn::defaultHash)); - - bool blockOutsideDns = openvpnConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); - set_checkBoxBlockDnsChecked(blockOutsideDns); - - bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - set_checkBoxAutoEncryptionChecked(!isNcpDisabled); - - bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - set_checkBoxTlsAuthChecked(isTlsAuth); - - QString additionalClientConfig = openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig); - set_textAreaAdditionalClientConfig(additionalClientConfig); - - QString additionalServerConfig = openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig); - set_textAreaAdditionalServerConfig(additionalServerConfig); - - set_lineEditPortText(openvpnConfig.value(config_key::port). - toString(protocols::openvpn::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::OpenVpn); - - auto lastConfig = openvpnConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString openVpnLastConfigText; - for (const QString &l: lines) { - openVpnLastConfigText.append(l + "\n"); - } - - set_openVpnLastConfigText(openVpnLastConfigText); - set_isThirdPartyConfig(openvpnConfig.value(config_key::isThirdPartyConfig).isBool()); -} - -void OpenVpnLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::OpenVpn); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::OpenVpn), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelProtoOpenVpnInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelProtoOpenVpnInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -QJsonObject OpenVpnLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::subnet_address, lineEditSubnetText()); - oldConfig.insert(config_key::transport_proto, - ProtocolProps::transportProtoToString(radioButtonUdpChecked() ? ProtocolEnumNS::Udp : ProtocolEnumNS::Tcp)); - - oldConfig.insert(config_key::ncp_disable, ! checkBoxAutoEncryptionChecked()); - oldConfig.insert(config_key::cipher, comboBoxVpnCipherText()); - oldConfig.insert(config_key::hash, comboBoxVpnHashText()); - oldConfig.insert(config_key::block_outside_dns, checkBoxBlockDnsChecked()); - oldConfig.insert(config_key::port, lineEditPortText()); - oldConfig.insert(config_key::tls_auth, checkBoxTlsAuthChecked()); - oldConfig.insert(config_key::additional_client_config, textAreaAdditionalClientConfig()); - oldConfig.insert(config_key::additional_server_config, textAreaAdditionalServerConfig()); - return oldConfig; -} - -void OpenVpnLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.h b/client/ui/pages_logic/protocols/OpenVpnLogic.h deleted file mode 100644 index db7d3baff..000000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef OPENVPN_LOGIC_H -#define OPENVPN_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class OpenVpnLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditSubnetText) - - AUTO_PROPERTY(bool, radioButtonTcpEnabled) - AUTO_PROPERTY(bool, radioButtonUdpEnabled) - AUTO_PROPERTY(bool, radioButtonTcpChecked) - AUTO_PROPERTY(bool, radioButtonUdpChecked) - - AUTO_PROPERTY(bool, checkBoxAutoEncryptionChecked) - AUTO_PROPERTY(QString, comboBoxVpnCipherText) - AUTO_PROPERTY(QString, comboBoxVpnHashText) - AUTO_PROPERTY(bool, checkBoxBlockDnsChecked) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, checkBoxTlsAuthChecked) - AUTO_PROPERTY(QString, textAreaAdditionalClientConfig) - AUTO_PROPERTY(QString, textAreaAdditionalServerConfig) - - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - - AUTO_PROPERTY(bool, lineEditPortEnabled) - - AUTO_PROPERTY(bool, labelProtoOpenVpnInfoVisible) - AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - - AUTO_PROPERTY(QString, openVpnLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit OpenVpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OpenVpnLogic() = default; - - void updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // OPENVPN_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp b/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp deleted file mode 100644 index 965a3baf9..000000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include -#include -#include -#include - -#include "OtherProtocolsLogic.h" -#include -#include "../../uilogic.h" -#include "utilities.h" - -#ifdef Q_OS_WINDOWS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -OtherProtocolsLogic::OtherProtocolsLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_checkBoxSftpRestoreChecked{false} - -{ - -} - -OtherProtocolsLogic::~OtherProtocolsLogic() -{ -#ifdef Q_OS_WINDOWS - for (QProcess *p: m_sftpMountProcesses) { - if (p) Utils::signalCtrl(p->processId(), CTRL_C_EVENT); - if (p) p->kill(); - if (p) p->waitForFinished(); - if (p) delete p; - } -#endif -} - -void OtherProtocolsLogic::updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) -{ - set_labelTftpUserNameText(config.value(config_key::userName).toString()); - set_labelTftpPasswordText(config.value(config_key::password).toString(protocols::sftp::defaultUserName)); - set_labelTftpPortText(config.value(config_key::port).toString()); - - set_labelTorWebSiteAddressText(config.value(config_key::site).toString()); - set_pushButtonSftpMountEnabled(true); -} - -#ifdef Q_OS_WINDOWS -QString OtherProtocolsLogic::getNextDriverLetter() const -{ - QProcess drivesProc; - drivesProc.start("wmic logicaldisk get caption"); - drivesProc.waitForFinished(); - QString drives = drivesProc.readAll(); - qDebug() << drives; - - - QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; - QString letter; - for (int i = letters.size() - 1; i > 0; i--) { - letter = letters.at(i); - if (!drives.contains(letter + ":")) break; - } - if (letter == "C:") { - // set err info - qDebug() << "Can't find free drive letter"; - return ""; - } - return letter; -} -#endif - -//QJsonObject OtherProtocolsLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -//{ - -//} - - -void OtherProtocolsLogic::onPushButtonSftpMountDriveClicked() -{ - QString mountPath; - QString cmd; - QString host = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex).hostName; - - -#ifdef Q_OS_WINDOWS - mountPath = getNextDriverLetter() + ":"; - // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") - // .arg(labelTftpUserNameText()) - // .arg(labelTftpPortText()) - // .arg(labelTftpPasswordText()); - - cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; -#elif defined AMNEZIA_DESKTOP - mountPath = QString("%1/sftp:%2:%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)) - .arg(host) - .arg(labelTftpPortText()); - QDir dir(mountPath); - if (!dir.exists()){ - dir.mkpath(mountPath); - } - - cmd = "/usr/local/bin/sshfs"; -#endif - -#ifdef AMNEZIA_DESKTOP - set_pushButtonSftpMountEnabled(false); - QProcess *p = new QProcess; - m_sftpMountProcesses.append(p); - p->setProcessChannelMode(QProcess::MergedChannels); - - connect(p, &QProcess::readyRead, this, [this, p, mountPath](){ - QString s = p->readAll(); - if (s.contains("The service sshfs has been started")) { - QDesktopServices::openUrl(QUrl("file:///" + mountPath)); - set_pushButtonSftpMountEnabled(true); - } - qDebug() << s; - }); - - - - p->setProgram(cmd); - - QString args = QString( - "%1@%2:/ %3 " - "-o port=%4 " - "-f " - "-o reconnect " - "-o rellinks " - "-o fstypename=SSHFS " - "-o ssh_command=/usr/bin/ssh.exe " - "-o UserKnownHostsFile=/dev/null " - "-o StrictHostKeyChecking=no " - "-o password_stdin") - .arg(labelTftpUserNameText()) - .arg(host) - .arg(mountPath) - .arg(labelTftpPortText()); - - -// args.replace("\n", " "); -// args.replace("\r", " "); -//#ifndef Q_OS_WIN -// args.replace("reconnect-orellinks", ""); -//#endif - p->setArguments(args.split(" ", Qt::SkipEmptyParts)); - p->start(); - p->waitForStarted(50); - if (p->state() != QProcess::Running) { - qDebug() << "onPushButtonSftpMountDriveClicked process not started"; - qDebug() << args; - } - else { - p->write((labelTftpPasswordText() + "\n").toUtf8()); - } - - //qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; - - set_pushButtonSftpMountEnabled(true); -#endif -} - -void OtherProtocolsLogic::checkBoxSftpRestoreClicked() -{ - -} diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h b/client/ui/pages_logic/protocols/OtherProtocolsLogic.h deleted file mode 100644 index 508c29141..000000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef OTHER_PROTOCOLS_LOGIC_H -#define OTHER_PROTOCOLS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -#include - -class UiLogic; - -class OtherProtocolsLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelTftpUserNameText) - AUTO_PROPERTY(QString, labelTftpPasswordText) - AUTO_PROPERTY(QString, labelTftpPortText) - AUTO_PROPERTY(bool, pushButtonSftpMountEnabled) - AUTO_PROPERTY(bool, checkBoxSftpRestoreChecked) - - AUTO_PROPERTY(QString, labelTorWebSiteAddressText) - - -public: - Q_INVOKABLE void onPushButtonSftpMountDriveClicked(); - Q_INVOKABLE void checkBoxSftpRestoreClicked(); -public: - explicit OtherProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OtherProtocolsLogic(); - - void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) override; - //QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -#ifdef AMNEZIA_DESKTOP - QList m_sftpMountProcesses; -#endif - -#ifdef Q_OS_WINDOWS - QString getNextDriverLetter() const; -#endif - -}; -#endif // OTHER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp b/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp deleted file mode 100644 index 62c5aa89b..000000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "PageProtocolLogicBase.h" - - -PageProtocolLogicBase::PageProtocolLogicBase(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h b/client/ui/pages_logic/protocols/PageProtocolLogicBase.h deleted file mode 100644 index ab66f8a95..000000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PAGE_PROTOCOL_LOGIC_BASE_H -#define PAGE_PROTOCOL_LOGIC_BASE_H - -#include "settings.h" -#include "../PageLogicBase.h" - -using namespace amnezia; -using namespace PageEnumNS; - -class UiLogic; - -class PageProtocolLogicBase : public PageLogicBase -{ - Q_OBJECT - -public: - explicit PageProtocolLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageProtocolLogicBase() = default; - - virtual void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) {} - virtual QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) { return QJsonObject(); } - -}; -#endif // PAGE_PROTOCOL_LOGIC_BASE_H diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp deleted file mode 100644 index f9220a92b..000000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "ShadowSocksLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void ShadowSocksLogic::updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ssConfig.value(config_key::cipher). - toString(protocols::shadowsocks::defaultCipher)); - - set_lineEditPortText(ssConfig.value(config_key::port). - toString(protocols::shadowsocks::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::ShadowSocks); -} - -QJsonObject ShadowSocksLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void ShadowSocksLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::ShadowSocks); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::ShadowSocks), protocolConfig); - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void ShadowSocksLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.h b/client/ui/pages_logic/protocols/ShadowSocksLogic.h deleted file mode 100644 index bf9269286..000000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SHADOWSOCKS_LOGIC_H -#define SHADOWSOCKS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class ShadowSocksLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit ShadowSocksLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShadowSocksLogic() = default; - - void updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // SHADOWSOCKS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.cpp b/client/ui/pages_logic/protocols/WireGuardLogic.cpp deleted file mode 100644 index a6c661f51..000000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "WireGuardLogic.h" -#include "core/servercontroller.h" -#include -#include "../../uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -WireGuardLogic::WireGuardLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent) -{ - -} - -void WireGuardLogic::updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "WireGuardLogic::updateProtocolPage"; - - auto lastConfigJsonDoc = QJsonDocument::fromJson(wireGuardConfig.value(config_key::last_config).toString().toUtf8()); - auto lastConfigJson = lastConfigJsonDoc.object(); - - QString wireGuardLastConfigText; - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &l: lines) { - wireGuardLastConfigText.append(l + "\n"); - } - - set_wireGuardLastConfigText(wireGuardLastConfigText); - set_isThirdPartyConfig(wireGuardConfig.value(config_key::isThirdPartyConfig).toBool()); -} diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.h b/client/ui/pages_logic/protocols/WireGuardLogic.h deleted file mode 100644 index 0b3bfc7b7..000000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef WIREGUARDLOGIC_H -#define WIREGUARDLOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class WireGuardLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, wireGuardLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - explicit WireGuardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WireGuardLogic() = default; - - void updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) override; - -private: - UiLogic *m_uiLogic; - -}; - -#endif // WIREGUARDLOGIC_H diff --git a/client/ui/qml/Controls/BackButton.qml b/client/ui/qml/Controls/BackButton.qml deleted file mode 100644 index 47f0970ce..000000000 --- a/client/ui/qml/Controls/BackButton.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - x: 10 - y: 5 - width: 41 - height: 35 - - hoverEnabled: true - property bool containsMouse: hovered - - background: Item {} - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } - - onClicked: { - UiLogic.closePage() - } - - contentItem: Image { - id: img - source: "qrc:/images/arrow_left.png" - anchors.fill: root - anchors.margins: root.containsMouse ? 9 : 10 - } -} diff --git a/client/ui/qml/Controls/BasicButtonType.qml b/client/ui/qml/Controls/BasicButtonType.qml deleted file mode 100644 index e115df29b..000000000 --- a/client/ui/qml/Controls/BasicButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - property bool containsMouse: hovered - hoverEnabled: true - flat: true - highlighted: false - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } -} diff --git a/client/ui/qml/Controls/BlueButtonType.qml b/client/ui/qml/Controls/BlueButtonType.qml deleted file mode 100644 index a3602c4ab..000000000 --- a/client/ui/qml/Controls/BlueButtonType.qml +++ /dev/null @@ -1,28 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import "../Config" - -BasicButtonType { - id: root - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled ? (root.containsMouse ? "#211966" : "#100A44") : "#888888" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/Caption.qml b/client/ui/qml/Controls/Caption.qml deleted file mode 100644 index 50fc9aca0..000000000 --- a/client/ui/qml/Controls/Caption.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Text { - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 24 - color: "#100A44" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - x: 10 - y: 35 - width: parent.width - 40 - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Text.Wrap - //height: 31 -} diff --git a/client/ui/qml/Controls/CheckBoxType.qml b/client/ui/qml/Controls/CheckBoxType.qml deleted file mode 100644 index 0331706ce..000000000 --- a/client/ui/qml/Controls/CheckBoxType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - -CheckBox { - id: root - property int imageWidth : 20 - property int imageHeight : 20 - indicator: Image { - id: indicator - anchors.verticalCenter: root.verticalCenter - height: imageHeight - width: imageWidth - source: root.checked ? "qrc:/images/controls/check_on.png" - : "qrc:/images/controls/check_off.png" - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - wrapMode: Text.Wrap - } -} diff --git a/client/ui/qml/Controls/ComboBoxType.qml b/client/ui/qml/Controls/ComboBoxType.qml deleted file mode 100644 index 090ca9de8..000000000 --- a/client/ui/qml/Controls/ComboBoxType.qml +++ /dev/null @@ -1,11 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ComboBox { - id: root - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - - popup.font.pixelSize: 16 -} diff --git a/client/ui/qml/Controls/ContextMenu.qml b/client/ui/qml/Controls/ContextMenu.qml deleted file mode 100644 index 867fcb106..000000000 --- a/client/ui/qml/Controls/ContextMenu.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform - -Menu { - property var textObj - - MenuItem { - text: qsTr("C&ut") - shortcut: StandardKey.Cut - enabled: textObj.selectedText - onTriggered: textObj.cut() - } - MenuItem { - text: qsTr("&Copy") - shortcut: StandardKey.Copy - enabled: textObj.selectedText - onTriggered: textObj.copy() - } - MenuItem { - text: qsTr("&Paste") - shortcut: StandardKey.Paste - enabled: textObj.canPaste - onTriggered: textObj.paste() - } - - MenuItem { - text: qsTr("&SelectAll") - shortcut: StandardKey.SelectAll - enabled: textObj.length > 0 - onTriggered: textObj.selectAll() - } -} diff --git a/client/ui/qml/Controls/FadeBehavior.qml b/client/ui/qml/Controls/FadeBehavior.qml deleted file mode 100644 index e523061f8..000000000 --- a/client/ui/qml/Controls/FadeBehavior.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick -import QtQml - -Behavior { - id: root - - property QtObject fadeTarget: targetProperty.object - property string fadeProperty: "scale" - property int fadeDuration: 150 - property string easingType: "Quad" - - property alias outAnimation: outAnimation - property alias inAnimation: inAnimation - - SequentialAnimation { - NumberAnimation { - id: outAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: 0 - easing.type: Easing["In"+root.easingType] - } - PropertyAction { } - NumberAnimation { - id: inAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: target[property] - easing.type: Easing["Out"+root.easingType] - } - } - -} diff --git a/client/ui/qml/Controls/FlickableType.qml b/client/ui/qml/Controls/FlickableType.qml deleted file mode 100644 index 79bfabfdd..000000000 --- a/client/ui/qml/Controls/FlickableType.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import "../Config" - -Flickable { - id: fl - - clip: true - width: parent.width - - anchors.topMargin: GC.defaultMargin - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - anchors.left: root.left - anchors.leftMargin: GC.defaultMargin - anchors.right: root.right - anchors.rightMargin: 1 - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() - - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } -} diff --git a/client/ui/qml/Controls/ImageButtonType.qml b/client/ui/qml/Controls/ImageButtonType.qml deleted file mode 100644 index 74b90c6e5..000000000 --- a/client/ui/qml/Controls/ImageButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias iconMargin: img.anchors.margins - property alias img: img - property int imgMargin: 4 - property int imgMarginHover: 3 - background: Item {} - contentItem: Image { - id: img - source: root.icon.source - anchors.fill: root - anchors.margins: root.containsMouse ? imgMarginHover : imgMargin - } -} diff --git a/client/ui/qml/Controls/LabelType.qml b/client/ui/qml/Controls/LabelType.qml deleted file mode 100644 index 9cce61b1b..000000000 --- a/client/ui/qml/Controls/LabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import "../Config" - -Text { - id: root - width: parent.width - 2 * GC.defaultMargin - anchors.topMargin: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap -} - diff --git a/client/ui/qml/Controls/Logo.qml b/client/ui/qml/Controls/Logo.qml deleted file mode 100644 index 74d828724..000000000 --- a/client/ui/qml/Controls/Logo.qml +++ /dev/null @@ -1,8 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Image { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 30 - source: "qrc:/images/AmneziaVPN.png" -} diff --git a/client/ui/qml/Controls/PopupWarning.qml b/client/ui/qml/Controls/PopupWarning.qml deleted file mode 100644 index 57c332ebe..000000000 --- a/client/ui/qml/Controls/PopupWarning.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string popupWarningText - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - width: parent.width - 20 - - ColumnLayout { - width: parent.width - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: root.popupWarningText - } - - BlueButtonType { - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: "Continue" - onClicked: { - root.close() - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithQuestion.qml b/client/ui/qml/Controls/PopupWithQuestion.qml deleted file mode 100644 index b55edf903..000000000 --- a/client/ui/qml/Controls/PopupWithQuestion.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string questionText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.CloseOnEscape - - width: parent.width - 20 - focus: true - - onAboutToHide: { - parent.forceActiveFocus(true) - } - - ColumnLayout { - width: parent.width - - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: questionText - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithTextField.qml b/client/ui/qml/Controls/PopupWithTextField.qml deleted file mode 100644 index acdf12478..000000000 --- a/client/ui/qml/Controls/PopupWithTextField.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property alias text: textField.text - property alias placeholderText: textField.placeholderText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - signal editingFinished() - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - - width: parent.width - 20 - - ColumnLayout { - width: parent.width - - TextField { - id: textField - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - font.pixelSize: 16 - echoMode: TextInput.Password - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/RadioButtonType.qml b/client/ui/qml/Controls/RadioButtonType.qml deleted file mode 100644 index cda28ea5c..000000000 --- a/client/ui/qml/Controls/RadioButtonType.qml +++ /dev/null @@ -1,36 +0,0 @@ -import QtQuick -import QtQuick.Controls - -RadioButton { - id: root - - indicator: Rectangle { - implicitWidth: 13 - implicitHeight: 13 - x: root.leftPadding - y: parent.height / 2 - height / 2 - radius: 13 - border.color: root.down ? "#777777" : "#777777" - - Rectangle { - width: 7 - height: 7 - x: 3 - y: 3 - radius: 4 - color: root.down ? "#15CDCB" : "#15CDCB" - visible: root.checked - } - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: enabled ? "#181922" : "#686972" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - } - height: 10 -} diff --git a/client/ui/qml/Controls/RichLabelType.qml b/client/ui/qml/Controls/RichLabelType.qml deleted file mode 100644 index f354f974c..000000000 --- a/client/ui/qml/Controls/RichLabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick - -LabelType { - id: label_connection_code - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - textFormat: Text.RichText - onLinkActivated: Qt.openUrlExternally(link) - - MouseArea { - anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.NoButton - } -} - diff --git a/client/ui/qml/Controls/SettingButtonType.qml b/client/ui/qml/Controls/SettingButtonType.qml deleted file mode 100644 index 6166793d2..000000000 --- a/client/ui/qml/Controls/SettingButtonType.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias textItem: textItem - height: 30 - - background: Item {} - contentItem: Item { - SvgImageType { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - svg.source: root.icon.source - enabled: root.enabled - color: "#100A44" - width: 25 - height: 25 - } - Text { - id: textItem - anchors.fill: parent - leftPadding: 30 - text: root.text - color: root.enabled ? "#100A44": "#AAAAAA" - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 20 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml b/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml deleted file mode 100644 index 31b3591e5..000000000 --- a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ShareConnectionButtonType { - property string start_text: qsTr("Copy") - property string end_text: qsTr("Copied") - - property string copyText - - enabled: copyText.length > 0 - visible: copyText.length > 0 - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: text = start_text - } - - text: start_text - - onClicked: { - text = end_text - timer.running = true - UiLogic.copyToClipboard(copyText) - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonType.qml b/client/ui/qml/Controls/ShareConnectionButtonType.qml deleted file mode 100644 index 77ebbac07..000000000 --- a/client/ui/qml/Controls/ShareConnectionButtonType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - - -BasicButtonType { - id: root - height: 40 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled - ? (root.containsMouse ? "#282932" : "#181922") - : "#484952" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/ShareConnectionContent.qml b/client/ui/qml/Controls/ShareConnectionContent.qml deleted file mode 100644 index 99427aef5..000000000 --- a/client/ui/qml/Controls/ShareConnectionContent.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 - -Item { - id: root - property bool active: false - property string text: "" - height: active ? contentLoader.item.height + 40 + 5 * 2 : 40 - signal clicked() - - Rectangle { - x: 0 - y: 0 - width: parent.width - height: 40 - color: "transparent" - clip: true - radius: 2 - gradient: LinearGradient { - x1: 0 ; y1: 0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#E1E1E1" }, - GradientStop { position: 0.4; color: "#DDDDDD" }, - GradientStop { position: 0.5; color: "#D8D8D8" }, - GradientStop { position: 1.0; color: "#D3D3D3" } - ] - } - Image { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - source: "qrc:/images/share.png" - } - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 2 - color: "#148CD2" - visible: ms.containsMouse ? true : false - } - Text { - x: 40 - anchors.verticalCenter: parent.verticalCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - color: "#100A44" - font.bold: true - text: root.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - } - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: root.clicked() - } - } -} - diff --git a/client/ui/qml/Controls/SvgButtonType.qml b/client/ui/qml/Controls/SvgButtonType.qml deleted file mode 100644 index e6f78c875..000000000 --- a/client/ui/qml/Controls/SvgButtonType.qml +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "." - -BasicButtonType { - id: root - icon.color: "#181922" - - background: Item {} - contentItem: SvgImageType { - svg.source: icon.source - color: icon.color - anchors.fill: parent - anchors.margins: parent.containsMouse ? 0 : 1 - } -} diff --git a/client/ui/qml/Controls/SvgImageType.qml b/client/ui/qml/Controls/SvgImageType.qml deleted file mode 100644 index aee928ba0..000000000 --- a/client/ui/qml/Controls/SvgImageType.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt5Compat.GraphicalEffects - -Item { - id: root - property color color: "#181922" - property alias svg: image - Image { - anchors.fill: parent - id: image - sourceSize: Qt.size(root.width, root.height) - - antialiasing: true - visible: false - } - - ColorOverlay { - anchors.fill: image - source: image - color: root.enabled ? root.color : "grey" - } -} diff --git a/client/ui/qml/Controls/TextAreaType.qml b/client/ui/qml/Controls/TextAreaType.qml deleted file mode 100644 index 2f6e08433..000000000 --- a/client/ui/qml/Controls/TextAreaType.qml +++ /dev/null @@ -1,63 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform - -import "../Config" - -Flickable -{ - property alias textArea: root - id: flickable - flickableDirection: Flickable.VerticalFlick - clip: true - TextArea.flickable: - - TextArea { - id: root - property bool error: false - - height: 40 - anchors.topMargin: 5 - selectByMouse: false - - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - -// MouseArea { -// anchors.fill: root -// enabled: GC.isDesktop() -// acceptedButtons: Qt.RightButton -// onClicked: contextMenu.open() -// } - -// ContextMenu { -// id: contextMenu -// textObj: root -// } - } - -} diff --git a/client/ui/qml/Controls/TextFieldType.qml b/client/ui/qml/Controls/TextFieldType.qml deleted file mode 100644 index 5d7b2a651..000000000 --- a/client/ui/qml/Controls/TextFieldType.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import "../Config" - -TextField { - id: root - property bool error: false - - width: parent.width - 2 * GC.defaultMargin - height: 40 - anchors.topMargin: 5 - selectByMouse: true - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() - } - - ContextMenu { - id: contextMenu - textObj: root - } -} diff --git a/client/ui/qml/Controls/UrlButtonType.qml b/client/ui/qml/Controls/UrlButtonType.qml deleted file mode 100644 index d77763dc0..000000000 --- a/client/ui/qml/Controls/UrlButtonType.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 - -BasicButtonType { - property alias label: lbl - id: root - antialiasing: true - height: 21 - background: Item {} - - contentItem: Text { - id: lbl - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - font.underline: true - - text: root.text - color: "#3045ee" - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } -} diff --git a/client/ui/qml/Controls/VisibleBehavior.qml b/client/ui/qml/Controls/VisibleBehavior.qml deleted file mode 100644 index 9aeb4e858..000000000 --- a/client/ui/qml/Controls/VisibleBehavior.qml +++ /dev/null @@ -1,6 +0,0 @@ -FadeBehavior { - fadeProperty: "opacity" - fadeDuration: 200 - outAnimation.duration: targetValue ? 0 : fadeDuration - inAnimation.duration: targetValue ? fadeDuration : 0 -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml deleted file mode 100644 index 7fde5bab7..000000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml +++ /dev/null @@ -1,15 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ClientInfo - logic: ClientInfoLogic -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml deleted file mode 100644 index da5ba53c9..000000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - visible: ClientInfoLogic.pageContentVisible - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate id") - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelOpenVpnCertId - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaOpenVpnCertData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Certificate") - onClicked: { - ClientInfoLogic.onRevokeOpenVpnCertificateClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml deleted file mode 100644 index befaf7f8b..000000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml +++ /dev/null @@ -1,100 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Public Key") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaWireGuardKeyData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Key") - onClicked: { - ClientInfoLogic.onRevokeWireGuardKeyClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml b/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml deleted file mode 100644 index 8e04c605d..000000000 --- a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml +++ /dev/null @@ -1,75 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "./" -import "../../Controls" -import "../../Config" - -Rectangle { - signal containerChecked(bool checked) - property bool initiallyChecked: false - property string containerDescription - default property alias itemSettings: container.data - - x: 5 - y: 5 - width: parent.width - 20 - anchors.horizontalCenter: parent.horizontalCenter - - height: frame_settings.visible ? 140 : 72 - border.width: 1 - border.color: "lightgray" - radius: 2 - Rectangle { - id: frame_settings - height: 77 - width: parent.width - border.width: 1 - border.color: "lightgray" - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - visible: false - radius: 2 - Grid { - id: container - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - } - } - Row { - anchors.top: parent.top - anchors.topMargin: 5 - leftPadding: 15 - rightPadding: 5 - height: 55 - width: parent.width - CheckBoxType { - text: containerDescription - height: parent.height - width: parent.width - 50 - checked: initiallyChecked - onCheckedChanged: containerChecked(checked) - } - ImageButtonType { - width: 35 - height: 35 - anchors.verticalCenter: parent.verticalCenter - icon.source: "qrc:/images/settings.png" - checkable: true - checked: initiallyChecked - onCheckedChanged: { - //NewServerProtocolsLogic.pushButtonSettingsCloakChecked = checked - if (checked) { - frame_settings.visible = true - } else { - frame_settings.visible = false - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml b/client/ui/qml/Pages/InstallSettings/SelectContainer.qml deleted file mode 100644 index 59e8f4646..000000000 --- a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml +++ /dev/null @@ -1,200 +0,0 @@ -import QtQuick -import QtQuick.Controls -import SortFilterProxyModel 0.2 -import ProtocolEnum 1.0 -import "./" -import "../../Controls" -import "../../Config" - -Drawer { - id: root - signal containerSelected(int c_index) - property int selectedIndex: -1 - - y: 0 - x: 0 - edge: Qt.RightEdge - width: parent.width * 0.85 - height: parent.height - - modal: true - interactive: activeFocus - - SortFilterProxyModel { - id: proxyModel - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Vpn } - ] - - } - - SortFilterProxyModel { - id: proxyModel_other - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Other } - ] - - } - - FlickableType { - anchors.fill: parent - contentHeight: col.height - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - Caption { - id: cap1 - text: qsTr("VPN containers") - font.pixelSize: 20 - - } - - ListView { - id: tb - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentIndex - - } - Text { - id: text_name - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb.currentIndex = index - tb_other.currentIndex = -1 - containerSelected(proxyModel.mapToSource(index)) - selectedIndex = proxyModel.mapToSource(index) - root.close() - } - } - } - } - - - Caption { - id: cap2 - font.pixelSize: 20 - text: qsTr("Other containers") - } - - ListView { - id: tb_other - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel_other - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1_other - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_other.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb_other.currentIndex - - } - Text { - id: text_name_other - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb_other.currentIndex = index - tb.currentIndex = -1 - containerSelected(proxyModel_other.mapToSource(index)) - selectedIndex = proxyModel_other.mapToSource(index) - root.close() - } - } - } - } - - - } - - - } - -} diff --git a/client/ui/qml/Pages/PageAbout.qml b/client/ui/qml/Pages/PageAbout.qml deleted file mode 100644 index ab25c23e3..000000000 --- a/client/ui/qml/Pages/PageAbout.qml +++ /dev/null @@ -1,90 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.About - - BackButton { - id: back_from_start - } - - Caption { - id: caption - font.pixelSize: 22 - text: qsTr("About Amnezia") - } - - RichLabelType { - id: label_about - anchors.top: caption.bottom - - text: qsTr("AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. -

-") - } - - Caption { - id: caption2 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Support") - anchors.top: label_about.bottom - } - - RichLabelType { - id: label_support - anchors.top: caption2.bottom - - text: qsTr("Have questions? You can get support by: -") - } - - Caption { - id: caption3 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Donate") - width: undefined - anchors.top: label_support.bottom - } - - LabelType { - anchors.bottom: caption3.bottom - anchors.left: caption3.right - anchors.leftMargin: 5 - font.pixelSize: 24 - text: "♥" - color: "red" - } - - RichLabelType { - id: label_donate - anchors.top: caption3.bottom - - text: qsTr("Please support Amnezia project by donation, we really need it now more than ever. - -") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAdvancedServerSettings.qml b/client/ui/qml/Pages/PageAdvancedServerSettings.qml deleted file mode 100644 index 9f71b2baf..000000000 --- a/client/ui/qml/Pages/PageAdvancedServerSettings.qml +++ /dev/null @@ -1,118 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AdvancedServerSettings - logic: AdvancedServerSettingsLogic - - enabled: AdvancedServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Advanced server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: !AdvancedServerSettingsLogic.pageEnabled - running: !AdvancedServerSettingsLogic.pageEnabled - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelWaitInfoText - visible: AdvancedServerSettingsLogic.labelWaitInfoVisible - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: "Scan the server for installed containers" - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - AdvancedServerSettingsLogic.onPushButtonScanServerClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: AdvancedServerSettingsLogic.pushButtonClearText - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - popupClearServer.open() - } - } - - BlueButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - text: qsTr("Clients Management") - onClicked: { - UiLogic.goToPage(PageEnum.ClientManagement) - } - } - - PopupWithQuestion { - id: popupClearServer - questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?" - yesFunc: function() { - close() - AdvancedServerSettingsLogic.onPushButtonClearServerClicked() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAppSetting.qml b/client/ui/qml/Pages/PageAppSetting.qml deleted file mode 100644 index 2bf0e306d..000000000 --- a/client/ui/qml/Pages/PageAppSetting.qml +++ /dev/null @@ -1,152 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AppSettings - logic: AppSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Application Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto connect") - checked: AppSettingsLogic.checkBoxAutoConnectChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutoConnectChecked = checked - AppSettingsLogic.onCheckBoxAutoconnectToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto start") - checked: AppSettingsLogic.checkBoxAutostartChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutostartChecked = checked - AppSettingsLogic.onCheckBoxAutostartToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Start minimized") - checked: AppSettingsLogic.checkBoxStartMinimizedChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxStartMinimizedChecked = checked - AppSettingsLogic.onCheckBoxStartMinimizedToggled(checked) - } - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: AppSettingsLogic.labelVersionText - } - BlueButtonType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Check for updates") - onClicked: { - Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") - } - } - - CheckBoxType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Keep logs") - checked: AppSettingsLogic.checkBoxSaveLogsChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxSaveLogsChecked = checked - AppSettingsLogic.onCheckBoxSaveLogsCheckedToggled(checked) - } - } - BlueButtonType { - Layout.fillWidth: true - text: qsTr("Open logs folder") - onClicked: { - AppSettingsLogic.onPushButtonOpenLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Export logs") - onClicked: { - AppSettingsLogic.onPushButtonExportLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - property string start_text: qsTr("Clear logs") - property string end_text: qsTr("Cleared") - text: start_text - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: parent.text = parent.start_text - } - onClicked: { - text = end_text - timer.running = true - AppSettingsLogic.onPushButtonClearLogsClicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 30 - text: qsTr("Backup and restore configuration") - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Backup app config") - onClicked: { - AppSettingsLogic.onPushButtonBackupAppConfigClicked() - } - } - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageBase.qml b/client/ui/qml/Pages/PageBase.qml deleted file mode 100644 index c398515b4..000000000 --- a/client/ui/qml/Pages/PageBase.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -Item { - id: root - property var page: PageEnum.Start - property var logic: UiLogic - - property bool pageActive: false - - signal activated(bool reset) - signal deactivated() - - onActivated: pageActive = true - onDeactivated: pageActive = false -} diff --git a/client/ui/qml/Pages/PageClientManagement.qml b/client/ui/qml/Pages/PageClientManagement.qml deleted file mode 100644 index 39defe4c1..000000000 --- a/client/ui/qml/Pages/PageClientManagement.qml +++ /dev/null @@ -1,119 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes 1.4 -import SortFilterProxyModel 0.2 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ClientManagement - logic: ClientManagementLogic - enabled: !ClientManagementLogic.busyIndicatorIsRunning - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Clients Management") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientManagementLogic.busyIndicatorIsRunning - running: ClientManagementLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - Column { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelType { - font.pixelSize: 20 - leftPadding: -20 - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - text: ClientManagementLogic.labelCurrentVpnProtocolText - } - - SortFilterProxyModel { - id: proxyClientManagementModel - sourceModel: UiLogic.clientManagementModel - sorters: RoleSorter { roleName: "clientName" } - } - - ListView { - id: lv_clients - width: parent.width - implicitHeight: contentHeight + 20 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 20 - topMargin: 10 - spacing: 10 - clip: true - model: proxyClientManagementModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - delegate: Item { - implicitWidth: lv_clients.width - implicitHeight: 60 - - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index)) - } - } - - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - x: 20 - y: 20 - font.pixelSize: 20 - text: clientName - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageGeneralSettings.qml b/client/ui/qml/Pages/PageGeneralSettings.qml deleted file mode 100644 index c85aa8a7a..000000000 --- a/client/ui/qml/Pages/PageGeneralSettings.qml +++ /dev/null @@ -1,166 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.GeneralSettings - logic: GeneralSettingsLogic - - BackButton { - id: back - z: -1 - } - - FlickableType { - id: fl - anchors.top: back.bottom - anchors.topMargin: 0 - anchors.bottomMargin: 10 - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.topMargin: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - spacing: 15 - - - // ---------- App settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - text: qsTr("App settings") - onClicked: { - UiLogic.goToPage(PageEnum.AppSettings) - } - } - - // ---------- Network settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_suggest_black_24dp.svg" - text: qsTr("Network settings") - onClicked: { - UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - // ---------- Server settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/vpn_key_black_24dp.svg" - text: qsTr("Server Settings") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsServerSettingsClicked() - } - } - - // ---------- Share connection ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/share_black_24dp.svg" - text: qsTr("Share connection") - enabled: GeneralSettingsLogic.pushButtonGeneralSettingsShareConnectionEnable && - GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsShareConnectionClicked() - } - } - - // ---------- Servers ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/format_list_bulleted_black_24dp.svg" - text: qsTr("Servers") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - - // ---------- Add server ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - text: qsTr("Add server") - onClicked: { - if(GeneralSettingsLogic.existsAnyServer) - // If there is any server set we will go to Start Page - UiLogic.goToPage(PageEnum.Start) - else - // Else just come back to start page - UiLogic.closePage() - } - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: fl.height > (75+1) * 6 ? fl.height - (75+1) * 6 : 0 - } - - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - Layout.bottomMargin: 20 - icon.source: "qrc:/images/svg/logout_black_24dp.svg" - text: qsTr("Exit") - onClicked: { - Qt.quit() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNetworkSetting.qml b/client/ui/qml/Pages/PageNetworkSetting.qml deleted file mode 100644 index e14c04a6f..000000000 --- a/client/ui/qml/Pages/PageNetworkSetting.qml +++ /dev/null @@ -1,113 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NetworkSettings - logic: NetworkSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("DNS Servers") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS service (recommended)") - checked: NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked - onCheckedChanged: { - NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked = checked - NetworkSettingsLogic.onCheckBoxUseAmneziaDnsToggled(checked) - UiLogic.onUpdateAllPages() - } - } - - LabelType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS container on your server, when it installed.\n -Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254\n -If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used:") - } - - LabelType { - Layout.topMargin: 15 - text: qsTr("Primary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns1Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns1Text = text - NetworkSettingsLogic.onLineEditDns1EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns1Clicked() - UiLogic.onUpdateAllPages() - } - } - - LabelType { - text: qsTr("Secondary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns2Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns2Text = text - NetworkSettingsLogic.onLineEditDns2EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns2Clicked() - UiLogic.onUpdateAllPages() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNewServer.qml b/client/ui/qml/Pages/PageNewServer.qml deleted file mode 100644 index 00cb51bcc..000000000 --- a/client/ui/qml/Pages/PageNewServer.qml +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NewServer - - BackButton { - id: back_from_new_server - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - LabelType { - id: labelWizard - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("If you want easily configure your server just run Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 30 - } - BlueButtonType { - id: pushButtonWizard - text: qsTr("Run Setup Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelWizard.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.Wizard); - } - } - LabelType { - id: labelManual - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("Press configure manually to choose VPN protocols you want to install") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: pushButtonWizard.bottom - anchors.topMargin: 40 - } - - BlueButtonType { - text: qsTr("Configure") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelManual.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.NewServerProtocols); - } - } - - Logo { - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageNewServerProtocols.qml b/client/ui/qml/Pages/PageNewServerProtocols.qml deleted file mode 100644 index 0ce2090f1..000000000 --- a/client/ui/qml/Pages/PageNewServerProtocols.qml +++ /dev/null @@ -1,154 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.NewServerProtocols - logic: NewServerProtocolsLogic - - onActivated: { - container_selector.selectedIndex = -1 - UiLogic.containersModel.setSelectedServerIndex(-1) - } - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Select VPN protocols") - } - - BlueButtonType { - id: pushButtonConfigure - enabled: container_selector.selectedIndex > 0 - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: 40 - text: qsTr("Setup server") - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - NewServerProtocolsLogic.onPushButtonConfigureClicked(cont, port, tp) - } - } - - BlueButtonType { - id: pb_add_container - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - 40 - height: 40 - text: qsTr("Select protocol container") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - - } - - SelectContainer { - id: container_selector - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index){ - var containerProto = ContainerProps.defaultProtocol(c_index) - - tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: pb_add_container.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } -} diff --git a/client/ui/qml/Pages/PageQrDecoderIos.qml b/client/ui/qml/Pages/PageQrDecoderIos.qml deleted file mode 100644 index 21bdbfe77..000000000 --- a/client/ui/qml/Pages/PageQrDecoderIos.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import QRCodeReader 1.0 - -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.QrDecoderIos - logic: QrDecoderLogic - - onDeactivated: { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - - BackButton { - } - Caption { - id: caption - text: qsTr("Import configuration") - } - - Connections { - target: Qt.platform.os == "ios" ? QrDecoderLogic : null - function onStartDecode() { - console.debug("Starting QR decoder") - loader.sourceComponent = component - } - function onStopDecode() { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - } - - Loader { - id: loader - - anchors.top: caption.bottom - anchors.bottom: progressColumn.top - anchors.left: parent.left - anchors.right: parent.right - } - - Column{ - height: 40 - id: progressColumn - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - ProgressBar { - id: progress - anchors.left: parent.left - anchors.right: parent.right - value: QrDecoderLogic.totalChunksCount === 0? 0 : (QrDecoderLogic.receivedChunksCount/QrDecoderLogic.totalChunksCount) - } - Text { - id: chunksCount - text: "Progress: " + QrDecoderLogic.receivedChunksCount +"/"+QrDecoderLogic.totalChunksCount - } - } - - Component { - id: component - - Item { - anchors.fill: parent - - QRCodeReader { - id: qrCodeReader - - onCodeReaded: { - QrDecoderLogic.onDetectedQrCode(code) - } - - Component.onCompleted: { - qrCodeReader.setCameraSize(Qt.rect(loader.x, - loader.y, - loader.width, - loader.height)) - qrCodeReader.startReading() - } - Component.onDestruction: qrCodeReader.stopReading() - } - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageServerConfiguringProgress.qml b/client/ui/qml/Pages/PageServerConfiguringProgress.qml deleted file mode 100644 index 04f1b6f58..000000000 --- a/client/ui/qml/Pages/PageServerConfiguringProgress.qml +++ /dev/null @@ -1,121 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerConfiguringProgress - logic: ServerConfiguringProgressLogic - - Caption { - id: caption - text: qsTr("Configuring...") - } - LabelType { - id: label - x: 0 - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - height: 31 - text: qsTr("Please wait.") - horizontalAlignment: Text.AlignHCenter - } - - LabelType { - id: labelServerBusy - x: 0 - anchors.top: label.bottom - anchors.topMargin: 30 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - - text: ServerConfiguringProgressLogic.labelServerBusyText - visible: ServerConfiguringProgressLogic.labelServerBusyVisible - } - - LabelType { - anchors.bottom: pr.top - anchors.bottomMargin: 20 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - text: ServerConfiguringProgressLogic.labelWaitInfoText - visible: ServerConfiguringProgressLogic.labelWaitInfoVisible - } - - - BlueButtonType { - id: pb_cancel - z: 1 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.bottom - anchors.bottomMargin: 40 - width: root.width - 60 - height: 40 - text: qsTr("Cancel") - visible: ServerConfiguringProgressLogic.pushButtonCancelVisible - enabled: ServerConfiguringProgressLogic.pushButtonCancelVisible - onClicked: { - ServerConfiguringProgressLogic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: pr - enabled: ServerConfiguringProgressLogic.pageEnabled - anchors.fill: pb_cancel - from: 0 - to: ServerConfiguringProgressLogic.progressBarMaximum - value: ServerConfiguringProgressLogic.progressBarValue - visible: ServerConfiguringProgressLogic.progressBarVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: pr.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: ServerConfiguringProgressLogic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: ServerConfiguringProgressLogic.progressBarTextVisible - } - } - - Logo { - id : logo - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageServerContainers.qml b/client/ui/qml/Pages/PageServerContainers.qml deleted file mode 100644 index ddd607b05..000000000 --- a/client/ui/qml/Pages/PageServerContainers.qml +++ /dev/null @@ -1,434 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.ServerContainers - logic: ServerContainersLogic - - enabled: ServerContainersLogic.pageEnabled - - function resetPage() { - container_selector.selectedIndex = -1 - } - - Connections { - target: logic - function onUpdatePage() { - root.resetPage() - } - } - - BackButton { - id: back - onClicked: tb_c.currentIndex = -1 - } - Caption { - id: caption - text: container_selector.selectedIndex > 0 ? qsTr("Install new service") : qsTr("Installed services") - } - - SelectContainer { - id: container_selector - - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index) { - var containerProto = ContainerProps.defaultProtocol(c_index) - - - if (ProtocolProps.defaultPort(containerProto) < 0) { - tf_port_num.enabled = false - tf_port_num.text = qsTr("Default") - } - else tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } - - BlueButtonType { - id: pb_cancel_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: pb_continue_add.top - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Cancel") - font.pixelSize: 16 - onClicked: container_selector.selectedIndex = -1 - - } - - BlueButtonType { - id: pb_continue_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Continue") - font.pixelSize: 16 - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - ServerContainersLogic.onPushButtonContinueClicked(cont, port, tp) - } - } - - FlickableType { - visible: container_selector.selectedIndex <= 0 - clip: true - width: parent.width - anchors.top: caption.bottom - anchors.bottom: pb_add_container.top - contentHeight: col.height - - Column { - visible: container_selector.selectedIndex <= 0 - id: col - anchors { - left: parent.left; - right: parent.right; - } - spacing: 10 - - Caption { - id: cap1 - text: qsTr("Installed Protocols and Services") - leftPadding: -20 - font.pixelSize: 20 - - } - - SortFilterProxyModel { - id: proxyContainersModel - sourceModel: UiLogic.containersModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 5 - clip: true - interactive: false - model: proxyContainersModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - Item { - id: c_item - width: parent.width - height: row_container.height + tb_p.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_c.currentIndex - } - Rectangle { - anchors.top: row_container.top - anchors.bottom: row_container.bottom - anchors.left: parent.left - anchors.right: parent.right - - color: "#63B4FB" - visible: index === tb_c.currentIndex - } - - RowLayout { - id: row_container - anchors.left: parent.left - anchors.right: parent.right - - Text { - id: lb_container_name - text: name_role - font.pixelSize: 17 - color: "#100A44" - topPadding: 16 - bottomPadding: 12 - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - Layout.fillWidth: true - - MouseArea { - enabled: col.visible - anchors.top: lb_container_name.top - anchors.bottom: lb_container_name.bottom - anchors.left: parent.left - anchors.right: parent.right - propagateComposedEvents: true - onClicked: { - if (tb_c.currentIndex === index) tb_c.currentIndex = -1 - else tb_c.currentIndex = index - - UiLogic.protocolsModel.setSelectedDockerContainer(proxyContainersModel.mapToSource(index)) - } - } - } - - ImageButtonType { - id: button_remove - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - checkable: true - icon.source: "qrc:/images/delete.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: popupRemove.open() - - VisibleBehavior on visible { } - } - - PopupWithQuestion { - id: popupRemove - questionText: qsTr("Remove container") + " " + name_role + "?" + "\n" + qsTr("This action will erase all data of this container on the server.") - yesFunc: function() { - tb_c.currentIndex = -1 - ServerContainersLogic.onPushButtonRemoveClicked(proxyContainersModel.mapToSource(index)) - close() - } - noFunc: function() { - close() - } - } - - ImageButtonType { - id: button_share - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - icon.source: "qrc:/images/share.png" - implicitWidth: 30 - implicitHeight: 30 - onClicked: { - ServerContainersLogic.onPushButtonShareClicked(proxyContainersModel.mapToSource(index)) - } - - VisibleBehavior on visible { } - } - - ImageButtonType { - id: button_default - visible: service_type_role == ProtocolEnum.Vpn - - Layout.alignment: Qt.AlignRight - checkable: true - img.source: checked ? "qrc:/images/check.png" : "qrc:/images/uncheck.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: { - ServerContainersLogic.onPushButtonDefaultClicked(proxyContainersModel.mapToSource(index)) - } - } - } - - - ListView { - id: tb_p - currentIndex: -1 - x: 10 - anchors.top: row_container.bottom - - width: parent.width - 40 - height: index === tb_c.currentIndex ? tb_p.contentItem.height : 0 - implicitHeight: height - - spacing: 0 - clip: true - interactive: false - model: proxyProtocolsModel - - - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - delegate: Item { - id: dp_item - - implicitWidth: tb_p.width - 10 - implicitHeight: p_item.height - Item { - id: p_item - width: parent.width - height: lb_protocol_name.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index > 0 - } - - SettingButtonType { - id: lb_protocol_name - topPadding: 10 - bottomPadding: 10 - - anchors.left: parent.left - anchors.leftMargin: 10 - - width: parent.width - height: 45 - text: qsTr(name_role + " settings") - textItem.font.pixelSize: 16 - icon.source: "qrc:/images/settings.png" - onClicked: { - tb_p.currentIndex = index - ServerContainersLogic.onPushButtonProtoSettingsClicked( - proxyContainersModel.mapToSource(tb_c.currentIndex), - proxyProtocolsModel.mapToSource(tb_p.currentIndex)) - } - } - } - } - } - } - } - } - } - } - - - BlueButtonType { - id: pb_add_container - visible: container_selector.selectedIndex < 0 && ServerContainersLogic.isManagedServer - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.topMargin: 10 - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Install new service") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - } -} diff --git a/client/ui/qml/Pages/PageServerList.qml b/client/ui/qml/Pages/PageServerList.qml deleted file mode 100644 index 80ac9a1b8..000000000 --- a/client/ui/qml/Pages/PageServerList.qml +++ /dev/null @@ -1,185 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 -import PageEnum 1.0 -import "../Controls" -import "./" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServersList - logic: ServerListLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Servers") - width: undefined - } - - SvgButtonType { - anchors.verticalCenter: caption.verticalCenter - anchors.leftMargin: 10 - anchors.left: caption.right - width: 27 - height: 27 - - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - onClicked: { - UiLogic.goToPage(PageEnum.Start); - } - } - - ListView { - id: listWidget_servers - x: GC.defaultMargin - anchors.top: caption.bottom - anchors.topMargin: 15 - width: parent.width - GC.defaultMargin - 1 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - model: ServerListLogic.serverListModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - currentIndex: ServerListLogic.currServerIdx - spacing: 5 - clip: true - delegate: Item { - height: 60 - width: listWidget_servers.width - 15 - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (GC.isMobile()) { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - mouse.accepted = false - } - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - } - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - id: label_address - x: 20 - y: 40 - width: listWidget_servers.width - 100 - height: 16 - text: address - } - Text { - x: 10 - y: 10 - width: listWidget_servers.width - 100 - height: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - font.bold: true - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: desc - } - ImageButtonType { - x: parent.width - 30 - y: 15 - width: 30 - height: 30 - checkable: true - icon.source: checked ? "qrc:/images/check.png" - : "qrc:/images/uncheck.png" - onClicked: { - ServerListLogic.onServerListPushbuttonDefaultClicked(index) - } - checked: is_default - enabled: !is_default - } - SvgButtonType { - id: pushButtonSetting - x: parent.width - 70 - y: 15 - width: 30 - height: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - opacity: 0 - - OpacityAnimator { - id: mouseEnterAni - target: pushButtonSetting; - from: 0; - to: 1; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - OpacityAnimator { - id: mouseExitAni - target: pushButtonSetting; - from: 1; - to: 0; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - - onClicked: { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - } - } - } - - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } - } -} diff --git a/client/ui/qml/Pages/PageServerSettings.qml b/client/ui/qml/Pages/PageServerSettings.qml deleted file mode 100644 index 4ef22ce4e..000000000 --- a/client/ui/qml/Pages/PageServerSettings.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerSettings - logic: ServerSettingsLogic - - enabled: ServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - text: ServerSettingsLogic.labelWaitInfoText - visible: ServerSettingsLogic.labelWaitInfoVisible - } - TextFieldType { - Layout.fillWidth: true - text: ServerSettingsLogic.lineEditDescriptionText - onEditingFinished: { - ServerSettingsLogic.lineEditDescriptionText = text - ServerSettingsLogic.onLineEditDescriptionEditingFinished() - } - } - - BlueButtonType { - text: qsTr("Protocols and Services") - Layout.topMargin: 20 - Layout.fillWidth: true - onClicked: { - UiLogic.goToPage(PageEnum.ServerContainers) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Share Server (FULL ACCESS)") - visible: ServerSettingsLogic.pushButtonShareFullVisible - onClicked: { - ServerSettingsLogic.onPushButtonShareFullClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Advanced server settings") - onClicked: { - UiLogic.goToPage(PageEnum.AdvancedServerSettings) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 60 - text: ServerSettingsLogic.pushButtonClearClientCacheText - visible: ServerSettingsLogic.pushButtonClearClientCacheVisible - onClicked: { - ServerSettingsLogic.onPushButtonClearClientCacheClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Forget this server") - onClicked: { - if (ServerSettingsLogic.isCurrentServerHasCredentials()) { - popupForgetServer.questionText = "Attention! This action will not remove any data from the server, it will just remove server from the list. Continue?" - } - else { - popupForgetServer.questionText = "Remove server from the list?" - } - popupForgetServer.open() - } - } - - PopupWithQuestion { - id: popupForgetServer - yesFunc: function() { - ServerSettingsLogic.onPushButtonForgetServer() - close() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageSetupWizard.qml b/client/ui/qml/Pages/PageSetupWizard.qml deleted file mode 100644 index a7e93a3ac..000000000 --- a/client/ui/qml/Pages/PageSetupWizard.qml +++ /dev/null @@ -1,108 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Wizard - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - RadioButtonType { - id: radioButton_setup_wizard_high - Layout.fillWidth: true - text: qsTr("High censorship level") - checked: WizardLogic.radioButtonHighChecked - onCheckedChanged: { - WizardLogic.radioButtonHighChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. -OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_medium - Layout.fillWidth: true - text: qsTr("Medium censorship level") - checked: WizardLogic.radioButtonMediumChecked - onCheckedChanged: { - WizardLogic.radioButtonMediumChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. -OpenVPN over ShadowSocks profile will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_low - Layout.fillWidth: true - text: qsTr("Low censorship level") - checked: WizardLogic.radioButtonLowChecked - onCheckedChanged: { - WizardLogic.radioButtonLowChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I want to improve my privacy on the internet. -OpenVPN profile will be installed.\n") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - if (radioButton_setup_wizard_high.checked) { - UiLogic.goToPage(PageEnum.WizardHigh, false); - } else if (radioButton_setup_wizard_medium.checked) { - UiLogic.goToPage(PageEnum.WizardMedium, false); - } else if (radioButton_setup_wizard_low.checked) { - UiLogic.goToPage(PageEnum.WizardLow, false); - } - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml b/client/ui/qml/Pages/PageSetupWizardHighLevel.qml deleted file mode 100644 index e0f194fff..000000000 --- a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardHigh - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr("AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. - -You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN.") - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN.") - } - - TextFieldType { - id: website_masking - Layout.fillWidth: true - text: WizardLogic.lineEditHighWebsiteMaskingText - onEditingFinished: { - let _text = website_masking.text - _text = _text.replace("http://", ""); - _text = _text.replace("https://", ""); - if (!_text) { - return - } - _text = _text.split("/")[0]; - WizardLogic.lineEditHighWebsiteMaskingText = _text - } - onAccepted: { - next_button.clicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - -This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin).") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - let domain = website_masking.text; - if (!domain || !domain.includes(".")) { - return - } - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml b/client/ui/qml/Pages/PageSetupWizardLowLevel.qml deleted file mode 100644 index d4e590c1c..000000000 --- a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardLow - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. - -You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. - -We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys.') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN profile will be installed') - verticalAlignment: Text.AlignBottom - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonLowFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml b/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml deleted file mode 100644 index 6e1a45c1f..000000000 --- a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardMedium - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking".\n\nThis protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN).') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN over ShadowSocks profile will be installed') - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml b/client/ui/qml/Pages/PageSetupWizardVPNMode.qml deleted file mode 100644 index 497ccad8a..000000000 --- a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardVpnMode - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: vpn_mode_finish.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('Optional.\n -You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.') - } - - CheckBoxType { - Layout.fillWidth: true - text: qsTr('Turn on mode "VPN for selected sites"') - checked: WizardLogic.checkBoxVpnModeChecked - onCheckedChanged: { - WizardLogic.checkBoxVpnModeChecked = checked - } - } - } - } - - BlueButtonType { - id: vpn_mode_finish - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonVpnModeFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageShareConnection.qml b/client/ui/qml/Pages/PageShareConnection.qml deleted file mode 100644 index b5439b473..000000000 --- a/client/ui/qml/Pages/PageShareConnection.qml +++ /dev/null @@ -1,87 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ShareConnection - logic: ShareConnectionLogic - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share protocol config") - width: undefined - } - - - FlickableType { - clip: true - anchors.top: caption.bottom - contentHeight: col.height - boundsBehavior: Flickable.StopAtBounds - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ShareConnectionContent { - text: qsTr("Share for Amnezia") - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(ProtocolEnum.Any) - } - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 10 - clip: true - interactive: false - model: proxyProtocolsModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - - ShareConnectionContent { - id: c_item - text: qsTr("Share for ") + name_role - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(proxyProtocolsModel.mapToSource(index)) - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageSites.qml b/client/ui/qml/Pages/PageSites.qml deleted file mode 100644 index 673e7e7a8..000000000 --- a/client/ui/qml/Pages/PageSites.qml +++ /dev/null @@ -1,315 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQml.Models -import Qt.labs.platform -import QtQuick.Dialogs -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Sites - logic: SitesLogic - - property int lastIndex: 0 - - BackButton { - id: back - } - - Caption { - id: caption - text: SitesLogic.labelSitesAddCustomText - } - - LabelType { - id: lb_addr - color: "#333333" - text: qsTr("Web site/Hostname/IP address/Subnet") - x: 20 - anchors.top: caption.bottom - anchors.topMargin: 10 - width: parent.width - height: 21 - } - - TextFieldType { - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: 20 - anchors.right: sites_add.left - anchors.rightMargin: 10 - height: 31 - placeholderText: qsTr("yousite.com or IP address") - text: SitesLogic.lineEditSitesAddCustomText - onEditingFinished: { - SitesLogic.lineEditSitesAddCustomText = text - } - onAccepted: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BlueButtonType { - id: sites_add - anchors.right: sites_import.left - anchors.rightMargin: 10 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - font.pixelSize: 24 - text: "+" - onClicked: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BasicButtonType { - id: sites_import - anchors.right: parent.right - anchors.rightMargin: 20 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: parent.containsMouse ? "#211966" : "#100A44" - } - font.pixelSize: 16 - contentItem: Item { - anchors.fill: parent - Image { - anchors.centerIn: parent - width: 20 - height: 20 - source: "qrc:/images/folder.png" - fillMode: Image.Stretch - } - } - antialiasing: true - onClicked: { - fileDialog.open() - } - } - FileDialog { - id: fileDialog - title: qsTr("Import IP addresses") - visible: false - currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) - onAccepted: { - SitesLogic.onPushButtonSitesImportClicked(fileUrl) - } - } - - DelegateModel { - id: visualModel - model: SitesLogic.tableViewSitesModel - groups: [ - DelegateModelGroup { - id : delegateModelGroup - name: "multiSelect" - function removeAll(){ - var count = delegateModelGroup.count; - if (count !== 0){ - delegateModelGroup.remove(0,count); - } - } - function selectAll(){ - for(var i = 0; i < visualModel.count; i++){ - visualModel.items.get(i).inMultiSelect = true - } - } - } - ] - delegate: Rectangle { - id: item - focus: true - height: 25 - width: root.width - color: item.DelegateModel.inMultiSelect ? '#63b4fb' : 'transparent' - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: 170 - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: url_path - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - Item { - anchors.left: c1.right - width: 170 - height: 30 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: ip - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked:{ - tb.focus = true - if(mouse.button === Qt.RightButton){ - //copyPasteMenu.popup() - console.log("RightButton") - } - if(mouse.button === Qt.LeftButton){ - switch(mouse.modifiers){ - case Qt.ControlModifier : - item.DelegateModel.inMultiSelect = !item.DelegateModel.inMultiSelect - break; - case Qt.ShiftModifier : - delegateModelGroup.removeAll(); - var start = lastIndex <= index? lastIndex: index; - var end = lastIndex >= index? lastIndex: index; - for(var i = start;i <= end;i++){ - visualModel.items.get(i).inMultiSelect = true - } - break; - default: - delegateModelGroup.removeAll(); - item.DelegateModel.inMultiSelect = true - lastIndex = index - break; - } - } - } - } - } - } - - ListView { - id: tb - x: 20 - anchors.top: sites_add.bottom - anchors.topMargin: 10 - width: parent.width - 40 - anchors.bottom: sites_delete.top - anchors.bottomMargin: 10 - spacing: 1 - clip: true - focus: true - activeFocusOnTab: true - keyNavigationEnabled: true - property int currentRow: -1 - model: visualModel - - Keys.onPressed: { - if (event.key === Qt.Key_PageUp) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx-20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_PageDown) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx+20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_Home) { - tb.positionViewAtBeginning() - event.accepted = true - } - else if (event.key === Qt.Key_End) { - tb.positionViewAtEnd() - event.accepted = true - } - else if (event.key === Qt.Key_Delete) { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - SitesLogic.onPushButtonSitesDeleteClicked(items) - event.accepted = true - } - else if (event.key === Qt.Key_A) { - delegateModelGroup.selectAll() - event.accepted = true - } - } - - } - - BlueButtonType { - id: sites_delete - anchors.bottom: select_all.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Delete selected") - onClicked: { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - - SitesLogic.onPushButtonSitesDeleteClicked(items) - } - } - - BlueButtonType { - id: select_all - anchors.bottom: sites_export.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Select all") - onClicked: { - delegateModelGroup.selectAll() - } - } - - BlueButtonType { - id: sites_export - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Export all") - onClicked: { - SitesLogic.onPushButtonSitesExportClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml deleted file mode 100644 index a752817f9..000000000 --- a/client/ui/qml/Pages/PageStart.qml +++ /dev/null @@ -1,356 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Start - logic: StartPageLogic - - Connections { - target: StartPageLogic - - function onShowPassphraseRequestMessage() { - popupWithTextField.open() - } - } - - BackButton { - id: back_from_start - visible: pageLoader.depth > 1 - } - - ImageButtonType { - anchors { - right: parent.right - top: parent.top - } - - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - visible: !GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - Caption { - id: caption - text: start_switch_page.checked ? - qsTr("Setup your server to use VPN") : - qsTr("Connect to the already created VPN server") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } - - BasicButtonType { - id: start_switch_page - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.top - anchors.bottomMargin: 10 - anchors.topMargin: 20 - - text: qsTr("Set up your own server") - checked: false - checkable: true - onCheckedChanged: { - if (checked) { - page_start_new_server.visible = true - page_start_import.visible = false - text = qsTr("Import connection"); - } - else { - page_start_new_server.visible = false - page_start_import.visible = true - text = qsTr("Set up your own server"); - } - } - - background: Rectangle { - anchors.fill: parent - border.width: 1 - border.color: "#211C4A" - radius: 4 - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#100A44" - text: start_switch_page.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - } - - Item { - id: page_start_import - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: true - - LabelType { - id: label_connection_code - anchors.top: parent.top - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Connection code") - } - TextFieldType { - id: lineEdit_start_existing_code - anchors.top: label_connection_code.bottom - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "vpn://..." - text: StartPageLogic.lineEditStartExistingCodeText - onEditingFinished: { - StartPageLogic.lineEditStartExistingCodeText = text - } - } - BlueButtonType { - id: new_sever_import - anchors.horizontalCenter: parent.horizontalCenter - y: 210 - anchors.top: lineEdit_start_existing_code.bottom - anchors.topMargin: 10 - text: qsTr("Connect") - onClicked: { - StartPageLogic.onPushButtonImport() - } - } - - - BlueButtonType { - id: qr_code_import_open - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_import.bottom - anchors.topMargin: 40 - - text: qsTr("Open file") - onClicked: { - StartPageLogic.onPushButtonImportOpenFile() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: qr_code_import - visible: GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import_open.bottom - anchors.topMargin: 10 - - text: qsTr("Scan QR code") - onClicked: { - if (Qt.platform.os === "ios") { - UiLogic.goToPage(PageEnum.QrDecoderIos) - } else { - StartPageLogic.startQrDecoder() - } - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: btn_restore_cfg - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import.bottom - anchors.topMargin: 30 - visible: UiLogic.pagesStackDepth === 1 - enabled: StartPageLogic.pushButtonConnectEnabled - - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - - - Item { - id: page_start_new_server - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: false - - BasicButtonType { - id: new_sever_get_info - width: parent.width - 80 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 5 - - text: qsTr("How to get own server? →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: new_sever_get_info.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: true - onClicked: { - Qt.openUrlExternally("https://amnezia.org/instruction.html") - } - } - LabelType { - id: label_server_ip - x: 40 - anchors.top: new_sever_get_info.bottom - text: qsTr("Server IP address [:port]") - } - TextFieldType { - id: new_server_ip - anchors.top: label_server_ip.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditIpText - onEditingFinished: { StartPageLogic.lineEditIpText = text } - onTextEdited: { StartPageLogic.lineEditIpText = text } - - validator: RegularExpressionValidator { - regularExpression: StartPageLogic.ipAddressPortRegex - } - } - - LabelType { - id:label_login - x: 40 - anchors.top: new_server_ip.bottom - text: qsTr("Login to connect via SSH") - } - TextFieldType { - id: new_server_login - anchors.top: label_login.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditLoginText - onEditingFinished: { StartPageLogic.lineEditLoginText = text } - onTextEdited: { StartPageLogic.lineEditLoginText = text } - } - - LabelType { - id: label_new_server_password - x: 40 - anchors.top: new_server_login.bottom - text: qsTr("Password") - } - TextFieldType { - id: new_server_password - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - inputMethodHints: Qt.ImhSensitiveData - echoMode: TextInput.Password - text: StartPageLogic.lineEditPasswordText - onEditingFinished: { StartPageLogic.lineEditPasswordText = text } - onTextEdited: { StartPageLogic.lineEditPasswordText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - TextFieldType { - id: new_server_ssh_key - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - visible: false - height: 71 - font.pixelSize: 10 - verticalAlignment: Text.AlignTop - inputMethodHints: Qt.ImhSensitiveData - - text: StartPageLogic.textEditSshKeyText - onEditingFinished: { StartPageLogic.textEditSshKeyText = text } - onTextEdited: { StartPageLogic.textEditSshKeyText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - - LabelType { - x: 40 - y: 390 - width: 301 - height: 41 - text: StartPageLogic.labelWaitInfoText - visible: StartPageLogic.labelWaitInfoVisible - wrapMode: Text.Wrap - } - - BlueButtonType { - id: new_sever_connect - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_server_ssh_key.bottom - anchors.topMargin: 10 - - text: StartPageLogic.pushButtonConnectText - onClicked: { - StartPageLogic.onPushButtonConnect() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - UrlButtonType { - id: new_sever_connect_key - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_connect.bottom - - width: 281 - height: 21 - text: qsTr("Connect using SSH key") - - label.font.pixelSize: 16 - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - onCheckedChanged: { - StartPageLogic.pushButtonConnectKeyChecked = checked - label_new_server_password.text = checked ? qsTr("Private key") : qsTr("Password") - new_sever_connect_key.text = checked ? qsTr("Connect using SSH password") : qsTr("Connect using SSH key") - new_server_password.visible = !checked - new_server_ssh_key.visible = checked - } - } - } - - PopupWithTextField { - id: popupWithTextField - placeholderText: "Enter private key passphrase" - yesFunc: function() { - editingFinished() - close() - StartPageLogic.passphraseDialogClosed() - text = "" - } - noFunc: function() { - close() - StartPageLogic.passphraseDialogClosed() - } - onEditingFinished: { - StartPageLogic.privateKeyPassphrase = text - } - } -} diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml deleted file mode 100644 index 0e6f5078e..000000000 --- a/client/ui/qml/Pages/PageVPN.qml +++ /dev/null @@ -1,387 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Vpn - logic: VpnLogic - - Image { - id: bg_top - anchors.horizontalCenter: parent.horizontalCenter - y: 0 - width: parent.width - height: parent.height * 0.28 - source: "qrc:/images/background_connected.png" - } - - LabelType { - x: 10 - y: 10 - width: 100 - height: 21 - text: VpnLogic.labelVersionText - color: "#dddddd" - font.pixelSize: 12 - } - - UrlButtonType { - id: button_donate - y: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - label.color: "#D4D4D4" - label.text: qsTr("Donate") - - onClicked: { - UiLogic.goToPage(PageEnum.Test) - } - } - - ImageButtonType { - x: parent.width - 40 - y: 0 - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - LabelType { - id: lb_log_enabled - anchors.top: button_donate.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - text: "Logging enabled!" - color: "#D4D4D4" - - visible: VpnLogic.labelLogEnabledVisible - } - - AnimatedImage { - id: connect_anim - source: "qrc:/images/animation.gif" - anchors.top: bg_top.bottom - anchors.topMargin: 10 - anchors.horizontalCenter: root.horizontalCenter - width: Math.min(parent.width, parent.height) / 4 - height: width - - visible: !VpnLogic.pushButtonConnectVisible - paused: VpnLogic.pushButtonConnectVisible && !root.pageActive - //VisibleBehavior on visible { } - } - - BasicButtonType { - id: button_connect - anchors.horizontalCenter: connect_anim.horizontalCenter - anchors.verticalCenter: connect_anim.verticalCenter - width: connect_anim.width - height: width - checkable: true - checked: VpnLogic.pushButtonConnectChecked - onClicked: VpnLogic.onPushButtonConnectClicked() - background: Image { - anchors.fill: parent - source: button_connect.checked ? "qrc:/images/connected.png" - : "qrc:/images/disconnected.png" - } - contentItem: Item {} - antialiasing: true - enabled: VpnLogic.pushButtonConnectEnabled && VpnLogic.isContainerSupportedByCurrentPlatform - opacity: VpnLogic.pushButtonConnectVisible ? 1 : 0 - -// transitions: Transition { -// NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 500 } -// } - } - - - LabelType { - id: lb_state - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: button_connect.bottom - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - text: VpnLogic.labelStateText - } - - RowLayout { - id: layout1 - anchors.top: lb_state.bottom - //anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: ( VpnLogic.isContainerHaveAuthData ? qsTr("Server") : qsTr("Profile")) + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentServer + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - } - - RowLayout { - id: layout2 - anchors.top: layout1.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("Proto") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentService + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.onGotoCurrentProtocolsPage() - } - } - } - - RowLayout { - id: layout3 - anchors.top: layout2.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("DNS") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - implicitWidth: implicitContentWidth > root.width * 0.6 ? root.width * 0.6 : implicitContentWidth + leftPadding + rightPadding - background: Item {} - text: VpnLogic.labelCurrentDns + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - SvgImageType { - svg.source: VpnLogic.amneziaDnsEnabled ? "qrc:/images/svg/gpp_good_black_24dp.svg" : "qrc:/images/svg/gpp_maybe_black_24dp.svg" - color: VpnLogic.amneziaDnsEnabled ? "#22aa33" : "orange" - width: 25 - height: 25 - } - } - - - LabelType { - id: error_text - anchors.top: layout3.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 - width: parent.width - 20 - - height: 21 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - text: VpnLogic.labelErrorText - color: "red" - } - - Item { - x: 0 - anchors.bottom: line.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: parent.width - height: 51 - Image { - anchors.horizontalCenter: upload_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/upload.png" - } - Image { - anchors.horizontalCenter: download_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/download.png" - } - Text { - id: download_label - x: 0 - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#4171D6" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedReceivedText - } - Text { - id: upload_label - x: parent.width - width - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#42D185" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedSentText - } - } - - Rectangle { - id: line - x: 20 - width: parent.width - 40 - height: 1 - anchors.bottom: GC.isMobile() ? root.bottom : conn_type_label.top - anchors.bottomMargin: 10 - color: "#DDDDDD" - } - - Text { - id: conn_type_label - visible: !GC.isMobile() - x: 20 - anchors.bottom: conn_type_group.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: 281 - height: GC.isMobile() ? 0: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 15 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: qsTr("How to use VPN") - } - - Item { - id: conn_type_group - x: 20 - visible: !GC.isMobile() - anchors.bottom: button_add_site.top - width: 351 - height: GC.isMobile() ? 0: 91 - enabled: VpnLogic.widgetVpnModeEnabled - RadioButtonType { - x: 0 - y: 0 - width: 341 - height: 19 - checked: VpnLogic.radioButtonVpnModeAllSitesChecked - text: qsTr("For all connections") - onClicked: VpnLogic.onRadioButtonVpnModeAllSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 60 - width: 341 - height: 19 - text: qsTr("Except selected sites") - checked: VpnLogic.radioButtonVpnModeExceptSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeExceptSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 30 - width: 341 - height: 19 - text: qsTr("For selected sites") - checked: VpnLogic.radioButtonVpnModeForwardSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeForwardSitesClicked(true) - } - } - - BasicButtonType { - id: button_add_site - visible: !GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: GC.isMobile() ? 0: 40 - text: qsTr("+ Add site") - enabled: ! VpnLogic.radioButtonVpnModeAllSitesChecked - background: Rectangle { - anchors.fill: parent - radius: 4 - color: { - if (!button_add_site.enabled) { - return "#484952" - } - if (button_add_site.containsMouse) { - return "#282932" - } - return "#181922" - } - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - text: button_add_site.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - onClicked: { - UiLogic.goToPage(PageEnum.Sites) - } - } -} diff --git a/client/ui/qml/Pages/PageViewConfig.qml b/client/ui/qml/Pages/PageViewConfig.qml deleted file mode 100644 index 24ccda451..000000000 --- a/client/ui/qml/Pages/PageViewConfig.qml +++ /dev/null @@ -1,138 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ViewConfig - logic: ViewConfigLogic - - readonly property double rowHeight: ta_last_config.contentHeight / ta_last_config.textArea.lineCount - - BackButton {} - - Caption { - id: caption - text: qsTr("Check config") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: ViewConfigLogic.warningActive ? 250 : fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.configText - } - - LabelType { - id: lb_att - visible: ViewConfigLogic.warningActive - text: qsTr("Attention! -The config above contains cached OpenVPN connection profile. -AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it.") - Layout.fillWidth: true - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Suspicious string:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_mal - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 60 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnMalStrings - textArea.textFormat: TextEdit.RichText - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Cached connection profile:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_last_config - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 350 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigs - textArea.textFormat: TextEdit.RichText - - Connections { - target: logic - function onWarningStringNumberChanged(n) { - ta_last_config.contentY = rowHeight * n - ta_last_config.height / 2 - } - } - } - - RowLayout { - id: btns_row - - BasicButtonType { - Layout.preferredWidth: (content.width - parent.spacing) /2 - Layout.preferredHeight: 40 - font.pixelSize: btn_import.font.pixelSize - text: qsTr("Cancel") - onClicked: { - UiLogic.closePage() - } - } - - BlueButtonType { - id: btn_import - Layout.preferredWidth: (content.width - parent.spacing) /2 - text: qsTr("Import config") - onClicked: { - logic.importConfig() - } - } - } - } - } - - } diff --git a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml b/client/ui/qml/Pages/Protocols/PageProtoCloak.qml deleted file mode 100644 index 2b5b12ca0..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml +++ /dev/null @@ -1,194 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("Cloak Settings") - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - Layout.fillWidth: true - height: 31 - model: [ - qsTr("chacha20-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxCipherText = currentText - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Fake Web Site") - } - - TextFieldType { - id: lineEdit_proto_cloak_site - Layout.fillWidth: true - height: 31 - text: logic.lineEditSiteText - onEditingFinished: { - logic.lineEditSiteText = text - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_cloak_port - Layout.fillWidth: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - Item { - Layout.fillHeight: true - } - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_cloak_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - - ProgressBar { - id: progressBar_proto_cloak_reset - anchors.horizontalCenter: parent.horizontalCenter - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_proto_cloak_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - visible: logic.progressBarResetVisible - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - BlueButtonType { - id: pb_save - anchors.horizontalCenter: parent.horizontalCenter - enabled: logic.pageEnabled - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml b/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml deleted file mode 100644 index 507f5f091..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml +++ /dev/null @@ -1,454 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - 1 - - ColumnLayout { - visible: !logic.isThirdPartyConfig - - LabelType { - id: lb_subnet - enabled: logic.pageEnabled - height: 21 - text: qsTr("VPN Addresses Subnet") - } - - TextFieldType { - id: tf_subnet - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 31 - text: logic.lineEditSubnetText - onEditingFinished: { - logic.lineEditSubnetText = text - } - } - - LabelType { - id: lb_proto - enabled: logic.pageEnabled - Layout.topMargin: 20 - height: 21 - text: qsTr("Network protocol") - } - - Rectangle { - id: rect_proto - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 71 - border.width: 1 - border.color: "lightgray" - radius: 2 - RadioButtonType { - x: 10 - y: 40 - width: 171 - height: 19 - text: qsTr("TCP") - enabled: logic.radioButtonTcpEnabled - checked: logic.radioButtonTcpChecked - onCheckedChanged: { - logic.radioButtonTcpChecked = checked - } - } - RadioButtonType { - x: 10 - y: 10 - width: 171 - height: 19 - text: qsTr("UDP") - checked: logic.radioButtonUdpChecked - onCheckedChanged: { - logic.radioButtonUdpChecked = checked - } - enabled: logic.radioButtonUdpEnabled - } - } - - RowLayout { - enabled: logic.pageEnabled - Layout.topMargin: 10 - Layout.fillWidth: true - LabelType { - id: lb_port - height: 31 - text: qsTr("Port") - Layout.preferredWidth: root.width / 2 - 10 - } - TextFieldType { - id: tf_port - Layout.fillWidth: true - - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - CheckBoxType { - id: check_auto_enc - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Auto-negotiate encryption") - checked: logic.checkBoxAutoEncryptionChecked - onCheckedChanged: { - logic.checkBoxAutoEncryptionChecked = checked - } - onClicked: { - logic.checkBoxAutoEncryptionClicked() - } - } - - LabelType { - id: lb_cipher - enabled: logic.pageEnabled - height: 21 - text: qsTr("Cipher") - } - - ComboBoxType { - id: cb_cipher - enabled: logic.pageEnabled && !check_auto_enc.checked - implicitWidth: parent.width - - height: 31 - model: [ - qsTr("AES-256-GCM"), - qsTr("AES-192-GCM"), - qsTr("AES-128-GCM"), - qsTr("AES-256-CBC"), - qsTr("AES-192-CBC"), - qsTr("AES-128-CBC"), - qsTr("ChaCha20-Poly1305"), - qsTr("ARIA-256-CBC"), - qsTr("CAMELLIA-256-CBC"), - qsTr("none") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnCipherText = currentText - } - } - - LabelType { - id: lb_hash - enabled: logic.pageEnabled - height: 21 - Layout.topMargin: 20 - text: qsTr("Hash") - } - - ComboBoxType { - id: cb_hash - enabled: logic.pageEnabled && !check_auto_enc.checked - height: 31 - implicitWidth: parent.width - model: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnHashText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnHashText = currentText - } - } - - CheckBoxType { - id: check_tls - enabled: logic.pageEnabled - implicitWidth: parent.width - Layout.topMargin: 20 - height: 21 - text: qsTr("Enable TLS auth") - checked: logic.checkBoxTlsAuthChecked - onCheckedChanged: { - logic.checkBoxTlsAuthChecked = checked - } - - } - - CheckBoxType { - id: check_block_dns - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Block DNS requests outside of VPN") - checked: logic.checkBoxBlockDnsChecked - onCheckedChanged: { - logic.checkBoxBlockDnsChecked = checked - } - } - - BasicButtonType { - id: pb_client_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional client config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_client_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_client_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_client_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_client_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalClientConfig - onEditingFinished: { - logic.textAreaAdditionalClientConfig = text - } - } - } - } - - BasicButtonType { - id: pb_server_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional server config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_server_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_server_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_server_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_server_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalServerConfig - onEditingFinished: { - logic.textAreaAdditionalServerConfig = text - } - } - } - } - - LabelType { - id: label_server_busy - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_openvpn_info - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - height: 41 - visible: logic.labelProtoOpenVpnInfoVisible - text: logic.labelProtoOpenVpnInfoText - } - - Rectangle { - id: it_save - implicitWidth: parent.width - Layout.topMargin: 20 - height: 40 - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - z: 1 - height: 40 - text: qsTr("Save and restart VPN") - width: parent.width - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - z: 1 - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: progress_save - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progress_save.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - } - - ColumnLayout { - visible: logic.isThirdPartyConfig - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigText - } - } - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml b/client/ui/qml/Pages/Protocols/PageProtoSftp.qml deleted file mode 100644 index c6a0602f0..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("SFTP settings") - } - - Rectangle { - id: frame_settings - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 30 - rightPadding: 30 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPortText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("User Name") - } - TextFieldType { - id: tf_user_name - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpUserNameText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("Password") - } - TextFieldType { - id: tf_password - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPasswordText - readOnly: true - } - } - } - - RichLabelType { - anchors.bottom: check_persist.top - anchors.bottomMargin: 10 - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - - readonly property string windows_text: "In order to mount remote SFTP folder as local drive, perform following steps: -
    -
  • Install the latest version of WinFsp.
  • -
  • Install the latest version of SSHFS-Win. Choose the x64 or x86 installer according to your computer's architecture.
  • -
" - - readonly property string macos_text: "In order to mount remote SFTP folder as local folder, perform following steps: -
    -
  • Install the latest version of macFUSE.
  • -
  • Install the latest version of SSHFS.
  • -
" - - text: { - if (Qt.platform.os == "windows") return windows_text - else if (Qt.platform.os == "osx") return macos_text - else if (Qt.platform.os == "linux") return "" - else return "" - } - } - - CheckBoxType { - id: check_persist - visible: false - anchors.bottom: pb_mount.top - anchors.bottomMargin: 10 - x: 30 - width: parent.width - height: 21 - text: qsTr("Restore drive when client starts") - checked: logic.checkBoxSftpRestoreChecked - onCheckedChanged: { - logic.checkBoxSftpRestoreChecked = checked - } - onClicked: { - logic.checkBoxSftpRestoreClicked() - } - } - - BlueButtonType { - id: pb_mount - visible: GC.isDesktop() - enabled: logic.pushButtonSftpMountEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - x: 30 - width: parent.width - 60 - height: 40 - text: qsTr("Mount drive") - onClicked: { - logic.onPushButtonSftpMountDriveClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml b/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml deleted file mode 100644 index f3b1f83de..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml +++ /dev/null @@ -1,175 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("ShadowSocks Settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - height: 31 - Layout.fillWidth: true - - model: [ - qsTr("chacha20-ietf-poly1305"), - qsTr("xchacha20-ietf-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_shadowsocks_port - Layout.fillWidth: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - Item { - Layout.fillHeight: true - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_shadowsocks_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - } - - ProgressBar { - id: progressBar_reset - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml b/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml deleted file mode 100644 index aa4d35d47..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml +++ /dev/null @@ -1,69 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Tor Web Site settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - id: lbl_onion - Layout.preferredWidth: 0.3 * root.width - 10 - text: qsTr("Web site onion address") - } - TextFieldType { - id: tf_site_address - Layout.fillWidth: true - text: logic.labelTorWebSiteAddressText - readOnly: true - } - } - - ShareConnectionButtonCopyType { - Layout.fillWidth: true - Layout.topMargin: 5 - copyText: tf_site_address.text - } - - RichLabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Notes:
    -
  • Use Tor Browser to open this url.
  • -
  • After installation it takes several minutes while your onion site will become available in the Tor Network.
  • -
  • When configuring WordPress set the domain as this onion address.
  • -
-") - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml b/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml deleted file mode 100644 index e4dde50ec..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("WireGuard Settings") - } - - Flickable { - id: fl - width: root.width - anchors.top: caption.bottom - anchors.topMargin: 20 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.left: root.left - anchors.leftMargin: 30 - anchors.right: root.right - anchors.rightMargin: 30 - - contentHeight: content.height - clip: true - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.wireGuardLastConfigText - } - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml b/client/ui/qml/Pages/Protocols/PageProtocolBase.qml deleted file mode 100644 index 97a0f1eb4..000000000 --- a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolSettings -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml b/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml deleted file mode 100644 index 4e52e5018..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml +++ /dev/null @@ -1,145 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Any - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share for Amnezia") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height + 20 - - Behavior on contentY{ - NumberAnimation { - duration: 300 - easing.type: Easing.InOutCubic - } - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - Text { - id: lb_desc - Layout.fillWidth: true - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: ShareConnectionLogic.shareFullAccess - ? qsTr("Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. \n -This code includes your server credentials!\n -Provide this code only to TRUSTED users.") - : qsTr("Anyone who logs in with this code will be able to connect to this VPN server. \n -This code does not include server credentials.\n -New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: ShareConnectionLogic.shareFullAccess - ? showConfigText - : (genConfigProcess ? generatingConfigText : generateConfigText) - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareAmneziaGenerateClicked() - enabled = true - genConfigProcess = false - fl.contentY = tfShareCode.mapToItem(fl.contentItem, 0, 0).y - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.preferredHeight: 200 - - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareAmneziaCodeText - - visible: tfShareCode.textArea.length > 0 - } - - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config.vpn", "*.vpn", tfShareCode.textArea.text) - } - } - - Image { - id: image_share_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - - Timer { - property int idx: 0 - interval: 1000 - running: root.pageActive && ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - repeat: true - onTriggered: { - idx++ - if (idx >= ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength) { - idx = 0 - } - image_share_code.source = ShareConnectionLogic.shareAmneziaQrCodeTextSeries[idx] - } - } - - visible: ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - } - - LabelType { - Layout.fillWidth: true - text: qsTr("Scan QR code using AmneziaVPN mobile") - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml b/client/ui/qml/Pages/Share/PageShareProtoCloak.qml deleted file mode 100644 index 92bcb8328..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml +++ /dev/null @@ -1,99 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share Cloak Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: Cloak protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareCloakGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 200 - - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareCloakText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config_cloak.json", "*.json", tfShareCode.textArea.text) - } - } - - } - } - -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml b/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml deleted file mode 100644 index 7e41650a7..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml +++ /dev/null @@ -1,131 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Ikev2 - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share IKEv2 Settings") - } - - TextAreaType { - id: tfCert - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2CertText - - visible: false - } - - TextAreaType { - id: tfMobileConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2MobileConfigText - - visible: false - } - - TextAreaType { - id: tfStrongSwanConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2StrongSwanConfigText - - visible: false - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - -// LabelType { -// id: lb_desc -// Layout.fillWidth: true -// Layout.topMargin: 10 - -// horizontalAlignment: Text.AlignHCenter - -// wrapMode: Text.Wrap -// text: qsTr("Note: ShadowSocks protocol using same password for all connections") -// } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareIkev2GenerateClicked() - enabled = true - genConfigProcess = false - } - } - - ShareConnectionButtonType { - Layout.topMargin: 30 - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export p12 certificate") - enabled: tfCert.textArea.length > 0 - visible: tfCert.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export p12 certificate"), "amnezia_ikev2_cert_for_windows.p12", "*.p12", tfCert.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for Apple") - enabled: tfMobileConfig.textArea.length > 0 - visible: tfMobileConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for Apple"), "amnezia_for_apple.plist", "*.plist", tfMobileConfig.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for StrongSwan") - enabled: tfStrongSwanConfig.textArea.length > 0 - visible: tfStrongSwanConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for StrongSwan"), "amnezia_for_StrongSwan.profile", "*.profile", tfStrongSwanConfig.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml b/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml deleted file mode 100644 index 4246be216..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareOpenVpnGenerateClicked() - genConfigProcess = false - enabled = true - } - } - - TextAreaType { - id: tfShareCode - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareOpenVpnCodeText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_openvpn.ovpn", "*.ovpn", tfShareCode.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml b/client/ui/qml/Pages/Share/PageShareProtoSftp.qml deleted file mode 100644 index 8f990ac1d..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml +++ /dev/null @@ -1,21 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share SFTP settings") - } - - } diff --git a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml b/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml deleted file mode 100644 index 91f5e8d29..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share ShadowSocks Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: ShadowSocks protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareShadowSocksGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareShadowSocksText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - Layout.bottomMargin: 20 - - start_text: qsTr("Copy config") - copyText: tfShareCode.textArea.text - } - - LabelType { - height: 20 - visible: tfConnString.length > 0 - text: qsTr("Connection string") - } - TextFieldType { - id: tfConnString - height: 100 - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - text: ShareConnectionLogic.lineEditShareShadowSocksStringText - visible: tfConnString.length > 0 - - readOnly: true - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - start_text: qsTr("Copy string") - copyText: tfConnString.text - } - - Image { - id: label_share_ss_qr_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareShadowSocksQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml b/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml deleted file mode 100644 index 1c68f6cb8..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share Tor Web site") - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml b/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml deleted file mode 100644 index 7265de81c..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml +++ /dev/null @@ -1,103 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share WireGuard Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareWireGuardGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareWireGuardCodeText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_wireguard.conf", "*.conf", tfShareCode.textArea.text) - } - } - - Image { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareWireGuardQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml b/client/ui/qml/Pages/Share/PageShareProtocolBase.qml deleted file mode 100644 index 603abdfad..000000000 --- a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolShare - logic: ShareConnectionLogic - - readonly property string generateConfigText: qsTr("Generate config") - readonly property string generatingConfigText: qsTr("Generating config...") - readonly property string showConfigText: qsTr("Show config") - property bool genConfigProcess: false -} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml deleted file mode 100644 index 615197f01..000000000 --- a/client/ui/qml/main.qml +++ /dev/null @@ -1,387 +0,0 @@ -import QtCore -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Basic -import QtQuick.Dialogs -import QtQuick.Layouts -import QtQuick.Window - -import Qt.labs.platform as LabsPlatform -import Qt.labs.folderlistmodel as LabsFolderlistmodel - -import PageEnum 1.0 -import PageType 1.0 - -import "Controls" -import "Pages" -import "Pages/Protocols" -import "Pages/Share" -import "Pages/ClientInfo" -import "Config" - -Window { - property var pages: ({}) - property var protocolPages: ({}) - property var sharePages: ({}) - property var clientInfoPages: ({}) - - id: root - visible: true - width: GC.screenWidth - height: GC.screenHeight - minimumWidth: GC.isDesktop() ? 360 : 0 - minimumHeight: GC.isDesktop() ? 640 : 0 - onClosing: function() { - console.debug("QML onClosing signal") - UiLogic.onCloseWindow() - } - - title: "AmneziaVPN" - - function gotoPage(type, page, reset, slide) { - - let p_obj; - if (type === PageType.Basic) p_obj = pages[page] - else if (type === PageType.Proto) p_obj = protocolPages[page] - else if (type === PageType.ShareProto) p_obj = sharePages[page] - else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page] - else return - - //console.debug("QML gotoPage " + type + " " + page + " " + p_obj) - - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - if (slide) { - pageLoader.push(p_obj, {}, StackView.PushTransition) - } else { - pageLoader.push(p_obj, {}, StackView.Immediate) - } - - if (reset) { - p_obj.logic.onUpdatePage(); - } - - p_obj.activated(reset) - } - - function close_page() { - if (pageLoader.depth <= 1) { - if (GC.isMobile()) { - root.close() - } - return - } - - pageLoader.currentItem.deactivated() - pageLoader.pop() - } - - function set_start_page(page, slide) { - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - pageLoader.clear() - if (slide) { - pageLoader.push(pages[page], {}, StackView.PushTransition) - } else { - pageLoader.push(pages[page], {}, StackView.Immediate) - } - if (page === PageEnum.Start) { - UiLogic.pushButtonBackFromStartVisible = !pageLoader.empty - UiLogic.onUpdatePage(); - } - } - - Rectangle { - anchors.fill: parent - color: "white" - } - - StackView { - id: pageLoader - y: 0 - anchors.fill: parent - focus: true - - onCurrentItemChanged: function() { - UiLogic.currentPageValue = currentItem.page - } - - onDepthChanged: function() { - UiLogic.pagesStackDepth = depth - } - - Keys.onPressed: function(event) { - UiLogic.keyPressEvent(event.key) - event.accepted = true - } - } - - LabsFolderlistmodel.FolderListModel { - id: folderModelPages - folder: "qrc:/ui/qml/Pages/" - nameFilters: ["*.qml"] - showDirs: false - - onStatusChanged: if (status == LabsFolderlistmodel.FolderListModel.Ready) { - for (var i=0; i -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "amnezia_application.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/ssh_configurator.h" -#include "configurators/vpn_configurator.h" - -#include "core/errorstrings.h" -#include "core/server_defs.h" -#include "core/servercontroller.h" - -#include "containers/containers_defs.h" - -#include "ui/qautostart.h" - -#include "logger.h" -#include "uilogic.h" -#include "utilities.h" -#include "version.h" -#include "vpnconnection.h" -#include - -#if defined Q_OS_MAC || defined Q_OS_LINUX - #include "ui/macos_util.h" -#endif - -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif - -#include "platforms/ios/MobileUtils.h" - -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/AppSettingsLogic.h" -#include "pages_logic/ClientInfoLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/GeneralSettingsLogic.h" -#include "pages_logic/NetworkSettingsLogic.h" -#include "pages_logic/NewServerProtocolsLogic.h" -#include "pages_logic/QrDecoderLogic.h" -#include "pages_logic/ServerConfiguringProgressLogic.h" -#include "pages_logic/ServerContainersLogic.h" -#include "pages_logic/ServerListLogic.h" -#include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ShareConnectionLogic.h" -#include "pages_logic/SitesLogic.h" -#include "pages_logic/StartPageLogic.h" -#include "pages_logic/ViewConfigLogic.h" -#include "pages_logic/VpnLogic.h" -#include "pages_logic/WizardLogic.h" - -#include "pages_logic/protocols/CloakLogic.h" -#include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/OtherProtocolsLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" -#include "pages_logic/protocols/WireGuardLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) - : QObject(parent), m_settings(settings), m_configurator(configurator) -{ - m_protocolsModel = new ProtocolsModel(settings, this); - m_clientManagementModel = new ClientManagementModel(this); - m_vpnConnection = new VpnConnection(settings, configurator); - m_vpnConnection->moveToThread(&m_vpnConnectionThread); - m_vpnConnectionThread.start(); - - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); - m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); - m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); - m_protocolLogicMap.insert(Proto::WireGuard, new WireGuardLogic(this)); - - m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::TorWebSite, new OtherProtocolsLogic(this)); -} - -UiLogic::~UiLogic() -{ - emit hide(); - -#ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - m_vpnConnection->disconnectFromVpn(); - for (int i = 0; i < 50; i++) { - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - QThread::msleep(100); - if (m_vpnConnection->isDisconnected()) { - break; - } - } - } -#endif - - m_vpnConnection->deleteLater(); - m_vpnConnectionThread.quit(); - m_vpnConnectionThread.wait(3000); - - qDebug() << "Application closed"; -} - -void UiLogic::initializeUiLogic() -{ -#ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::initialized, - [this](bool status, bool connected, const QDateTime &connectionDate) { - if (connected) { - pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); - if (m_vpnConnection) - m_vpnConnection->restoreConnection(); - } - }); -// if (!AndroidController::instance()->initialize(pageLogic())) { -// qCritical() << QString("Init failed"); -// if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); -// return; -// } -#endif - - m_notificationHandler = NotificationHandler::create(qmlRoot()); - - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, - &NotificationHandler::setConnectionState); - connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); - connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), - &VpnLogic::onDisconnect); - - // if (m_settings->serversCount() > 0) { - // if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - // emit goToPage(Page::PageStart, true, false); - // } - // else { - // emit goToPage(Page::PageSetupWizardStart, true, false); - // } - - m_selectedServerIndex = m_settings->defaultServerIndex(); -} - -void UiLogic::showOnStartup() -{ - if (!m_settings->isStartMinimized()) { - emit show(); - } else { -#ifdef Q_OS_WIN - emit hide(); -#elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); -#endif - } -} - -void UiLogic::onUpdateAllPages() -{ - for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic) - || dynamic_cast(logic)) { - continue; - } - logic->onUpdatePage(); - } -} - -void UiLogic::keyPressEvent(Qt::Key key) -{ - switch (key) { - case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; - case Qt::Key_L: Logger::openLogsFolder(); break; - case Qt::Key_K: Logger::openServiceLogsFolder(); break; -#ifdef QT_DEBUG - case Qt::Key_Q: qApp->quit(); break; - case Qt::Key_H: - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - - // updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); - emit goToPage(Page::ShareConnection); - break; -#endif - case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() - << m_settings->defaultContainerName(m_settings->defaultServerIndex()); - qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); - break; - case Qt::Key_A: emit goToPage(Page::Start); break; - case Qt::Key_S: - m_selectedServerIndex = m_settings->defaultServerIndex(); - emit goToPage(Page::ServerSettings); - break; - case Qt::Key_P: onGotoCurrentProtocolsPage(); break; - case Qt::Key_T: - m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); - break; - case Qt::Key_Escape: - if (currentPage() == Page::Vpn) - break; - if (currentPage() == Page::ServerConfiguringProgress) - break; - case Qt::Key_Back: - - // if (currentPage() == Page::Start && pagesStack.size() < 2) break; - // if (currentPage() == Page::Sites && - // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { - // ui->tableView_sites->clearSelection(); - // break; - // } - - emit closePage(); - //} - default:; - } -} - -void UiLogic::onCloseWindow() -{ -#ifdef Q_OS_ANDROID - qApp->quit(); -#else - if (m_settings->serversCount() == 0) { - qApp->quit(); - } else { - emit hide(); - } -#endif -} - -QString UiLogic::containerName(int container) -{ - return ContainerProps::containerHumanNames().value(static_cast(container)); -} - -QString UiLogic::containerDesc(int container) -{ - return ContainerProps::containerDescriptions().value(static_cast(container)); -} - -void UiLogic::onGotoCurrentProtocolsPage() -{ - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - emit goToPage(Page::ServerContainers); -} - -void UiLogic::installServer(QPair &container) -{ - emit goToPage(Page::ServerConfiguringProgress); - QEventLoop loop; - QTimer::singleShot(500, &loop, SLOT(quit())); - loop.exec(); - qApp->processEvents(); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this](bool enabled) -> void { - pageLogic()->set_pageEnabled(enabled); - }; - - ServerConfiguringProgressLogic::ButtonFunc noButton; - - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_labelWaitInfoText(text); - }; - waitInfoFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_labelWaitInfoVisible(visible); - }; - - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_progressBarVisible(visible); - }; - progressBarFunc.setValueFunc = [this](int value) -> void { - pageLogic()->set_progressBarValue(value); - }; - progressBarFunc.getValueFunc = [this](void) -> int { - return pageLogic()->progressBarValue(); - }; - progressBarFunc.getMaximumFunc = [this](void) -> int { - return pageLogic()->progressBarMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_labelServerBusyText(text); - }; - busyInfoFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_pushButtonCancelVisible(visible); - }; - - bool isServerCreated = false; - ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode == ErrorCode::NoError) { - if (!isContainerAlreadyAddedToGui(container.first)) { - progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&]() { - ServerController serverController(m_settings); - return serverController.setupContainer(m_installCredentials, container.first, container.second); - }; - errorCode = pageLogic()->doInstallAction( - installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); - if (errorCode == ErrorCode::NoError) { - if (!isServerCreated) { - QJsonObject server; - server.insert(config_key::hostName, m_installCredentials.hostName); - server.insert(config_key::userName, m_installCredentials.userName); - server.insert(config_key::password, m_installCredentials.secretData); - server.insert(config_key::port, m_installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - server.insert(config_key::containers, QJsonArray { container.second }); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); - - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - } else { - m_settings->setContainerConfig(m_settings->serversCount() - 1, container.first, container.second); - m_settings->setDefaultContainer(m_settings->serversCount() - 1, container.first); - } - onUpdateAllPages(); - - emit setStartPage(Page::Vpn); - qApp->processEvents(); - return; - } - } else { - onUpdateAllPages(); - emit showWarningMessage( - "Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - emit setStartPage(Page::Vpn); - return; - } - } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") - + errorString(errorCode) + "\n" + tr("See logs for details.")); - emit closePage(); -} - -PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) -{ - PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) - return logic; - else { - qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; - return new PageProtocolLogicBase(this); - } -} - -QObject *UiLogic::qmlRoot() const -{ - return m_qmlRoot; -} - -void UiLogic::setQmlRoot(QObject *newQmlRoot) -{ - m_qmlRoot = newQmlRoot; -} - -NotificationHandler *UiLogic::notificationHandler() const -{ - return m_notificationHandler; -} - -void UiLogic::setQmlContextProperty(PageLogicBase *logic) -{ - amnApp->qmlEngine()->rootContext()->setContextProperty(logic->metaObject()->className(), logic); -} - -PageEnumNS::Page UiLogic::currentPage() -{ - return static_cast(currentPageValue()); -} - -void UiLogic::saveTextFile(const QString &desc, const QString &suggestedName, QString ext, const QString &data) -{ -#ifdef Q_OS_IOS - shareTempFile(suggestedName, ext, data); - return; -#endif - - ext.replace("*", ""); - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileName; -#ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) - return; - if (!fileName.toString().endsWith(ext)) - fileName = QUrl(fileName.toString() + ext); -#elif defined Q_OS_ANDROID - qDebug() << "UiLogic::shareConfig" << data; - AndroidController::instance()->shareConfig(data, suggestedName); - return; -#endif - - if (fileName.isEmpty()) - return; - -#ifdef AMNEZIA_DESKTOP - QFile save(fileName.toLocalFile()); -#else - QFile save(QQmlFile::urlToLocalFileOrQrc(fileName)); -#endif - - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) -{ - ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName( - nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(QByteArray::fromBase64(data.toUtf8())); - save.close(); - - QFileInfo fi(fileName); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::copyToClipboard(const QString &text) -{ - qApp->clipboard()->setText(text); -} - -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString &data) -{ - ext.replace("*", ""); - QString fileName = QDir::tempPath() + "/" + suggestedName; - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile::remove(fileName); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QStringList filesToSend; - filesToSend.append(fileName); - MobileUtils::shareText(filesToSend); -} - -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, - QString *selectedFilter, QFileDialog::Options options) -{ - QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep { "raw%3A%2F" }; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } -#endif - return fileName; -} - -void UiLogic::registerPagesLogic() -{ - amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); - - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); -} - -ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) -{ - isServerCreated = false; - ServerCredentials installCredentials = m_installCredentials; - bool createNewServer = true; - int serverIndex; - - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - createNewServer = false; - isServerCreated = true; - installCredentials = credentials; - serverIndex = i; - break; - } - } - - QMap installedContainers; - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.getAlreadyInstalledContainers(installCredentials, installedContainers); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (!installedContainers.empty()) { - QJsonObject server; - QJsonArray containerConfigs; - if (createNewServer) { - server.insert(config_key::hostName, installCredentials.hostName); - server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.secretData); - server.insert(config_key::port, installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - } - - for (auto container = installedContainers.begin(); container != installedContainers.end(); container++) { - if (isContainerAlreadyAddedToGui(container.key())) { - continue; - } - - if (createNewServer) { - containerConfigs.append(container.value()); - server.insert(config_key::containers, containerConfigs); - } else { - m_settings->setContainerConfig(serverIndex, container.key(), container.value()); - m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); - } - } - - if (createNewServer) { - server.insert(config_key::defaultContainer, - ContainerProps::containerToString(installedContainers.firstKey())); - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - isServerCreated = true; - } - } - - return ErrorCode::NoError; -} - -bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) -{ - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - const QJsonObject containerConfig = m_settings->containerConfig(i, container); - if (!containerConfig.isEmpty()) { - return true; - } - } - } - return false; -} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h deleted file mode 100644 index 081b8e5a6..000000000 --- a/client/ui/uilogic.h +++ /dev/null @@ -1,201 +0,0 @@ -#ifndef UILOGIC_H -#define UILOGIC_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "property_helper.h" -#include "pages.h" -#include "protocols/vpnprotocol.h" -#include "containers/containers_defs.h" - -#include "models/containers_model.h" -#include "models/protocols_model.h" -#include "models/clientManagementModel.h" - -#include "notificationhandler.h" - -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase; - -class AppSettingsLogic; -class GeneralSettingsLogic; -class NetworkSettingsLogic; -class NewServerProtocolsLogic; -class QrDecoderLogic; -class ServerConfiguringProgressLogic; -class ServerListLogic; -class ServerSettingsLogic; -class ServerContainersLogic; -class ShareConnectionLogic; -class SitesLogic; -class StartPageLogic; -class ViewConfigLogic; -class VpnLogic; -class WizardLogic; -class ClientManagementLogic; -class ClientInfoLogic; -class AdvancedServerSettingsLogic; - -class PageProtocolLogicBase; -class OpenVpnLogic; -class ShadowSocksLogic; -class CloakLogic; - -class OtherProtocolsLogic; - -class VpnConnection; - -class CreateServerTest; - -class UiLogic : public QObject -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(int, pagesStackDepth) - AUTO_PROPERTY(int, currentPageValue) - - READONLY_PROPERTY(QObject *, protocolsModel) - READONLY_PROPERTY(QObject *, clientManagementModel) - -public: - explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent = nullptr); - ~UiLogic(); - void showOnStartup(); - - friend class PageLogicBase; - - friend class AppSettingsLogic; - friend class GeneralSettingsLogic; - friend class NetworkSettingsLogic; - friend class ServerConfiguringProgressLogic; - friend class NewServerProtocolsLogic; - friend class ServerListLogic; - friend class ServerSettingsLogic; - friend class ServerContainersLogic; - friend class ShareConnectionLogic; - friend class SitesLogic; - friend class StartPageLogic; - friend class ViewConfigLogic; - friend class VpnLogic; - friend class WizardLogic; - friend class ClientManagementLogic; - friend class ClientInfoLogic; - friend class AdvancedServerSettingsLogic; - - friend class PageProtocolLogicBase; - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - - friend class OtherProtocolsLogic; - - friend class CreateServerTest; - - Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages - Q_INVOKABLE void onUpdateAllPages(); - - Q_INVOKABLE void initializeUiLogic(); - Q_INVOKABLE void onCloseWindow(); - - Q_INVOKABLE QString containerName(int container); - Q_INVOKABLE QString containerDesc(int container); - - Q_INVOKABLE void onGotoCurrentProtocolsPage(); - - Q_INVOKABLE void keyPressEvent(Qt::Key key); - - Q_INVOKABLE void saveTextFile(const QString& desc, const QString &suggestedName, QString ext, const QString& data); - Q_INVOKABLE void saveBinaryFile(const QString& desc, QString ext, const QString& data); - Q_INVOKABLE void copyToClipboard(const QString& text); - - Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); - - void shareTempFile(const QString &suggestedName, QString ext, const QString& data); - static QString getOpenFileName(QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &dir = QString(), - const QString &filter = QString(), - QString *selectedFilter = nullptr, - QFileDialog::Options options = QFileDialog::Options()); -signals: - void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true); - void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true); - - void closePage(); - void setStartPage(PageEnumNS::Page page, bool slide = true); - void showPublicKeyWarning(); - void showConnectErrorDialog(); - void show(); - void hide(); - void raise(); - void toggleLogPanel(); - void showWarningMessage(QString message); - -private slots: - // containers - INOUT arg - void installServer(QPair &container); - -private: - PageEnumNS::Page currentPage(); - bool isContainerAlreadyAddedToGui(DockerContainer container); - -public: - Q_INVOKABLE PageProtocolLogicBase *protocolLogic(Proto p); - - QObject *qmlRoot() const; - void setQmlRoot(QObject *newQmlRoot); - - NotificationHandler *notificationHandler() const; - - void setQmlContextProperty(PageLogicBase *logic); - void registerPagesLogic(); - - template - void registerPageLogic() - { - T* logic = new T(this); - m_logicMap[std::type_index(typeid(T))] = logic; - setQmlContextProperty(logic); - } - - template - T* pageLogic() - { - return static_cast(m_logicMap.value(std::type_index(typeid(T)))); - } - -private: - QObject *m_qmlRoot{nullptr}; - - QMap m_logicMap; - - QMap m_protocolLogicMap; - - VpnConnection* m_vpnConnection; - QThread m_vpnConnectionThread; - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - - NotificationHandler* m_notificationHandler; - - int m_selectedServerIndex = -1; // server index to use when proto settings page opened - DockerContainer m_selectedDockerContainer; // same - ServerCredentials m_installCredentials; // used to save cred between pages new_server and new_server_protocols and wizard -}; -#endif // UILOGIC_H From cbcf18781415963b689da1e54746798ea1544c2c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 31 Aug 2023 21:49:36 +0500 Subject: [PATCH 083/131] added missing include files --- client/configurators/openvpn_configurator.cpp | 5 +++++ client/fileUtilites.cpp | 1 + client/ui/controllers/installController.cpp | 1 + client/ui/qml/Pages2/PageServiceSftpSettings.qml | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index bfde4a913..a62bdd9cd 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -7,6 +7,11 @@ #include #include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "containers/containers_defs.h" #include "core/scripts_registry.h" diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index b3c65216c..4eedfc9a9 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 7d6ba5904..0a3da475d 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,7 @@ #include "installController.h" #include +#include #include #include #include diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 32c0c170c..5eb288f8c 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -253,7 +253,7 @@ PageType { text: qsTr("Remove SFTP and all data stored there") onClicked: { - questionDrawer.headerText = qsTr("Some description") + questionDrawer.headerText = qsTr("Remove SFTP and all data stored there?") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") From b58295d1d68d9756a61e48bf217a6a70c4d3c2e6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 00:48:58 +0500 Subject: [PATCH 084/131] added the ability to restore settings from backup on the initial screen - fixed the display of services in the settings for mobile devices --- client/fileUtilites.cpp | 2 +- .../qml/Components/HomeContainersListView.qml | 6 ++++++ .../ui/qml/Filters/ContainersModelFilters.qml | 8 ++++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 6 ++++++ .../Pages2/PageSetupWizardConfigSource.qml | 14 ++++++++++---- client/ui/qml/Pages2/PageSetupWizardStart.qml | 19 +++++++++++++++++++ client/ui/qml/main2.qml | 4 +--- 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 4eedfc9a9..96f1c7414 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -81,5 +81,5 @@ QString FileUtilites::getFileName(QString fileName) return fileName; #endif - return QUrl(FileUtilites::getFileName(fileName)).toLocalFile(); + return QUrl(fileName).toLocalFile(); } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 0c2408be0..fc4dd8b38 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -53,6 +53,12 @@ ListView { checkable: isInstalled checked: isDefault + onPressed: function(mouse) { + if (!isSupported) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + } + } + onClicked: { if (checked) { isDefault = true diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml index fa8bd83b9..dd6d95256 100644 --- a/client/ui/qml/Filters/ContainersModelFilters.qml +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -32,16 +32,16 @@ Item { } function getWriteAccessProtocolsListFilters() { - return [vpnTypeFilter, supportedFilter] + return [vpnTypeFilter] } function getReadAccessProtocolsListFilters() { - return [vpnTypeFilter, supportedFilter, installedFilter] + return [vpnTypeFilter, installedFilter] } function getWriteAccessServicesListFilters() { - return [serviceTypeFilter, supportedFilter] + return [serviceTypeFilter } function getReadAccessServicesListFilters() { - return [serviceTypeFilter, supportedFilter, installedFilter] + return [serviceTypeFilter, installedFilter] } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 15c5e5317..8ae903512 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -22,7 +22,7 @@ PageType { if (isInstalledContainerFound) { message = qsTr("All installed containers have been added to the application") } else { - message = qsTr("No installed containers found") + message = qsTr("No new installed containers found") } PageController.showErrorMessage(message) diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 535ab18cd..e074b4ce1 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -241,8 +241,10 @@ PageType { buttonImageSource: "qrc:/images/controls/plus.svg" clickedFunc: function() { + PageController.showBusyIndicator(true) SitesController.addSite(textFieldText) textFieldText = "" + PageController.showBusyIndicator(false) } } @@ -312,8 +314,10 @@ PageType { currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" defaultSuffix: ".json" onAccepted: { + PageController.showBusyIndicator(true) SitesController.exportSites(saveFileDialog.currentFile.toString()) moreActionsDrawer.close() + PageController.showBusyIndicator(false) } } } @@ -394,9 +398,11 @@ PageType { acceptLabel: qsTr("Open sites file") nameFilters: [ "Sites files (*.json)" ] onAccepted: { + PageController.showBusyIndicator(true) SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) importSitesDrawer.close() moreActionsDrawer.close() + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index de8b8c877..6362ca6d3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -65,7 +65,7 @@ It's okay as long as it's from someone you trust.") Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("File with connection settings") + text: qsTr("File with connection settings or backup") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" @@ -76,10 +76,16 @@ It's okay as long as it's from someone you trust.") FileDialog { id: fileDialog acceptLabel: qsTr("Open config file") - nameFilters: [ "Config files (*.vpn *.ovpn *.conf)" ] + nameFilters: [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] onAccepted: { - ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - goToPage(PageEnum.PageSetupWizardViewConfig) + if (fileDialog.selectedFile.toString().indexOf(".backup") != -1) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) + goToPage(PageEnum.PageSetupWizardViewConfig) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 11d7ba290..3bc57495f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -19,6 +19,19 @@ PageType { function onGoToPageViewConfig() { goToPage(PageEnum.PageSetupWizardViewConfig) } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + } + } + + Connections { + target: SettingsController + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + PageController.replaceStartPage() + } } FlickableType { @@ -93,4 +106,10 @@ PageType { id: connectionTypeSelection } } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6ae434d7e..d3c3aa1be 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -47,9 +47,7 @@ Window { function onReplaceStartPage() { var pagePath = PageController.getInitialPage() - while (rootStackView.depth > 1) { - rootStackView.pop() - } + rootStackView.clear() PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor()) rootStackView.replace(pagePath, { "objectName" : pagePath }) } From 4e9f68acff8b95cb890b61ef3d3000cccab4d8c0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 02:07:52 +0500 Subject: [PATCH 085/131] returned the lost comma --- client/ui/qml/Filters/ContainersModelFilters.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml index dd6d95256..8c51c7eed 100644 --- a/client/ui/qml/Filters/ContainersModelFilters.qml +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -39,7 +39,7 @@ Item { } function getWriteAccessServicesListFilters() { - return [serviceTypeFilter + return [serviceTypeFilter] } function getReadAccessServicesListFilters() { return [serviceTypeFilter, installedFilter] From cacf74af3c537afe6db507d3f6468e7c613e3038 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 01:08:44 +0300 Subject: [PATCH 086/131] Fix iOS build --- client/amnezia_application.cpp | 1 + client/fileUtilites.cpp | 11 ++--------- client/platforms/ios/QtAppDelegate-C-Interface.h | 4 ++-- client/platforms/ios/QtAppDelegate.h | 4 ++-- client/platforms/ios/QtAppDelegate.mm | 6 +++--- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/importController.h | 2 +- client/ui/macos_util.h | 3 +++ 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d14917f01..d42d4cf99 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -112,6 +112,7 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); + setImportController(m_importController.get()); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 96f1c7414..9cbc5a105 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -40,17 +40,10 @@ void FileUtilites::saveFile(QString fileName, const QString &data) filesToSend.append(fileName); MobileUtils::shareText(filesToSend); return; -#endif - -#ifdef Q_OS_IOS - QStringList filesToSend; - filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); - return; -#endif - +#else QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +#endif } QString FileUtilites::getFileName(QString fileName) diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index afd31e7d8..dd37dd2a3 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -1,9 +1,9 @@ #ifndef QTAPPDELEGATECINTERFACE_H #define QTAPPDELEGATECINTERFACE_H -#include "ui/pages_logic/StartPageLogic.h" +#include "ui/controllers/importController.h" void QtAppDelegateInitialize(); -void setStartPageLogic(StartPageLogic*); +void setImportController(ImportController*); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index 1e0dc4121..a32f1b646 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,9 +1,9 @@ #import #import "QtAppDelegate-C-Interface.h" -#include "ui/pages_logic/StartPageLogic.h" +#include "ui/controllers/importController.h" @interface QtAppDelegate : UIResponder +(QtAppDelegate *)sharedQtAppDelegate; -@property (nonatomic) StartPageLogic* startPageLogic; +@property (nonatomic) ImportController* ImportController; @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index f65856d93..432fc1726 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -79,7 +79,7 @@ bool isOpenFile = file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - [QtAppDelegate sharedQtAppDelegate].startPageLogic->importAnyFile(QString(data)); + [QtAppDelegate sharedQtAppDelegate].ImportController->extractConfigFromData(QString(data)); return YES; } return NO; @@ -92,8 +92,8 @@ void QtAppDelegateInitialize() NSLog(@"Created a new AppDelegate"); } -void setStartPageLogic(StartPageLogic* startPage) { - [QtAppDelegate sharedQtAppDelegate].startPageLogic = startPage; +void setImportController(ImportController* controller) { + [QtAppDelegate sharedQtAppDelegate].ImportController = controller; } @end diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 9a374aa19..b76302c51 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -97,7 +97,7 @@ void ImportController::extractConfigFromFile(const QString &fileName) } } -void ImportController::extractConfigFromData(QString &data) +void ImportController::extractConfigFromData(QString data) { auto configFormat = checkConfigFormat(data); if (configFormat == ConfigTypes::OpenVpn) { diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 7def7733e..1f8f8bbb3 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -22,7 +22,7 @@ public: public slots: void importConfig(); void extractConfigFromFile(const QString &fileName); - void extractConfigFromData(QString &data); + void extractConfigFromData(QString data); void extractConfigFromCode(QString code); bool extractConfigFromQr(const QByteArray &data); QString getConfig(); diff --git a/client/ui/macos_util.h b/client/ui/macos_util.h index f5add9021..15677e42a 100644 --- a/client/ui/macos_util.h +++ b/client/ui/macos_util.h @@ -1,9 +1,12 @@ #ifndef OSXUTIL_H #define OSXUTIL_H + +#ifndef Q_OS_IOS #include #include void setDockIconVisible(bool visible); void fixWidget(QWidget *widget); +#endif #endif From 1b3a32f83f2cc7f64ff487c7e19d017871dd07f9 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 14:49:10 +0300 Subject: [PATCH 087/131] Import config from filesystem on iOS --- client/amnezia_application.cpp | 6 ++++-- client/platforms/ios/QtAppDelegate-C-Interface.h | 1 - client/platforms/ios/QtAppDelegate.h | 3 --- client/platforms/ios/QtAppDelegate.mm | 7 ++----- client/platforms/ios/ios_controller.h | 1 + 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d42d4cf99..64adb1d1e 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -21,7 +21,6 @@ #include "protocols/qml_register_protocols.h" #if defined(Q_OS_IOS) - #include "platforms/ios/QtAppDelegate-C-Interface.h" #include "platforms/ios/ios_controller.h" #endif @@ -112,7 +111,10 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); - setImportController(m_importController.get()); + connect(IosController::Instance(), &IosController::importConfigFromOutside, m_importController.get(), + &ImportController::extractConfigFromData); + connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), + &PageController::goToPageViewConfig); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index dd37dd2a3..39adbb923 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -4,6 +4,5 @@ #include "ui/controllers/importController.h" void QtAppDelegateInitialize(); -void setImportController(ImportController*); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index a32f1b646..cd8bae274 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,9 +1,6 @@ #import -#import "QtAppDelegate-C-Interface.h" #include "ui/controllers/importController.h" @interface QtAppDelegate : UIResponder -+(QtAppDelegate *)sharedQtAppDelegate; -@property (nonatomic) ImportController* ImportController; @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index 432fc1726..7528f2fff 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -1,4 +1,5 @@ #import "QtAppDelegate.h" +#import "ios_controller.h" #include @@ -79,7 +80,7 @@ bool isOpenFile = file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - [QtAppDelegate sharedQtAppDelegate].ImportController->extractConfigFromData(QString(data)); + IosController::Instance()->importConfigFromOutside(QString(data)); return YES; } return NO; @@ -92,8 +93,4 @@ void QtAppDelegateInitialize() NSLog(@"Created a new AppDelegate"); } -void setImportController(ImportController* controller) { - [QtAppDelegate sharedQtAppDelegate].ImportController = controller; -} - @end diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 4d1122b29..d6a12529e 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -49,6 +49,7 @@ public: signals: void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); + void importConfigFromOutside(const QString); protected slots: From a96f485e3c8d683323dfc889f64e0f2a648eec06 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 17:39:23 +0500 Subject: [PATCH 088/131] added display of all protocols in the settings after installing a new container - set the app's max height and width to 600/800 - expanded the description when removing containers --- CMakeLists.txt | 2 +- client/amnezia_application.cpp | 2 +- client/ui/controllers/installController.cpp | 31 +++++++++++-------- .../Pages2/PageProtocolOpenVpnSettings.qml | 1 + client/ui/qml/Pages2/PageProtocolRaw.qml | 1 + .../qml/Pages2/PageSettingsServerProtocol.qml | 1 + client/ui/qml/main2.qml | 2 ++ 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aec2a9745..875d664d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.3.1 +project(${PROJECT} VERSION 4.0.4.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d14917f01..ebd763dd2 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -206,8 +206,8 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); + m_translator.reset(new QTranslator()); if (locale != QLocale::English) { - m_translator.reset(new QTranslator()); if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 0a3da475d..3c0752cc8 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -59,20 +59,25 @@ InstallController::~InstallController() void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { - Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject containerConfig; - - containerConfig.insert(config_key::port, QString::number(port)); - containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto)); - - if (container == DockerContainer::Sftp) { - containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); - } - QJsonObject config; - config.insert(config_key::container, ContainerProps::containerToString(container)); - config.insert(ProtocolProps::protoToString(mainProto), containerConfig); + auto mainProto = ContainerProps::defaultProtocol(container); + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + + if (protocol == mainProto) { + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, + ProtocolProps::transportProtoToString(transportProto, protocol)); + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } if (m_shouldCreateServer) { if (isServerAlreadyExists()) { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 659193cab..607b65849 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -362,6 +362,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 5c70b1c00..4c155d619 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -174,6 +174,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index f1f067c2f..2163a71c7 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -114,6 +114,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index d3c3aa1be..e78f35a22 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -15,6 +15,8 @@ Window { height: GC.screenHeight minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + maximumWidth: 600 + maximumHeight: 800 color: "#0E0E11" From 195a3ab170eb23e749cf1edf7f257d6867371c97 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 17:29:48 +0300 Subject: [PATCH 089/131] Save files for iOS --- client/fileUtilites.cpp | 5 +++-- client/platforms/ios/QtAppDelegate-C-Interface.h | 2 -- client/platforms/ios/QtAppDelegate.h | 2 -- client/ui/qml/Components/ShareConnectionDrawer.qml | 8 +++++++- client/ui/qml/Pages2/PageSettingsLogging.qml | 8 +++++++- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 6 +++++- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 9cbc5a105..301344a77 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -24,7 +24,8 @@ void FileUtilites::saveFile(QString fileName, const QString &data) #endif #ifdef Q_OS_IOS - QFile file(fileName); + QUrl fileUrl = QDir::tempPath() + "/" + fileName; + QFile file(fileUrl.toString()); #else QUrl fileUrl = QUrl(fileName); QFile file(fileUrl.toLocalFile()); @@ -37,7 +38,7 @@ void FileUtilites::saveFile(QString fileName, const QString &data) #ifdef Q_OS_IOS QStringList filesToSend; - filesToSend.append(fileName); + filesToSend.append(fileUrl.toString()); MobileUtils::shareText(filesToSend); return; #else diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index 39adbb923..dd358097a 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -1,8 +1,6 @@ #ifndef QTAPPDELEGATECINTERFACE_H #define QTAPPDELEGATECINTERFACE_H -#include "ui/controllers/importController.h" - void QtAppDelegateInitialize(); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index cd8bae274..5148c2ca9 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,6 +1,4 @@ #import -#include "ui/controllers/importController.h" - @interface QtAppDelegate : UIResponder @end diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 41dd2ab2d..8f4498a94 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -69,7 +69,13 @@ DrawerType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: fileDialog.open() + onClicked: { + if (Qt.platform.os === "ios") { + ExportController.saveFile("amnezia_config.vpn") + } else { + fileDialog.open() + } + } FileDialog { id: fileDialog diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index cfe7d7c99..0e4e486ec 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -100,7 +100,13 @@ PageType { image: "qrc:/images/controls/save.svg" - onClicked: fileDialog.open() + onClicked: { + if (Qt.platform.os === "ios") { + SettingsController.exportLogsFile("AmneziaVPN.log") + } else { + fileDialog.open() + } + } FileDialog { id: fileDialog diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index e074b4ce1..898ce17ba 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -302,7 +302,11 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - saveFileDialog.open() + if (Qt.platform.os === "ios") { + ExportController.saveFile("amezia_tunnel.json") + } else { + saveFileDialog.open() + } } FileDialog { From c4f94efe249146f675b2156745c74de3053e2d26 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 2 Sep 2023 17:04:35 -0400 Subject: [PATCH 090/131] Android fileSave fixes --- client/ui/qml/Components/ShareConnectionDrawer.qml | 6 ++++-- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 8f4498a94..4971f93af 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -13,6 +13,8 @@ import ContainerProps 1.0 import "./" import "../Controls2" import "../Controls2/TextTypes" +import "../Config" +import "../Components" DrawerType { id: root @@ -70,8 +72,8 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { - if (Qt.platform.os === "ios") { - ExportController.saveFile("amnezia_config.vpn") + if (GC.isMobile()) { + ExportController.saveFile(configFileName) } else { fileDialog.open() } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 0e4e486ec..bab36c4ce 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -101,7 +101,7 @@ PageType { image: "qrc:/images/controls/save.svg" onClicked: { - if (Qt.platform.os === "ios") { + if (GC.isMobile()) { SettingsController.exportLogsFile("AmneziaVPN.log") } else { fileDialog.open() diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 898ce17ba..0c2ebdfa0 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -302,7 +302,7 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - if (Qt.platform.os === "ios") { + if (GC.isMobile()) { ExportController.saveFile("amezia_tunnel.json") } else { saveFileDialog.open() From 7eaaef6e7509a09d9575ebc08b05ae4f3abc1a17 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 3 Sep 2023 23:18:08 +0300 Subject: [PATCH 091/131] Connection button status change for background start --- client/platforms/ios/ios_controller.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 6f23ac81c..af0d71007 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -98,6 +98,7 @@ bool IosController::initialize() if (manager.connection.status == NEVPNStatusConnected) { m_currentTunnel = manager; qDebug() << "IosController::initialize : VPN already connected"; + emit connectionStateChanged(Vpn::ConnectionState::Connected); break; // TODO: show connected state From 4ab006f065c63bf02a86c6caafd5c92ad7d6162c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 13:37:37 +0500 Subject: [PATCH 092/131] added swipe up for menu on PageHome --- client/ui/controllers/pageController.h | 2 ++ .../ConnectionTypeSelectionDrawer.qml | 4 +-- .../qml/Components/HomeContainersListView.qml | 2 +- .../Components/SettingsContainersListView.qml | 16 +++++----- client/ui/qml/Controls2/BackButtonType.qml | 2 +- client/ui/qml/Controls2/DrawerType.qml | 1 + client/ui/qml/Controls2/PageType.qml | 32 ++++--------------- client/ui/qml/Pages2/PageHome.qml | 11 +++++-- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 2 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 2 +- client/ui/qml/Pages2/PageSettings.qml | 10 +++--- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 4 +-- .../ui/qml/Pages2/PageSettingsServerData.qml | 12 +++---- .../qml/Pages2/PageSettingsServerProtocol.qml | 4 +-- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 10 +++--- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 6 ++-- .../qml/Pages2/PageSetupWizardInstalling.qml | 16 +++++----- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 2 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 6 ++-- client/ui/qml/Pages2/PageStart.qml | 15 +++++++++ 28 files changed, 90 insertions(+), 85 deletions(-) diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1dd16090b..8d3da507b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -79,6 +79,8 @@ public slots: void showOnStartup(); signals: + void goToPage(PageLoader::PageEnum page, bool slide = true); + void goToStartPage(); void goToPageHome(); void goToPageSettings(); void goToPageViewConfig(); diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 8b377600a..ecde15540 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -41,7 +41,7 @@ DrawerType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardCredentials) + PageController.goToPage(PageEnum.PageSetupWizardCredentials) root.visible = false } } @@ -55,7 +55,7 @@ DrawerType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardConfigSource) + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) root.visible = false } } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index fc4dd8b38..f5d27c00e 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -67,7 +67,7 @@ ListView { } else { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false } diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 2489323b0..a0c74a04c 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -45,44 +45,44 @@ ListView { if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) return } switch (containerIndex) { case ContainerEnum.OpenVpn: { OpenVpnConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolOpenVpnSettings) + PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings) break } case ContainerEnum.WireGuard: { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) // WireGuardConfigModel.updateModel(config) // goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) // Ikev2ConfigModel.updateModel(config) // goToPage(PageEnum.PageProtocolIKev2Settings) break } case ContainerEnum.Sftp: { SftpConfigModel.updateModel(config) - goToPage(PageEnum.PageServiceSftpSettings) + PageController.goToPage(PageEnum.PageServiceSftpSettings) break } case ContainerEnum.TorWebSite: { - goToPage(PageEnum.PageServiceTorWebsiteSettings) + PageController.goToPage(PageEnum.PageServiceTorWebsiteSettings) break } default: { if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageSettingsServerProtocol) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) } } } @@ -90,7 +90,7 @@ ListView { } else { ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 91f5f28fc..67ffbd9cf 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -27,7 +27,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - closePage() + PageController.closePage() } } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 97fbf0343..35d03449d 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -6,6 +6,7 @@ Drawer { clip: true modal: true + dragMargin: -10 enter: Transition { SmoothedAnimation { diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index cb6fa142b..193fbcf25 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -7,35 +7,15 @@ Item { property StackView stackView: StackView.view - function goToPage(page, slide = true) { - var pagePath = PageController.getPagePath(page) - if (slide) { - root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) - } else { - root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) - } - } - - function closePage() { - if (root.stackView.depth <= 1) { - return - } - root.stackView.pop() - } - - function goToStartPage() { - while (root.stackView.depth > 1) { - root.stackView.pop() - } - } - MouseArea { - z: -1 + z: 99 anchors.fill: parent - onClicked: { - console.log("base mouse area pressed") - focus = true + enabled: true + + onPressed: function(mouse) { + forceActiveFocus() + mouse.accepted = false } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d40f8748d..0c7782156 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,11 +26,15 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName - ConnectButton { + Item { anchors.top: parent.top anchors.bottom: buttonBackground.top anchors.right: parent.right anchors.left: parent.left + + ConnectButton { + anchors.centerIn: parent + } } Connections { @@ -125,6 +129,9 @@ PageType { DrawerType { id: menu + interactive: true + dragMargin: buttonBackground.height + 56 // page start tabBar height + width: parent.width height: parent.height * 0.9 @@ -320,7 +327,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index - goToPage(PageEnum.PageSettingsServerInfo) + PageController.goToPage(PageEnum.PageSettingsServerInfo) menu.visible = false } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 607b65849..aed1dbc1f 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -368,7 +368,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 4c155d619..8bbfab146 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -180,7 +180,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 5eb288f8c..fead034b9 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -259,7 +259,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 384903752..04d7076c9 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -144,7 +144,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 6c088a703..e020dc2cf 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -46,7 +46,7 @@ PageType { leftImageSource: "qrc:/images/controls/server.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsServersList) + PageController.goToPage(PageEnum.PageSettingsServersList) } } @@ -60,7 +60,7 @@ PageType { leftImageSource: "qrc:/images/controls/radio.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsConnection) + PageController.goToPage(PageEnum.PageSettingsConnection) } } @@ -74,7 +74,7 @@ PageType { leftImageSource: "qrc:/images/controls/app.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsApplication) + PageController.goToPage(PageEnum.PageSettingsApplication) } } @@ -88,7 +88,7 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsBackup) + PageController.goToPage(PageEnum.PageSettingsBackup) } } @@ -102,7 +102,7 @@ PageType { leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + PageController.goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 7b628037b..6f5e48a20 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -112,7 +112,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsLogging) + PageController.goToPage(PageEnum.PageSettingsLogging) } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 0692abda6..78d4a6813 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -86,7 +86,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsDns) + PageController.goToPage(PageEnum.PageSettingsDns) } } @@ -100,7 +100,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsSplitTunneling) + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 8ae903512..3eb07ce99 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -32,20 +32,20 @@ PageType { if (!ServersModel.getServersCount()) { PageController.replaceStartPage() } else { - goToStartPage() - goToPage(PageEnum.PageSettingsServersList) + PageController.goToStartPage() + PageController.goToPage(PageEnum.PageSettingsServersList) } PageController.showNotificationMessage(finishedMessage) } function onRemoveAllContainersFinished(finishedMessage) { - closePage() // close deInstalling page + PageController.closePage() // close deInstalling page PageController.showNotificationMessage(finishedMessage) } function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { - closePage() // close deInstalling page - closePage() // close page with remove button + PageController.closePage() // close deInstalling page + PageController.closePage() // close page with remove button PageController.showNotificationMessage(finishedMessage) } } @@ -173,7 +173,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 2163a71c7..14d345909 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -87,7 +87,7 @@ PageType { case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } - goToPage(protocolPage); + PageController.goToPage(protocolPage); } MouseArea { @@ -120,7 +120,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 40e51e9ec..c0807f352 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -98,7 +98,7 @@ PageType { clickedFunction: function() { ServersModel.currentlyProcessedIndex = index - goToPage(PageEnum.PageSettingsServerInfo) + PageController.goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 6362ca6d3..c0ad6249f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -17,8 +17,8 @@ PageType { target: ImportController function onQrDecodingFinished() { - closePage() - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.closePage() + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -84,7 +84,7 @@ It's okay as long as it's from someone you trust.") PageController.showBusyIndicator(false) } else { ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } } @@ -103,7 +103,7 @@ It's okay as long as it's from someone you trust.") clickedFunction: function() { ImportController.startDecodingQr() if (Qt.platform.os === "ios") { - goToPage(PageEnum.PageSetupWizardQrReader) + PageController.goToPage(PageEnum.PageSetupWizardQrReader) } } } @@ -120,7 +120,7 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/text-cursor.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) + PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index d089a70d8..bc24c1964 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -104,7 +104,7 @@ PageType { return } - goToPage(PageEnum.PageSetupWizardEasy) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 375a83327..6fdc4b3e6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -159,12 +159,12 @@ PageType { onClicked: function() { if (root.isEasySetup) { ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) } else { - goToPage(PageEnum.PageSetupWizardProtocols) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } } @@ -186,7 +186,7 @@ PageType { text: qsTr("Set up later") onClicked: function() { - goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.addEmptyServer() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 20c589ee8..f29193985 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,28 +24,28 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) - goToPage(PageEnum.PageSettingsServerInfo, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) if (isServiceInstall) { PageController.goToPageSettingsServerServices() } } else { - goToPage(PageEnum.PageHome) + PageController.goToPage(PageEnum.PageHome) } PageController.showNotificationMessage(finishedMessage) } function onInstallServerFinished(finishedMessage) { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) } else { PageController.replaceStartPage() } @@ -54,9 +54,9 @@ PageType { } function onServerAlreadyExists(serverIndex) { - goToStartPage() + PageController.goToStartPage() ServersModel.currentlyProcessedIndex = serverIndex - goToPage(PageEnum.PageSettingsServerInfo, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index d64fa0975..6552d873a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -229,7 +229,7 @@ PageType { text: qsTr("Install") onClicked: function() { - goToPage(PageEnum.PageSetupWizardInstalling); + PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 8c7f5de05..71b33eb0a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -105,7 +105,7 @@ PageType { clickedFunction: function() { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 3bc57495f..2ce93e53b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -17,7 +17,7 @@ PageType { target: PageController function onGoToPageViewConfig() { - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } function onShowBusyIndicator(visible) { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 504645d15..4cdfc4447 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -72,7 +72,7 @@ PageType { onClicked: function() { ImportController.extractConfigFromCode(textKey.textFieldText) - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 222fbd11c..2f1fc3927 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -19,16 +19,16 @@ PageType { target: ImportController function onImportErrorOccurred(errorMessage) { - closePage() + PageController.closePage() PageController.showErrorMessage(errorMessage) } function onImportFinished() { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) } else { PageController.replaceStartPage() } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e4cc02eec..31474c127 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -47,6 +47,21 @@ PageType { } tabBarStackView.pop() } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + } + + function onGoToStartPage() { + while (tabBarStackView.depth > 1) { + tabBarStackView.pop() + } + } } Connections { From c1663278355db5cb508d64b7f5b252653eddf5a8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 22:20:59 +0500 Subject: [PATCH 093/131] filedialog for qml moved to main.qml --- client/amnezia_application.cpp | 6 + client/amnezia_application.h | 2 + client/ui/controllers/importController.cpp | 7 +- client/ui/controllers/pageController.h | 4 + client/ui/controllers/systemController.cpp | 103 ++++++++++++++++++ client/ui/controllers/systemController.h | 30 +++++ client/ui/qml/Pages2/PageSettingsBackup.qml | 14 +-- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 28 ++--- .../Pages2/PageSetupWizardConfigSource.qml | 21 +--- client/ui/qml/main2.qml | 48 ++++++++ 11 files changed, 213 insertions(+), 52 deletions(-) create mode 100644 client/ui/controllers/systemController.cpp create mode 100644 client/ui/controllers/systemController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index f248c34fe..8db6f8de0 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "logger.h" #include "version.h" @@ -130,6 +132,7 @@ void AmneziaApplication::init() &ConnectionController::closeConnection); m_engine->load(url); + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -345,4 +348,7 @@ void AmneziaApplication::initControllers() m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + + m_systemController.reset(new SystemController(m_settings)); + m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 40ea81b49..1ace6d8b0 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -23,6 +23,7 @@ #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/controllers/sitesController.h" +#include "ui/controllers/systemController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -114,6 +115,7 @@ private: QScopedPointer m_exportController; QScopedPointer m_settingsController; QScopedPointer m_sitesController; + QScopedPointer m_systemController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index b76302c51..c0aaeeb9b 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "core/errorstrings.h" @@ -87,7 +88,11 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile(const QString &fileName) { - QFile file(FileUtilites::getFileName(fileName)); + QQuickItem *obj = findChild("saveFileDialog"); + + QUrl url(fileName); + QString path = url.toLocalFile(); + QFile file(path); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8d3da507b..952f716c7 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -103,6 +103,10 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); + void setupFileDialogForConfig(); + void setupFileDialogForSites(bool replaceExistingSites); + void setupFileDialogForBackup(); + private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp new file mode 100644 index 000000000..a843b660b --- /dev/null +++ b/client/ui/controllers/systemController.cpp @@ -0,0 +1,103 @@ +#include "systemController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#ifdef Q_OS_IOS + #include "platforms/ios/MobileUtils.h" + #include +#endif + +SystemController::SystemController(const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ +} + +void SystemController::saveFile(QString fileName, const QString &data) +{ +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(data, fileName); + return; +#endif + +#ifdef Q_OS_IOS + QUrl fileUrl = QDir::tempPath() + "/" + fileName; + QFile file(fileUrl.toString()); +#else + QUrl fileUrl = QUrl(fileName); + QFile file(fileUrl.toLocalFile()); +#endif + + // todo check if save successful + file.open(QIODevice::WriteOnly); + file.write(data.toUtf8()); + file.close(); + +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileUrl.toString()); + MobileUtils::shareText(filesToSend); + return; +#else + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +#endif +} + +QString SystemController::getFileName() +{ + auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + if (!mainFileDialog) { + return ""; + } + QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + + QEventLoop wait; + QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + wait.exec(); + + auto fileName = mainFileDialog->property("selectedFile").toString(); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } + + return fileName; +#endif + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep { "raw%3A%2F" }; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } + + return fileName; +#endif + + return QUrl(fileName).toLocalFile(); +} + +void SystemController::setQmlRoot(QObject *qmlRoot) +{ + m_qmlRoot = qmlRoot; +} diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h new file mode 100644 index 000000000..fbcc52f19 --- /dev/null +++ b/client/ui/controllers/systemController.h @@ -0,0 +1,30 @@ +#ifndef SYSTEMCONTROLLER_H +#define SYSTEMCONTROLLER_H + +#include + +#include "settings.h" + +class SystemController : public QObject +{ + Q_OBJECT +public: + explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + +public slots: + void saveFile(QString fileName, const QString &data); + QString getFileName(); + + void setQmlRoot(QObject *qmlRoot); + +signals: + void fileDialogAccepted(); + void fileDialogRejected(); + +private: + std::shared_ptr m_settings; + + QObject *m_qmlRoot; +}; + +#endif // SYSTEMCONTROLLER_H diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 363bc66f0..edd527c15 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -125,18 +125,8 @@ PageType { text: qsTr("Restore from backup") onClicked: { - openFileDialog.open() - } - - FileDialog { - id: openFileDialog - acceptLabel: qsTr("Open backup file") - nameFilters: [ "Backup files (*.backup)" ] - onAccepted: { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(openFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } + PageController.setupFileDialogForBackup() + SystemController.getFileName() } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 0e4e486ec..c0a35c0f6 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -117,7 +117,7 @@ PageType { currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" defaultSuffix: ".log" onAccepted: { - ExportController.saveFile(fileDialog.currentFile.toString()) + SettingsController.exportLogsFile(fileDialog.currentFile.toString()) } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 898ce17ba..155ecc3cf 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -44,8 +44,6 @@ PageType { allExceptSites ] - property bool replaceExistingSites - QtObject { id: onlyForwardSites property string name: qsTr("Only the addresses in the list must be opened via VPN") @@ -303,7 +301,7 @@ PageType { clickedFunction: function() { if (Qt.platform.os === "ios") { - ExportController.saveFile("amezia_tunnel.json") + SitesController.exportSites("amezia_tunnel.json") } else { saveFileDialog.open() } @@ -311,6 +309,7 @@ PageType { FileDialog { id: saveFileDialog + objectName: saveFileDialog acceptLabel: qsTr("Save sites") nameFilters: [ "Sites files (*.json)" ] fileMode: FileDialog.SaveFile @@ -378,8 +377,8 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - root.replaceExistingSites = true - openFileDialog.open() + PageController.setupFileDialogForSites(true) + SystemController.getFileName() } } @@ -390,25 +389,14 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - root.replaceExistingSites = false - openFileDialog.open() + PageController.setupFileDialogForSites(false) + SystemController.getFileName() + importSitesDrawer.close() + moreActionsDrawer.close() } } DividerType {} - - FileDialog { - id: openFileDialog - acceptLabel: qsTr("Open sites file") - nameFilters: [ "Sites files (*.json)" ] - onAccepted: { - PageController.showBusyIndicator(true) - SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) - importSitesDrawer.close() - moreActionsDrawer.close() - PageController.showBusyIndicator(false) - } - } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index c0ad6249f..3f460f651 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -65,28 +65,13 @@ It's okay as long as it's from someone you trust.") Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("File with connection settings or backup") + text: !ServersModel.getServersCount() ? qsTr("File with connection settings or backup") : qsTr("File with connection settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - fileDialog.open() - } - - FileDialog { - id: fileDialog - acceptLabel: qsTr("Open config file") - nameFilters: [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] - onAccepted: { - if (fileDialog.selectedFile.toString().indexOf(".backup") != -1) { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(fileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } else { - ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } + PageController.setupFileDialogForConfig() + SystemController.getFileName() } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index e78f35a22..e09882c06 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs import PageEnum 1.0 @@ -10,6 +11,7 @@ import "Controls2" Window { id: root + objectName: "mainWindow" visible: true width: GC.screenWidth height: GC.screenHeight @@ -79,6 +81,42 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } + + function onSetupFileDialogForConfig() { + mainFileDialog.acceptLabel = qsTr("Open config file") + mainFileDialog.nameFilters = !ServersModel.getServersCount() ? [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] : + [ "Config files (*.vpn *.ovpn *.conf)" ] + mainFileDialog.acceptFunction = function() { + if (mainFileDialog.selectedFile.toString().indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(mainFileDialog.selectedFile) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + } + + function onSetupFileDialogForSites(replaceExistingSites) { + mainFileDialog.acceptLabel = qsTr("Open sites file") + mainFileDialog.nameFilters = [ "Sites files (*.json)" ] + mainFileDialog.acceptFunction = function() { + PageController.showBusyIndicator(true) + SitesController.importSites(mainFileDialog.selectedFile.toString(), replaceExistingSites) + PageController.showBusyIndicator(false) + } + } + + function onSetupFileDialogForBackup() { + mainFileDialog.acceptLabel = qsTr("Open backup file") + mainFileDialog.nameFilters = [ "Backup files (*.backup)" ] + mainFileDialog.acceptFunction = function() { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } + } } Connections { @@ -192,4 +230,14 @@ Window { } } } + + FileDialog { + id: mainFileDialog + + property var acceptFunction + + objectName: "mainFileDialog" + + onAccepted: acceptFunction() + } } From e1fa24c2519a8c62528587f6c9ec7909278a2788 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 22:26:37 +0500 Subject: [PATCH 094/131] moved the qml filedialog opening code below the ios section --- client/ui/controllers/systemController.cpp | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index a843b660b..87d040900 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -56,18 +56,6 @@ void SystemController::saveFile(QString fileName, const QString &data) QString SystemController::getFileName() { - auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); - if (!mainFileDialog) { - return ""; - } - QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); - - QEventLoop wait; - QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); - wait.exec(); - - auto fileName = mainFileDialog->property("selectedFile").toString(); - #ifdef Q_OS_IOS CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, @@ -81,6 +69,18 @@ QString SystemController::getFileName() return fileName; #endif + auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + if (!mainFileDialog) { + return ""; + } + QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + + QEventLoop wait; + QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + wait.exec(); + + auto fileName = mainFileDialog->property("selectedFile").toString(); + #ifdef Q_OS_ANDROID // patch for files containing spaces etc const QString sep { "raw%3A%2F" }; From b5dd48ad7b74e9bb8dc6d97f7b7eeaf803354ffd Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Sep 2023 22:45:01 +0500 Subject: [PATCH 095/131] reworking of getting the path to the file when saving/opening files --- client/fileUtilites.cpp | 79 ------------------- client/fileUtilites.h | 16 ---- client/platforms/ios/MobileUtils.h | 7 +- client/platforms/ios/MobileUtils.mm | 28 +++++++ client/ui/controllers/exportController.cpp | 6 +- client/ui/controllers/exportController.h | 4 +- client/ui/controllers/importController.cpp | 7 +- client/ui/controllers/installController.cpp | 1 - client/ui/controllers/pageController.h | 4 - client/ui/controllers/settingsController.cpp | 8 +- client/ui/controllers/settingsController.h | 2 + client/ui/controllers/sitesController.cpp | 6 +- client/ui/controllers/sitesController.h | 2 + client/ui/controllers/systemController.cpp | 36 +++++++-- client/ui/controllers/systemController.h | 9 ++- .../qml/Components/ShareConnectionDrawer.qml | 25 +++--- client/ui/qml/Pages2/PageSettingsBackup.qml | 40 +++++----- client/ui/qml/Pages2/PageSettingsLogging.qml | 25 +++--- .../qml/Pages2/PageSettingsSplitTunneling.qml | 48 ++++++----- .../Pages2/PageSetupWizardConfigSource.qml | 15 +++- client/ui/qml/main2.qml | 42 +--------- 21 files changed, 167 insertions(+), 243 deletions(-) delete mode 100644 client/fileUtilites.cpp delete mode 100644 client/fileUtilites.h diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp deleted file mode 100644 index 301344a77..000000000 --- a/client/fileUtilites.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "fileUtilites.h" - -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif - -#ifdef Q_OS_IOS - #include "platforms/ios/MobileUtils.h" - #include -#endif - -void FileUtilites::saveFile(QString fileName, const QString &data) -{ -#if defined Q_OS_ANDROID - AndroidController::instance()->shareConfig(data, fileName); - return; -#endif - -#ifdef Q_OS_IOS - QUrl fileUrl = QDir::tempPath() + "/" + fileName; - QFile file(fileUrl.toString()); -#else - QUrl fileUrl = QUrl(fileName); - QFile file(fileUrl.toLocalFile()); -#endif - - // todo check if save successful - file.open(QIODevice::WriteOnly); - file.write(data.toUtf8()); - file.close(); - -#ifdef Q_OS_IOS - QStringList filesToSend; - filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); - return; -#else - QFileInfo fi(fileUrl.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -#endif -} - -QString FileUtilites::getFileName(QString fileName) -{ -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } - - return fileName; -#endif - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep { "raw%3A%2F" }; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } - - return fileName; -#endif - - return QUrl(fileName).toLocalFile(); -} diff --git a/client/fileUtilites.h b/client/fileUtilites.h deleted file mode 100644 index 21cb03a9b..000000000 --- a/client/fileUtilites.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef FILEUTILITES_H -#define FILEUTILITES_H - -#include -#include - -class FileUtilites : public QObject -{ - Q_OBJECT - -public: - static void saveFile(QString fileName, const QString &data); - static QString getFileName(QString fileName); -}; - -#endif // FILEUTILITES_H diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index a7967fdf6..cad9de9e4 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -4,15 +4,16 @@ #include #include -class MobileUtils : public QObject { +class MobileUtils : public QObject +{ Q_OBJECT public: MobileUtils() = delete; public slots: - static void shareText(const QStringList& filesToSend); - + static void shareText(const QStringList &filesToSend); + static void openFile(); }; #endif // MOBILEUTILS_H diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index a9ad52b5d..63ee03646 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -35,3 +35,31 @@ void MobileUtils::shareText(const QStringList& filesToSend) { } } +@interface MyFilePickerDelegate : NSObject +@end + +@implementation MyFilePickerDelegate + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { + for (NSURL *url in urls) { + NSString *filePath = [url path]; + + NSData *fileData = [NSData dataWithContentsOfFile:filePath]; + NSString *fileContent = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]; + NSLog(@"Содержимое файла: %@", fileContent); + } +} + +@end + +void MobileUtils::openFile() { + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; + + MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; + documentPicker.delegate = filePickerDelegate; + + UIViewController *qtController = getViewController(); + if (!qtController) return; + + [qtController presentViewController:documentPicker animated:YES completion:nil]; +} diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index ddc976cc7..ef5cc4e3b 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,7 +11,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" -#include "fileUtilites.h" +#include "systemController.h" #ifdef Q_OS_ANDROID #include "platforms/android/androidutils.h" #endif @@ -200,9 +200,9 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile(const QString &fileName) +void ExportController::exportConfig(const QString &fileName) { - FileUtilites::saveFile(fileName, m_config); + SystemController::saveFile(fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index b526521ef..24eaa5c85 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(const QString &fileName); + void exportConfig(const QString &fileName); signals: void generateConfig(int type); @@ -43,6 +43,8 @@ signals: void exportConfigChanged(); + void saveFile(const QString &fileName, const QString &data); + private: QList generateQrCodeImageSeries(const QByteArray &data); QString svgToBase64(const QString &image); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index c0aaeeb9b..d9278ece4 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -14,7 +14,6 @@ #ifdef Q_OS_IOS #include #endif -#include "fileUtilites.h" namespace { @@ -88,11 +87,7 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile(const QString &fileName) { - QQuickItem *obj = findChild("saveFileDialog"); - - QUrl url(fileName); - QString path = url.toLocalFile(); - QFile file(path); + QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 3c0752cc8..1db84b36c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,7 +8,6 @@ #include "core/errorstrings.h" #include "core/servercontroller.h" -#include "fileUtilites.h" #include "utilities.h" namespace diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 952f716c7..8d3da507b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -103,10 +103,6 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); - void setupFileDialogForConfig(); - void setupFileDialogForSites(bool replaceExistingSites); - void setupFileDialogForBackup(); - private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 7c7402e02..46993f6a5 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,8 +2,8 @@ #include -#include "fileUtilites.h" #include "logger.h" +#include "systemController.h" #include "ui/qautostart.h" #include "version.h" @@ -70,7 +70,7 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile(const QString &fileName) { - FileUtilites::saveFile(fileName, Logger::getLogFile()); + SystemController::saveFile(fileName, Logger::getLogFile()); } void SettingsController::clearLogs() @@ -81,12 +81,12 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig(const QString &fileName) { - FileUtilites::saveFile(fileName, m_settings->backupAppConfig()); + SystemController::saveFile(fileName, m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig(const QString &fileName) { - QFile file(FileUtilites::getFileName(fileName)); + QFile file(fileName); file.open(QIODevice::ReadOnly); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index af816d465..3d96dc039 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -63,6 +63,8 @@ signals: void changeSettingsFinished(const QString &finishedMessage); void changeSettingsErrorOccurred(const QString &errorMessage); + void saveFile(const QString &fileName, const QString &data); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 5821b3719..a27e91d05 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -4,7 +4,7 @@ #include #include -#include "fileUtilites.h" +#include "systemController.h" #include "utilities.h" SitesController::SitesController(const std::shared_ptr &settings, @@ -82,7 +82,7 @@ void SitesController::removeSite(int index) void SitesController::importSites(const QString &fileName, bool replaceExisting) { - QFile file(FileUtilites::getFileName(fileName)); + QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); @@ -145,7 +145,7 @@ void SitesController::exportSites(const QString &fileName) QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - FileUtilites::saveFile(fileName, jsonData); + SystemController::saveFile(fileName, jsonData); emit finished(tr("Export completed")); } diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h index 2171e7b42..e66478da9 100644 --- a/client/ui/controllers/sitesController.h +++ b/client/ui/controllers/sitesController.h @@ -26,6 +26,8 @@ signals: void errorOccurred(const QString &errorMessage); void finished(const QString &message); + void saveFile(const QString &fileName, const QString &data); + private: std::shared_ptr m_settings; diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 87d040900..a6210b696 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -34,8 +34,7 @@ void SystemController::saveFile(QString fileName, const QString &data) QUrl fileUrl = QDir::tempPath() + "/" + fileName; QFile file(fileUrl.toString()); #else - QUrl fileUrl = QUrl(fileName); - QFile file(fileUrl.toLocalFile()); + QFile file(fileName); #endif // todo check if save successful @@ -49,14 +48,18 @@ void SystemController::saveFile(QString fileName, const QString &data) MobileUtils::shareText(filesToSend); return; #else - QFileInfo fi(fileUrl.toLocalFile()); + QFileInfo fi(fileName); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); #endif } -QString SystemController::getFileName() +QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter, + const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) { + QString fileName; #ifdef Q_OS_IOS + MobileUtils::openFile(); + CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), @@ -69,17 +72,34 @@ QString SystemController::getFileName() return fileName; #endif - auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + QObject *mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); if (!mainFileDialog) { return ""; } - QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel)); + mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter))); + if (!selectedFile.isEmpty()) { + mainFileDialog->setProperty("selectedFile", QVariant::fromValue(selectedFile)); + } + mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode)); + mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix)); + QMetaObject::invokeMethod(mainFileDialog, "open"); + + bool isFileDialogAccepted = false; QEventLoop wait; - QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + QObject::connect(this, &SystemController::fileDialogClosed, [&wait, &isFileDialogAccepted](const bool isAccepted) { + isFileDialogAccepted = isAccepted; + wait.quit(); + }); wait.exec(); + QObject::disconnect(this, &SystemController::fileDialogClosed, nullptr, nullptr); - auto fileName = mainFileDialog->property("selectedFile").toString(); + if (!isFileDialogAccepted) { + return ""; + } + + fileName = mainFileDialog->property("selectedFile").toString(); #ifdef Q_OS_ANDROID // patch for files containing spaces etc diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index fbcc52f19..274df2349 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -11,15 +11,16 @@ class SystemController : public QObject public: explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + static void saveFile(QString fileName, const QString &data); + public slots: - void saveFile(QString fileName, const QString &data); - QString getFileName(); + QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "", + const bool isSaveMode = false, const QString &defaultSuffix = ""); void setQmlRoot(QObject *qmlRoot); signals: - void fileDialogAccepted(); - void fileDialogRejected(); + void fileDialogClosed(const bool isAccepted); private: std::shared_ptr m_settings; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 4971f93af..4d719d1a1 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -72,23 +72,20 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { + var fileName = "" if (GC.isMobile()) { - ExportController.saveFile(configFileName) + fileName = configFileName } else { - fileDialog.open() + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName, + true, + configExtension) } - } - - FileDialog { - id: fileDialog - acceptLabel: configCaption - nameFilters: [ "Config files (*" + configExtension + ")" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName - defaultSuffix: configExtension - onAccepted: { - ExportController.saveFile(fileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + ExportController.exportConfig(fileName) + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index edd527c15..6a9ea58f6 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -84,31 +84,22 @@ PageType { text: qsTr("Make a backup") onClicked: { + var fileName = "" if (GC.isMobile()) { - backupAppConfig("AmneziaVPN.backup") + fileName = "AmneziaVPN.backup" } else { - saveFileDialog.open() + fileName = SystemController.getFileName(qsTr("Save backup file"), + qsTr("Backup files (*.backup)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".backup") } - } - - FileDialog { - id: saveFileDialog - acceptLabel: qsTr("Save backup file") - nameFilters: [ "Backup files (*.backup)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" - defaultSuffix: ".backup" - onAccepted: { - makeBackupButton.backupAppConfig(saveFileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.backupAppConfig(fileName) + PageController.showBusyIndicator(false) } } - - function backupAppConfig(fileName) { - PageController.showBusyIndicator(true) - SettingsController.backupAppConfig(fileName) - PageController.showBusyIndicator(false) - } } BasicButtonType { @@ -125,8 +116,13 @@ PageType { text: qsTr("Restore from backup") onClicked: { - PageController.setupFileDialogForBackup() - SystemController.getFileName() + var fileName = SystemController.getFileName(qsTr("Open backup file"), + qsTr("Backup files (*.backup)")) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileName) + PageController.showBusyIndicator(false) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index d4f1a1d40..42f33901b 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -101,23 +101,20 @@ PageType { image: "qrc:/images/controls/save.svg" onClicked: { + var fileName = "" if (GC.isMobile()) { - SettingsController.exportLogsFile("AmneziaVPN.log") + fileName = "AmneziaVPN.log" } else { - fileDialog.open() + fileName = SystemController.getFileName(qsTr("Save logs"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".log") } - } - - FileDialog { - id: fileDialog - acceptLabel: qsTr("Save logs") - nameFilters: [ "Logs files (*.log)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" - defaultSuffix: ".log" - onAccepted: { - SettingsController.exportLogsFile(fileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportLogsFile(fileName) + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index eb06a5867..b79d5d226 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -300,25 +300,19 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { + var fileName = "" if (GC.isMobile()) { - SitesController.exportSites("amezia_tunnel.json") + fileName = "amnezia_sites.json" } else { - saveFileDialog.open() + fileName = SystemController.getFileName(qsTr("Save sites"), + qsTr("Sites files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", + true, + ".json") } - } - - FileDialog { - id: saveFileDialog - objectName: saveFileDialog - acceptLabel: qsTr("Save sites") - nameFilters: [ "Sites files (*.json)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" - defaultSuffix: ".json" - onAccepted: { + if (fileName !== "") { PageController.showBusyIndicator(true) - SitesController.exportSites(saveFileDialog.currentFile.toString()) + SitesController.exportSites(fileName) moreActionsDrawer.close() PageController.showBusyIndicator(false) } @@ -377,8 +371,11 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - PageController.setupFileDialogForSites(true) - SystemController.getFileName() + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } } } @@ -389,13 +386,22 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - PageController.setupFileDialogForSites(false) - SystemController.getFileName() - importSitesDrawer.close() - moreActionsDrawer.close() + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } } } + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + SitesController.importSites(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + DividerType {} } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 3f460f651..07189eb7b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -70,8 +70,19 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - PageController.setupFileDialogForConfig() - SystemController.getFileName() + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.backup)" : + "Config files (*.vpn *.ovpn *.conf)" + var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) + if (fileName !== "") { + if (fileName.indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileName) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(fileName) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index e09882c06..c9f38753e 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,42 +81,6 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } - - function onSetupFileDialogForConfig() { - mainFileDialog.acceptLabel = qsTr("Open config file") - mainFileDialog.nameFilters = !ServersModel.getServersCount() ? [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] : - [ "Config files (*.vpn *.ovpn *.conf)" ] - mainFileDialog.acceptFunction = function() { - if (mainFileDialog.selectedFile.toString().indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } else { - ImportController.extractConfigFromFile(mainFileDialog.selectedFile) - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } - } - - function onSetupFileDialogForSites(replaceExistingSites) { - mainFileDialog.acceptLabel = qsTr("Open sites file") - mainFileDialog.nameFilters = [ "Sites files (*.json)" ] - mainFileDialog.acceptFunction = function() { - PageController.showBusyIndicator(true) - SitesController.importSites(mainFileDialog.selectedFile.toString(), replaceExistingSites) - PageController.showBusyIndicator(false) - } - } - - function onSetupFileDialogForBackup() { - mainFileDialog.acceptLabel = qsTr("Open backup file") - mainFileDialog.nameFilters = [ "Backup files (*.backup)" ] - mainFileDialog.acceptFunction = function() { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } - } } Connections { @@ -234,10 +198,12 @@ Window { FileDialog { id: mainFileDialog - property var acceptFunction + property bool isSaveMode: false objectName: "mainFileDialog" + fileMode: isSaveMode ? FileDialog.SaveFile : FileDialog.OpenFile - onAccepted: acceptFunction() + onAccepted: SystemController.fileDialogClosed(true) + onRejected: SystemController.fileDialogClosed(false) } } From fdff57da7c5896fa07bff7b49aeadc45b43da85e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 8 Sep 2023 18:05:08 +0500 Subject: [PATCH 096/131] fixed navigation during initial installation --- client/platforms/ios/MobileUtils.cpp | 8 +++++-- client/ui/qml/Pages2/PageSetupWizardStart.qml | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index 3923d2910..f94d56fc9 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,5 +1,9 @@ #include "MobileUtils.h" -void MobileUtils::shareText(const QStringList&) {} - +void MobileUtils::shareText(const QStringList &) +{ +} +void MobileUtils::openFile() +{ +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 2ce93e53b..36d90bd8f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -23,6 +23,28 @@ PageType { function onShowBusyIndicator(visible) { busyIndicator.visible = visible } + + function onClosePage() { + if (stackView.depth <= 1) { + return + } + stackView.pop() + } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + } + + function onGoToStartPage() { + while (stackView.depth > 1) { + stackView.pop() + } + } } Connections { From 85414eb65f5eb3fff953f46b9229c613d60d9ff9 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 8 Sep 2023 21:31:47 +0800 Subject: [PATCH 097/131] fixed protocol reloads old value in settings page --- client/amnezia_application.cpp | 2 +- client/amnezia_application.h | 2 +- client/ui/controllers/installController.cpp | 9 ++++++++- client/ui/controllers/installController.h | 3 +++ client/ui/qml/Pages2/PageProtocolCloakSettings.qml | 10 +++++++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 8db6f8de0..1725e1f82 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -330,7 +330,7 @@ void AmneziaApplication::initControllers() m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), &PageController::showPassphraseRequestDrawer); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 1ace6d8b0..2dd74fcbc 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -91,7 +91,7 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; QSharedPointer m_languageModel; - QScopedPointer m_protocolsModel; + QSharedPointer m_protocolsModel; QSharedPointer m_sitesModel; QScopedPointer m_openVpnConfigModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 1db84b36c..c0e9acbb1 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -40,8 +40,13 @@ namespace InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_protocolModel(protocolsModel), + m_settings(settings) { } @@ -252,6 +257,8 @@ void InstallController::updateContainer(QJsonObject config) auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); + m_protocolModel->updateModel(config); + emit updateContainerFinished(); return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 4060c97c4..47fc5dab1 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -8,6 +8,7 @@ #include "core/defs.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#include "ui/models/protocols_model.h" class InstallController : public QObject { @@ -15,6 +16,7 @@ class InstallController : public QObject public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, const std::shared_ptr &settings, QObject *parent = nullptr); ~InstallController(); @@ -71,6 +73,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_protocolModel; std::shared_ptr m_settings; ServerCredentials m_currentlyInstalledServerCredentials; diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index b53fdfdf2..e15c5ec7e 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -91,7 +91,15 @@ PageType { textField.onEditingFinished: { if (textFieldText !== site) { - site = textFieldText + var tmpText = textFieldText + tmpText = tmpText.toLocaleLowerCase() + + var indexHttps = tmpText.indexOf("https://") + if (indexHttps === 0) { + tmpText = textFieldText.substring(8) + } else { + site = textFieldText + } } } } From 3cfca046ba5ad8ed5d00c18cfd13e0c446697b00 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 8 Sep 2023 21:40:55 +0800 Subject: [PATCH 098/131] adapted installer wizard to macos style --- deploy/installer/config/macos.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/installer/config/macos.xml.in b/deploy/installer/config/macos.xml.in index 74b682b7a..3888d08d2 100644 --- a/deploy/installer/config/macos.xml.in +++ b/deploy/installer/config/macos.xml.in @@ -8,7 +8,7 @@ /Applications/AmneziaVPN.app 600 380 - Modern + Mac true true false From 1c7868312dad02d3e1f19248d5160b99974036bf Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 01:29:28 +0500 Subject: [PATCH 099/131] added getting the path to the file for iOS --- client/platforms/ios/MobileUtils.cpp | 3 +- client/platforms/ios/MobileUtils.h | 9 +++-- client/platforms/ios/MobileUtils.mm | 39 ++++++++++++++++++---- client/ui/controllers/systemController.cpp | 5 ++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index f94d56fc9..fd20fcda3 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -4,6 +4,7 @@ void MobileUtils::shareText(const QStringList &) { } -void MobileUtils::openFile() +QString MobileUtils::openFile() { + return QString(); } diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index cad9de9e4..fe7549c58 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -8,12 +8,15 @@ class MobileUtils : public QObject { Q_OBJECT -public: - MobileUtils() = delete; +//public: +// MobileUtils() = delete; public slots: static void shareText(const QStringList &filesToSend); - static void openFile(); + QString openFile(); + +signals: + void finished(); }; #endif // MOBILEUTILS_H diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index 63ee03646..7f5fc1e6e 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -4,6 +4,7 @@ #include #include +#include static UIViewController* getViewController() { NSArray *windows = [[UIApplication sharedApplication]windows]; @@ -35,24 +36,33 @@ void MobileUtils::shareText(const QStringList& filesToSend) { } } +typedef void (^FileSelectionCallback)(NSString *fileContent); + @interface MyFilePickerDelegate : NSObject + +@property (nonatomic, copy) FileSelectionCallback fileSelectionCallback; + @end @implementation MyFilePickerDelegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { for (NSURL *url in urls) { - NSString *filePath = [url path]; - - NSData *fileData = [NSData dataWithContentsOfFile:filePath]; - NSString *fileContent = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]; - NSLog(@"Содержимое файла: %@", fileContent); + if (self.fileSelectionCallback) { + self.fileSelectionCallback([url path]); + } + } +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + if (self.fileSelectionCallback) { + self.fileSelectionCallback(nil); } } @end -void MobileUtils::openFile() { +QString MobileUtils::openFile() { UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; @@ -62,4 +72,21 @@ void MobileUtils::openFile() { if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; + + __block QString path1; + + filePickerDelegate.fileSelectionCallback = ^(NSString *filePath) { + if (filePath) { + path1 = QString::fromUtf8(filePath.UTF8String); + } else { + path1 = QString(""); + } + emit finished(); + }; + + QEventLoop wait1; + QObject::connect(this, &MobileUtils::finished, &wait1, &QEventLoop::quit); + wait1.exec(); + + return path1; } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index a6210b696..8e1fb6c86 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" @@ -58,8 +59,10 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString { QString fileName; #ifdef Q_OS_IOS - MobileUtils::openFile(); + MobileUtils mobileUtils; + fileName = mobileUtils.openFile(); + CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), From 0a5657738e98544135abebf8d531f5906cecf349 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 15:00:34 +0500 Subject: [PATCH 100/131] added a wait for the file dialog to close when sharing ios files --- client/platforms/ios/MobileUtils.h | 8 +-- client/platforms/ios/MobileUtils.mm | 58 ++++++++++++++-------- client/ui/controllers/systemController.cpp | 7 ++- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index fe7549c58..04a12c51b 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -7,12 +7,12 @@ class MobileUtils : public QObject { Q_OBJECT - -//public: -// MobileUtils() = delete; + +public: + explicit MobileUtils(QObject *parent = nullptr); public slots: - static void shareText(const QStringList &filesToSend); + bool shareText(const QStringList &filesToSend); QString openFile(); signals: diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index 7f5fc1e6e..fbf26ffde 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -3,7 +3,6 @@ #include #include -#include #include static UIViewController* getViewController() { @@ -16,7 +15,11 @@ static UIViewController* getViewController() { return nil; } -void MobileUtils::shareText(const QStringList& filesToSend) { +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) { + +} + +bool MobileUtils::shareText(const QStringList& filesToSend) { NSMutableArray *sharingItems = [NSMutableArray new]; for (int i = 0; i < filesToSend.size(); i++) { @@ -28,35 +31,48 @@ void MobileUtils::shareText(const QStringList& filesToSend) { if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; + + __block bool isAccepted = false; + + [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + isAccepted = completed; + emit finished(); + }]; [qtController presentViewController:activityController animated:YES completion:nil]; UIPopoverPresentationController *popController = activityController.popoverPresentationController; if (popController) { popController.sourceView = qtController.view; } + + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); + + return isAccepted; } -typedef void (^FileSelectionCallback)(NSString *fileContent); +typedef void (^DocumentPickerClosedCallback)(NSString *path); -@interface MyFilePickerDelegate : NSObject +@interface DocumentPickerDelegate : NSObject -@property (nonatomic, copy) FileSelectionCallback fileSelectionCallback; +@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback; @end -@implementation MyFilePickerDelegate +@implementation DocumentPickerDelegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { for (NSURL *url in urls) { - if (self.fileSelectionCallback) { - self.fileSelectionCallback([url path]); + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback([url path]); } } } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { - if (self.fileSelectionCallback) { - self.fileSelectionCallback(nil); + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback(nil); } } @@ -65,28 +81,28 @@ typedef void (^FileSelectionCallback)(NSString *fileContent); QString MobileUtils::openFile() { UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; - MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; - documentPicker.delegate = filePickerDelegate; + DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init]; + documentPicker.delegate = documentPickerDelegate; UIViewController *qtController = getViewController(); if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; - __block QString path1; + __block QString filePath; - filePickerDelegate.fileSelectionCallback = ^(NSString *filePath) { - if (filePath) { - path1 = QString::fromUtf8(filePath.UTF8String); + documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { + if (path) { + filePath = QString::fromUtf8(path.UTF8String); } else { - path1 = QString(""); + filePath = QString(); } emit finished(); }; - QEventLoop wait1; - QObject::connect(this, &MobileUtils::finished, &wait1, &QEventLoop::quit); - wait1.exec(); + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); - return path1; + return filePath; } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 8e1fb6c86..7de071cca 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -46,7 +46,9 @@ void SystemController::saveFile(QString fileName, const QString &data) #ifdef Q_OS_IOS QStringList filesToSend; filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); + MobileUtils mobileUtils; + // todo check if save successful + mobileUtils.shareText(filesToSend); return; #else QFileInfo fi(fileName); @@ -62,6 +64,9 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString MobileUtils mobileUtils; fileName = mobileUtils.openFile(); + if (fileName.isEmpty()) { + return fileName; + } CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, From 4bf6cce4ba3792fb9d5a16310207467d197c0ba9 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 18:25:44 +0500 Subject: [PATCH 101/131] fixed return type of sharedText function --- client/platforms/ios/MobileUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index fd20fcda3..183781137 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,6 +1,6 @@ #include "MobileUtils.h" -void MobileUtils::shareText(const QStringList &) +bool MobileUtils::shareText(const QStringList &) { } From 89096554e83471caabd9da8dcfcd0878e616c3a4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 18:31:04 +0500 Subject: [PATCH 102/131] added constructor for MobileUtils.cpp --- client/platforms/ios/MobileUtils.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index 183781137..14c0854a0 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,7 +1,12 @@ #include "MobileUtils.h" +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) +{ +} + bool MobileUtils::shareText(const QStringList &) { + return false; } QString MobileUtils::openFile() From f751657903f13fda0dd59c45c1ab3a094d28b13d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 22:41:36 +0500 Subject: [PATCH 103/131] fixed false triggering of swipes for the main menu drawer of PageHome --- client/ui/qml/Pages2/PageHome.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 0c7782156..01ba3032c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -129,7 +129,13 @@ PageType { DrawerType { id: menu - interactive: true + interactive: { + if (stackView && stackView.currentItem) { + return (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) ? true : false + } else { + return false + } + } dragMargin: buttonBackground.height + 56 // page start tabBar height width: parent.width From 551f7616f00aac45b86e1c82c0efe5aac583aad4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 13:56:49 +0500 Subject: [PATCH 104/131] fixed the display of the protocol list in the settings when attempting to install a container that is already installed on the server --- client/core/servercontroller.cpp | 25 ++++++++++++++++------ client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index ef7a80242..b0f8146fb 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -665,7 +665,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); // TODO reimplement with netstat - QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); + QString script = + QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); for (auto &port : fixedPorts) { script = script.append("|:%1").arg(port); } @@ -739,8 +740,10 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (stdOut.contains("Packet manager not found")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed")) return ErrorCode::NoError; + if (stdOut.contains("Packet manager not found")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("fuser not installed")) + return ErrorCode::NoError; if (stdOut.isEmpty()) { return ErrorCode::NoError; @@ -798,11 +801,19 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString port = containerAndPortMatch.captured(2); QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); + + QJsonObject config; Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), - QJsonObject { { config_key::port, port }, - { config_key::transport_proto, transportProto } } } }; + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + if (protocol == mainProto) { + containerConfig.insert(config_key::port, port); + containerConfig.insert(config_key::transport_proto, transportProto); + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } installedContainers.insert(container, config); } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 1a6f7e806..e73ef88f4 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -122,7 +122,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_dev")) + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) } } From 844b552bf3ebfd86662fd83f29f9e420b28bb69d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 14:45:10 +0500 Subject: [PATCH 105/131] removed duplicate function routeMode --- client/settings.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/settings.cpp b/client/settings.cpp index 80a6be260..93871834f 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -240,15 +240,6 @@ Settings::RouteMode Settings::routeMode() const return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); } -Settings::RouteMode Settings::routeMode() const -{ -// TODO implement for mobiles -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - return RouteMode::VpnAllSites; -#endif - return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); -} - bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); From 97c0fe1ece0805784e860dee618aa879e53956aa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 14:55:52 +0500 Subject: [PATCH 106/131] increased the application version to 4.0.5.1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 875d664d8..49b8fa165 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.4.1 +project(${PROJECT} VERSION 4.0.5.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-26") +set(RELEASE_DATE "2023-09-11") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") From 72eb36f5b3f38daf8836fd6eecade21103d58e63 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 11 Sep 2023 18:55:50 +0800 Subject: [PATCH 107/131] fixed the implicitWidth error in protocol installation page --- client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 6552d873a..07eef1773 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -145,6 +145,7 @@ PageType { } TextField { + implicitWidth: parent.width Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 From f8e5e9f675e01373bd82115f6a50777e7e2ed6fa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 16:48:56 +0500 Subject: [PATCH 108/131] fixed display of cursorShape for all mouseArea --- client/ui/qml/Controls2/PageType.qml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 193fbcf25..2c176b404 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -7,15 +7,16 @@ Item { property StackView stackView: StackView.view - MouseArea { - z: 99 - anchors.fill: parent +// MouseArea { +// id: globalMouseArea +// z: 99 +// anchors.fill: parent - enabled: true +// enabled: true - onPressed: function(mouse) { - forceActiveFocus() - mouse.accepted = false - } - } +// onPressed: function(mouse) { +// forceActiveFocus() +// mouse.accepted = false +// } +// } } From a964d955f41ee36ba12a1a5db615f71ced7f5c83 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 11 Sep 2023 23:49:50 +0800 Subject: [PATCH 109/131] fixed scroll stuck on textarea in page OpenVpnSettings --- client/ui/qml/Controls2/TextAreaType.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index a75ea55d3..9b9921f81 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -15,6 +15,9 @@ Rectangle { radius: 16 FlickableType { + id: fl + interactive: false + anchors.top: parent.top anchors.bottom: parent.bottom contentHeight: textArea.implicitHeight @@ -46,12 +49,23 @@ Rectangle { } } + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + wrapMode: Text.Wrap MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() + onClicked: { + fl.interactive = true + contextMenu.open() + } } ContextMenuType { From f81ee1b267148295e40d7fc3453e1bd74f8938c0 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 12 Sep 2023 21:38:36 +0800 Subject: [PATCH 110/131] reconnect to server when changed the protocol and status is connected or connnecting --- .../ui/qml/Components/HomeContainersListView.qml | 14 ++++++++++++++ client/ui/qml/Pages2/PageHome.qml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index f5d27c00e..e265574b7 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -62,8 +62,22 @@ ListView { onClicked: { if (checked) { isDefault = true + var needReconnected = false + if (menuContent.currentIndex !== index) { + needReconnected = true + } + menuContent.currentIndex = index containersDropDown.menuVisible = false + + + if (needReconnected && + (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { + PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) + PageController.goToPageHome() + menu.visible = false + ConnectionController.openConnection() + } } else { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 01ba3032c..b9bc23665 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,7 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - currentIndex: ContainersModel.getDefaultContainer() + currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 9c0f27edb49bbaa5605ca47fc83a54173b8a44a6 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 08:01:14 +0800 Subject: [PATCH 111/131] removed invalid codes --- client/ui/qml/Components/HomeContainersListView.qml | 5 +++-- client/ui/qml/Pages2/PageHome.qml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index e265574b7..4708128f0 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -61,12 +61,13 @@ ListView { onClicked: { if (checked) { - isDefault = true var needReconnected = false - if (menuContent.currentIndex !== index) { + if (!isDefault) { needReconnected = true } + isDefault = true + menuContent.currentIndex = index containersDropDown.menuVisible = false diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b9bc23665..cedff2cb0 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,7 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - currentIndex: ContainersModel.getDefaultContainer() - 1 + // currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 3c9b42b9f73db40a652b3fac3e2b84706c5777f5 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 08:44:50 +0800 Subject: [PATCH 112/131] deleted unused code --- client/ui/qml/Pages2/PageHome.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index cedff2cb0..c5ba89d06 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,6 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - // currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 16cadfeae804885fdd5ce9561883410063d079d3 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 09:39:17 +0800 Subject: [PATCH 113/131] the cursor changes to Qt.PointingHandCursor when hovering over links --- client/ui/qml/Pages2/PageServiceSftpSettings.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index fead034b9..9287bd20f 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -218,6 +218,11 @@ PageType { } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } } BasicButtonType { From e2aef1fc1d08c7a145de233e82791d6526d36ef5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 11:09:29 +0500 Subject: [PATCH 114/131] fixed display of installation errors on the initial installation screen --- client/ui/qml/Pages2/PageSetupWizardStart.qml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 36d90bd8f..9f5e57a57 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -56,6 +56,20 @@ PageType { } } + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var currentPageName = tabBarStackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + PageController.closePage() + } + } + } + FlickableType { id: fl anchors.top: parent.top From 4ae608ed936100fb7d7f8369cc5ecf11fcc45c37 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 16:11:08 +0500 Subject: [PATCH 115/131] added import backup file from outside for ios --- client/amnezia_application.cpp | 5 + client/containers/containers_defs.cpp | 4 +- client/platforms/ios/QtAppDelegate.mm | 14 +- client/platforms/ios/ios_controller.h | 50 +++--- client/protocols/protocols_defs.cpp | 149 +++++++++--------- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.h | 2 + .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 35 +++- client/ui/qml/main2.qml | 4 + 10 files changed, 157 insertions(+), 111 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 1725e1f82..de75db85c 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -117,6 +117,11 @@ void AmneziaApplication::init() &ImportController::extractConfigFromData); connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), &PageController::goToPageViewConfig); + + connect(IosController::Instance(), &IosController::importBackupFromOutside, m_pageController.get(), + &PageController::goToPageSettingsBackup); + connect(IosController::Instance(), &IosController::importBackupFromOutside, m_settingsController.get(), + &SettingsController::importBackupFromOutside); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 98b2d0da8..766d89dac 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -86,7 +86,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, - { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; @@ -129,7 +129,7 @@ QMap ContainerProps::containerDetailedDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, { DockerContainer::Ipsec, QObject::tr("IPsec container") }, - { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("DNS Service") }, //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index 7528f2fff..48cef9141 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -76,11 +76,15 @@ QString filePath(url.path.UTF8String); if (filePath.isEmpty()) return NO; - QFile file(filePath); - bool isOpenFile = file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - IosController::Instance()->importConfigFromOutside(QString(data)); + if (filePath.contains("backup")) { + IosController::Instance()->importBackupFromOutside(filePath); + } else { + QFile file(filePath); + bool isOpenFile = file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + + IosController::Instance()->importConfigFromOutside(QString(data)); + } return YES; } return NO; diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index a81498aff..ea8adbc0c 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -4,28 +4,30 @@ #include "protocols/vpnprotocol.h" #ifdef __OBJC__ -#import + #import @class NETunnelProviderManager; #endif using namespace amnezia; -struct Action { - static const char* start; - static const char* restart; - static const char* stop; - static const char* getTunnelId; - static const char* getStatus; +struct Action +{ + static const char *start; + static const char *restart; + static const char *stop; + static const char *getTunnelId; + static const char *getStatus; }; -struct MessageKey { - static const char* action; - static const char* tunnelId; - static const char* config; - static const char* errorCode; - static const char* host; - static const char* port; - static const char* isOnDemand; +struct MessageKey +{ + static const char *action; + static const char *tunnelId; + static const char *config; + static const char *errorCode; + static const char *host; + static const char *port; + static const char *isOnDemand; }; class IosController : public QObject @@ -33,27 +35,27 @@ class IosController : public QObject Q_OBJECT public: - static IosController* Instance(); + static IosController *Instance(); virtual ~IosController() override = default; bool initialize(); - bool connectVpn(amnezia::Proto proto, const QJsonObject& configuration); + bool connectVpn(amnezia::Proto proto, const QJsonObject &configuration); void disconnectVpn(); void vpnStatusDidChange(void *pNotification); void vpnConfigurationDidChange(void *pNotification); - void getBackendLogs(std::function &&callback); + void getBackendLogs(std::function &&callback); void checkStatus(); signals: void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void importConfigFromOutside(const QString); + void importBackupFromOutside(const QString); protected slots: - private: explicit IosController(); @@ -67,12 +69,12 @@ private: void startTunnel(); private: - void *m_iosControllerWrapper{}; + void *m_iosControllerWrapper {}; #ifdef __OBJC__ - NETunnelProviderManager *m_currentTunnel{}; - NSString *m_serverAddress{}; - bool isOurManager(NETunnelProviderManager* manager); - void sendVpnExtensionMessage(NSDictionary* message, std::function callback = nullptr); + NETunnelProviderManager *m_currentTunnel {}; + NSString *m_serverAddress {}; + bool isOurManager(NETunnelProviderManager *manager); + void sendVpnExtensionMessage(NSDictionary *message, std::function callback = nullptr); #endif amnezia::Proto m_proto; diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 633513112..5f8600db1 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -10,17 +10,21 @@ QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) return debug; } -amnezia::Proto ProtocolProps::protoFromString(QString proto){ +amnezia::Proto ProtocolProps::protoFromString(QString proto) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { Proto p = static_cast(i); - if (proto == protoToString(p)) return p; + if (proto == protoToString(p)) + return p; } return Proto::Any; } -QString ProtocolProps::protoToString(amnezia::Proto p){ - if (p == Proto::Any) return ""; +QString ProtocolProps::protoToString(amnezia::Proto p) +{ + if (p == Proto::Any) + return ""; QMetaEnum metaEnum = QMetaEnum::fromType(); QString protoKey = metaEnum.valueToKey(static_cast(p)); @@ -43,7 +47,8 @@ TransportProto ProtocolProps::transportProtoFromString(QString p) QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { TransportProto tp = static_cast(i); - if (p.toLower() == transportProtoToString(tp).toLower()) return tp; + if (p.toLower() == transportProtoToString(tp).toLower()) + return tp; } return TransportProto::Udp; } @@ -55,22 +60,19 @@ QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p) return protoKey.toLower(); } - QMap ProtocolProps::protocolHumanNames() { - return { - {Proto::OpenVpn, "OpenVPN"}, - {Proto::ShadowSocks, "ShadowSocks"}, - {Proto::Cloak, "Cloak"}, - {Proto::WireGuard, "WireGuard"}, - {Proto::Ikev2, "IKEv2"}, - {Proto::L2tp, "L2TP"}, + return { { Proto::OpenVpn, "OpenVPN" }, + { Proto::ShadowSocks, "ShadowSocks" }, + { Proto::Cloak, "Cloak" }, + { Proto::WireGuard, "WireGuard" }, + { Proto::Ikev2, "IKEv2" }, + { Proto::L2tp, "L2TP" }, - {Proto::TorWebSite, "Web site in Tor network"}, - {Proto::Dns, "DNS Service"}, - {Proto::FileShare, "File Sharing Service"}, - {Proto::Sftp, QObject::tr("Sftp service")} - }; + { Proto::TorWebSite, "Website in Tor network" }, + { Proto::Dns, "DNS Service" }, + { Proto::FileShare, "File Sharing Service" }, + { Proto::Sftp, QObject::tr("Sftp service") } }; } QMap ProtocolProps::protocolDescriptions() @@ -81,90 +83,89 @@ QMap ProtocolProps::protocolDescriptions() amnezia::ServiceType ProtocolProps::protocolService(Proto p) { switch (p) { - case Proto::Any : return ServiceType::None; - case Proto::OpenVpn : return ServiceType::Vpn; - case Proto::Cloak : return ServiceType::Vpn; - case Proto::ShadowSocks : return ServiceType::Vpn; - case Proto::WireGuard : return ServiceType::Vpn; - case Proto::TorWebSite : return ServiceType::Other; - case Proto::Dns : return ServiceType::Other; - case Proto::FileShare : return ServiceType::Other; - default: return ServiceType::Other; + case Proto::Any: return ServiceType::None; + case Proto::OpenVpn: return ServiceType::Vpn; + case Proto::Cloak: return ServiceType::Vpn; + case Proto::ShadowSocks: return ServiceType::Vpn; + case Proto::WireGuard: return ServiceType::Vpn; + case Proto::TorWebSite: return ServiceType::Other; + case Proto::Dns: return ServiceType::Other; + case Proto::FileShare: return ServiceType::Other; + default: return ServiceType::Other; } } int ProtocolProps::defaultPort(Proto p) { switch (p) { - case Proto::Any : return -1; - case Proto::OpenVpn : return 1194; - case Proto::Cloak : return 443; - case Proto::ShadowSocks : return 6789; - case Proto::WireGuard : return 51820; - case Proto::Ikev2 : return -1; - case Proto::L2tp : return -1; + case Proto::Any: return -1; + case Proto::OpenVpn: return 1194; + case Proto::Cloak: return 443; + case Proto::ShadowSocks: return 6789; + case Proto::WireGuard: return 51820; + case Proto::Ikev2: return -1; + case Proto::L2tp: return -1; - case Proto::TorWebSite : return -1; - case Proto::Dns : return 53; - case Proto::FileShare : return 139; - case Proto::Sftp : return 222; - default: return -1; + case Proto::TorWebSite: return -1; + case Proto::Dns: return 53; + case Proto::FileShare: return 139; + case Proto::Sftp: return 222; + default: return -1; } } bool ProtocolProps::defaultPortChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return true; - case Proto::ShadowSocks : return true; - case Proto::WireGuard : return true; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return true; + case Proto::ShadowSocks: return true; + case Proto::WireGuard: return true; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; - - case Proto::TorWebSite : return true; - case Proto::Dns : return false; - case Proto::FileShare : return false; - default: return -1; + case Proto::TorWebSite: return true; + case Proto::Dns: return false; + case Proto::FileShare: return false; + default: return -1; } } TransportProto ProtocolProps::defaultTransportProto(Proto p) { switch (p) { - case Proto::Any : return TransportProto::Udp; - case Proto::OpenVpn : return TransportProto::Udp; - case Proto::Cloak : return TransportProto::Tcp; - case Proto::ShadowSocks : return TransportProto::Tcp; - case Proto::WireGuard : return TransportProto::Udp; - case Proto::Ikev2 : return TransportProto::Udp; - case Proto::L2tp : return TransportProto::Udp; + case Proto::Any: return TransportProto::Udp; + case Proto::OpenVpn: return TransportProto::Udp; + case Proto::Cloak: return TransportProto::Tcp; + case Proto::ShadowSocks: return TransportProto::Tcp; + case Proto::WireGuard: return TransportProto::Udp; + case Proto::Ikev2: return TransportProto::Udp; + case Proto::L2tp: return TransportProto::Udp; // non-vpn - case Proto::TorWebSite : return TransportProto::Tcp; - case Proto::Dns : return TransportProto::Udp; - case Proto::FileShare : return TransportProto::Udp; - case Proto::Sftp : return TransportProto::Tcp; + case Proto::TorWebSite: return TransportProto::Tcp; + case Proto::Dns: return TransportProto::Udp; + case Proto::FileShare: return TransportProto::Udp; + case Proto::Sftp: return TransportProto::Tcp; } } bool ProtocolProps::defaultTransportProtoChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return false; - case Proto::ShadowSocks : return false; - case Proto::WireGuard : return false; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return false; + case Proto::ShadowSocks: return false; + case Proto::WireGuard: return false; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; // non-vpn - case Proto::TorWebSite : return false; - case Proto::Dns : return false; - case Proto::FileShare : return false; - case Proto::Sftp : return false; - default: return false; + case Proto::TorWebSite: return false; + case Proto::Dns: return false; + case Proto::FileShare: return false; + case Proto::Sftp: return false; + default: return false; } } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8d3da507b..07e772837 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -85,6 +85,7 @@ signals: void goToPageSettings(); void goToPageViewConfig(); void goToPageSettingsServerServices(); + void goToPageSettingsBackup(); void closePage(); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 3d96dc039..5a51a3b4b 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -65,6 +65,8 @@ signals: void saveFile(const QString &fileName, const QString &data); + void importBackupFromOutside(QString filePath); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 4d719d1a1..6513ea7fb 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -169,7 +169,7 @@ DrawerType { Layout.topMargin: 16 } - TextArea { + TextField { id: configText Layout.fillWidth: true @@ -180,6 +180,8 @@ DrawerType { leftPadding: 0 height: 24 + readOnly: true + color: "#D7D8DB" selectionColor: "#633303" selectedTextColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 6a9ea58f6..7a556dfbf 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -10,6 +10,7 @@ import PageEnum 1.0 import "./" import "../Controls2" import "../Config" +import "../Components" import "../Controls2/TextTypes" PageType { @@ -27,6 +28,10 @@ PageType { //goToStartPage() PageController.goToPageHome() } + + function onImportBackupFromOutside(filePath) { + restoreBackup(filePath) + } } BackButtonType { @@ -116,15 +121,35 @@ PageType { text: qsTr("Restore from backup") onClicked: { - var fileName = SystemController.getFileName(qsTr("Open backup file"), + var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) - if (fileName !== "") { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(fileName) - PageController.showBusyIndicator(false) + if (filePath !== "") { + restoreBackup(filePath) } } } } } + + function restoreBackup(filePath) { + questionDrawer.headerText = qsTr("Import settings from a backup file?") + questionDrawer.descriptionText = qsTr("All current settings will be reset"); + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + QuestionDrawer { + id: questionDrawer + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index c9f38753e..073fb4ad1 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,6 +81,10 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } + + function onGoToPageSettingsBackup() { + PageController.goToPage(PageEnum.PageSettingsBackup) + } } Connections { From d93b5a7b5c0e4657f6ae34a94604cee055676cf1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 16:34:03 +0500 Subject: [PATCH 116/131] fixed saving of configs for mobile platforms --- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 6513ea7fb..3a8a41754 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -33,7 +33,7 @@ DrawerType { onClosed: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") - configFileName = "amnezia_config.vpn" + configFileName = "amnezia_config" } Item { @@ -74,7 +74,7 @@ DrawerType { onClicked: { var fileName = "" if (GC.isMobile()) { - fileName = configFileName + fileName = configFileName + configExtension } else { fileName = SystemController.getFileName(configCaption, qsTr("Config files (*" + configExtension + ")"), From bfc8c10f3de9cb46170fbe059e378d655193999d Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 20:49:44 +0800 Subject: [PATCH 117/131] auto reconection when current-server's defaul container hase been changed --- client/amnezia_application.cpp | 2 ++ client/core/servercontroller.cpp | 8 +++----- client/core/servercontroller.h | 12 ++++++++---- client/ui/controllers/connectionController.cpp | 8 ++++++++ client/ui/controllers/connectionController.h | 3 +++ client/ui/controllers/installController.cpp | 18 ++++++++++++++++-- client/ui/controllers/installController.h | 4 +++- .../qml/Pages2/PageProtocolCloakSettings.qml | 12 ++++++++++-- .../qml/Pages2/PageProtocolOpenVpnSettings.qml | 12 ++++++++++-- .../Pages2/PageProtocolShadowSocksSettings.qml | 13 +++++++++++-- 10 files changed, 74 insertions(+), 18 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 1725e1f82..cdcee5f45 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -336,6 +336,8 @@ void AmneziaApplication::initControllers() &PageController::showPassphraseRequestDrawer); connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), &InstallController::setEncryptedPassphrase); + connect(m_installController.get(), &InstallController::currentContainerChanged, m_connectionController.get(), + &ConnectionController::onCurrentContainerChanged); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index b0f8146fb..a82785ebc 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -290,13 +290,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, return startupContainerWorker(credentials, container, config); } -ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, +ErrorCode ServerController::updateContainer(const bool reinstallRequired, + const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig) { - bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" - << reinstallRequired; - if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); } else { diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index cb74d571e..d3f242a34 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -26,8 +26,11 @@ public: ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); - ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig); + ErrorCode updateContainer(const bool reinstallRequired, const ServerCredentials &credentials, + DockerContainer container, + const QJsonObject &oldConfig, + QJsonObject &newConfig); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); @@ -60,6 +63,8 @@ public: ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, @@ -72,8 +77,7 @@ private: ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, - const QJsonObject &newConfig); + ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 0754b024e..180d96e70 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -118,6 +118,14 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) emit connectionStateChanged(); } +void ConnectionController::onCurrentContainerChanged() +{ + if(m_isConnected || m_isConnectionInProgress) { + emit reconnectWithChangedContainer(tr("Settings updated successfully, Reconnnection...")); + openConnection(); + } +} + QString ConnectionController::connectionStateText() const { return m_connectionStateText; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 5a35f9d8a..5ee7a4ca5 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -32,6 +32,8 @@ public slots: QString getLastConnectionError(); void onConnectionStateChanged(Vpn::ConnectionState state); + void onCurrentContainerChanged(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -39,6 +41,7 @@ signals: void connectionStateChanged(); void connectionErrorOccurred(const QString &errorMessage); + void reconnectWithChangedContainer(const QString &message); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index c0e9acbb1..2d82a4c2c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -254,12 +254,26 @@ void InstallController::updateContainer(QJsonObject config) ServerController serverController(m_settings); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + bool reinstallRequired = serverController.isReinstallContainerRequired(container, oldContainerConfig, config); + auto errorCode = serverController.updateContainer(reinstallRequired, serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); m_protocolModel->updateModel(config); - emit updateContainerFinished(); + bool isCurrentContainerChanged = false; + if (reinstallRequired && + (serverIndex == m_serversModel->getDefaultServerIndex()) && + (container == m_containersModel->getDefaultContainer()) ) { + isCurrentContainerChanged = true; + } + + + if (isCurrentContainerChanged) { + emit currentContainerChanged(); + } else { + emit updateContainerFinished(tr("Settings updated successfully")); + } + return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 47fc5dab1..fc924d725 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -49,7 +49,7 @@ signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); - void updateContainerFinished(); + void updateContainerFinished(const QString& message); void scanServerFinished(bool isInstalledContainerFound); @@ -66,6 +66,8 @@ signals: void serverIsBusy(const bool isBusy); + void currentContainerChanged(); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index e15c5ec7e..1dd9af71b 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,11 +13,19 @@ import "../Components" PageType { id: root + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index aed1dbc1f..741655eac 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -15,11 +15,19 @@ import "../Components" PageType { id: root + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index fe0ef8c3b..f168efc16 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,11 +13,20 @@ import "../Components" PageType { id: root + + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } From 2b3383a1639b7f1d0c5e04e6b02818c858fb038b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 14 Sep 2023 15:21:35 +0500 Subject: [PATCH 118/131] removed the transition animation between tabs in the main menu - fixed Drawer freezing when importing files from outside the application --- client/amnezia_application.cpp | 27 +++++++++++++++------------ client/ui/qml/Pages2/PageStart.qml | 4 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index de75db85c..efdb92217 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -105,23 +105,26 @@ void AmneziaApplication::init() return; } - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_importController.get(), - &ImportController::extractConfigFromData); - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), - &PageController::goToPageViewConfig); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); #endif #ifdef Q_OS_IOS IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, m_importController.get(), - &ImportController::extractConfigFromData); - connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), - &PageController::goToPageViewConfig); + connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); - connect(IosController::Instance(), &IosController::importBackupFromOutside, m_pageController.get(), - &PageController::goToPageSettingsBackup); - connect(IosController::Instance(), &IosController::importBackupFromOutside, m_settingsController.get(), - &SettingsController::importBackupFromOutside); + connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { + m_pageController->replaceStartPage(); + m_pageController->goToPageSettingsBackup(); + m_settingsController->importBackupFromOutside(filePath); + }); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 31474c127..7f9cb2123 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -97,8 +97,8 @@ PageType { function goToTabBarPage(page) { var pagePath = PageController.getPagePath(page) - tabBarStackView.clear(StackView.PopTransition) - tabBarStackView.replace(pagePath, { "objectName" : pagePath }) + tabBarStackView.clear(StackView.Immediate) + tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) } Component.onCompleted: { From 2fd25f53cca13e796183703d05c18c45e1f13445 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 14 Sep 2023 15:35:24 +0500 Subject: [PATCH 119/131] fixed auto-connection starting after starting the application --- client/amnezia_application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index efdb92217..0d90cc5a1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -353,6 +353,9 @@ void AmneziaApplication::initControllers() m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + if (m_settingsController->isAutoStartEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + } m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); From 8a3bdf136bb8e0d30f9d3e363b987ca98b0a313f Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 16 Sep 2023 08:05:43 +0800 Subject: [PATCH 120/131] added close button when drawer shown in the topright corner --- client/resources.qrc | 1 + client/ui/controllers/pageController.cpp | 30 ++++++++++++++++ client/ui/controllers/pageController.h | 11 ++++++ client/ui/qml/Controls2/DrawerType.qml | 21 +++++++++++ .../ui/qml/Controls2/TopCloseButtonType.qml | 35 +++++++++++++++++++ client/ui/qml/Pages2/PageShare.qml | 6 ++++ client/ui/qml/Pages2/PageStart.qml | 20 +++++++++++ 7 files changed, 124 insertions(+) create mode 100644 client/ui/qml/Controls2/TopCloseButtonType.qml diff --git a/client/resources.qrc b/client/resources.qrc index d0ff176f7..16372e072 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -213,5 +213,6 @@ images/controls/trash.svg images/controls/more-vertical.svg ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Controls2/TopCloseButtonType.qml diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 46a1b1fdd..6501aea8c 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -115,3 +115,33 @@ void PageController::showOnStartup() #endif } } + +void PageController::updateDrawerRootPage(PageLoader::PageEnum page) +{ + m_drwaerLayer = 0; + m_currentRootPage = page; +} + +void PageController::goToDrawerRootPage() +{ + + m_drwaerLayer = 0; + + emit showTopCloseButton(false); + emit forceCloseDrawer(); +} + +void PageController::drawerOpen() +{ + m_drwaerLayer = m_drwaerLayer + 1; + emit showTopCloseButton(true); +} + +void PageController::drawerClose() +{ + m_drwaerLayer = m_drwaerLayer -1; + if (m_drwaerLayer <= 0) { + emit showTopCloseButton(false); + m_drwaerLayer = 0; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 07e772837..f4cac4276 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -78,6 +78,11 @@ public slots: void showOnStartup(); + void updateDrawerRootPage(PageLoader::PageEnum page); + void goToDrawerRootPage(); + void drawerOpen(); + void drawerClose(); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); @@ -104,10 +109,16 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); + void showTopCloseButton(bool visible); + void forceCloseDrawer(); + private: QSharedPointer m_serversModel; std::shared_ptr m_settings; + + PageLoader::PageEnum m_currentRootPage; + int m_drwaerLayer; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 35d03449d..2b70ef3ce 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -2,6 +2,16 @@ import QtQuick import QtQuick.Controls Drawer { + property bool needCloseButton: true + + Connections { + target: PageController + + function onForceCloseDrawer() { + visible = false + } + } + edge: Qt.BottomEdge clip: true @@ -40,7 +50,18 @@ Drawer { } } + onOpened: { + if (needCloseButton) { + PageController.drawerOpen() + } + } + + onClosed: { + if (needCloseButton) { + PageController.drawerClose() + } + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml new file mode 100644 index 000000000..cd1406c4a --- /dev/null +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + + modal: false + closePolicy: Popup.NoAutoClose + width: 40 + height: 40 + padding: 4 + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + ImageButtonType { + image: "qrc:/images/svg/close_black_24dp.svg" + imageColor: "#D7D8DB" + + implicitWidth: 32 + implicitHeight: 32 + + onClicked: { + PageController.goToDrawerRootPage() + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 135c7fbc6..623ceebfb 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -27,6 +27,8 @@ PageType { target: ExportController function onGenerateConfig(type) { + shareConnectionDrawer.needCloseButton = false + shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -58,6 +60,10 @@ PageType { } PageController.showBusyIndicator(false) + + shareConnectionDrawer.needCloseButton = true + PageController.showTopCloseButton(true) + shareConnectionDrawer.contentVisible = true } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 7f9cb2123..e497c4550 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -19,16 +19,22 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 tabBarStackView.goToTabBarPage(PageEnum.PageHome) + + PageController.updateDrawerRootPage(PageEnum.PageHome) } function onGoToPageSettings() { tabBar.currentIndex = 2 tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + + PageController.updateDrawerRootPage(PageEnum.PageSettings) } function onGoToPageViewConfig() { var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + + PageController.updateDrawerRootPage(PageEnum.PageSetupWizardViewConfig) } function onShowBusyIndicator(visible) { @@ -37,6 +43,10 @@ PageType { tabBar.enabled = !visible } + function onShowTopCloseButton(visible) { + topCloseButton.visible = visible + } + function onEnableTabBar(enabled) { tabBar.enabled = enabled } @@ -55,6 +65,8 @@ PageType { } else { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } + + PageController.updateDrawerRootPage(page) } function onGoToStartPage() { @@ -99,6 +111,8 @@ PageType { var pagePath = PageController.getPagePath(page) tabBarStackView.clear(StackView.Immediate) tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) + + PageController.updateDrawerRootPage(page) } Component.onCompleted: { @@ -183,4 +197,10 @@ PageType { anchors.centerIn: parent z: 1 } + + TopCloseButtonType { + id: topCloseButton + x: tabBarStackView.width - topCloseButton.width + z: 1 + } } From 9eebee3ce38395c6b6e274fda2cfc828150194b2 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 16 Sep 2023 16:20:19 +0800 Subject: [PATCH 121/131] fixed additional info can not be save in page OpenVpn settings --- client/ui/qml/Controls2/TextAreaType.qml | 9 ++------ .../Pages2/PageProtocolOpenVpnSettings.qml | 22 ++++++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index a75ea55d3..a7e2fee76 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -6,7 +6,8 @@ Rectangle { property string placeholderText property string text - property var onEditingFinished + property alias textArea: textArea + property alias textAreaText: textArea.text height: 148 color: "#1C1D21" @@ -40,12 +41,6 @@ Rectangle { placeholderText: root.placeholderText text: root.text - onEditingFinished: { - if (root.onEditingFinished && typeof root.onEditingFinished === "function") { - root.onEditingFinished() - } - } - wrapMode: Text.Wrap MouseArea { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index aed1dbc1f..acb313271 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -312,12 +312,12 @@ PageType { visible: additionalClientCommandsSwitcher.checked - text: additionalClientCommands + textAreaText: additionalClientCommands placeholderText: qsTr("Commands:") - onEditingFinished: { - if (additionalClientCommands !== text) { - additionalClientCommands = text + textArea.onEditingFinished: { + if (additionalClientCommands !== textAreaText) { + additionalClientCommands = textAreaText } } } @@ -330,6 +330,12 @@ PageType { checked: additionalServerCommands !== "" text: qsTr("Additional server configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalServerCommands = "" + } + } } TextAreaType { @@ -338,12 +344,12 @@ PageType { visible: additionalServerCommandsSwitcher.checked - text: additionalServerCommands + textAreaText: additionalServerCommands placeholderText: qsTr("Commands:") - onEditingFinished: { - if (additionalServerCommands !== text) { - additionalServerCommands = text + textArea.onEditingFinished: { + if (additionalServerCommands !== textAreaText) { + additionalServerCommands = textAreaText } } } From f40bf2d9ba1c015be9cd6ddead57cb32b49da3e4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 15:01:31 +0500 Subject: [PATCH 122/131] limited the length of the displayed server name - added auto-selection of the first available protocol when changing the server on the PageShare page --- CMakeLists.txt | 4 +- client/ui/qml/Controls2/DropDownType.qml | 4 + client/ui/qml/Controls2/HeaderType.qml | 5 + .../ui/qml/Controls2/LabelWithButtonType.qml | 5 + .../Controls2/ListViewWithRadioButtonType.qml | 26 ++- .../ui/qml/Controls2/VerticalRadioButton.qml | 4 + client/ui/qml/Pages2/PageHome.qml | 15 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 186 ++++++++---------- 9 files changed, 136 insertions(+), 115 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49b8fa165..f7ada65ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.5.1 +project(${PROJECT} VERSION 4.0.6.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-09-11") +set(RELEASE_DATE "2023-09-17") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index c21fa48f0..2feb5e17d 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -10,6 +10,8 @@ Item { property string text property string textColor: "#d7d8db" property string textDisabledColor: "#878B91" + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight property string descriptionText property string descriptionTextColor: "#878B91" @@ -102,6 +104,8 @@ Item { color: root.enabled ? root.textColor : root.textDisabledColor text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 19958205f..b4af37844 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -10,6 +10,9 @@ Item { property var actionButtonFunction property string headerText + property int headerTextMaximumLineCount: 2 + property int headerTextElide: Qt.ElideRight + property string descriptionText implicitWidth: content.implicitWidth @@ -26,6 +29,8 @@ Item { Layout.fillWidth: true text: root.headerText + maximumLineCount: root.headerTextMaximumLineCount + elide: root.headerTextElide } ImageButtonType { diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 82aef55a9..7a1489c03 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -8,6 +8,9 @@ Item { id: root property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + property string descriptionText property var clickedFunction @@ -68,6 +71,8 @@ Item { ListItemTitleType { text: root.text color: root.descriptionOnTop ? root.descriptionColor : root.textColor + maximumLineCount: root.textMaximumLineCount + elide: root.textElide opacity: root.textOpacity diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index f7f99decc..4138c0872 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -5,12 +5,15 @@ import QtQuick.Layouts import "TextTypes" ListView { - id: menuContent + id: root property var rootWidth property var selectedText + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + property string imageSource: "qrc:/images/controls/check.svg" property var clickedFunction @@ -18,7 +21,7 @@ ListView { currentIndex: 0 width: rootWidth - height: menuContent.contentItem.height + height: root.contentItem.height clip: true interactive: false @@ -27,6 +30,12 @@ ListView { id: buttonGroup } + function triggerCurrentItem() { + var item = root.itemAtIndex(currentIndex) + var radioButton = item.children[0].children[0] + radioButton.clicked() + } + delegate: Item { implicitWidth: rootWidth implicitHeight: content.implicitHeight @@ -74,6 +83,9 @@ ListView { Layout.bottomMargin: 20 text: name + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + } Image { @@ -88,11 +100,11 @@ ListView { } ButtonGroup.group: buttonGroup - checked: menuContent.currentIndex === index + checked: root.currentIndex === index onClicked: { - menuContent.currentIndex = index - menuContent.selectedText = name + root.currentIndex = index + root.selectedText = name if (clickedFunction && typeof clickedFunction === "function") { clickedFunction() } @@ -101,8 +113,8 @@ ListView { } Component.onCompleted: { - if (menuContent.currentIndex === index) { - menuContent.selectedText = name + if (root.currentIndex === index) { + root.selectedText = name } } } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 8229c959f..dd9a55901 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -8,6 +8,8 @@ import "TextTypes" RadioButton { id: root + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight property string descriptionText property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) @@ -104,6 +106,8 @@ RadioButton { ListItemTitleType { text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide color: { if (root.checked) { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index c5ba89d06..d87965245 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -75,12 +75,20 @@ PageType { RowLayout { Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 0 Header1TextType { + Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter } Image { @@ -148,10 +156,15 @@ PageType { anchors.left: parent.left Header1TextType { + Layout.fillWidth: true Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + maximumLineCount: 2 + elide: Qt.ElideRight } LabelTextType { diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 62a7a67ba..e2e7868cc 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -101,7 +101,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server name") textFieldText: name - textField.maximumLength: 20 + textField.maximumLength: 30 } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 135c7fbc6..67083949d 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -173,19 +173,22 @@ PageType { DropDownType { id: serverSelector + signal severSelectorIndexChanged + property int currentIndex: 0 + Layout.fillWidth: true Layout.topMargin: 16 drawerHeight: 0.4375 - descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") + descriptionText: qsTr("Servers") headerText: qsTr("Server") - listView: ListViewWithLabelsType { - rootWidth: root.width - dividerVisible: true + listView: ListViewWithRadioButtonType { + id: serverSelectorListView - imageSource: "qrc:/images/controls/chevron-right.svg" + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" model: SortFilterProxyModel { id: proxyServersModel @@ -203,14 +206,16 @@ PageType { clickedFunction: function() { handler() - if (accessTypeSelector.currentIndex === 0) { - protocolSelector.visible = true - root.shareButtonEnabled = false - } else { + if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { + serverSelector.currentIndex = serverSelectorListView.currentIndex + serverSelector.severSelectorIndexChanged() + } + + if (accessTypeSelector.currentIndex !== 0) { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - serverSelector.menuVisible = false } + serverSelector.menuVisible = false } Component.onCompleted: { @@ -224,117 +229,90 @@ PageType { ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) } } + } - DrawerType { - id: protocolSelector + DropDownType { + id: protocolSelector - width: parent.width - height: parent.height * 0.5 + Layout.fillWidth: true + Layout.topMargin: 16 - ColumnLayout { - id: protocolSelectorHeader + drawerHeight: 0.5 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + descriptionText: qsTr("Protocols") + headerText: qsTr("Protocol") - BackButtonType { - backButtonImage: "qrc:/images/controls/arrow-left.svg" - backButtonFunction: function() { - protocolSelector.visible = false + listView: ListViewWithRadioButtonType { + id: protocolSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isInstalled" + value: true + }, + ValueFilter { + roleName: "isShareable" + value: true } + ] + } + + currentIndex: 0 + + clickedFunction: function() { + handler() + + protocolSelector.menuVisible = false + } + + Component.onCompleted: { + if (accessTypeSelector.currentIndex === 0) { + handler() } } - FlickableType { - anchors.top: protocolSelectorHeader.bottom - anchors.topMargin: 16 - contentHeight: protocolSelectorContent.implicitHeight + Connections { + target: serverSelector - Column { - id: protocolSelectorContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + function onSeverSelectorIndexChanged() { + protocolSelectorListView.currentIndex = 0 + protocolSelectorListView.triggerCurrentItem() + } + } - spacing: 16 + function handler() { + if (!proxyContainersModel.count) { + root.shareButtonEnabled = false + return + } else { + root.shareButtonEnabled = true + } - Header2TextType { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 + protocolSelector.text = selectedText + root.connectionServerSelectorText = serverSelector.text - text: qsTr("Protocols and services") - wrapMode: Text.WordWrap - } + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) - ListViewWithRadioButtonType { - rootWidth: root.width + fillConnectionTypeModel() + } - imageSource: "qrc:/images/controls/check.svg" + function fillConnectionTypeModel() { + root.connectionTypesModel = [amneziaConnectionFormat] - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "isInstalled" - value: true - }, - ValueFilter { - roleName: "isShareable" - value: true - } - ] - } + var index = proxyContainersModel.mapToSource(currentIndex) - currentIndex: 0 - - clickedFunction: function() { - handler() - - protocolSelector.visible = false - serverSelector.menuVisible = false - } - - Component.onCompleted: { - if (accessTypeSelector.currentIndex === 0) { - handler() - } - } - - function handler() { - if (!proxyContainersModel.count) { - root.shareButtonEnabled = false - return - } else { - root.shareButtonEnabled = true - } - - serverSelector.text += ", " + selectedText - root.connectionServerSelectorText = serverSelector.text - - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) - - fillConnectionTypeModel() - } - - function fillConnectionTypeModel() { - root.connectionTypesModel = [amneziaConnectionFormat] - - var index = proxyContainersModel.mapToSource(currentIndex) - - if (index === ContainerProps.containerFromString("amnezia-openvpn")) { - root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { - root.connectionTypesModel.push(wireGuardConnectionFormat) - } - } - } + if (index === ContainerProps.containerFromString("amnezia-openvpn")) { + root.connectionTypesModel.push(openVpnConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + root.connectionTypesModel.push(wireGuardConnectionFormat) } } } From c0cb5b96bfc91e5023e2bd06defb66af9deb4c1b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 17:03:39 +0500 Subject: [PATCH 123/131] added reconnection to vpn after changing any protocol settings --- client/amnezia_application.cpp | 4 ++-- client/core/servercontroller.cpp | 8 +++++--- client/core/servercontroller.h | 11 ++++------- client/ui/controllers/connectionController.cpp | 6 +++--- client/ui/controllers/connectionController.h | 4 ++-- client/ui/controllers/installController.cpp | 16 ++++------------ client/ui/controllers/installController.h | 2 +- .../ui/qml/Pages2/PageProtocolCloakSettings.qml | 16 ---------------- .../qml/Pages2/PageProtocolOpenVpnSettings.qml | 16 ---------------- .../Pages2/PageProtocolShadowSocksSettings.qml | 17 ----------------- client/ui/qml/Pages2/PageStart.qml | 12 ++++++++++++ 11 files changed, 33 insertions(+), 79 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index cdcee5f45..37f4d68b3 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -336,8 +336,8 @@ void AmneziaApplication::initControllers() &PageController::showPassphraseRequestDrawer); connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), &InstallController::setEncryptedPassphrase); - connect(m_installController.get(), &InstallController::currentContainerChanged, m_connectionController.get(), - &ConnectionController::onCurrentContainerChanged); + connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), + &ConnectionController::onCurrentContainerUpdated); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index a82785ebc..b0f8146fb 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -290,11 +290,13 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, return startupContainerWorker(credentials, container, config); } -ErrorCode ServerController::updateContainer(const bool reinstallRequired, - const ServerCredentials &credentials, - DockerContainer container, +ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig) { + bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); + qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" + << reinstallRequired; + if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); } else { diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index d3f242a34..3191386ca 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -26,10 +26,8 @@ public: ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); - ErrorCode updateContainer(const bool reinstallRequired, const ServerCredentials &credentials, - DockerContainer container, - const QJsonObject &oldConfig, - QJsonObject &newConfig); + ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &oldConfig, QJsonObject &newConfig); ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); @@ -63,8 +61,6 @@ public: ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, - const QJsonObject &newConfig); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, @@ -77,7 +73,8 @@ private: ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 180d96e70..77ca0f5f9 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -118,10 +118,10 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) emit connectionStateChanged(); } -void ConnectionController::onCurrentContainerChanged() +void ConnectionController::onCurrentContainerUpdated() { - if(m_isConnected || m_isConnectionInProgress) { - emit reconnectWithChangedContainer(tr("Settings updated successfully, Reconnnection...")); + if (m_isConnected || m_isConnectionInProgress) { + emit reconnectWithUpdatedContainer(tr("Settings updated successfully, Reconnnection...")); openConnection(); } } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 5ee7a4ca5..7bfe0faca 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -32,7 +32,7 @@ public slots: QString getLastConnectionError(); void onConnectionStateChanged(Vpn::ConnectionState state); - void onCurrentContainerChanged(); + void onCurrentContainerUpdated(); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, @@ -41,7 +41,7 @@ signals: void connectionStateChanged(); void connectionErrorOccurred(const QString &errorMessage); - void reconnectWithChangedContainer(const QString &message); + void reconnectWithUpdatedContainer(const QString &message); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2d82a4c2c..63510d1a0 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -254,22 +254,14 @@ void InstallController::updateContainer(QJsonObject config) ServerController serverController(m_settings); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - bool reinstallRequired = serverController.isReinstallContainerRequired(container, oldContainerConfig, config); - auto errorCode = serverController.updateContainer(reinstallRequired, serverCredentials, container, oldContainerConfig, config); + auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); m_protocolModel->updateModel(config); - bool isCurrentContainerChanged = false; - if (reinstallRequired && - (serverIndex == m_serversModel->getDefaultServerIndex()) && - (container == m_containersModel->getDefaultContainer()) ) { - isCurrentContainerChanged = true; - } - - - if (isCurrentContainerChanged) { - emit currentContainerChanged(); + if ((serverIndex == m_serversModel->getDefaultServerIndex()) + && (container == m_containersModel->getDefaultContainer())) { + emit currentContainerUpdated(); } else { emit updateContainerFinished(tr("Settings updated successfully")); } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index fc924d725..a5fd28753 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -66,7 +66,7 @@ signals: void serverIsBusy(const bool isBusy); - void currentContainerChanged(); + void currentContainerUpdated(); private: void installServer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 1dd9af71b..78e666a7e 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,22 +13,6 @@ import "../Components" PageType { id: root - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 741655eac..f5313e160 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -15,22 +15,6 @@ import "../Components" PageType { id: root - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index f168efc16..2453281fe 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,23 +13,6 @@ import "../Components" PageType { id: root - - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 31474c127..f7020a2d4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -82,6 +82,18 @@ PageType { PageController.closePage() } } + + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) + } + } + + Connections { + target: ConnectionController + + function onReconnectWithUpdatedContainer(message) { + PageController.showNotificationMessage(message) + } } StackViewType { From 8965b1fbba6debeb53195c1791e83490d5c353bb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 23:21:00 +0500 Subject: [PATCH 124/131] fixed the size of the drawer close button --- client/ui/qml/Controls2/DrawerType.qml | 7 +++---- client/ui/qml/Controls2/TopCloseButtonType.qml | 5 ----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 2b70ef3ce..34b141b49 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -48,20 +48,19 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } - } - onOpened: { if (needCloseButton) { PageController.drawerOpen() } } - - onClosed: { + onAboutToHide: { if (needCloseButton) { PageController.drawerClose() } + } + onClosed: { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index cd1406c4a..ed89b5a6b 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -7,8 +7,6 @@ Popup { modal: false closePolicy: Popup.NoAutoClose - width: 40 - height: 40 padding: 4 visible: false @@ -25,9 +23,6 @@ Popup { image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" - implicitWidth: 32 - implicitHeight: 32 - onClicked: { PageController.goToDrawerRootPage() } From ad236baa8686b12f7330df52a8cea4ecb9ef620b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 23:24:21 +0500 Subject: [PATCH 125/131] fixed a typo in the variable name --- client/ui/controllers/pageController.cpp | 12 ++++++------ client/ui/controllers/pageController.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 6501aea8c..7b8f74ab4 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -118,14 +118,14 @@ void PageController::showOnStartup() void PageController::updateDrawerRootPage(PageLoader::PageEnum page) { - m_drwaerLayer = 0; + m_drawerLayer = 0; m_currentRootPage = page; } void PageController::goToDrawerRootPage() { - m_drwaerLayer = 0; + m_drawerLayer = 0; emit showTopCloseButton(false); emit forceCloseDrawer(); @@ -133,15 +133,15 @@ void PageController::goToDrawerRootPage() void PageController::drawerOpen() { - m_drwaerLayer = m_drwaerLayer + 1; + m_drawerLayer = m_drawerLayer + 1; emit showTopCloseButton(true); } void PageController::drawerClose() { - m_drwaerLayer = m_drwaerLayer -1; - if (m_drwaerLayer <= 0) { + m_drawerLayer = m_drawerLayer -1; + if (m_drawerLayer <= 0) { emit showTopCloseButton(false); - m_drwaerLayer = 0; + m_drawerLayer = 0; } } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f4cac4276..e047e6d69 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -118,7 +118,7 @@ private: std::shared_ptr m_settings; PageLoader::PageEnum m_currentRootPage; - int m_drwaerLayer; + int m_drawerLayer; }; #endif // PAGECONTROLLER_H From fd09321f8e71ff68217e5108c0003476146ffb00 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 00:16:58 +0500 Subject: [PATCH 126/131] removed the 'mount sftp folder' button for mobile platforms --- client/ui/qml/Pages2/PageServiceSftpSettings.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 9287bd20f..61ba663d6 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -165,6 +165,8 @@ PageType { } BasicButtonType { + visible: !GC.isMobile() + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 From 9e7cf3ccd93eff6dadadb3efa46c3a5a7e5b967a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 16:39:26 +0500 Subject: [PATCH 127/131] added PageServiceDnsSettings --- client/resources.qrc | 1 + client/ui/controllers/pageController.h | 1 + .../Components/SettingsContainersListView.qml | 23 +++-- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 95 +++++++++++++++++++ 4 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 client/ui/qml/Pages2/PageServiceDnsSettings.qml diff --git a/client/resources.qrc b/client/resources.qrc index d0ff176f7..5b919cf40 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -213,5 +213,6 @@ images/controls/trash.svg images/controls/more-vertical.svg ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Pages2/PageServiceDnsSettings.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 07e772837..508a9d583 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -32,6 +32,7 @@ namespace PageLoader PageServiceSftpSettings, PageServiceTorWebsiteSettings, + PageServiceDnsSettings, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index a0c74a04c..edd96bd7a 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -43,10 +43,12 @@ ListView { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolRaw) - return + if (serviceType !== ProtocolEnum.Other) { + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + return + } } switch (containerIndex) { @@ -78,12 +80,13 @@ ListView { PageController.goToPage(PageEnum.PageServiceTorWebsiteSettings) break } - - default: { - if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageSettingsServerProtocol) - } + case ContainerEnum.Dns: { + PageController.goToPage(PageEnum.PageServiceDnsSettings) + break + } + default: { // go to the settings page of the container with multiple protocols + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) } } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml new file mode 100644 index 000000000..016a7c881 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -0,0 +1,95 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: "Amnezia DNS" + descriptionText: qsTr("A DNS service is installed on your server, and it is only accessible via VPN.\n") + + qsTr("The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.") + } + + LabelWithButtonType { + id: removeButton + + Layout.topMargin: 24 + width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } +} From 152d7bc3b3d0ed3fd63d201a57e3161543f3c3a5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 17:52:41 +0500 Subject: [PATCH 128/131] added restore default settings for dns settings page --- client/ui/qml/Pages2/PageSettingsDns.qml | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 2851e9b1a..0bc13eaef 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -72,6 +73,38 @@ PageType { } } + BasicButtonType { + Layout.fillWidth: true + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Restore default") + + onClicked: function() { + questionDrawer.headerText = qsTr("Restore default DNS settings?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.primaryDns = "1.1.1.1" + primaryDns.textFieldText = SettingsController.primaryDns + SettingsController.secondaryDns = "1.0.0.1" + secondaryDns.textFieldText = SettingsController.secondaryDns + PageController.showNotificationMessage(qsTr("Settings have been reset")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + BasicButtonType { Layout.fillWidth: true @@ -84,8 +117,12 @@ PageType { if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { SettingsController.secondaryDns = secondaryDns.textFieldText } + PageController.showNotificationMessage(qsTr("Settings saved")) } } } + QuestionDrawer { + id: questionDrawer + } } } From d4d6fbab882124770b8065865c995df1ed06801c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 21:06:10 +0500 Subject: [PATCH 129/131] changed the protocols for easySetup setup --- client/containers/containers_defs.cpp | 22 ++++++++++++++------ client/containers/containers_defs.h | 1 + client/ui/models/containers_model.cpp | 7 ++----- client/ui/models/containers_model.h | 1 + client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 766d89dac..20fc59f48 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -223,9 +223,9 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return true; + case DockerContainer::WireGuard: return true; case DockerContainer::Cloak: return true; - case DockerContainer::ShadowSocks: return true; + case DockerContainer::OpenVpn: return true; default: return false; } } @@ -233,9 +233,9 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return tr("Low"); + case DockerContainer::WireGuard: return tr("Low"); case DockerContainer::Cloak: return tr("High"); - case DockerContainer::ShadowSocks: return tr("Medium"); + case DockerContainer::OpenVpn: return tr("Medium"); default: return ""; } } @@ -243,13 +243,23 @@ QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); + case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy"); case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::ShadowSocks: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); default: return ""; } } +int ContainerProps::easySetupOrder(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return 1; + case DockerContainer::Cloak: return 3; + case DockerContainer::OpenVpn: return 2; + default: return 0; + } +} + bool ContainerProps::isShareable(DockerContainer container) { switch (container) { diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 247824071..9ca51a961 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -64,6 +64,7 @@ namespace amnezia static bool isEasySetupContainer(amnezia::DockerContainer container); static QString easySetupHeader(amnezia::DockerContainer container); static QString easySetupDescription(amnezia::DockerContainer container); + static int easySetupOrder(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container); }; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 67847727d..afff44b6d 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -75,6 +75,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); + case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); case IsInstalledRole: return m_containers.contains(container); case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; @@ -213,11 +214,6 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } -// bool ContainersModel::isOnlyServicesInstalled(const int serverIndex) -//{ - -//} - QHash ContainersModel::roleNames() const { QHash roles; @@ -231,6 +227,7 @@ QHash ContainersModel::roleNames() const roles[IsEasySetupContainerRole] = "isEasySetupContainer"; roles[EasySetupHeaderRole] = "easySetupHeader"; roles[EasySetupDescriptionRole] = "easySetupDescription"; + roles[EasySetupOrderRole] = "easySetupOrder"; roles[IsInstalledRole] = "isInstalled"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 547eea831..741a0620b 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -26,6 +26,7 @@ public: IsEasySetupContainerRole, EasySetupHeaderRole, EasySetupDescriptionRole, + EasySetupOrderRole, IsInstalledRole, IsCurrentlyProcessedRole, diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 6fdc4b3e6..b228a7a3e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -27,7 +27,7 @@ PageType { } ] sorters: RoleSorter { - roleName: "dockerContainer" + roleName: "easySetupOrder" sortOrder: Qt.DescendingOrder } } From 893ec2d61c40da40e821f1dd11a32851c19df8ed Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 20 Sep 2023 00:18:10 +0800 Subject: [PATCH 130/131] researching: tried to close drawer easily --- client/ui/qml/Controls2/DrawerType.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 34b141b49..60db1e48f 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls Drawer { + id: drawer property bool needCloseButton: true Connections { @@ -66,4 +67,16 @@ Drawer { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } + + MouseArea { + id: mouseArea + anchors.fill: parent + + onCanceled: { + Drag.cancel() + drawer.close() + } + + preventStealing: false + } } From dd039a612fef510cc8c598ab4f82098f24f86bad Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 20 Sep 2023 14:18:21 +0800 Subject: [PATCH 131/131] used position-changed to closes drawer --- client/ui/qml/Controls2/DrawerType.qml | 28 +++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 60db1e48f..c22d00c24 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -4,6 +4,7 @@ import QtQuick.Controls Drawer { id: drawer property bool needCloseButton: true + property bool isOpened: false Connections { target: PageController @@ -61,22 +62,39 @@ Drawer { } } + onOpened: { + isOpened = true + } + onClosed: { + isOpened = false + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } + + onPositionChanged: { + if (isOpened && (position <= 0.99 && position >= 0.95)) { + mouseArea.canceled() + drawer.close() + mouseArea.exited() + dropArea.exited() + } + } + + DropArea { + id: dropArea + } + MouseArea { id: mouseArea anchors.fill: parent - onCanceled: { - Drag.cancel() - drawer.close() + onPressed: { + isOpened = true } - - preventStealing: false } }