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) + } +}