From be7386f0d76645b97e44b775c6b90b3cc740ab5f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 13 Jun 2023 20:03:20 +0900 Subject: [PATCH] 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 dbeaac38..598645d8 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 0fd6842c..510f580f 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 zcmV+g6#DClP)V?3 z6&Yjl`c(GiwM115;%8e{en(xuqz{sh3XlavWfv<{%IK_L7f;G|)k@!&Zaa`%N5~Dz zjRLe`9_x6V94k3G%6`Uc%F$XOC#COBwtRhbY_g(fUI_A?Hi}z$;qnh92j1H z_dOc_m(8-Cb;s)QxNA#$_5-fJ=~hbq?tilLw|^QG-&>%+e~Qn?IkVQJWYr&PdLIN( z(QVtRbf)vC=c}>-ia1cZ-{AXaxOgnjU!L3FJd)k=ocABRGu&?ak20&t!_f3BW5`+=$0GZ+CpL|#6ypI>w)>f!pcr(yQn`}aj&y^>eF zJPcMfO**UV2m8jtMaGd?j#Y&T9+S0IEw?!J>&VRjWSy^MW<+ql%YlK}I8X$j@boG; zRQJ*R*TaFjcMo5@7J1VQ)t9_^2Y{SqK)u27Cz%6w;5f!uGlDc>LjTBskpK;E46^GS1ZnKs^Kdk` z=RyA8aF}2Bo3^%79EZ`lu>ij2Z-z#~oLzqeQ1h3@nqL{HvO{;yygbs~6hLmstTLzM zgq^P`gG1laaljk`n5nX$Fw%cuV&89K(2nnSl|en11l;4 zMH9?@mC#Q)HWj| z=L^53dU$;7FIAW4S!~Pew)|C(Uf^ie3{SmyW@K42(!sHu7+K&9R>@)nCe>kPVA7xs zMx7d&{4dz_XCvbu%yz$2?flDFq%T3vI7}`F=bI`x^fLgb#$F~kA$q-?F=W4aSC5T& zfJ$5c|^9@EsHv~jN`o=zutMtOYpB9V+~WEIN}n|e4$UCl5v zW$MuRIy3X`c;y$LIwl>jnr5zM(N=&Q{VFuv2~O^Btgit$xj7ALU=7TIjk!oJJiVyt z$$06*(|g^HawkLAjqOtpgc8i_@PRC-s83Kn)jRHg<8) zw2pE%fw5n5AfVr~OxgjG3ua~Ik9M)imt9h}%5t2u&F5I=jP#*3DL7O1Y_o=H(EyF3 zo@yN{nv?=4TD(Mw7u17!s~>whkg$esH=ZB*I$^J+aTI6=lS3Jz*1E(IXT*&;G`f<>Bz6vIsamk&hFUyA%BtDw@hNmcxjY$wYW zY$`EN=6jw3`6sRyI9f3pF2HG8=Fp@@U`-zmSmol919L8?!cMQGzbpNB}_w4!Gyd;(2Jvk z|4dBq)%$`&QCc_oY)$_7ACl~S(L&j+x~BzZH#Jj_b+T^jmoQ@50fM^GaYNq**-qUl zY|K%)(YuHJ4HMgpd!%Gqg;N%Pn2lEGr3@S>=D<;4jA$^Z=4rJE>dmNsnCQkGxdB1% z)d^G@4T74CPG!$qw3lp2B;uu}FRfIdlnewf|`Cu$BmJ6I6nvX!fcfN`D;~7gD6LN8=ZlfZS|~) zDX%&?A$u5|a`E9bH!TK%h-wc24o4DuVE9%efU^YkBi;1W-~rrVARkG{Ce{&1t-%hl z9fNRS43PF}@Pls}dQgv)$B}IaQ{xgsIXc3`bzk zMaG^U=29IG^C+?>=FtAd!N>49R|$R6BJ6a-c&TU5V{$&Xx{05$$THrsh(cd9$S1Qd zaJo{^xPFhJ62(Sm!(#a7hb` zT5y0}u2-;^<~Ycn#2d(GmU!+T2qJby@6#o~@nbcrIWwE9H)w}UD95u$0?wc@cQ z4l4aH_dj-l9gFFwaCDZcwS~IC`b}nDiBevwqItccbiFB`Ss0~j>H?i4ik0#c)#nX> zMBTaeeV?Dv>5rXnrLWCbE7l|GDnJ$+wR(u+sE85ZP!5GueFw_+dOgX^3yv8*)@bba zfRF`?;DOP`Bz`&M^$GB*tD4~6{q+3BP!)H>Sr_@P}=A8}*=U#9a!&E^ed zLuI2sn{(a}rQ0Dot(AZ5=G?Y}OIciOSI8BCVj(B%Up%z?SCVhf(5BrQa?5F<{hHhw z(Vk@x1`l?k)pal1FR?M46YhfKa$-j^*_dt=099;ngF!EH6I%%v+TEH(7}YU4Vxo9U z1S7T8#dRKaCF{AFG|2bb*5#-))R$$=#I_H0uP{M<<2L(P8IEz~7oprX38SRYm%8@b zHIp%DURs^lVq$Cau{8#552Ww^`M=DU*r>}FFs+GICBVaJBDX}~XOp1enb{01Z+fO% zx;N;LvfWuq$;1j-n~!aG(j_0nWhMFBi@flRo@9{SziMVm1dRpoF{#`ZQD#9-x0?AL zTa5ENce6Ybdc6MNh7sxAw=yYGQ*zX4$9-<#VK;9wr4kVq(rp0~xh;~)GDy&Q2tlYL zP0ZrMwQ9T5ze}zf#kN_P&7fGx!6M(?#Exf&J(To})IDQVdTSh!QOijPzt{pu)UgX) z_oy@E^pt(=Y^)S0+8x*HEO*!+AC*9PCQLW685K+#;CAnMwkvF@8%HKc7N%y-FXWa; zT8AzGXUY$8XfB*)(EV_)Zte~K-PmM@aVd+f*ek2bmZ=6sWqaAVFc*ipIfZm4#$bzO z4Dwnf5)FTZ4XJcFmCq~&bNGgWF3_p%H&TJG?6qcgKYuvPhM8C8#<(pYM~Yo>(CQ{v z6LEEtKWtHcp515436lkSHDln1y7eG!*Pui{PD8NBn8mq#W-;;fSL)KLVc#qv!)xXp zeI{JPpkKFXGfcvIUU}+R3S@RU>D5h@rLUEP#RplT%=EIE6lWrPV9^D+8-#G!z6tzV zT5FyEYQ!w$GYRXa4%8(xf9M9L?=c&Mt^vBU4fGg~DntBnc2ZZY2UTvaPd8w*_n2Nn zSu|`ais4YtUIL3|sc+bGsXuxxpGl0GpVIeGmkG{e0)znZEgdwi3+!Pk-@|gg7_jIq zZ5ur_jx!dumazLpl8QR}*`$lPd?K-?Rh#@A`ly?}hkgdW#C*ytjk1M-4|fVRd{x$KZNc@;#LG_da&W z*1h?4TeR<#%uQ>g1!6Ow4+e|&N4zjIMOZh;ji3ve{@7=FU`Nv<=Wcu1u@4tZL zDK#O;ETb;rg|JMNouUrbCDGfggV<5f<&>sH-I(^`z{y9hr&}6bZu_MGIjN#~yc+VY zWaEAu=E>aMetP;NhyJm#3IZfjeGDei+*(SKnVu#=s^ePY;!NtVe~8U504w^*)5(Z{ zmDiJa?TKD^I(?h!=%={;%U`Ab<;R&MH|3ZPXC3tlXp%NDX+m>K3) ztr{8~A_#1@Oh=&u<_$Z7_VzKn2Pua2j!Dh{&ci(Q4!-i?-LFy^hlOcvysM@zSxS{F zwtcE$cYgUKV-iQTM;sB{&<};IVqMrho#V8Xo|o7J-+>;dNwgA-K1s@4LNkv8Gh@NpObwkj{uXk8*SN zTlSQb$u9vXzkj(@bA~9#s@PO?iBdy zPzO%7lNhjpzKV{ve253eP3efm#x{JW3&!*$No*!7%bd^|kIb7_n1s%Ahddml&l!qr z1?R|Ansc=ay5`QRl2u`DXk>-Cxw)x`8{J%-v>f$q6nn~m5{)^lqt^$iZD7wkPu>A6 zHoDvOW5F>32<||jV;wPn1K*W_0^n>JWDP%~>_gPC_-2xI%p`k-eEmz(Z8>{EoPHDKe3iFp+Z1NI?DJa}}VIBw*A-G|FvE0z(ZT{Z@ zX5x%8@8mD8Wbcku&50w`!@Iv^j9GuVZo{lhv2$^w0J)+!iY#a0ufLn>5+9hb#N^L* zo-|N^waCt+g&G2}EO&>;XZJKGJh4*t*!HP&%NqIalp{`eY53tOskXpW^hW6 zy}wkmAo4ohi*^(+s`3iB&2oQSq|=*L}br9QYuB z+^=7E>!)k4Y|6J_)_-Trvhui+4);8R0RQq;W5!x*uY`ZcQr3VuK@f}a z!)!E3t%Bb7Wl#S2Z!7U*oh^M^dBSYVI9g*%sUD@|w*uqjMgekCXl^!vI>DCG z5$AS0HtKdtu{5#eF;x_QW5AS){-{!U_spHI|FrA`L{^UXV*s;O)u)k)Q!jzF?k`ph nlnqdC%D4P3zsv7(E9CzG5|M9*U-v3100000NkvXXu0mjfMGNn0 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 00000000..75b1b5b4 --- /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 e2f16853..d60acfdd 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 00000000..9ff73f33 --- /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 00000000..c5054143 --- /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 114bb531..561ea19c 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 2fe96962..8f9e1f88 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 946c1ce5..dcf2fc66 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 00000000..48414021 --- /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 00000000..730cbda7 --- /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 b6574439..1caf6944 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 5753a1dd..7bb58755 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 bb08e88c..9ef87d37 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 593babc3..6f55176e 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 926302c4..28f81b2e 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 8c036fc0..5c574832 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 d5664541..1b8ea40c 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 00000000..5c9ceee8 --- /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 389191e8..0ccb7345 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 700d9981..6456bbad 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 00000000..421b82a3 --- /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 50af3c79..7f2411fb 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 23ca9e01..780f3aef 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 800041f0..498006df 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 99f6ccfb..1836f892 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 c18c4d27..9631e258 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 57fa497d..c0483df4 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 75b16afd..e2ae4a16 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 5560aee7..a731e425 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 16dd00d3..8b9cefa4 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