diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp
index 4e25097d..3fee3724 100644
--- a/client/amnezia_application.cpp
+++ b/client/amnezia_application.cpp
@@ -69,7 +69,7 @@ void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
- const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
+ const QUrl url(QStringLiteral("qrc:/ui/qml/DefaultVpn/main.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
@@ -154,7 +154,7 @@ void AmneziaApplication::init()
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
- m_engine->addImportPath("qrc:/ui/qml/Modules/");
+ m_engine->addImportPath("qrc:/ui/qml/DefaultVpn");
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
@@ -228,7 +228,7 @@ void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
- QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/VelaSans-GX.ttf");
}
void AmneziaApplication::loadTranslator()
diff --git a/client/fonts/VelaSans-GX.ttf b/client/fonts/VelaSans-GX.ttf
new file mode 100644
index 00000000..0a75bb2e
Binary files /dev/null and b/client/fonts/VelaSans-GX.ttf differ
diff --git a/client/images/controls/connect-button.svg b/client/images/controls/connect-button.svg
new file mode 100644
index 00000000..74727c36
--- /dev/null
+++ b/client/images/controls/connect-button.svg
@@ -0,0 +1,4 @@
+
diff --git a/client/resources.qrc b/client/resources.qrc
index a10a784d..e8a3522e 100644
--- a/client/resources.qrc
+++ b/client/resources.qrc
@@ -220,6 +220,28 @@
ui/qml/Pages2/PageSettingsApiLanguageList.qml
images/controls/archive-restore.svg
images/controls/help-circle.svg
+ ui/qml/DefaultVpn/Controls/DropDownType.qml
+ ui/qml/DefaultVpn/main.qml
+ ui/qml/DefaultVpn/Pages/PageHome.qml
+ ui/qml/DefaultVpn/Controls/TextTypes/MediumTextType.qml
+ ui/qml/DefaultVpn/Config/DeviceInfo.qml
+ ui/qml/DefaultVpn/Config/qmldir
+ ui/qml/DefaultVpn/Controls/TextTypes/XSmallTextType.qml
+ ui/qml/DefaultVpn/Controls/ButtonType.qml
+ ui/qml/DefaultVpn/Pages/PageSettingsServersList.qml
+ ui/qml/DefaultVpn/Controls/TextTypes/Header1TextType.qml
+ ui/qml/DefaultVpn/Controls/TextTypes/Header3TextType.qml
+ ui/qml/DefaultVpn/Config/Style.qml
+ ui/qml/DefaultVpn/Components/WhiteButtonWithBorder.qml
+ ui/qml/DefaultVpn/Components/WhiteButtonNoBorder.qml
+ ui/qml/DefaultVpn/Pages/PageSetupWizardConfigSource.qml
+ ui/qml/DefaultVpn/Components/BlueButtonNoBorder.qml
+ ui/qml/DefaultVpn/Controls/InputType.qml
+ ui/qml/DefaultVpn/Controls/PopupType.qml
+ ui/qml/DefaultVpn/Pages/PageSettingsServerInfo.qml
+ ui/qml/DefaultVpn/Controls/BusyIndicatorType.qml
+ images/controls/connect-button.svg
+ fonts/VelaSans-GX.ttf
images/flagKit/ZW.svg
diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp
index bbcc55a1..34450fea 100644
--- a/client/ui/controllers/pageController.cpp
+++ b/client/ui/controllers/pageController.cpp
@@ -51,7 +51,7 @@ QString PageController::getPagePath(PageLoader::PageEnum page)
{
QMetaEnum metaEnum = QMetaEnum::fromType();
QString pageName = metaEnum.valueToKey(static_cast(page));
- return "qrc:/ui/qml/Pages2/" + pageName + ".qml";
+ return "qrc:/ui/qml/DefaultVpn/Pages/" + pageName + ".qml";
}
void PageController::closeWindow()
diff --git a/client/ui/qml/DefaultVpn/Components/BlueButtonNoBorder.qml b/client/ui/qml/DefaultVpn/Components/BlueButtonNoBorder.qml
new file mode 100644
index 00000000..6e8b4234
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Components/BlueButtonNoBorder.qml
@@ -0,0 +1,36 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "../Controls/TextTypes"
+import "../Controls"
+
+ButtonType {
+ defaultBackgroundColor: Style.color.accent1
+ defaultBorderColor: Style.color.gray3
+ defaultTextColor: Style.color.white
+ defaultImageColor: Style.color.white
+
+ hoveredBackgroundColor: Style.color.accent2
+ hoveredBorderColor: Style.color.gray3
+ hoveredTextColor: Style.color.white
+ hoveredImageColor: Style.color.white
+
+ pressedBackgroundColor: Style.color.accent3
+ pressedBorderColor: Style.color.gray3
+ pressedTextColor: Style.color.white
+ pressedImageColor: Style.color.white
+
+ disabledBackgroundColor: Style.color.gray6
+ disabledBorderColor: Style.color.gray3
+ disabledTextColor: Style.color.gray2
+ disabledImageColor: Style.color.gray2
+
+ defaultBorderWidth: 0
+ disabledBorderWidth: 0
+}
diff --git a/client/ui/qml/DefaultVpn/Components/WhiteButtonNoBorder.qml b/client/ui/qml/DefaultVpn/Components/WhiteButtonNoBorder.qml
new file mode 100644
index 00000000..95099321
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Components/WhiteButtonNoBorder.qml
@@ -0,0 +1,36 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "../Controls/TextTypes"
+import "../Controls"
+
+ButtonType {
+ defaultBackgroundColor: Style.color.white
+ defaultBorderColor: Style.color.gray3
+ defaultTextColor: Style.color.accent1
+ defaultImageColor: Style.color.accent1
+
+ hoveredBackgroundColor: Style.color.gray1
+ hoveredBorderColor: Style.color.gray3
+ hoveredTextColor: Style.color.accent2
+ hoveredImageColor: Style.color.accent2
+
+ pressedBackgroundColor: Style.color.gray2
+ pressedBorderColor: Style.color.gray3
+ pressedTextColor: Style.color.accent3
+ pressedImageColor: Style.color.accent3
+
+ disabledBackgroundColor: Style.color.white
+ disabledBorderColor: Style.color.gray3
+ disabledTextColor: Style.color.gray8
+ disabledImageColor: Style.color.gray8
+
+ defaultBorderWidth: 0
+ disabledBorderWidth: 0
+}
diff --git a/client/ui/qml/DefaultVpn/Components/WhiteButtonWithBorder.qml b/client/ui/qml/DefaultVpn/Components/WhiteButtonWithBorder.qml
new file mode 100644
index 00000000..7c05c271
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Components/WhiteButtonWithBorder.qml
@@ -0,0 +1,37 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "../Controls/TextTypes"
+import "../Controls"
+
+ButtonType {
+ defaultBackgroundColor: Style.color.white
+ defaultBorderColor: Style.color.gray3
+ defaultTextColor: Style.color.black
+ defaultImageColor: Style.color.black
+
+ hoveredBackgroundColor: Style.color.white
+ hoveredBorderColor: Style.color.gray6
+ hoveredTextColor: Style.color.black
+ hoveredImageColor: Style.color.black
+
+ pressedBackgroundColor: Style.color.gray1
+ pressedBorderColor: Style.color.gray6
+ pressedTextColor: Style.color.black
+ pressedImageColor: Style.color.black
+
+ disabledBackgroundColor: Style.color.gray3
+ disabledBorderColor: Style.color.gray2
+ disabledTextColor: Style.color.gray9
+ disabledImageColor: Style.color.gray9
+
+ defaultBorderWidth: 1
+ disabledBorderWidth: 1
+ hoveredBorderWidth: 1
+}
diff --git a/client/ui/qml/DefaultVpn/Config/DeviceInfo.qml b/client/ui/qml/DefaultVpn/Config/DeviceInfo.qml
new file mode 100644
index 00000000..b46bdf58
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Config/DeviceInfo.qml
@@ -0,0 +1,37 @@
+pragma Singleton
+
+import QtQuick
+
+Item {
+ readonly property int screenWidth: 380
+ readonly property int screenHeight: 680
+
+ function isMobile() {
+ 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") {
+ return true
+ }
+ return false
+ }
+
+ TextEdit {
+ id: clipboard
+ visible: false
+ }
+
+ function copyToClipBoard(text) {
+ clipboard.text = text
+ clipboard.selectAll()
+ clipboard.copy()
+ clipboard.select(0, 0)
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Config/Style.qml b/client/ui/qml/DefaultVpn/Config/Style.qml
new file mode 100644
index 00000000..b4a32679
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Config/Style.qml
@@ -0,0 +1,30 @@
+pragma Singleton
+
+import QtQuick
+
+QtObject {
+ property QtObject color: QtObject {
+ readonly property color transparent: 'transparent'
+ readonly property color gray1: '#F2F2F7'
+ readonly property color gray2: '#E5E5EA'
+ readonly property color gray3: '#D1D1D6'
+ readonly property color gray4: '#C7C7CC'
+ readonly property color gray5: '#AEAEB2'
+ readonly property color gray6: '#8E8E93'
+ readonly property color gray7: '#7C7C83'
+ readonly property color gray8: '#707075'
+ readonly property color gray9: '#57575B'
+ readonly property color accent1: '#007AFF'
+ readonly property color accent2: '#0B6EDA'
+ readonly property color accent3: '#1256A1'
+ readonly property color error: '#FF3B30'
+ readonly property color warning: '#FF9500'
+ readonly property color success: '#34C759'
+ readonly property color black: '#000000'
+ readonly property color white: '#FFFFFF'
+
+ readonly property color transparentBlack: Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ }
+
+ readonly property string font: "Vela Sans GX"
+}
diff --git a/client/ui/qml/DefaultVpn/Config/qmldir b/client/ui/qml/DefaultVpn/Config/qmldir
new file mode 100644
index 00000000..beaa3d4e
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Config/qmldir
@@ -0,0 +1,4 @@
+module Config
+
+singleton DeviceInfo 1.0 DeviceInfo.qml
+singleton Style 1.0 Style.qml
diff --git a/client/ui/qml/DefaultVpn/Controls/BusyIndicatorType.qml b/client/ui/qml/DefaultVpn/Controls/BusyIndicatorType.qml
new file mode 100644
index 00000000..ca4e9516
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/BusyIndicatorType.qml
@@ -0,0 +1,70 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Shapes
+
+import Config 1.0
+
+Popup {
+ id: root
+ anchors.centerIn: parent
+
+ modal: true
+ closePolicy: Popup.NoAutoClose
+
+ visible: false
+
+ Overlay.modal: Rectangle {
+ color: Style.color.transparentBlack
+ }
+
+ background: Rectangle {
+ color: Style.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: Style.color.transparent
+ strokeColor: Style.color.gray3
+ 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/DefaultVpn/Controls/ButtonType.qml b/client/ui/qml/DefaultVpn/Controls/ButtonType.qml
new file mode 100644
index 00000000..15f46248
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/ButtonType.qml
@@ -0,0 +1,154 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "TextTypes"
+
+Button {
+ id: root
+
+ property string defaultBackgroundColor: Style.color.white
+ property string defaultBorderColor: Style.color.gray3
+ property string defaultTextColor: Style.color.accent1
+ property string defaultImageColor: Style.color.accent1
+
+ property string hoveredBackgroundColor: Style.color.gray1
+ property string hoveredBorderColor: Style.color.gray3
+ property string hoveredTextColor: Style.color.accent2
+ property string hoveredImageColor: Style.color.accent2
+
+ property string pressedBackgroundColor: Style.color.gray2
+ property string pressedBorderColor: Style.color.gray3
+ property string pressedTextColor: Style.color.accent3
+ property string pressedImageColor: Style.color.accent3
+
+ property string disabledBackgroundColor: Style.color.white
+ property string disabledBorderColor: Style.color.gray3
+ property string disabledTextColor: Style.color.gray8
+ property string disabledImageColor: Style.color.gray8
+
+ property int defaultBorderWidth: 0
+ property int disabledBorderWidth: 0
+ property int hoveredBorderWidth: 0
+
+ property string imageSource: ""
+
+ readonly property bool isImageOnly: root.text !== ""
+
+ background: Rectangle {
+ id: background
+
+ anchors.fill: parent
+
+ radius: 6
+
+ color: root.enabled ? root.defaultBackgroundColor : root.disabledBackgroundColor
+ border.color: root.enabled ? root.defaultBorderColor : root.disabledBorderColor
+ border.width: root.enabled ? root.defaultBorderWidth : root.disabledBorderWidth
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ anchors.fill: background
+ cursorShape: Qt.PointingHandCursor
+
+ hoverEnabled: true
+ enabled: root.enabled
+
+ onEntered: {
+ background.color = root.hoveredBackgroundColor
+ background.border.color = root.hoveredBorderColor
+ background.border.width = root.hoveredBorderWidth
+ image.imageColor = root.hoveredImageColor
+ buttonText.color = root.hoveredTextColor
+ }
+
+ onExited: {
+ background.color = root.defaultBackgroundColor
+ background.border.color = root.defaultBorderColor
+ background.border.width = root.defaultBorderWidth
+ image.imageColor = root.defaultImageColor
+ buttonText.color = root.defaultTextColor
+ }
+
+ onPressedChanged: {
+ if (pressed) {
+ background.color = root.pressedBackgroundColor
+ background.border.color = root.pressedBorderColor
+ image.imageColor = root.pressedImageColor
+ buttonText.color = root.pressedTextColor
+ } else if (entered) {
+ background.color = root.hoveredBackgroundColor
+ background.border.color = root.hoveredBorderColor
+ image.imageColor = root.hoveredImageColor
+ buttonText.color = root.hoveredTextColor
+ } else {
+ background.color = root.defaultBackgroundColor
+ background.border.color = root.defaultBorderColor
+ image.imageColor = root.defaultImageColor
+ buttonText.color = root.defaultTextColor
+ }
+ }
+
+ onClicked: {
+ root.clicked()
+ }
+ }
+
+ contentItem: Item {
+ implicitWidth: content.implicitWidth
+ implicitHeight: content.implicitHeight
+
+ RowLayout {
+ id: content
+ anchors.fill: parent
+
+ MediumTextType {
+ id: buttonText
+
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ Layout.bottomMargin: 12
+ Layout.leftMargin: 12
+ Layout.rightMargin: 12
+ visible: root.isImageOnly
+
+ color: root.defaultTextColor
+ text: root.text
+
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ Image {
+ id: image
+
+ property color imageColor: root.enabled ? root.defaultImageColor : root.disabledImageColor
+
+ Layout.preferredHeight: 22
+ Layout.preferredWidth: 22
+ Layout.alignment: Qt.AlignCenter
+ Layout.topMargin: 12
+ Layout.bottomMargin: 12
+ Layout.leftMargin: 12
+ Layout.rightMargin: 12
+
+ source: root.imageSource
+ visible: root.imageSource === "" ? false : true
+
+ layer {
+ enabled: true
+ effect: ColorOverlay {
+ color: image.imageColor
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/DropDownType.qml b/client/ui/qml/DefaultVpn/Controls/DropDownType.qml
new file mode 100644
index 00000000..2f593921
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/DropDownType.qml
@@ -0,0 +1,99 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "TextTypes"
+
+Button {
+ id: root
+
+ property string defaultBackgroundColor: "#FFFFFF"
+ property string defaultBorderColor: "#D1D1D6"
+ property string defaultTextColor: "#000000"
+ property string defaultImageColor: "#000000"
+
+ property string hoveredBackgroundColor: "#FFFFFF"
+ property string hoveredBorderColor: "#D1D1D6"
+ property string hoveredTextColor: "#D1D1D6"
+ property string hoveredImageColor: "#D1D1D6"
+
+ property string pressedBackgroundColor: "#FFFFFF"
+ property string pressedBorderColor: "#D1D1D6"
+ property string pressedTextColor: "#D1D1D6"
+ property string pressedImageColor: "#D1D1D6"
+
+ property string disabledBackgroundColor: "#FFFFFF"
+ property string disabledBorderColor: "#D1D1D6"
+ property string disabledTextColor: "#D1D1D6"
+ property string disabledImageColor: "#D1D1D6"
+
+ property string imageSource: "qrc:/images/controls/chevron-down.svg"
+
+ hoverEnabled: true
+
+ background: Rectangle {
+ id: focusBorder
+
+ color: root.defaultBackgroundColor
+ border.color: root.defaultBorderColor
+ border.width: 1
+
+ anchors.fill: parent
+
+ radius: 6
+ }
+
+ MouseArea {
+ anchors.fill: focusBorder
+ enabled: false
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ contentItem: Item {
+ anchors.fill: focusBorder
+
+ implicitWidth: content.implicitWidth
+ implicitHeight: content.implicitHeight
+
+ RowLayout {
+ id: content
+ anchors.fill: parent
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ MediumTextType {
+ id: buttonText
+
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ Layout.bottomMargin: 12
+
+ color: root.defaultTextColor
+ text: root.text
+
+ horizontalAlignment: Qt.AlignLeft
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ Image {
+ Layout.preferredHeight: 22
+ Layout.preferredWidth: 22
+
+ source: root.imageSource
+ visible: root.imageSource === "" ? false : true
+
+ layer {
+ enabled: true
+ effect: ColorOverlay {
+ color: root.defaultImageColor
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/InputType.qml b/client/ui/qml/DefaultVpn/Controls/InputType.qml
new file mode 100644
index 00000000..9e447b06
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/InputType.qml
@@ -0,0 +1,58 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "TextTypes"
+
+ScrollView {
+ id: root
+
+ property string defaultBackgroundColor: Style.color.white
+ property string defaultBorderColor: Style.color.gray3
+ property string defaultTextColor: Style.color.gray6
+
+ property string hoveredBackgroundColor: Style.color.white
+ property string hoveredBorderColor: Style.color.gray6
+ property string hoveredTextColor: Style.color.black
+
+ property string disabledBackgroundColor: Style.color.gray2
+ property string disabledBorderColor: Style.color.gray3
+ property string disabledTextColor: Style.color.gray9
+
+ property string placeholderText
+
+ TextArea {
+ color: root.enabled ? root.defaultTextColor : (root.hovered || root.pressed) ? root.hoveredTextColor : root.disabledTextColor
+ background: Rectangle {
+ anchors.fill: parent
+
+ color: root.enabled ? root.defaultBackgroundColor : (root.hovered || root.pressed) ? root.hoveredBackgroundColor : root.disabledBackgroundColor
+ border.color: root.enabled ? root.defaultBorderColor : (root.hovered || root.pressed) ? root.hoveredBorderColor : root.disabledBorderColor
+ border.width: 1
+ radius: 6
+ }
+
+ topPadding: 12
+ bottomPadding: 12
+ leftPadding: 16
+ rightPadding: 16
+
+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
+
+ selectionColor: Style.color.accent1
+ selectedTextColor: Style.color.white
+
+ font.pixelSize: 17
+ font.weight: 400
+ font.family: Style.font
+
+ wrapMode: TextEdit.Wrap
+
+ placeholderText: root.placeholderText
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/PopupType.qml b/client/ui/qml/DefaultVpn/Controls/PopupType.qml
new file mode 100644
index 00000000..3c461b23
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/PopupType.qml
@@ -0,0 +1,96 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+
+import Config 1.0
+
+import "TextTypes"
+import "../Components"
+
+Popup {
+ id: root
+
+ property string text
+ property bool closeButtonVisible: true
+
+ leftMargin: 25
+ rightMargin: 25
+ bottomMargin: 70
+
+ width: parent.width - leftMargin - rightMargin
+
+ anchors.centerIn: parent
+ 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: Style.color.white
+ radius: 8
+
+ layer.enabled: true
+ layer.effect: DropShadow {
+ color: Style.color.gray3
+ horizontalOffset: 0
+ verticalOffset: 1
+ radius: 10
+ samples: 25
+ }
+ }
+
+ contentItem: Item {
+ implicitWidth: content.implicitWidth
+ implicitHeight: content.implicitHeight
+
+ anchors.fill: parent
+
+ RowLayout {
+ id: content
+
+ anchors.fill: parent
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ XSmallTextType {
+ horizontalAlignment: Text.AlignLeft
+ Layout.fillWidth: true
+
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+
+ text: root.text
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.NoButton
+ cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
+ }
+ }
+
+ Item {
+ id: focusItem
+ KeyNavigation.tab: closeButton
+ }
+
+ WhiteButtonNoBorder {
+ id: closeButton
+ visible: closeButtonVisible
+
+ imageSource: "qrc:/images/controls/x-circle.svg"
+
+ onClicked: function() {
+ root.close()
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/TextTypes/Header1TextType.qml b/client/ui/qml/DefaultVpn/Controls/TextTypes/Header1TextType.qml
new file mode 100644
index 00000000..ecb24fb7
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/TextTypes/Header1TextType.qml
@@ -0,0 +1,15 @@
+import QtQuick
+
+import Config 1.0
+
+Text {
+ lineHeight: 34
+ lineHeightMode: Text.FixedHeight
+
+ color: Style.color.black
+ font.pixelSize: 28
+ font.weight: 700
+ font.family: Style.font
+
+ wrapMode: Text.WordWrap
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/TextTypes/Header3TextType.qml b/client/ui/qml/DefaultVpn/Controls/TextTypes/Header3TextType.qml
new file mode 100644
index 00000000..48405079
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/TextTypes/Header3TextType.qml
@@ -0,0 +1,15 @@
+import QtQuick
+
+import Config 1.0
+
+Text {
+ lineHeight: 24
+ lineHeightMode: Text.FixedHeight
+
+ color: Style.color.black
+ font.pixelSize: 20
+ font.weight: 700
+ font.family: Style.font
+
+ wrapMode: Text.WordWrap
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/TextTypes/MediumTextType.qml b/client/ui/qml/DefaultVpn/Controls/TextTypes/MediumTextType.qml
new file mode 100644
index 00000000..7a2aad61
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/TextTypes/MediumTextType.qml
@@ -0,0 +1,15 @@
+import QtQuick
+
+import Config 1.0
+
+Text {
+ lineHeight: 22
+ lineHeightMode: Text.FixedHeight
+
+ color: Style.color.black
+ font.pixelSize: 17
+ font.weight: 400
+ font.family: Style.font
+
+ wrapMode: Text.WordWrap
+}
diff --git a/client/ui/qml/DefaultVpn/Controls/TextTypes/XSmallTextType.qml b/client/ui/qml/DefaultVpn/Controls/TextTypes/XSmallTextType.qml
new file mode 100644
index 00000000..22e37a86
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Controls/TextTypes/XSmallTextType.qml
@@ -0,0 +1,15 @@
+import QtQuick
+
+import Config 1.0
+
+Text {
+ lineHeight: 18
+ lineHeightMode: Text.FixedHeight
+
+ color: Style.color.black
+ font.pixelSize: 13
+ font.weight: 400
+ font.family: Style.font
+
+ wrapMode: Text.WordWrap
+}
diff --git a/client/ui/qml/DefaultVpn/Pages/PageHome.qml b/client/ui/qml/DefaultVpn/Pages/PageHome.qml
new file mode 100644
index 00000000..bfe49afd
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Pages/PageHome.qml
@@ -0,0 +1,122 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import PageEnum 1.0
+import Config 1.0
+
+import "../Components"
+import "../Controls"
+import "../Controls/TextTypes"
+
+Page {
+ id: root
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.topMargin: 8
+ anchors.bottomMargin: 36
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ spacing: 0
+
+ Text {
+ lineHeight: 68
+ lineHeightMode: Text.FixedHeight
+
+ color: Style.color.gray2
+ font.pixelSize: 56
+ font.weight: 700
+ font.family: Style.font
+
+ horizontalAlignment: Qt.AlignLeft
+
+ text: ConnectionController.isConnected ? qsTr("Online") : qsTr("Offline")
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ XSmallTextType {
+ text: qsTr("Connection to")
+
+ horizontalAlignment: Qt.AlignLeft
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ RowLayout {
+ DropDownType {
+ Layout.fillWidth: true
+
+ text: ServersModel.defaultServerName
+
+ onClicked: function() {
+ PageController.goToPage(PageEnum.PageSettingsServersList)
+ }
+ }
+
+ WhiteButtonWithBorder {
+ imageSource: "qrc:/images/controls/plus.svg"
+
+ onClicked: function() {
+ PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
+ }
+ }
+ }
+
+ Button {
+ id: connectButton
+
+ Layout.fillWidth: true
+ implicitHeight: 358
+
+ Layout.topMargin: 16
+
+ background: Rectangle {
+ anchors.fill: parent
+
+ radius: 16
+
+ color: {
+ if (ConnectionController.isConnectionInProgress) {
+ return Style.color.accent3
+ } else if (ConnectionController.isConnected) {
+ return Style.color.accent1
+ } else {
+ return Style.color.black
+ }
+ }
+
+ ColumnLayout {
+ anchors.centerIn: parent
+
+ Image {
+ Layout.alignment: Qt.AlignCenter
+
+ source: "qrc:/images/controls/connect-button.svg"
+ }
+
+ Header3TextType {
+ Layout.alignment: Qt.AlignCenter
+ Layout.topMargin: 24
+
+ text: ConnectionController.connectionStateText
+
+ color: Style.color.white
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ onClicked: function() {
+ ServersModel.setProcessedServerIndex(ServersModel.defaultIndex)
+ ConnectionController.connectButtonClicked()
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Pages/PageSettingsServerInfo.qml b/client/ui/qml/DefaultVpn/Pages/PageSettingsServerInfo.qml
new file mode 100644
index 00000000..2b2e5881
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Pages/PageSettingsServerInfo.qml
@@ -0,0 +1,103 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import PageEnum 1.0
+import Config 1.0
+
+import "../Components"
+import "../Controls"
+import "../Controls/TextTypes"
+
+Page {
+ id: root
+
+ Connections {
+ target: InstallController
+
+ function onRemoveProcessedServerFinished(finishedMessage) {
+ if (!ServersModel.getServersCount()) {
+ PageController.goToStartPage()
+ } else {
+ PageController.closePage()
+ }
+ PageController.showNotificationMessage(finishedMessage)
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 0
+
+ RowLayout {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.topMargin: 8
+
+ WhiteButtonNoBorder {
+ id: backButton
+ imageSource: "qrc:/images/controls/arrow-left.svg"
+
+ onClicked: PageController.closePage()
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+
+ Header1TextType {
+ id: header
+
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.bottomMargin: 24
+ Layout.fillWidth: true
+
+ text: qsTr("Server settings")
+
+ horizontalAlignment: Qt.AlignLeft
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ XSmallTextType {
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+
+ text: qsTr("Name")
+ }
+
+ InputType {
+ id: textKey
+
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.fillWidth: true
+ }
+
+ WhiteButtonWithBorder {
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.topMargin: 24
+ Layout.fillWidth: true
+
+ text: qsTr("Remove server")
+
+ onClicked: function() {
+ PageController.showBusyIndicator(true)
+ InstallController.removeProcessedServer()
+ PageController.showBusyIndicator(false)
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Pages/PageSettingsServersList.qml b/client/ui/qml/DefaultVpn/Pages/PageSettingsServersList.qml
new file mode 100644
index 00000000..fa82cc1e
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Pages/PageSettingsServersList.qml
@@ -0,0 +1,165 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import PageEnum 1.0
+import Config 1.0
+
+import "../Components"
+import "../Controls"
+import "../Controls/TextTypes"
+
+Page {
+ id: root
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ RowLayout {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.topMargin: 8
+
+ WhiteButtonNoBorder {
+ id: backButton
+ imageSource: "qrc:/images/controls/arrow-left.svg"
+
+ onClicked: PageController.closePage()
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ WhiteButtonNoBorder {
+ imageSource: "qrc:/images/controls/plus.svg"
+
+ onClicked: function() {
+ PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
+ }
+ }
+ }
+
+ Header1TextType {
+ id: header
+
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.fillWidth: true
+
+ text: qsTr("Connect to")
+
+ horizontalAlignment: Qt.AlignLeft
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ ButtonGroup {
+ id: serversRadioButtonGroup
+ }
+
+ ListView {
+ id: serversListView
+
+ Layout.topMargin: 16
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ model: ServersModel
+ currentIndex: ServersModel.defaultIndex
+
+ ScrollBar.vertical: ScrollBar {}
+
+ Connections {
+ target: ServersModel
+ function onDefaultServerIndexChanged(serverIndex) {
+ serversListView.currentIndex = serverIndex
+ serversListView.positionViewAtIndex(serversListView.currentIndex, ListView.Contain)
+ }
+ }
+
+ Component.onCompleted: positionViewAtIndex(currentIndex, ListView.Center)
+
+ delegate: Item {
+ id: menuContentDelegate
+ required property string name
+ required property int index
+
+ implicitWidth: serversListView.width
+ implicitHeight: serverItem.implicitHeight
+
+ RadioButton {
+ id: serverItem
+
+ anchors.fill: parent
+ anchors.rightMargin: 16
+ anchors.leftMargin: 16
+
+ ButtonGroup.group: serversRadioButtonGroup
+
+ checked: index === serversListView.currentIndex
+
+ indicator: Item { }
+
+ contentItem: Item {
+ id: contentContainer
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ implicitHeight: content.implicitHeight
+
+ Rectangle {
+ anchors.fill: parent
+
+ radius: 8
+
+ color: serverItem.checked ? Style.color.gray1 : Style.color.transparent
+ }
+
+ RowLayout {
+ id: content
+ anchors.fill: parent
+
+ Header3TextType {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.topMargin: 19
+ Layout.bottomMargin: 19
+
+ text: name
+
+ color: serverItem.hovered ? Style.color.gray9 : Style.color.black
+ }
+
+ ButtonType {
+ Layout.rightMargin: 8
+ imageSource: "qrc:/images/controls/edit-3.svg"
+
+ hoveredBorderColor: Style.color.gray2
+ hoveredBorderWidth: 1
+
+ onClicked: function() {
+ ServersModel.processedIndex = index
+ PageController.goToPage(PageEnum.PageSettingsServerInfo)
+ }
+ }
+ }
+ }
+
+ onClicked: function() {
+ ServersModel.defaultIndex = index
+ }
+
+ MouseArea {
+ anchors.fill: serverItem
+ cursorShape: Qt.PointingHandCursor
+ enabled: false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/Pages/PageSetupWizardConfigSource.qml b/client/ui/qml/DefaultVpn/Pages/PageSetupWizardConfigSource.qml
new file mode 100644
index 00000000..795fab9a
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/Pages/PageSetupWizardConfigSource.qml
@@ -0,0 +1,112 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import PageEnum 1.0
+import Config 1.0
+
+import "../Components"
+import "../Controls"
+import "../Controls/TextTypes"
+
+Page {
+ id: root
+
+ Connections {
+ target: ImportController
+
+ function onImportErrorOccurred(error, goToPageHome) {
+ PageController.showErrorMessage(error)
+ }
+
+ function onImportFinished() {
+ if (!ConnectionController.isConnected) {
+ ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
+ ServersModel.processedIndex = ServersModel.defaultIndex
+ }
+
+ PageController.goToStartPage()
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ spacing: 0
+
+ RowLayout {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.topMargin: 8
+
+ WhiteButtonNoBorder {
+ id: backButton
+ imageSource: "qrc:/images/controls/arrow-left.svg"
+
+ onClicked: PageController.closePage()
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+
+ Header1TextType {
+ id: header
+
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.bottomMargin: 24
+ Layout.fillWidth: true
+
+ text: qsTr("Adding a server to connect to")
+
+ horizontalAlignment: Qt.AlignLeft
+ verticalAlignment: Qt.AlignVCenter
+ }
+
+ XSmallTextType {
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+
+ text: qsTr("Key")
+ }
+
+ InputType {
+ id: textKey
+
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.fillWidth: true
+ Layout.preferredHeight: 308
+
+ placeholderText: qsTr("VPN://")
+ }
+
+ BlueButtonNoBorder {
+ Layout.topMargin: 24
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ Layout.fillWidth: true
+
+ text: qsTr("Add")
+
+ onClicked: function() {
+ if (ImportController.extractConfigFromData(textKey.text)) {
+ ImportController.importConfig()
+ } else {
+ PageController.showErrorMessage(qsTr("Unsupported config file"))
+ }
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/client/ui/qml/DefaultVpn/main.qml b/client/ui/qml/DefaultVpn/main.qml
new file mode 100644
index 00000000..cb958bdd
--- /dev/null
+++ b/client/ui/qml/DefaultVpn/main.qml
@@ -0,0 +1,195 @@
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs
+
+import Config 1.0
+import PageEnum 1.0
+
+import "Controls"
+import "Pages"
+
+ApplicationWindow {
+ id: root
+ objectName: "mainWindow"
+ visible: true
+ width: DeviceInfo.screenWidth
+ height: DeviceInfo.screenHeight
+ minimumWidth: DeviceInfo.isDesktop() ? 360 : 0
+ minimumHeight: DeviceInfo.isDesktop() ? 640 : 0
+ maximumWidth: 600
+ maximumHeight: 800
+
+ color: Style.color.white
+
+ onClosing: function() {
+ console.debug("QML onClosing signal")
+ PageController.closeWindow()
+ }
+
+ title: "DefaultVPN"
+
+ Connections {
+ target: PageController
+
+ function onRaiseMainWindow() {
+ root.show()
+ root.raise()
+ root.requestActivate()
+ }
+
+ 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()
+ }
+
+ function onShowBusyIndicator(visible) {
+ busyIndicator.visible = visible
+ PageController.disableControls(visible)
+ }
+
+ function onClosePage() {
+ if (stackview.depth <= 1) {
+ PageController.hideWindow()
+ 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 {
+ target: SettingsController
+
+ function onChangeSettingsFinished(finishedMessage) {
+ PageController.showNotificationMessage(finishedMessage)
+ }
+ }
+
+ StackView {
+ id: stackview
+ anchors.fill: parent
+
+ Component.onCompleted: {
+ var pagePath = PageController.getPagePath(PageEnum.PageHome)
+ ServersModel.processedIndex = ServersModel.defaultIndex
+
+ stackview.push(pagePath, { "objectName" : pagePath })
+ }
+ }
+
+ 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
+ }
+ }
+
+ // Item {
+ // anchors.fill: parent
+
+ // QuestionDrawer {
+ // id: questionDrawer
+
+ // anchors.fill: parent
+ // }
+ // }
+
+ Item {
+ anchors.fill: parent
+
+ BusyIndicatorType {
+ id: busyIndicator
+ anchors.centerIn: parent
+ z: 1
+ }
+ }
+
+ // function showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) {
+ // questionDrawer.headerText = headerText
+ // questionDrawer.descriptionText = descriptionText
+ // questionDrawer.yesButtonText = yesButtonText
+ // questionDrawer.noButtonText = noButtonText
+
+ // questionDrawer.yesButtonFunction = function() {
+ // questionDrawer.close()
+ // if (yesButtonFunction && typeof yesButtonFunction === "function") {
+ // yesButtonFunction()
+ // }
+ // }
+ // questionDrawer.noButtonFunction = function() {
+ // questionDrawer.close()
+ // if (noButtonFunction && typeof noButtonFunction === "function") {
+ // noButtonFunction()
+ // }
+ // }
+ // questionDrawer.open()
+ // }
+
+ FileDialog {
+ id: mainFileDialog
+
+ property bool isSaveMode: false
+
+ objectName: "mainFileDialog"
+ fileMode: isSaveMode ? FileDialog.SaveFile : FileDialog.OpenFile
+
+ onAccepted: SystemController.fileDialogClosed(true)
+ onRejected: SystemController.fileDialogClosed(false)
+ }
+}