diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4e25097d..aeed439b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -404,6 +404,9 @@ void AmneziaApplication::initControllers() m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + m_focusController.reset(new FocusController(m_engine, this)); + m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get()); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_apiServicesModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 64566216..cfeac0d1 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/exportController.h" #include "ui/controllers/importController.h" #include "ui/controllers/installController.h" +#include "ui/controllers/focusController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/controllers/sitesController.h" @@ -124,6 +125,7 @@ private: #endif QScopedPointer m_connectionController; + QScopedPointer m_focusController; QScopedPointer m_pageController; QScopedPointer m_installController; QScopedPointer m_importController; diff --git a/client/resources.qrc b/client/resources.qrc index a10a784d..5edd1e8a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -1,225 +1,226 @@ + fonts/pt-root-ui_vf.ttf + images/amneziaBigLogo.png + images/AmneziaVPN.png + images/controls/alert-circle.svg + images/controls/amnezia.svg + images/controls/app.svg + images/controls/archive-restore.svg + images/controls/arrow-left.svg + images/controls/arrow-right.svg + images/controls/bug.svg + images/controls/check.svg + images/controls/chevron-down.svg + images/controls/chevron-right.svg + images/controls/chevron-up.svg + images/controls/close.svg + images/controls/copy.svg + images/controls/delete.svg + images/controls/download.svg + images/controls/edit-3.svg + images/controls/eye-off.svg + images/controls/eye.svg + images/controls/file-check-2.svg + images/controls/file-cog-2.svg + images/controls/folder-open.svg + images/controls/folder-search-2.svg + images/controls/gauge.svg + images/controls/github.svg + images/controls/help-circle.svg + images/controls/history.svg + images/controls/home.svg + images/controls/info.svg + images/controls/mail.svg + images/controls/map-pin.svg + images/controls/more-vertical.svg + images/controls/plus.svg + images/controls/qr-code.svg + images/controls/radio-button-inner-circle-pressed.png + images/controls/radio-button-inner-circle.png + images/controls/radio-button-pressed.svg + images/controls/radio-button.svg + images/controls/radio.svg + images/controls/refresh-cw.svg + images/controls/save.svg + images/controls/scan-line.svg + images/controls/search.svg + images/controls/server.svg + images/controls/settings-2.svg + images/controls/settings.svg + images/controls/share-2.svg + images/controls/split-tunneling.svg + images/controls/tag.svg + images/controls/telegram.svg + images/controls/text-cursor.svg + images/controls/trash.svg + images/controls/x-circle.svg images/tray/active.png images/tray/default.png images/tray/error.png - images/AmneziaVPN.png - server_scripts/remove_container.sh - server_scripts/setup_host_firewall.sh - server_scripts/openvpn_cloak/Dockerfile + server_scripts/awg/configure_container.sh + server_scripts/awg/Dockerfile + server_scripts/awg/run_container.sh + server_scripts/awg/start.sh + server_scripts/awg/template.conf + server_scripts/build_container.sh + server_scripts/check_connection.sh + server_scripts/check_server_is_busy.sh + server_scripts/check_user_in_sudo.sh + server_scripts/dns/configure_container.sh + server_scripts/dns/Dockerfile + server_scripts/dns/run_container.sh + server_scripts/install_docker.sh + server_scripts/ipsec/configure_container.sh + server_scripts/ipsec/Dockerfile + server_scripts/ipsec/mobileconfig.plist + server_scripts/ipsec/run_container.sh + server_scripts/ipsec/start.sh + server_scripts/ipsec/strongswan.profile server_scripts/openvpn_cloak/configure_container.sh + server_scripts/openvpn_cloak/Dockerfile + server_scripts/openvpn_cloak/run_container.sh server_scripts/openvpn_cloak/start.sh server_scripts/openvpn_cloak/template.ovpn - server_scripts/install_docker.sh - server_scripts/build_container.sh - server_scripts/prepare_host.sh - server_scripts/check_connection.sh - server_scripts/remove_all_containers.sh - server_scripts/openvpn_cloak/run_container.sh - server_scripts/openvpn/configure_container.sh - server_scripts/openvpn/run_container.sh - server_scripts/openvpn/template.ovpn - server_scripts/openvpn/Dockerfile - server_scripts/openvpn/start.sh server_scripts/openvpn_shadowsocks/configure_container.sh server_scripts/openvpn_shadowsocks/Dockerfile server_scripts/openvpn_shadowsocks/run_container.sh server_scripts/openvpn_shadowsocks/start.sh server_scripts/openvpn_shadowsocks/template.ovpn + server_scripts/openvpn/configure_container.sh + server_scripts/openvpn/Dockerfile + server_scripts/openvpn/run_container.sh + server_scripts/openvpn/start.sh + server_scripts/openvpn/template.ovpn + server_scripts/prepare_host.sh + server_scripts/remove_all_containers.sh + server_scripts/remove_container.sh + server_scripts/setup_host_firewall.sh + server_scripts/sftp/configure_container.sh + server_scripts/sftp/Dockerfile + server_scripts/sftp/run_container.sh + server_scripts/socks5_proxy/configure_container.sh + server_scripts/socks5_proxy/Dockerfile + server_scripts/socks5_proxy/run_container.sh + server_scripts/socks5_proxy/start.sh + server_scripts/website_tor/configure_container.sh + server_scripts/website_tor/Dockerfile + server_scripts/website_tor/run_container.sh server_scripts/wireguard/configure_container.sh server_scripts/wireguard/Dockerfile server_scripts/wireguard/run_container.sh server_scripts/wireguard/start.sh server_scripts/wireguard/template.conf - server_scripts/website_tor/configure_container.sh - server_scripts/website_tor/run_container.sh - ui/qml/Config/GlobalConfig.qml - ui/qml/Config/qmldir - server_scripts/check_server_is_busy.sh - server_scripts/dns/configure_container.sh - server_scripts/dns/Dockerfile - server_scripts/dns/run_container.sh - server_scripts/sftp/configure_container.sh - server_scripts/sftp/Dockerfile - server_scripts/sftp/run_container.sh - server_scripts/ipsec/configure_container.sh - server_scripts/ipsec/Dockerfile - server_scripts/ipsec/run_container.sh - server_scripts/ipsec/start.sh - server_scripts/ipsec/mobileconfig.plist - server_scripts/ipsec/strongswan.profile - server_scripts/website_tor/Dockerfile - server_scripts/check_user_in_sudo.sh - ui/qml/Controls2/BasicButtonType.qml - ui/qml/Controls2/TextFieldWithHeaderType.qml - ui/qml/Controls2/LabelWithButtonType.qml - images/controls/arrow-right.svg - images/controls/chevron-right.svg - ui/qml/Controls2/ImageButtonType.qml - ui/qml/Controls2/CardType.qml - ui/qml/Controls2/CheckBoxType.qml - images/controls/check.svg - ui/qml/Controls2/DropDownType.qml - ui/qml/Pages2/PageSetupWizardStart.qml - ui/qml/main2.qml - images/amneziaBigLogo.png - ui/qml/Controls2/FlickableType.qml - ui/qml/Pages2/PageSetupWizardCredentials.qml - ui/qml/Controls2/HeaderType.qml - images/controls/arrow-left.svg - ui/qml/Pages2/PageSetupWizardProtocols.qml - ui/qml/Pages2/PageSetupWizardEasy.qml - images/controls/chevron-down.svg - images/controls/chevron-up.svg - ui/qml/Controls2/TextTypes/ParagraphTextType.qml - ui/qml/Controls2/TextTypes/Header2TextType.qml - ui/qml/Controls2/HorizontalRadioButton.qml - ui/qml/Controls2/VerticalRadioButton.qml - ui/qml/Controls2/SwitcherType.qml - ui/qml/Controls2/TabButtonType.qml - ui/qml/Pages2/PageSetupWizardProtocolSettings.qml - ui/qml/Pages2/PageSetupWizardInstalling.qml - ui/qml/Pages2/PageSetupWizardConfigSource.qml - images/controls/folder-open.svg - images/controls/qr-code.svg - images/controls/text-cursor.svg - ui/qml/Pages2/PageSetupWizardTextKey.qml - ui/qml/Pages2/PageStart.qml - ui/qml/Controls2/TabImageButtonType.qml - images/controls/home.svg - images/controls/settings-2.svg - images/controls/share-2.svg - ui/qml/Pages2/PageHome.qml - ui/qml/Pages2/PageSettingsServersList.qml - ui/qml/Pages2/PageShare.qml - ui/qml/Controls2/TextTypes/Header1TextType.qml - ui/qml/Controls2/TextTypes/LabelTextType.qml - ui/qml/Controls2/TextTypes/ButtonTextType.qml - ui/qml/Controls2/Header2Type.qml - images/controls/plus.svg - ui/qml/Components/ConnectButton.qml - images/controls/download.svg - ui/qml/Controls2/ProgressBarType.qml - ui/qml/Components/ConnectionTypeSelectionDrawer.qml - ui/qml/Components/HomeContainersListView.qml - ui/qml/Controls2/TextTypes/CaptionTextType.qml - images/controls/settings.svg - ui/qml/Pages2/PageSettingsServerInfo.qml - ui/qml/Controls2/PageType.qml - ui/qml/Controls2/PopupType.qml - images/controls/edit-3.svg - ui/qml/Pages2/PageSettingsServerData.qml - ui/qml/Components/SettingsContainersListView.qml - ui/qml/Controls2/TextTypes/ListItemTitleType.qml - ui/qml/Controls2/DividerType.qml - ui/qml/Controls2/StackViewType.qml - ui/qml/Pages2/PageSettings.qml - images/controls/amnezia.svg - images/controls/app.svg - images/controls/radio.svg - images/controls/save.svg - images/controls/server.svg - ui/qml/Pages2/PageSettingsServerProtocols.qml - ui/qml/Pages2/PageSettingsServerServices.qml - ui/qml/Pages2/PageSetupWizardViewConfig.qml - images/controls/file-cog-2.svg - ui/qml/Components/QuestionDrawer.qml - ui/qml/Pages2/PageDeinstalling.qml - ui/qml/Controls2/BackButtonType.qml - ui/qml/Pages2/PageSettingsServerProtocol.qml - ui/qml/Components/TransportProtoSelector.qml - ui/qml/Controls2/ListViewWithRadioButtonType.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 - ui/qml/Pages2/PageSettingsConnection.qml - ui/qml/Pages2/PageSettingsDns.qml - ui/qml/Pages2/PageSettingsApplication.qml - ui/qml/Pages2/PageSettingsBackup.qml - images/controls/delete.svg - ui/qml/Pages2/PageSettingsAbout.qml - images/controls/github.svg - images/controls/mail.svg - images/controls/telegram.svg - ui/qml/Controls2/TextTypes/SmallTextType.qml - ui/qml/Filters/ContainersModelFilters.qml - ui/qml/Components/SelectLanguageDrawer.qml - ui/qml/Controls2/BusyIndicatorType.qml - ui/qml/Pages2/PageProtocolOpenVpnSettings.qml - ui/qml/Pages2/PageProtocolShadowSocksSettings.qml - ui/qml/Pages2/PageProtocolCloakSettings.qml - ui/qml/Pages2/PageProtocolXraySettings.qml - ui/qml/Pages2/PageProtocolRaw.qml - ui/qml/Pages2/PageSettingsLogging.qml - ui/qml/Pages2/PageServiceSftpSettings.qml - images/controls/copy.svg - ui/qml/Pages2/PageServiceTorWebsiteSettings.qml - ui/qml/Pages2/PageSetupWizardQrReader.qml - images/controls/eye.svg - images/controls/eye-off.svg - ui/qml/Pages2/PageSettingsSplitTunneling.qml - ui/qml/Controls2/ContextMenuType.qml - ui/qml/Controls2/TextAreaType.qml - images/controls/trash.svg - images/controls/more-vertical.svg - ui/qml/Controls2/ListViewWithLabelsType.qml - ui/qml/Pages2/PageServiceDnsSettings.qml - ui/qml/Controls2/TopCloseButtonType.qml - images/controls/x-circle.svg - ui/qml/Pages2/PageProtocolAwgSettings.qml - server_scripts/awg/template.conf - server_scripts/awg/start.sh - server_scripts/awg/configure_container.sh - server_scripts/awg/run_container.sh - server_scripts/awg/Dockerfile - ui/qml/Pages2/PageShareFullAccess.qml - images/controls/close.svg - images/controls/search.svg server_scripts/xray/configure_container.sh server_scripts/xray/Dockerfile server_scripts/xray/run_container.sh server_scripts/xray/start.sh server_scripts/xray/template.json - ui/qml/Pages2/PageProtocolWireGuardSettings.qml + ui/qml/Components/ConnectButton.qml + ui/qml/Components/ConnectionTypeSelectionDrawer.qml + ui/qml/Components/HomeContainersListView.qml ui/qml/Components/HomeSplitTunnelingDrawer.qml - images/controls/split-tunneling.svg - ui/qml/Controls2/DrawerType2.qml - ui/qml/Pages2/PageSettingsAppSplitTunneling.qml ui/qml/Components/InstalledAppsDrawer.qml - images/controls/alert-circle.svg - images/controls/file-check-2.svg + ui/qml/Components/QuestionDrawer.qml + ui/qml/Components/SelectLanguageDrawer.qml + ui/qml/Components/ServersListView.qml + ui/qml/Components/SettingsContainersListView.qml + ui/qml/Components/ShareConnectionDrawer.qml + ui/qml/Components/TransportProtoSelector.qml + ui/qml/Config/GlobalConfig.qml + ui/qml/Config/qmldir + ui/qml/Controls2/BackButtonType.qml + ui/qml/Controls2/BasicButtonType.qml + ui/qml/Controls2/BusyIndicatorType.qml + ui/qml/Controls2/CardType.qml + ui/qml/Controls2/CardWithIconsType.qml + ui/qml/Controls2/CheckBoxType.qml + ui/qml/Controls2/ContextMenuType.qml + ui/qml/Controls2/DividerType.qml + ui/qml/Controls2/DrawerType2.qml + ui/qml/Controls2/DropDownType.qml + ui/qml/Controls2/FlickableType.qml + ui/qml/Controls2/Header2Type.qml + ui/qml/Controls2/HeaderType.qml + ui/qml/Controls2/HorizontalRadioButton.qml + ui/qml/Controls2/ImageButtonType.qml + ui/qml/Controls2/LabelWithButtonType.qml + ui/qml/Controls2/LabelWithImageType.qml + ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Controls2/ListViewWithRadioButtonType.qml + ui/qml/Controls2/PageType.qml + ui/qml/Controls2/PopupType.qml + ui/qml/Controls2/ProgressBarType.qml + ui/qml/Controls2/StackViewType.qml + ui/qml/Controls2/SwitcherType.qml + ui/qml/Controls2/TabButtonType.qml + ui/qml/Controls2/TabImageButtonType.qml + ui/qml/Controls2/TextAreaType.qml + ui/qml/Controls2/TextAreaWithFooterType.qml + ui/qml/Controls2/TextFieldWithHeaderType.qml + ui/qml/Controls2/TextTypes/ButtonTextType.qml + ui/qml/Controls2/TextTypes/CaptionTextType.qml + ui/qml/Controls2/TextTypes/Header1TextType.qml + ui/qml/Controls2/TextTypes/Header2TextType.qml + ui/qml/Controls2/TextTypes/LabelTextType.qml + ui/qml/Controls2/TextTypes/ListItemTitleType.qml + ui/qml/Controls2/TextTypes/ParagraphTextType.qml + ui/qml/Controls2/TextTypes/SmallTextType.qml + ui/qml/Controls2/TopCloseButtonType.qml + ui/qml/Controls2/VerticalRadioButton.qml ui/qml/Controls2/WarningType.qml - fonts/pt-root-ui_vf.ttf - ui/qml/Modules/Style/qmldir + ui/qml/Filters/ContainersModelFilters.qml + ui/qml/main2.qml ui/qml/Modules/Style/AmneziaStyle.qml + ui/qml/Modules/Style/qmldir + ui/qml/Pages2/PageDeinstalling.qml + ui/qml/Pages2/PageDevMenu.qml + ui/qml/Pages2/PageHome.qml + ui/qml/Pages2/PageProtocolAwgSettings.qml + ui/qml/Pages2/PageProtocolCloakSettings.qml + ui/qml/Pages2/PageProtocolOpenVpnSettings.qml + ui/qml/Pages2/PageProtocolRaw.qml + ui/qml/Pages2/PageProtocolShadowSocksSettings.qml + ui/qml/Pages2/PageProtocolWireGuardSettings.qml + ui/qml/Pages2/PageProtocolXraySettings.qml + ui/qml/Pages2/PageServiceDnsSettings.qml + ui/qml/Pages2/PageServiceSftpSettings.qml ui/qml/Pages2/PageServiceSocksProxySettings.qml - server_scripts/socks5_proxy/run_container.sh - server_scripts/socks5_proxy/Dockerfile - server_scripts/socks5_proxy/configure_container.sh - server_scripts/socks5_proxy/start.sh + ui/qml/Pages2/PageServiceTorWebsiteSettings.qml + ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageSettingsAbout.qml + ui/qml/Pages2/PageSettingsApiLanguageList.qml + ui/qml/Pages2/PageSettingsApiServerInfo.qml + ui/qml/Pages2/PageSettingsApplication.qml + ui/qml/Pages2/PageSettingsAppSplitTunneling.qml + ui/qml/Pages2/PageSettingsBackup.qml + ui/qml/Pages2/PageSettingsConnection.qml + ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsLogging.qml + ui/qml/Pages2/PageSettingsServerData.qml + ui/qml/Pages2/PageSettingsServerInfo.qml + ui/qml/Pages2/PageSettingsServerProtocol.qml + ui/qml/Pages2/PageSettingsServerProtocols.qml + ui/qml/Pages2/PageSettingsServerServices.qml + ui/qml/Pages2/PageSettingsServersList.qml + ui/qml/Pages2/PageSettingsSplitTunneling.qml ui/qml/Pages2/PageProtocolAwgClientSettings.qml ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml - ui/qml/Pages2/PageSetupWizardApiServicesList.qml ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml - ui/qml/Controls2/CardWithIconsType.qml - images/controls/tag.svg - images/controls/history.svg - images/controls/gauge.svg - images/controls/map-pin.svg - ui/qml/Controls2/LabelWithImageType.qml - images/controls/info.svg - ui/qml/Controls2/TextAreaWithFooterType.qml - images/controls/scan-line.svg - images/controls/folder-search-2.svg - ui/qml/Pages2/PageSettingsApiServerInfo.qml - images/controls/bug.svg - ui/qml/Pages2/PageDevMenu.qml - images/controls/refresh-cw.svg - ui/qml/Pages2/PageSettingsApiLanguageList.qml - images/controls/archive-restore.svg - images/controls/help-circle.svg + ui/qml/Pages2/PageSetupWizardApiServicesList.qml + ui/qml/Pages2/PageSetupWizardConfigSource.qml + ui/qml/Pages2/PageSetupWizardCredentials.qml + ui/qml/Pages2/PageSetupWizardEasy.qml + ui/qml/Pages2/PageSetupWizardInstalling.qml + ui/qml/Pages2/PageSetupWizardProtocols.qml + ui/qml/Pages2/PageSetupWizardProtocolSettings.qml + ui/qml/Pages2/PageSetupWizardQrReader.qml + ui/qml/Pages2/PageSetupWizardStart.qml + ui/qml/Pages2/PageSetupWizardTextKey.qml + ui/qml/Pages2/PageSetupWizardViewConfig.qml + ui/qml/Pages2/PageShare.qml + ui/qml/Pages2/PageShareFullAccess.qml + ui/qml/Pages2/PageStart.qml images/flagKit/ZW.svg diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp new file mode 100644 index 00000000..f206620d --- /dev/null +++ b/client/ui/controllers/focusController.cpp @@ -0,0 +1,324 @@ +#include "focusController.h" + +#include "listViewFocusController.h" + +#include +#include + + +bool isListView(QObject* item) +{ + return item->inherits("QQuickListView"); +} + +bool isOnTheScene(QObject* object) +{ + QQuickItem* item = qobject_cast(object); + if (!item) { + qWarning() << "Couldn't recognize object as item"; + return false; + } + + if (!item->isVisible()) { + // qDebug() << "===>> The item is not visible: " << item; + return false; + } + + QRectF itemRect = item->mapRectToScene(item->childrenRect()); + + QQuickWindow* window = item->window(); + if (!window) { + qWarning() << "Couldn't get the window on the Scene check"; + return false; + } + + const auto contentItem = window->contentItem(); + if (!contentItem) { + qWarning() << "Couldn't get the content item on the Scene check"; + return false; + } + QRectF windowRect = contentItem->childrenRect(); + const auto res = (windowRect.contains(itemRect) || isListView(item)); + // qDebug() << (res ? "===>> item is inside the Scene" : "===>> ITEM IS OUTSIDE THE SCENE") << " itemRect: " << itemRect << "; windowRect: " << windowRect; + return res; +} + +QList getSubChain(QObject* object) +{ + QList res; + if (!object) { + qDebug() << "The object is NULL"; + return res; + } + + const auto children = object->children(); + + for(const auto child : children) { + if (child + && isFocusable(child) + && isOnTheScene(child) + && isEnabled(child) + ) { + res.append(child); + } else { + res.append(getSubChain(child)); + } + } + return res; +} + +FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) + : QObject{parent} + , m_engine{engine} + , m_focusChain{} + , m_focusedItem{nullptr} + , m_rootObjects{} + , m_defaultFocusItem{QSharedPointer()} + , m_lvfc{nullptr} +{ + QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url){ + QQuickItem* newDefaultFocusItem = object->findChild("defaultFocusItem"); + if(newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { + m_defaultFocusItem.reset(newDefaultFocusItem); + qDebug() << "===>> NEW DEFAULT FOCUS ITEM: " << m_defaultFocusItem; + } + }); + + QObject::connect(this, &FocusController::focusedItemChanged, this, [this]() { + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); + }); +} + + +void FocusController::nextKeyTabItem() +{ + nextItem(Direction::Forward); +} + +void FocusController::previousKeyTabItem() +{ + nextItem(Direction::Backward); +} + +void FocusController::nextKeyUpItem() +{ + nextItem(Direction::Backward); +} + +void FocusController::nextKeyDownItem() +{ + nextItem(Direction::Forward); +} + +void FocusController::nextKeyLeftItem() +{ + nextItem(Direction::Backward); +} + +void FocusController::nextKeyRightItem() +{ + nextItem(Direction::Forward); +} + +void FocusController::setFocusItem(QQuickItem* item) +{ + if (m_focusedItem != item) { + m_focusedItem = item; + emit focusedItemChanged(); + qDebug() << "===>> FocusItem is changed to " << item << "!"; + } else { + qDebug() << "===>> FocusItem is is the same: " << item << "!"; + } +} + +void FocusController::setFocusOnDefaultItem() +{ + qDebug() << "===>> Setting focus on DEFAULT FOCUS ITEM..."; + setFocusItem(m_defaultFocusItem.get()); +} + +void FocusController::pushRootObject(QObject* object) +{ + qDebug() << "===>> Calling < pushRootObject >..."; + m_rootObjects.push(object); + dropListView(); + // setFocusOnDefaultItem(); + qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); + qDebug() << "===>> ROOT OBJECTS: " << m_rootObjects; +} + +void FocusController::dropRootObject(QObject* object) +{ + qDebug() << "===>> Calling < dropRootObject >..."; + if (m_rootObjects.empty()) { + qDebug() << "ROOT OBJECT is already DEFAULT"; + + return; + } + + if (m_rootObjects.top() == object) { + m_rootObjects.pop(); + dropListView(); + setFocusOnDefaultItem(); + if(m_rootObjects.size()) { + qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); + } else { + qDebug() << "===>> ROOT OBJECT is changed to DEFAULT"; + } + } else { + qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object; + } +} + +void FocusController::resetRootObject() +{ + qDebug() << "===>> Calling < resetRootObject >..."; + m_rootObjects.clear(); + qDebug() << "===>> ROOT OBJECT IS RESETED"; +} + +void FocusController::reload(Direction direction) +{ + qDebug() << "===>> Calling < reload >..."; + m_focusChain.clear(); + + QObject* rootObject = (m_rootObjects.empty() + ? m_engine->rootObjects().value(0) + : m_rootObjects.top()); + + if(!rootObject) { + qCritical() << "No ROOT OBJECT found!"; + resetRootObject(); + dropListView(); + return; + } + + qDebug() << "===>> ROOT OBJECTS: " << rootObject; + + m_focusChain.append(getSubChain(rootObject)); + + std::sort(m_focusChain.begin(), m_focusChain.end(), direction == Direction::Forward ? isLess : isMore); + + if (m_focusChain.empty()) { + qWarning() << "Focus chain is empty!"; + resetRootObject(); + dropListView(); + return; + } +} + +void FocusController::nextItem(Direction direction) +{ + qDebug() << "===>> Calling < nextItem >..."; + + reload(direction); + + if (m_lvfc && isListView(m_focusedItem)) { + direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); + qDebug() << "===>> Handling the [ ListView ]..."; + + return; + } + + if(m_focusChain.empty()) { + qWarning() << "There are no items to navigate"; + setFocusOnDefaultItem(); + return; + } + + auto focusedItemIndex = m_focusChain.indexOf(m_focusedItem); + + if (focusedItemIndex == -1) { + qDebug() << "Current FocusItem is not in chain, switch to first in chain..."; + focusedItemIndex = 0; + } else if (focusedItemIndex == (m_focusChain.size() - 1)) { + qDebug() << "Last focus index. Starting from the beginning..."; + focusedItemIndex = 0; + } else { + qDebug() << "Incrementing focus index..."; + focusedItemIndex++; + } + + const auto focusedItem = qobject_cast(m_focusChain.at(focusedItemIndex)); + + if(focusedItem == nullptr) { + qWarning() << "Failed to get item to focus on. Setting focus on default"; + setFocusOnDefaultItem(); + return; + } + + if(isListView(focusedItem)) { + qDebug() << "===>> Found [ListView]"; + m_lvfc = new ListViewFocusController(focusedItem, this); + m_focusedItem = focusedItem; + if(direction == Direction::Forward) { + m_lvfc->nextDelegate(); + focusNextListViewItem(); + } else { + m_lvfc->previousDelegate(); + focusPreviousListViewItem(); + } + return; + } + + setFocusItem(focusedItem); + + printItems(m_focusChain, focusedItem); + + /////////////////////////////////////////////////////////// + + const auto w = m_defaultFocusItem->window(); + + qDebug() << "===>> CURRENT ACTIVE ITEM: " << w->activeFocusItem(); + qDebug() << "===>> CURRENT FOCUS OBJECT: " << w->focusObject(); + if(m_rootObjects.empty()) { + qDebug() << "===>> ROOT OBJECT IS DEFAULT"; + } else { + qDebug() << "===>> ROOT OBJECT: " << m_rootObjects.top(); + } +} + +void FocusController::focusNextListViewItem() +{ + qDebug() << "===>> Calling < focusNextListViewItem >..."; + + if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { + qDebug() << "===>> Last item in [ ListView ] was reached. Going to the NEXT element after [ ListView ]"; + dropListView(); + nextItem(Direction::Forward); + return; + } else if (m_lvfc->isLastFocusItemInDelegate()) { + qDebug() << "===>> End of delegate elements was reached. Going to the next delegate"; + m_lvfc->resetFocusChain(); + m_lvfc->nextDelegate(); + } + + m_lvfc->focusNextItem(); +} + +void FocusController::focusPreviousListViewItem() +{ + qDebug() << "===>> Calling < focusPreviousListViewItem >..."; + + if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { + qDebug() << "===>> First item in [ ListView ] was reached. Going to the PREVIOUS element after [ ListView ]"; + dropListView(); + nextItem(Direction::Backward); + return; + } else if (m_lvfc->isFirstFocusItemInDelegate()) { + m_lvfc->resetFocusChain(); + m_lvfc->previousDelegate(); + } + + m_lvfc->focusPreviousItem(); +} + +void FocusController::dropListView() +{ + qDebug() << "===>> Calling < dropListView >..."; + + if(m_lvfc) { + delete m_lvfc; + m_lvfc = nullptr; + } +} diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h new file mode 100644 index 00000000..54e0a05c --- /dev/null +++ b/client/ui/controllers/focusController.h @@ -0,0 +1,62 @@ +#ifndef FOCUSCONTROLLER_H +#define FOCUSCONTROLLER_H + +#include +#include +#include + + +class QQuickItem; +class QQmlApplicationEngine; +class ListViewFocusController; + +/*! + * \brief The FocusController class makes focus control more straightforward + * \details Focus is handled only for visible and enabled items which have + * `isFocused` property from top left to bottom right. + * \note There are items handled differently (e.g. ListView) + */ +class FocusController : public QObject +{ + Q_OBJECT +public: + explicit FocusController(QQmlApplicationEngine* engine, QObject *parent = nullptr); + ~FocusController() override = default; + + Q_INVOKABLE void nextKeyTabItem(); + Q_INVOKABLE void previousKeyTabItem(); + Q_INVOKABLE void nextKeyUpItem(); + Q_INVOKABLE void nextKeyDownItem(); + Q_INVOKABLE void nextKeyLeftItem(); + Q_INVOKABLE void nextKeyRightItem(); + Q_INVOKABLE void setFocusItem(QQuickItem* item); + Q_INVOKABLE void setFocusOnDefaultItem(); + Q_INVOKABLE void pushRootObject(QObject* object); + Q_INVOKABLE void dropRootObject(QObject* object); + Q_INVOKABLE void resetRootObject(); + +private: + enum class Direction { + Forward, + Backward, + }; + + void reload(Direction direction); + void nextItem(Direction direction); + void focusNextListViewItem(); + void focusPreviousListViewItem(); + void dropListView(); + + QSharedPointer m_engine; // Pointer to engine to get root object + QList m_focusChain; // List of current objects to be focused + QQuickItem* m_focusedItem; // Pointer to the active focus item + QStack m_rootObjects; + QSharedPointer m_defaultFocusItem; + + ListViewFocusController* m_lvfc; // ListView focus manager + +signals: + void focusedItemChanged(); +}; + +#endif // FOCUSCONTROLLER_H diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp new file mode 100644 index 00000000..21326597 --- /dev/null +++ b/client/ui/controllers/listViewFocusController.cpp @@ -0,0 +1,393 @@ +#include "listViewFocusController.h" + +#include +#include +#include +#include +#include + + +bool isVisible(QObject* item) +{ + const auto res = item->property("visible").toBool(); + return res; +} + +bool isFocusable(QObject* item) +{ + const auto res = item->property("isFocusable").toBool(); + return res; +} + +QPointF getItemCenterPointOnScene(QQuickItem* item) +{ + const auto x0 = item->x() + (item->width() / 2); + const auto y0 = item->y() + (item->height() / 2); + return item->parentItem()->mapToScene(QPointF{x0, y0}); +} + +bool isLess(QObject* item1, QObject* item2) +{ + const auto p1 = getItemCenterPointOnScene(qobject_cast(item1)); + const auto p2 = getItemCenterPointOnScene(qobject_cast(item2)); + return (p1.y() == p2.y()) ? (p1.x() < p2.x()) : (p1.y() < p2.y()); +} + +bool isMore(QObject* item1, QObject* item2) +{ + return !isLess(item1, item2); +} + +bool isEnabled(QObject* obj) +{ + const auto item = qobject_cast(obj); + return item && item->isEnabled(); +} + +QList getItemsChain(QObject* object) +{ + QList res; + if (!object) { + qDebug() << "The object is NULL"; + return res; + } + + const auto children = object->children(); + + for(const auto child : children) { + if (child + && isFocusable(child) + && isEnabled(child) + && isVisible(child) + ) { + res.append(child); + } else { + res.append(getItemsChain(child)); + } + } + return res; +} + +void printItems(const QList& items, QObject* current_item) +{ + for(const auto& item : items) { + QQuickItem* i = qobject_cast(item); + QPointF coords {getItemCenterPointOnScene(i)}; + QString prefix = current_item == i ? "==>" : " "; + qDebug() << prefix << " Item: " << i << " with coords: " << coords; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* parent) + : QObject{parent} + , m_listView{listView} + , m_focusChain{} + , m_currentSection{Section::Default} + , m_header{nullptr} + , m_footer{nullptr} + , m_focusedItem{nullptr} + , m_focusedItemIndex{-1} + , m_delegateIndex{0} + , m_isReturnNeeded{false} + , m_currentSectionString {"Default", "Header", "Delegate", "Footer"} +{ + QVariant headerItemProperty = m_listView->property("headerItem"); + m_header = headerItemProperty.canConvert() ? headerItemProperty.value() : nullptr; + + QVariant footerItemProperty = m_listView->property("footerItem"); + m_footer = footerItemProperty.canConvert() ? footerItemProperty.value() : nullptr; +} + +ListViewFocusController::~ListViewFocusController() +{ + +} + +void ListViewFocusController::viewAtCurrentIndex() const +{ + switch(m_currentSection) { + case Section::Default: + [[fallthrough]]; + case Section::Header: { + qDebug() << "===>> [FOCUS ON BEGINNING...]"; + QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); + break; + } + case Section::Delegate: { + qDebug() << "===>> [FOCUS ON INDEX...]"; + QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", + Q_ARG(int, m_delegateIndex), // Index + Q_ARG(int, 2)); // PositionMode (0 = Visible) + break; + } + case Section::Footer: { + qDebug() << "===>> [FOCUS ON END...]"; + QMetaObject::invokeMethod(m_listView, "positionViewAtEnd"); + break; + } + } + +} + +int ListViewFocusController::size() const +{ + return m_listView->property("count").toInt(); +} + +int ListViewFocusController::currentIndex() const +{ + return m_delegateIndex; +} + +void ListViewFocusController::nextDelegate() +{ + const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; + qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]"; + switch(m_currentSection) { + case Section::Default: { + if(hasHeader()) { + m_currentSection = Section::Header; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + } + case Section::Header: { + if (size() > 0) { + m_currentSection = Section::Delegate; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + } + case Section::Delegate: + if (m_delegateIndex < (size() - 1)) { + m_delegateIndex++; + viewAtCurrentIndex(); + break; + } else if (hasFooter()) { + m_currentSection = Section::Footer; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + case Section::Footer: { + m_isReturnNeeded = true; + m_currentSection = Section::Default; + viewAtCurrentIndex(); + break; + } + default: { + qCritical() << "Current section is invalid!"; + break; + } + } +} + +void ListViewFocusController::previousDelegate() +{ + switch(m_currentSection) { + case Section::Default: { + if(hasFooter()) { + m_currentSection = Section::Footer; + break; + } + [[fallthrough]]; + } + case Section::Footer: { + if (size() > 0) { + m_currentSection = Section::Delegate; + m_delegateIndex = size() - 1; + break; + } + [[fallthrough]]; + } + case Section::Delegate: { + if (m_delegateIndex > 0) { + m_delegateIndex--; + break; + } else if (hasHeader()) { + m_currentSection = Section::Header; + break; + } + [[fallthrough]]; + } + case Section::Header: { + m_isReturnNeeded = true; + m_currentSection = Section::Default; + break; + } + default: { + qCritical() << "Current section is invalid!"; + break; + } + } +} + +void ListViewFocusController::decrementIndex() +{ + m_delegateIndex--; +} + +QQuickItem* ListViewFocusController::itemAtIndex(const int index) const +{ + QQuickItem* item{nullptr}; + + QMetaObject::invokeMethod(m_listView, "itemAtIndex", + Q_RETURN_ARG(QQuickItem*, item), + Q_ARG(int, index)); + + return item; +} + +QQuickItem* ListViewFocusController::currentDelegate() const +{ + QQuickItem* result{nullptr}; + + switch(m_currentSection) { + case Section::Default: { + qWarning() << "No elements..."; + break; + } + case Section::Header: { + result = m_header; + break; + } + case Section::Delegate: { + result = itemAtIndex(m_delegateIndex); + break; + } + case Section::Footer: { + result = m_footer; + break; + } + } + return result; +} + +QQuickItem* ListViewFocusController::focusedItem() const +{ + return m_focusedItem; +} + +void ListViewFocusController::focusNextItem() +{ + if (m_isReturnNeeded) { + qDebug() << "===>> [ RETURN IS NEEDED... ]"; + return; + } + + m_focusChain = getItemsChain(currentDelegate()); + + if (m_focusChain.empty()) { + qWarning() << "No elements found in the delegate. Going to next delegate..."; + nextDelegate(); + focusNextItem(); + return; + } + m_focusedItemIndex++; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]"; + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); +} + +void ListViewFocusController::focusPreviousItem() +{ + if (m_isReturnNeeded) { + return; + } + + if (m_focusChain.empty()) { + qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; + m_focusChain = getItemsChain(currentDelegate()); + } + if (m_focusChain.empty()) { + qWarning() << "No elements found in the delegate. Going to next delegate..."; + previousDelegate(); + focusPreviousItem(); + return; + } + if (m_focusedItemIndex == -1) { + m_focusedItemIndex = m_focusChain.size(); + } + m_focusedItemIndex--; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]"; + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); +} + +void ListViewFocusController::resetFocusChain() +{ + m_focusChain.clear(); + m_focusedItem = nullptr; + m_focusedItemIndex = -1; +} + +bool ListViewFocusController::isFirstFocusItemInDelegate() const +{ + return m_focusedItem && (m_focusedItem == m_focusChain.first()); +} + +bool ListViewFocusController::isLastFocusItemInDelegate() const +{ + return m_focusedItem && (m_focusedItem == m_focusChain.last()); +} + +bool ListViewFocusController::hasHeader() const +{ + return m_header && !getItemsChain(m_header).isEmpty(); +} + +bool ListViewFocusController::hasFooter() const +{ + return m_footer && !getItemsChain(m_footer).isEmpty(); +} + +bool ListViewFocusController::isFirstFocusItemInListView() const +{ + switch (m_currentSection) { + case Section::Footer: { + return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0); + } + case Section::Delegate: { + return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader(); + } + case Section::Header: { + isFirstFocusItemInDelegate(); + } + case Section::Default: { + return true; + } + default: + qWarning() << "Wrong section"; + return true; + } +} + +bool ListViewFocusController::isLastFocusItemInListView() const +{ + switch (m_currentSection) { + case Section::Default: { + return !hasHeader() && (size() == 0) && !hasFooter(); + } + case Section::Header: { + return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter(); + } + case Section::Delegate: { + return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter(); + } + case Section::Footer: { + return isLastFocusItemInDelegate(); + } + default: + qWarning() << "Wrong section"; + return true; + } +} + +bool ListViewFocusController::isReturnNeeded() const +{ + return m_isReturnNeeded; +} diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h new file mode 100644 index 00000000..a94b4237 --- /dev/null +++ b/client/ui/controllers/listViewFocusController.h @@ -0,0 +1,76 @@ +#ifndef LISTVIEWFOCUSCONTROLLER_H +#define LISTVIEWFOCUSCONTROLLER_H + +#include +#include +#include +#include + + +bool isEnabled(QObject* item); +bool isFocusable(QObject* item); +bool isMore(QObject* item1, QObject* item2); +bool isLess(QObject* item1, QObject* item2); +QList getSubChain(QObject* object); + +void printItems(const QList& items, QObject* current_item); + +/*! + * \brief The ListViewFocusController class manages the focus of elements in ListView + * \details This class object moving focus to ListView's controls since ListView stores + * it's data implicitly and it could be got one by one. + * + * This class was made to store as less as possible data getting it from QML + * when it's needed. + */ +class ListViewFocusController : public QObject +{ + Q_OBJECT +public: + explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); + ~ListViewFocusController(); + + void nextDelegate(); + void previousDelegate(); + void decrementIndex(); + void focusNextItem(); + void focusPreviousItem(); + void resetFocusChain(); + bool isFirstFocusItemInListView() const; + bool isFirstFocusItemInDelegate() const; + bool isLastFocusItemInListView() const; + bool isLastFocusItemInDelegate() const; + bool isReturnNeeded() const; + +private: + enum class Section { + Default, + Header, + Delegate, + Footer, + }; + + int size() const; + int currentIndex() const; + void viewAtCurrentIndex() const; + QQuickItem* itemAtIndex(const int index) const; + QQuickItem* currentDelegate() const; + QQuickItem* focusedItem() const; + + bool hasHeader() const; + bool hasFooter() const; + + QQuickItem* m_listView; + QList m_focusChain; + Section m_currentSection; + QQuickItem* m_header; + QQuickItem* m_footer; + QQuickItem* m_focusedItem; // Pointer to focused item on Delegate + qsizetype m_focusedItemIndex; + qsizetype m_delegateIndex; + bool m_isReturnNeeded; + + QList m_currentSectionString; +}; + +#endif // LISTVIEWFOCUSCONTROLLER_H diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index bbcc55a1..d515df49 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -81,7 +81,7 @@ void PageController::keyPressEvent(Qt::Key key) case Qt::Key_Escape: { if (m_drawerDepth) { emit closeTopDrawer(); - setDrawerDepth(getDrawerDepth() - 1); + decrementDrawerDepth(); } else { emit escapePressed(); } @@ -142,11 +142,25 @@ void PageController::setDrawerDepth(const int depth) } } -int PageController::getDrawerDepth() +int PageController::getDrawerDepth() const { return m_drawerDepth; } +int PageController::incrementDrawerDepth() +{ + return ++m_drawerDepth; +} + +int PageController::decrementDrawerDepth() +{ + if (m_drawerDepth == 0) { + return m_drawerDepth; + } else { + return --m_drawerDepth; + } +} + void PageController::onShowErrorMessage(ErrorCode errorCode) { const auto fullErrorMessage = errorString(errorCode); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f89d39a1..ffbdd3a1 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -100,7 +100,9 @@ public slots: void closeApplication(); void setDrawerDepth(const int depth); - int getDrawerDepth(); + int getDrawerDepth() const; + int incrementDrawerDepth(); + int decrementDrawerDepth(); private slots: void onShowErrorMessage(amnezia::ErrorCode errorCode); @@ -135,9 +137,6 @@ signals: void escapePressed(); void closeTopDrawer(); - void forceTabBarActiveFocus(); - void forceStackActiveFocus(); - private: QSharedPointer m_serversModel; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index c87499a7..7dd76b84 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -79,6 +79,12 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int return true; } +bool ServersModel::setData(const int index, const QVariant &value, int role) +{ + QModelIndex modelIndex = this->index(index); + return setData(modelIndex, value, role); +} + QVariant ServersModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { @@ -679,6 +685,18 @@ QVariant ServersModel::getProcessedServerData(const QString roleString) return {}; } +bool ServersModel::setProcessedServerData(const QString& roleString, const QVariant& value) +{ + const auto roles = roleNames(); + for (auto it = roles.begin(); it != roles.end(); it++) { + if (QString(it.value()) == roleString) { + return setData(m_processedServerIndex, value, it.key()); + } + } + + return false; +} + bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() { auto server = m_servers.at(m_defaultServerIndex).toObject(); diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 0f18ea30..c15a5b51 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -46,6 +46,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + bool setData(const int index, const QVariant &value, int role = Qt::EditRole); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const int index, int role = Qt::DisplayRole) const; @@ -115,6 +116,7 @@ public slots: QVariant getDefaultServerData(const QString roleString); QVariant getProcessedServerData(const QString roleString); + bool setProcessedServerData(const QString &roleString, const QVariant &value); bool isDefaultServerDefaultContainerHasSplitTunneling(); diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index fa18703b..b90891a0 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -16,6 +16,32 @@ Button { property string connectedButtonColor: AmneziaStyle.color.goldenApricot property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv()) + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + implicitWidth: 190 implicitHeight: 190 diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 23fe0d2a..c9124d81 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -14,7 +14,7 @@ DrawerType2 { width: parent.width height: parent.height - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: content anchors.top: parent.top @@ -26,14 +26,6 @@ DrawerType2 { root.expandedHeight = content.implicitHeight + 32 } - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - Header2Type { Layout.fillWidth: true Layout.topMargin: 24 @@ -44,11 +36,6 @@ DrawerType2 { headerText: qsTr("Add new connection") } - Item { - id: focusItem - KeyNavigation.tab: ip.rightButton - } - LabelWithButtonType { id: ip Layout.fillWidth: true @@ -59,10 +46,8 @@ DrawerType2 { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardCredentials) - root.close() + root.closeTriggered() } - - KeyNavigation.tab: qrCode.rightButton } DividerType {} @@ -76,10 +61,8 @@ DrawerType2 { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardConfigSource) - root.close() + root.closeTriggered() } - - KeyNavigation.tab: focusItem } DividerType {} diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index b0e074d0..8ddccb5a 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -17,56 +17,87 @@ ListView { property var rootWidth property var selectedText - property bool a: true - width: rootWidth - height: menuContent.contentItem.height + height: contentItem.height // TODO: It should be fixed size, not content item height clip: true - interactive: false + // interactive: false - property FlickableType parentFlickable - property var lastItemTabClicked + // property FlickableType parentFlickable + // property var lastItemTabClicked - property int currentFocusIndex: 0 + // property int currentFocusIndex: 0 - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } - } + // snapMode: ListView.SnapToItem + + // ScrollBar.vertical: ScrollBar {} + + property bool isFocusable: true Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } else { - currentFocusIndex = 0 - if (lastItemTabClicked && typeof lastItemTabClicked === "function") { - lastItemTabClicked() - } - } + FocusController.nextKeyTabItem() } - onVisibleChanged: { - if (visible) { - currentFocusIndex = 0 - focusItem.forceActiveFocus() - } - } - - Item { - id: focusItem + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() } - onCurrentFocusIndexChanged: { - if (parentFlickable) { - parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex)) - } + Keys.onUpPressed: { + FocusController.nextKeyUpItem() } + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + // activeFocusOnTab: true + // onActiveFocusChanged: { + // console.log("===========================") + // positionViewAtEnd() + // parentFlickable.ensureVisible(this.itemAtIndex(6)) + // if (activeFocus) { + // this.currentFocusIndex = 0 + // this.itemAtIndex(currentFocusIndex).forceActiveFocus() + // } + // } + + // Keys.onTabPressed: { + // if (currentFocusIndex < this.count - 1) { + // currentFocusIndex += 1 + // this.itemAtIndex(currentFocusIndex).forceActiveFocus() + // } else { + // currentFocusIndex = 0 + // if (lastItemTabClicked && typeof lastItemTabClicked === "function") { + // lastItemTabClicked() + // } + // } + // } + + // onVisibleChanged: { + // if (visible) { + // currentFocusIndex = 0 + // focusItem.forceActiveFocus() + // } + // } + + // Item { + // id: focusItem + // } + + // onCurrentFocusIndexChanged: { + // if (parentFlickable) { + // parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex)) + // } + // } + ButtonGroup { id: containersRadioButtonGroup } @@ -75,12 +106,6 @@ ListView { implicitWidth: rootWidth implicitHeight: content.implicitHeight - onActiveFocusChanged: { - if (activeFocus) { - containerRadioButton.forceActiveFocus() - } - } - ColumnLayout { id: content @@ -111,13 +136,13 @@ ListView { } if (checked) { - containersDropDown.close() + containersDropDown.closeTriggered() // TODO: containersDropDown is outside this file ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) } else { ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.close() + containersDropDown.closeTriggered() } } diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index 405d4eda..097274a4 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -16,7 +16,7 @@ DrawerType2 { anchors.fill: parent expandedHeight: parent.height * 0.9 - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: content anchors.top: parent.top @@ -24,14 +24,6 @@ DrawerType2 { anchors.right: parent.right spacing: 0 - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - Header2Type { Layout.fillWidth: true Layout.topMargin: 24 @@ -43,11 +35,6 @@ DrawerType2 { descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") } - Item { - id: focusItem - KeyNavigation.tab: splitTunnelingSwitch.visible ? splitTunnelingSwitch : siteBasedSplitTunnelingSwitch.rightButton - } - LabelWithButtonType { id: splitTunnelingSwitch Layout.fillWidth: true @@ -59,11 +46,9 @@ DrawerType2 { descriptionText: qsTr("Enabled \nCan't be disabled for current server") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: siteBasedSplitTunnelingSwitch.visible ? siteBasedSplitTunnelingSwitch.rightButton : focusItem - clickedFunction: function() { -// PageController.goToPage(PageEnum.PageSettingsSplitTunneling) -// root.close() + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + root.closeTriggered() } } @@ -80,13 +65,9 @@ DrawerType2 { descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: appSplitTunnelingSwitch.visible ? - appSplitTunnelingSwitch.rightButton : - focusItem - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsSplitTunneling) - root.close() + root.closeTriggered() } } @@ -103,11 +84,9 @@ DrawerType2 { descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: focusItem - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) - root.close() + root.closeTriggered() } } diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index e5d10055..ca579491 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -26,7 +26,7 @@ DrawerType2 { id: installedAppsModel } - expandedContent: Item { + expandedStateContent: Item { id: container implicitHeight: expandedHeight @@ -43,7 +43,7 @@ DrawerType2 { BackButtonType { backButtonImage: "qrc:/images/controls/arrow-left.svg" backButtonFunction: function() { - root.close() + root.closeTriggered() } } @@ -69,6 +69,8 @@ DrawerType2 { clip: true interactive: true + property bool isFocusable: true + model: SortFilterProxyModel { id: proxyInstalledAppsModel sourceModel: installedAppsModel @@ -155,7 +157,7 @@ DrawerType2 { PageController.showBusyIndicator(true) AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo()) PageController.showBusyIndicator(false) - root.close() + root.closeTriggered() } } } diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index a0e86dbc..0c14e52d 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -20,7 +20,7 @@ DrawerType2 { property var yesButtonFunction property var noButtonFunction - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: content anchors.top: parent.top @@ -33,14 +33,6 @@ DrawerType2 { root.expandedHeight = content.implicitHeight + 32 } - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - Header2TextType { Layout.fillWidth: true Layout.topMargin: 16 @@ -59,11 +51,6 @@ DrawerType2 { text: descriptionText } - Item { - id: focusItem - KeyNavigation.tab: yesButton - } - BasicButtonType { id: yesButton Layout.fillWidth: true @@ -78,8 +65,6 @@ DrawerType2 { yesButtonFunction() } } - - KeyNavigation.tab: noButton } BasicButtonType { @@ -102,8 +87,6 @@ DrawerType2 { noButtonFunction() } } - - KeyNavigation.tab: focusItem } } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 4d9d7f0e..a48515cc 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -11,7 +11,7 @@ import "../Config" DrawerType2 { id: root - expandedContent: Item { + expandedStateContent: Item { id: container implicitHeight: root.height * 0.9 @@ -20,19 +20,6 @@ DrawerType2 { root.expandedHeight = container.implicitHeight } - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -43,167 +30,173 @@ DrawerType2 { BackButtonType { id: backButton + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + backButtonImage: "qrc:/images/controls/arrow-left.svg" - backButtonFunction: function() { root.close() } - KeyNavigation.tab: listView + backButtonFunction: function() { root.closeTriggered() } + } + + Header2Type { + id: header + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Choose language") } } - FlickableType { + ListView { + id: listView + anchors.top: backButtonLayout.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - contentHeight: content.implicitHeight - ColumnLayout { - id: content + clip: true + interactive: true - anchors.fill: parent + property bool isFocusable: true - Header2Type { - id: header - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.rightMargin: 16 - Layout.leftMargin: 16 + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - headerText: qsTr("Choose language") - } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } - ListView { - id: listView + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } - Layout.fillWidth: true - height: listView.contentItem.height + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } - clip: true - interactive: false + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } - model: LanguageModel - currentIndex: LanguageModel.currentLanguageIndex + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } - ButtonGroup { - id: buttonGroup - } + model: LanguageModel + currentIndex: LanguageModel.currentLanguageIndex - property int currentFocusIndex: 0 + ButtonGroup { + id: buttonGroup + } - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } - } + delegate: Item { + implicitWidth: root.width + implicitHeight: delegateContent.implicitHeight - Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } else { - listViewFocusItem.forceActiveFocus() - focusItem.forceActiveFocus() - } - } + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + property bool isFocusable: true - Item { - id: listViewFocusItem Keys.onTabPressed: { - root.forceActiveFocus() + FocusController.nextKeyTabItem() } - } - onVisibleChanged: { - if (visible) { - listViewFocusItem.forceActiveFocus() - focusItem.forceActiveFocus() + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() } - } - delegate: Item { - implicitWidth: root.width - implicitHeight: delegateContent.implicitHeight + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } - onActiveFocusChanged: { - if (activeFocus) { - radioButton.forceActiveFocus() + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + indicator: Rectangle { + width: parent.width - 1 + height: parent.height + color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack + border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent + border.width: radioButton.focus ? 1 : 0 + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } } } - ColumnLayout { - id: delegateContent - + RowLayout { + id: radioButtonContent anchors.fill: parent - RadioButton { - id: radioButton + anchors.rightMargin: 16 + anchors.leftMargin: 16 - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + spacing: 0 - hoverEnabled: true + z: 1 - indicator: Rectangle { - width: parent.width - 1 - height: parent.height - color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack - border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent - border.width: radioButton.focus ? 1 : 0 + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 - Behavior on color { - PropertyAnimation { duration: 200 } - } - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - } + text: languageName + } - RowLayout { - id: radioButtonContent - anchors.fill: parent + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked - anchors.rightMargin: 16 - anchors.leftMargin: 16 + width: 24 + height: 24 - spacing: 0 - - z: 1 - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - - text: languageName - } - - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - } - - ButtonGroup.group: buttonGroup - checked: listView.currentIndex === index - - onClicked: { - listView.currentIndex = index - LanguageModel.changeLanguage(languageIndex) - root.close() - } + Layout.rightMargin: 8 } } - Keys.onEnterPressed: radioButton.clicked() - Keys.onReturnPressed: radioButton.clicked() + ButtonGroup.group: buttonGroup + checked: listView.currentIndex === index + + onClicked: { + listView.currentIndex = index + LanguageModel.changeLanguage(languageIndex) + root.closeTriggered() + } } } + + Keys.onEnterPressed: radioButton.clicked() + Keys.onReturnPressed: radioButton.clicked() } } } diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml new file mode 100644 index 00000000..9270c0e6 --- /dev/null +++ b/client/ui/qml/Components/ServersListView.qml @@ -0,0 +1,133 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ContainersModelFilters 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +ListView { + id: root + + anchors.top: serversMenuHeader.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.topMargin: 16 + + model: ServersModel + currentIndex: ServersModel.defaultIndex + + ScrollBar.vertical: ScrollBar { + id: scrollBar + objectName: "scrollBar" + policy: root.height >= root.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } + + property bool isFocusable: true + + Connections { + target: ServersModel + function onDefaultServerIndexChanged(serverIndex) { + root.currentIndex = serverIndex + } + } + + clip: true + + delegate: Item { + id: menuContentDelegate + objectName: "menuContentDelegate" + + property variant delegateData: model + property VerticalRadioButton serverRadioButtonProperty: serverRadioButton + + implicitWidth: root.width + implicitHeight: serverRadioButtonContent.implicitHeight + + ColumnLayout { + id: serverRadioButtonContent + objectName: "serverRadioButtonContent" + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 0 + + RowLayout { + objectName: "serverRadioButtonRowLayout" + + Layout.fillWidth: true + VerticalRadioButton { + id: serverRadioButton + objectName: "serverRadioButton" + + Layout.fillWidth: true + focus: true + text: name + descriptionText: serverDescription + + checked: index === root.currentIndex + checkable: !ConnectionController.isConnected + + ButtonGroup.group: serversRadioButtonGroup + + onClicked: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) + return + } + + root.currentIndex = index + + ServersModel.defaultIndex = index + } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + + Keys.onEnterPressed: serverRadioButton.clicked() + Keys.onReturnPressed: serverRadioButton.clicked() + } + + ImageButtonType { + id: serverInfoButton + objectName: "serverInfoButton" + + image: "qrc:/images/controls/settings.svg" + imageColor: AmneziaStyle.color.paleGray + + implicitWidth: 56 + implicitHeight: 56 + + z: 1 + + onClicked: function() { + ServersModel.processedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) + drawer.closeTriggered() + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + } + } +} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 769e1abd..b3357b9c 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -22,28 +22,7 @@ ListView { clip: true interactive: false - activeFocusOnTab: true - Keys.onTabPressed: { - if (currentIndex < this.count - 1) { - this.incrementCurrentIndex() - } else { - currentIndex = 0 - lastItemTabClickedSignal() - } - } - - onCurrentIndexChanged: { - if (visible) { - if (fl.contentHeight > fl.height) { - var item = this.currentItem - if (item.y < fl.height) { - fl.contentY = item.y - } else if (item.y + item.height > fl.contentY + fl.height) { - fl.contentY = item.y + item.height - fl.height - } - } - } - } + property bool isFocusable: false onVisibleChanged: { if (visible) { @@ -55,12 +34,6 @@ ListView { implicitWidth: root.width implicitHeight: delegateContent.implicitHeight - onActiveFocusChanged: { - if (activeFocus) { - containerRadioButton.rightButton.forceActiveFocus() - } - } - ColumnLayout { id: delegateContent diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index d2bf28ab..7e8db993 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -36,17 +36,9 @@ DrawerType2 { configFileName = "amnezia_config" } - expandedContent: Item { + expandedStateContent: Item { implicitHeight: root.expandedHeight - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - header.forceActiveFocus() - } - } - Header2Type { id: header anchors.top: parent.top @@ -57,24 +49,52 @@ DrawerType2 { anchors.rightMargin: 16 headerText: root.headerText - - KeyNavigation.tab: shareButton } - FlickableType { + ListView { + id: listView + anchors.top: header.bottom anchors.bottom: parent.bottom - contentHeight: content.height + 32 + anchors.left: parent.left + anchors.right: parent.right - ColumnLayout { - id: content + property bool isFocusable: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - anchors.leftMargin: 16 - anchors.rightMargin: 16 + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + + model: 1 + + clip: true + + header: ColumnLayout { + width: listView.width visible: root.contentVisible @@ -82,12 +102,12 @@ DrawerType2 { id: shareButton Layout.fillWidth: true Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - KeyNavigation.tab: copyConfigTextButton - clickedFunc: function() { var fileName = "" if (GC.isMobile()) { @@ -111,6 +131,8 @@ DrawerType2 { id: copyConfigTextButton Layout.fillWidth: true Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -124,14 +146,14 @@ DrawerType2 { Keys.onReturnPressed: { copyConfigTextButton.clicked() } Keys.onEnterPressed: { copyConfigTextButton.clicked() } - - KeyNavigation.tab: copyNativeConfigStringButton.visible ? copyNativeConfigStringButton : showSettingsButton } BasicButtonType { id: copyNativeConfigStringButton Layout.fillWidth: true Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: false @@ -153,6 +175,8 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -164,10 +188,8 @@ DrawerType2 { text: qsTr("Show connection settings") clickedFunc: function() { - configContentDrawer.open() + configContentDrawer.openTriggered() } - - KeyNavigation.tab: header } DrawerType2 { @@ -178,30 +200,11 @@ DrawerType2 { anchors.fill: parent expandedHeight: parent.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - header.forceActiveFocus() - } - } - - expandedContent: Item { + expandedStateContent: Item { id: configContentContainer implicitHeight: configContentDrawer.expandedHeight - Connections { - target: configContentDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - Connections { target: copyNativeConfigStringButton function onClicked() { @@ -231,9 +234,7 @@ DrawerType2 { anchors.right: parent.right anchors.topMargin: 16 - backButtonFunction: function() { configContentDrawer.close() } - - KeyNavigation.tab: focusItem + backButtonFunction: function() { configContentDrawer.closeTriggered() } } FlickableType { @@ -302,6 +303,10 @@ DrawerType2 { } } } + } + + delegate: ColumnLayout { + width: listView.width Rectangle { id: qrCodeContainer @@ -309,6 +314,8 @@ DrawerType2 { Layout.fillWidth: true Layout.preferredHeight: width Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: ExportController.qrCodesCount > 0 @@ -320,6 +327,32 @@ DrawerType2 { source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + Timer { property int index: 0 interval: 1000 @@ -346,6 +379,8 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: ExportController.qrCodesCount > 0 diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml index e40dd4bb..323892fa 100644 --- a/client/ui/qml/Components/TransportProtoSelector.qml +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -39,8 +39,6 @@ Rectangle { implicitWidth: (rootWidth - 32) / 2 text: "UDP" - KeyNavigation.tab: tcpButton - onClicked: { root.currentIndex = 0 } diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 86fc86a2..40136ad5 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -4,7 +4,7 @@ import Qt5Compat.GraphicalEffects import Style 1.0 -Item { +FocusScope { id: root property string backButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -15,12 +15,6 @@ Item { visible: backButtonImage !== "" - onActiveFocusChanged: { - if (activeFocus) { - backButton.forceActiveFocus() - } - } - RowLayout { id: content diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 828c32bc..aa8103e9 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -35,6 +35,32 @@ Button { property alias buttonTextLabel: buttonText + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + implicitHeight: 56 hoverEnabled: true diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index f584a8fc..8e689541 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -22,6 +22,7 @@ RadioButton { property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot property string selectedBorderColor: AmneziaStyle.color.goldenApricot property string defaultBodredColor: AmneziaStyle.color.transparent + property string focusBorderColor: AmneziaStyle.color.paleGray property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -29,6 +30,32 @@ RadioButton { hoverEnabled: true + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + indicator: Rectangle { anchors.fill: parent radius: 16 @@ -52,6 +79,8 @@ RadioButton { return pressedBorderColor } else if (root.checked) { return selectedBorderColor + } else if (root.activeFocus) { + return focusBorderColor } } return defaultBodredColor @@ -59,7 +88,7 @@ RadioButton { border.width: { if (root.enabled) { - if(root.checked) { + if(root.checked || root.activeFocus) { return 1 } return root.pressed ? 1 : 0 diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml index fea65116..482b5217 100644 --- a/client/ui/qml/Controls2/CardWithIconsType.qml +++ b/client/ui/qml/Controls2/CardWithIconsType.qml @@ -25,10 +25,15 @@ Button { property real textOpacity: 1.0 + property alias focusItem: rightImage + + property FlickableType parentFlickable + hoverEnabled: true background: Rectangle { id: backgroundRect + anchors.fill: parent radius: 16 @@ -39,13 +44,31 @@ Button { } } + function ensureVisible(item) { + if (item.activeFocus) { + if (root.parentFlickable) { + root.parentFlickable.ensureVisible(root) + } + } + } + + onFocusChanged: { + ensureVisible(root) + } + + focusItem.onFocusChanged: { + root.ensureVisible(focusItem) + } + contentItem: Item { anchors.left: parent.left anchors.right: parent.right implicitHeight: content.implicitHeight + RowLayout { id: content + anchors.fill: parent Image { @@ -61,6 +84,7 @@ Button { } ColumnLayout { + ListItemTitleType { text: root.headerText visible: text !== "" @@ -123,6 +147,7 @@ Button { Rectangle { id: rightImageBackground + anchors.fill: parent radius: 12 color: "transparent" @@ -131,10 +156,9 @@ Button { PropertyAnimation { duration: 200 } } } + onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } + root.clicked() } } } diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml index c4b584c1..04a7635c 100644 --- a/client/ui/qml/Controls2/DrawerType2.qml +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -9,17 +9,17 @@ import "TextTypes" Item { id: root - readonly property string drawerExpanded: "expanded" - readonly property string drawerCollapsed: "collapsed" + readonly property string drawerExpandedStateName: "expanded" + readonly property string drawerCollapsedStateName: "collapsed" - readonly property bool isOpened: drawerContent.state === root.drawerExpanded || (drawerContent.state === root.drawerCollapsed && dragArea.drag.active === true) - readonly property bool isClosed: drawerContent.state === root.drawerCollapsed && dragArea.drag.active === false + // readonly property bool isExpanded: isExpandedStateActive() + // readonly property bool isCollapsed: isCollapsedStateActive() - readonly property bool isExpanded: drawerContent.state === root.drawerExpanded - readonly property bool isCollapsed: drawerContent.state === root.drawerCollapsed + readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive && (dragArea.drag.active === true)) + readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false) - property Component collapsedContent - property Component expandedContent + property Component collapsedStateContent + property Component expandedStateContent property string defaultColor: AmneziaStyle.color.onyxBlack property string borderColor: AmneziaStyle.color.slateGray @@ -29,29 +29,41 @@ Item { property int depthIndex: 0 - signal entered - signal exited + signal cursorEntered + signal cursorExited signal pressed(bool pressed, bool entered) signal aboutToHide signal aboutToShow - signal close - signal open + signal closeTriggered + signal openTriggered signal closed signal opened + function isExpandedStateActive() { + return isStateActive(drawerExpandedStateName) + } + + function isCollapsedStateActive() { + return isStateActive(drawerCollapsedStateName) + } + + function isStateActive(stateName) { + return drawerContent.state === stateName + } + Connections { target: PageController function onCloseTopDrawer() { if (depthIndex === PageController.getDrawerDepth()) { - if (isCollapsed) { + if (isCollapsedStateActive()) { return } aboutToHide() - drawerContent.state = root.drawerCollapsed + drawerContent.state = root.drawerCollapsedStateName depthIndex = 0 closed() } @@ -61,30 +73,52 @@ Item { Connections { target: root - function onClose() { - if (isCollapsed) { + function onCloseTriggered() { + if (isCollapsedStateActive()) { return } aboutToHide() - drawerContent.state = root.drawerCollapsed - depthIndex = 0 - PageController.setDrawerDepth(PageController.getDrawerDepth() - 1) closed() } - function onOpen() { - if (isExpanded) { + function onClosed() { + drawerContent.state = root.drawerCollapsedStateName + + if (root.isCollapsedStateActive()) { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } + + depthIndex = 0 + PageController.decrementDrawerDepth() + FocusController.dropRootObject(root) + } + + function onOpenTriggered() { + if (root.isExpandedStateActive()) { return } - aboutToShow() + root.aboutToShow() - drawerContent.state = root.drawerExpanded - depthIndex = PageController.getDrawerDepth() + 1 - PageController.setDrawerDepth(depthIndex) - opened() + root.opened() + } + + function onOpened() { + drawerContent.state = root.drawerExpandedStateName + + if (isExpandedStateActive()) { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + depthIndex = PageController.incrementDrawerDepth() + FocusController.pushRootObject(root) } } @@ -102,18 +136,17 @@ Item { MouseArea { id: emptyArea anchors.fill: parent - enabled: root.isExpanded - visible: enabled + onClicked: { - root.close() + root.closeTriggered() } } MouseArea { id: dragArea + objectName: "dragArea" anchors.fill: drawerContentBackground - cursorShape: root.isCollapsed ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true enabled: drawerContent.implicitHeight > 0 @@ -125,35 +158,36 @@ Item { /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { - if (root.isCollapsed && drawerContent.y < dragArea.drag.maximumY) { - root.open() + if (isCollapsedStateActive() && drawerContent.y < dragArea.drag.maximumY) { + root.openTriggered() return } - if (root.isExpanded && drawerContent.y > dragArea.drag.minimumY) { - root.close() + if (isExpandedStateActive() && drawerContent.y > dragArea.drag.minimumY) { + root.closeTriggered() return } } onEntered: { - root.entered() + root.cursorEntered() } onExited: { - root.exited() + root.cursorExited() } onPressedChanged: { root.pressed(pressed, entered) } onClicked: { - if (root.isCollapsed) { - root.open() + if (isCollapsedStateActive()) { + root.openTriggered() } } } Rectangle { id: drawerContentBackground + objectName: "drawerContentBackground" anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top } height: root.height @@ -174,53 +208,80 @@ Item { Item { id: drawerContent + objectName: "drawerContent" Drag.active: dragArea.drag.active anchors.right: root.right anchors.left: root.left - y: root.height - drawerContent.height - state: root.drawerCollapsed - implicitHeight: root.isCollapsed ? collapsedHeight : expandedHeight - - onStateChanged: { - if (root.isCollapsed) { - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - return - } - if (root.isExpanded) { - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } - return - } - } + state: root.drawerCollapsedStateName states: [ State { - name: root.drawerCollapsed + name: root.drawerCollapsedStateName PropertyChanges { target: drawerContent + implicitHeight: collapsedHeight y: root.height - root.collapsedHeight } + PropertyChanges { + target: background + color: AmneziaStyle.color.transparent + } + PropertyChanges { + target: dragArea + cursorShape: Qt.PointingHandCursor + } + PropertyChanges { + target: emptyArea + enabled: false + visible: false + } + PropertyChanges { + target: collapsedLoader + // visible: true + } + PropertyChanges { + target: expandedLoader + visible: false + + } }, State { - name: root.drawerExpanded + name: root.drawerExpandedStateName PropertyChanges { target: drawerContent + implicitHeight: expandedHeight y: dragArea.drag.minimumY - + } + PropertyChanges { + target: background + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + PropertyChanges { + target: dragArea + cursorShape: Qt.ArrowCursor + } + PropertyChanges { + target: emptyArea + enabled: true + visible: true + } + PropertyChanges { + target: collapsedLoader + // visible: false + } + PropertyChanges { + target: expandedLoader + visible: true } } ] transitions: [ Transition { - from: root.drawerCollapsed - to: root.drawerExpanded + from: root.drawerCollapsedStateName + to: root.drawerExpandedStateName PropertyAnimation { target: drawerContent properties: "y" @@ -228,8 +289,8 @@ Item { } }, Transition { - from: root.drawerExpanded - to: root.drawerCollapsed + from: root.drawerExpandedStateName + to: root.drawerCollapsedStateName PropertyAnimation { target: drawerContent properties: "y" @@ -241,7 +302,7 @@ Item { Loader { id: collapsedLoader - sourceComponent: root.collapsedContent + sourceComponent: root.collapsedStateContent anchors.right: parent.right anchors.left: parent.left @@ -250,8 +311,7 @@ Item { Loader { id: expandedLoader - visible: root.isExpanded - sourceComponent: root.expandedContent + sourceComponent: root.expandedStateContent anchors.right: parent.right anchors.left: parent.left diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 906cfffe..787890cb 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -45,33 +45,56 @@ Item { property Item drawerParent property Component listView - signal open - signal close + signal openTriggered + signal closeTriggered - function popupClosedFunc() { - if (!GC.isMobile()) { - this.forceActiveFocus() - } + readonly property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() } - property var parentFlickable - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() } implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight - onOpen: { - menu.open() + onOpenTriggered: { + menu.openTriggered() } - onClose: { - menu.close() + onCloseTriggered: { + menu.closeTriggered() + } + + Keys.onEnterPressed: { + if (menu.isClosed) { + menu.openTriggered() + } + } + + Keys.onReturnPressed: { + if (menu.isClosed) { + menu.openTriggered() + } } Rectangle { @@ -173,7 +196,7 @@ Item { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() } else { - menu.open() + menu.openTriggered() } } } @@ -186,27 +209,10 @@ Item { anchors.fill: parent expandedHeight: drawerParent.height * drawerHeight - onClosed: { - root.popupClosedFunc() - } - - expandedContent: Item { + expandedStateContent: Item { id: container implicitHeight: menu.expandedHeight - Connections { - target: menu - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: header @@ -218,61 +224,40 @@ Item { BackButtonType { id: backButton backButtonImage: root.headerBackButtonImage - backButtonFunction: function() { menu.close() } - KeyNavigation.tab: listViewLoader.item - } - } - - FlickableType { - id: flickable - 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 - - Header2Type { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - headerText: root.headerText - - width: parent.width - } - - Loader { - id: listViewLoader - sourceComponent: root.listView - - onLoaded: { - listViewLoader.item.parentFlickable = flickable - listViewLoader.item.lastItemTabClicked = function() { - focusItem.forceActiveFocus() - } + backButtonFunction: function() { menu.closeTriggered() } + onActiveFocusChanged: { + if(backButton.enabled && backButton.activeFocus) { + root.listView.positionViewAtBeginning() } } } } - } - } - Keys.onEnterPressed: { - if (menu.isClosed) { - menu.open() - } - } + Column { + id: col + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 - Keys.onReturnPressed: { - if (menu.isClosed) { - menu.open() + spacing: 16 + + Header2Type { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + headerText: root.headerText + + width: parent.width + } + + Loader { + id: listViewLoader + sourceComponent: root.listView + } + } } } } diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index bcd14487..2151e520 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -7,10 +7,11 @@ Flickable { function ensureVisible(item) { if (item.y < fl.contentY) { - fl.contentY = item.y + fl.contentY = item.y - 40 // 40 is a top margin } else if (item.y + item.height > fl.contentY + fl.height) { fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin } + fl.returnToBounds() } clip: true diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index f1cafbff..1366148d 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -19,8 +19,6 @@ Item { property string descriptionText - focus: true - implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 1ac1cd30..89cc1658 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -27,6 +27,32 @@ RadioButton { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + indicator: Rectangle { anchors.fill: parent radius: 16 diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index fffb6d84..d5f646a7 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -24,22 +24,39 @@ Button { property int borderFocusedWidth: 1 hoverEnabled: true - focus: true - focusPolicy: Qt.TabFocus icon.source: image icon.color: root.enabled ? imageColor : disableImageColor - property Flickable parentFlickable + property bool isFocusable: true - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(this) - } - } + Keys.onTabPressed: { + FocusController.nextKeyTabItem() } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + Keys.onEnterPressed: root.clicked() + Keys.onReturnPressed: root.clicked() + Behavior on icon.color { PropertyAnimation { duration: 200 } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 41faf108..087415f7 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -41,6 +41,32 @@ Item { property bool descriptionOnTop: false property bool hideDescription: true + property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index f7b777a7..8409e964 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -26,47 +26,31 @@ ListView { height: root.contentItem.height clip: true - interactive: false - property FlickableType parentFlickable - property var lastItemTabClicked - - property int currentFocusIndex: 0 - - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } - } + property bool isFocusable: true Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - } else { - currentFocusIndex = 0 - } - this.itemAtIndex(currentFocusIndex).forceActiveFocus() + FocusController.nextKeyTabItem() } - Item { - id: focusItem - Keys.onTabPressed: { - root.forceActiveFocus() - } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() } - onVisibleChanged: { - if (visible) { - focusItem.forceActiveFocus() - } + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() } - onCurrentFocusIndexChanged: { - if (parentFlickable) { - parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex)) - } + Keys.onRightPressed: { + FocusController.nextKeyRightItem() } ButtonGroup { @@ -75,94 +59,110 @@ ListView { function triggerCurrentItem() { var item = root.itemAtIndex(currentIndex) - var radioButton = item.children[0].children[0] + var radioButton = item.children[0] radioButton.clicked() } - delegate: Item { + delegate: ColumnLayout { + id: content + implicitWidth: rootWidth - implicitHeight: content.implicitHeight + // implicitHeight: content.implicitHeight - onActiveFocusChanged: { - if (activeFocus) { - radioButton.forceActiveFocus() + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() } - } - ColumnLayout { - id: content + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } - anchors.fill: parent + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } - RadioButton { - id: radioButton + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } - hoverEnabled: true + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } - indicator: Rectangle { - width: parent.width - 1 - height: parent.height - color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack - border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent - border.width: radioButton.focus ? 1 : 0 + indicator: Rectangle { + width: parent.width - 1 + height: parent.height + color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack + border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent + border.width: radioButton.focus ? 1 : 0 - Behavior on color { - PropertyAnimation { duration: 200 } - } - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - enabled: false - } + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } } - RowLayout { - id: radioButtonContent + MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } + } - anchors.rightMargin: 16 - anchors.leftMargin: 16 + RowLayout { + id: radioButtonContent + anchors.fill: parent - z: 1 + anchors.rightMargin: 16 + anchors.leftMargin: 16 - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 + z: 1 - text: name - maximumLineCount: root.textMaximumLineCount - elide: root.textElide + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 - } + text: name + maximumLineCount: root.textMaximumLineCount + elide: root.textElide - Image { - source: imageSource - visible: radioButton.checked - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } } - ButtonGroup.group: buttonGroup - checked: root.currentIndex === index + Image { + source: imageSource + visible: radioButton.checked - onClicked: { - root.currentIndex = index - root.selectedText = name - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: root.currentIndex === index + + onClicked: { + root.currentIndex = index + root.selectedText = name + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() } } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 0a2a2998..edeb07fe 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -9,51 +9,19 @@ Item { property StackView stackView: StackView.view - property var defaultActiveFocusItem: null - onVisibleChanged: { if (visible && !GC.isMobile()) { timer.start() } } - function lastItemTabClicked(focusItem) { - if (GC.isMobile()) { - return - } - - if (focusItem) { - focusItem.forceActiveFocus() - PageController.forceTabBarActiveFocus() - } else { - if (defaultActiveFocusItem) { - defaultActiveFocusItem.forceActiveFocus() - } - PageController.forceTabBarActiveFocus() - } - } - -// MouseArea { -// id: globalMouseArea -// z: 99 -// anchors.fill: parent - -// enabled: true - -// onPressed: function(mouse) { -// forceActiveFocus() -// mouse.accepted = false -// } -// } - // Set a timer to set focus after a short delay Timer { id: timer - interval: 100 // Milliseconds + interval: 200 // Milliseconds onTriggered: { - if (defaultActiveFocusItem) { - defaultActiveFocusItem.forceActiveFocus() - } + FocusController.resetRootObject() + FocusController.setFocusOnDefaultItem() } repeat: false // Stop the timer after one trigger running: !GC.isMobile() // Start the timer diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 7a6a770e..d067fc12 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import Style 1.0 import "TextTypes" +import "../Config" Popup { id: root @@ -28,11 +29,11 @@ Popup { } onOpened: { - focusItem.forceActiveFocus() + timer.start() } onClosed: { - PageController.forceStackActiveFocus() + FocusController.dropRootObject(root) } background: Rectangle { @@ -42,6 +43,19 @@ Popup { radius: 4 } + Timer { + id: timer + interval: 400 // Milliseconds + onTriggered: { + if (!GC.isMobile()) { + FocusController.setFocusItem(closeButton) + FocusController.pushRootObject(root) + } + } + repeat: false // Stop the timer after one trigger + running: !GC.isMobile() // Start the timer + } + contentItem: Item { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -72,11 +86,6 @@ Popup { } } - Item { - id: focusItem - KeyNavigation.tab: closeButton - } - BasicButtonType { id: closeButton visible: closeButtonVisible @@ -92,7 +101,6 @@ Popup { borderWidth: 0 text: qsTr("Close") - KeyNavigation.tab: focusItem clickedFunc: function() { root.close() diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index 43c35778..77b657e7 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -35,10 +35,37 @@ Switch { property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + hoverEnabled: enabled ? true : false focusPolicy: Qt.TabFocus property FlickableType parentFlickable: null + onFocusChanged: { if (root.activeFocus) { if (root.parentFlickable) { @@ -132,12 +159,26 @@ Switch { } Keys.onEnterPressed: { - root.checked = !root.checked - root.checkedChanged() + if (!event.isAutoRepeat) { + root.checked = !root.checked + root.checkedChanged() + } + event.accepted = true } Keys.onReturnPressed: { - root.checked = !root.checked - root.checkedChanged() + if (!event.isAutoRepeat) { + root.checked = !root.checked + root.checkedChanged() + } + event.accepted = true + } + + Keys.onSpacePressed: { + if (!event.isAutoRepeat) { + root.checked = !root.checked + root.checkedChanged() + } + event.accepted = true } } diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index d57ff3a0..0e48d975 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -17,10 +17,35 @@ TabButton { property bool isSelected: false + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + implicitHeight: 48 hoverEnabled: true - focusPolicy: Qt.TabFocus background: Rectangle { id: background diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml index abe544aa..b49ad8eb 100644 --- a/client/ui/qml/Controls2/TabImageButtonType.qml +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -14,13 +14,38 @@ TabButton { property bool isSelected: false + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + property string borderFocusedColor: AmneziaStyle.color.paleGray property int borderFocusedWidth: 1 property var clickedFunc hoverEnabled: true - focusPolicy: Qt.TabFocus icon.source: image icon.color: isSelected ? selectedColor : defaultColor @@ -41,7 +66,7 @@ TabButton { cursorShape: Qt.PointingHandCursor enabled: false } - + Keys.onEnterPressed: { if (root.clickedFunc && typeof root.clickedFunc === "function") { root.clickedFunc() diff --git a/client/ui/qml/Controls2/TextAreaWithFooterType.qml b/client/ui/qml/Controls2/TextAreaWithFooterType.qml index 102929e2..cf7b9146 100644 --- a/client/ui/qml/Controls2/TextAreaWithFooterType.qml +++ b/client/ui/qml/Controls2/TextAreaWithFooterType.qml @@ -78,9 +78,6 @@ Rectangle { placeholderText: root.placeholderText text: root.text - - KeyNavigation.tab: firstButton - onCursorVisibleChanged: { if (textArea.cursorVisible) { fl.interactive = true diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 365faa94..fbea618b 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -40,6 +40,7 @@ Item { implicitHeight: content.implicitHeight property FlickableType parentFlickable + Connections { target: textField function onFocusChanged() { @@ -84,7 +85,16 @@ Item { TextField { id: textField - activeFocusOnTab: false + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } enabled: root.textFieldEditable color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor @@ -209,9 +219,9 @@ Item { clickedFunc() } - if (KeyNavigation.tab) { - KeyNavigation.tab.forceActiveFocus(); - } + // if (KeyNavigation.tab) { + // KeyNavigation.tab.forceActiveFocus(); + // } } Keys.onReturnPressed: { @@ -219,8 +229,8 @@ Item { clickedFunc() } - if (KeyNavigation.tab) { - KeyNavigation.tab.forceActiveFocus(); - } + // if (KeyNavigation.tab) { + // KeyNavigation.tab.forceActiveFocus(); + // } } } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 1a781f20..dcf0f1d9 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -28,8 +28,34 @@ RadioButton { property string imageSource property bool showImage + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + hoverEnabled: true - focusPolicy: Qt.TabFocus + // focusPolicy: Qt.TabFocus indicator: Rectangle { id: background diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index 5da40eff..7f3ff1f0 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -16,13 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -34,7 +27,6 @@ PageType { BackButtonType { id: backButton - // KeyNavigation.tab: removeButton } } @@ -61,7 +53,6 @@ PageType { headerText: "Dev menu" } - TextFieldWithHeaderType { id: passwordTextField @@ -86,8 +77,6 @@ PageType { SettingsController.gatewayEndpoint = textFieldText } } - - // KeyNavigation.tab: saveButton } SwitcherType { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8422a10f..f5ac8ae3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -19,13 +19,13 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - Connections { + objectName: "pageControllerConnections" + target: PageController function onRestorePageHomeState(isContainerInstalled) { - drawer.open() + drawer.openTriggered() if (isContainerInstalled) { containersDropDown.rootButtonClickedFunction() } @@ -33,23 +33,22 @@ PageType { } Item { + objectName: "homeColumnItem" + anchors.fill: parent anchors.bottomMargin: drawer.collapsedHeight ColumnLayout { + objectName: "homeColumnLayout" + anchors.fill: parent anchors.topMargin: 34 anchors.bottomMargin: 34 - Item { - id: focusItem - KeyNavigation.tab: loggingButton.visible ? - loggingButton : - connectButton - } - BasicButtonType { id: loggingButton + objectName: "loggingButton" + property bool isLoggingEnabled: SettingsController.isLoggingEnabled Layout.alignment: Qt.AlignHCenter @@ -69,8 +68,6 @@ PageType { Keys.onEnterPressed: loggingButton.clicked() Keys.onReturnPressed: loggingButton.clicked() - KeyNavigation.tab: connectButton - onClicked: { PageController.goToPage(PageEnum.PageSettingsLogging) } @@ -78,13 +75,15 @@ PageType { ConnectButton { id: connectButton + objectName: "connectButton" + Layout.fillHeight: true Layout.alignment: Qt.AlignCenter - KeyNavigation.tab: splitTunnelingButton } BasicButtonType { id: splitTunnelingButton + objectName: "splitTunnelingButton" Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: 34 @@ -115,53 +114,44 @@ PageType { Keys.onEnterPressed: splitTunnelingButton.clicked() Keys.onReturnPressed: splitTunnelingButton.clicked() - KeyNavigation.tab: drawer - onClicked: { - homeSplitTunnelingDrawer.open() + homeSplitTunnelingDrawer.openTriggered() } HomeSplitTunnelingDrawer { id: homeSplitTunnelingDrawer - parent: root + objectName: "homeSplitTunnelingDrawer" - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } + parent: root } } } } - DrawerType2 { id: drawer + objectName: "drawerProtocol" + anchors.fill: parent - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } + collapsedStateContent: Item { + objectName: "ProtocolDrawerCollapsedContent" - collapsedContent: Item { implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77 Component.onCompleted: { drawer.expandedHeight = implicitHeight } + Connections { + objectName: "drawerConnections" + target: drawer enabled: !GC.isMobile() - function onActiveFocusChanged() { - if (drawer.activeFocus && !drawer.isOpened) { - collapsedButtonChevron.forceActiveFocus() - } - } } + ColumnLayout { id: collapsed + objectName: "collapsedColumnLayout" anchors.left: parent.left anchors.right: parent.right @@ -180,6 +170,8 @@ PageType { } RowLayout { + objectName: "rowLayout" + Layout.topMargin: 14 Layout.leftMargin: 24 Layout.rightMargin: 24 @@ -188,9 +180,11 @@ PageType { spacing: 0 Connections { + objectName: "drawerConnections" + target: drawer - function onEntered() { - if (drawer.isCollapsed) { + function onCursorEntered() { + if (drawer.isCollapsedStateActive) { collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor collapsedButtonHeader.opacity = 0.8 } else { @@ -198,8 +192,8 @@ PageType { } } - function onExited() { - if (drawer.isCollapsed) { + function onCursorExited() { + if (drawer.isCollapsedStateActive) { collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor collapsedButtonHeader.opacity = 1 } else { @@ -208,7 +202,7 @@ PageType { } function onPressed(pressed, entered) { - if (drawer.isCollapsed) { + if (drawer.isCollapsedStateActive) { collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor collapsedButtonHeader.opacity = 0.7 } else { @@ -219,6 +213,8 @@ PageType { Header1TextType { id: collapsedButtonHeader + objectName: "collapsedButtonHeader" + Layout.maximumWidth: drawer.width - 48 - 18 - 12 maximumLineCount: 2 @@ -227,8 +223,6 @@ PageType { text: ServersModel.defaultServerName horizontalAlignment: Qt.AlignHCenter - KeyNavigation.tab: tabBar - Behavior on opacity { PropertyAnimation { duration: 200 } } @@ -236,10 +230,11 @@ PageType { ImageButtonType { id: collapsedButtonChevron + objectName: "collapsedButtonChevron" Layout.leftMargin: 8 - visible: drawer.isCollapsed + visible: drawer.isCollapsedStateActive() hoverEnabled: false image: "qrc:/images/controls/chevron-down.svg" @@ -254,18 +249,17 @@ PageType { Keys.onEnterPressed: collapsedButtonChevron.clicked() Keys.onReturnPressed: collapsedButtonChevron.clicked() - Keys.onTabPressed: lastItemTabClicked() - onClicked: { - if (drawer.isCollapsed) { - drawer.open() + if (drawer.isCollapsedStateActive()) { + drawer.openTriggered() } } } } RowLayout { + objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16 @@ -316,6 +310,7 @@ PageType { ColumnLayout { id: serversMenuHeader + objectName: "serversMenuHeader" anchors.top: collapsed.bottom anchors.right: parent.right @@ -327,13 +322,9 @@ PageType { visible: !ServersModel.isDefaultServerFromApi - Item { - id: focusItem1 - KeyNavigation.tab: containersDropDown - } - DropDownType { id: containersDropDown + objectName: "containersDropDown" rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonBackgroundColor: AmneziaStyle.color.paleGray @@ -344,28 +335,29 @@ PageType { rootButtonTextTopMargin: 8 rootButtonTextBottomMargin: 8 + enabled: drawer.isOpened + text: ServersModel.defaultServerDefaultContainerName textColor: AmneziaStyle.color.midnightBlack headerText: qsTr("VPN protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { - containersDropDown.open() + containersDropDown.openTriggered() } drawerParent: root - KeyNavigation.tab: serversMenuContent listView: HomeContainersListView { id: containersListView + objectName: "containersListView" + rootWidth: root.width - onVisibleChanged: { - if (containersDropDown.visible && !GC.isMobile()) { - focusItem1.forceActiveFocus() - } - } + height: 500 // TODO: make calculated Connections { + objectName: "rowLayoutConnections" + target: ServersModel function onDefaultServerIndexChanged() { @@ -407,167 +399,21 @@ PageType { ButtonGroup { id: serversRadioButtonGroup + objectName: "serversRadioButtonGroup" } - ListView { + ServersListView { id: serversMenuContent + objectName: "serversMenuContent" - anchors.top: serversMenuHeader.bottom - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.topMargin: 16 - - model: ServersModel - currentIndex: ServersModel.defaultIndex - - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: serversMenuContent.height >= serversMenuContent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } - - - activeFocusOnTab: true - focus: true - - property int focusItemIndex: 0 - onActiveFocusChanged: { - if (activeFocus) { - serversMenuContent.focusItemIndex = 0 - serversMenuContent.itemAtIndex(focusItemIndex).forceActiveFocus() - } - } - - onFocusItemIndexChanged: { - const focusedElement = serversMenuContent.itemAtIndex(focusItemIndex) - if (focusedElement) { - if (focusedElement.y + focusedElement.height > serversMenuContent.height) { - serversMenuContent.contentY = focusedElement.y + focusedElement.height - serversMenuContent.height - } else { - serversMenuContent.contentY = 0 - } - } - } - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() + isFocusable: false Connections { target: drawer - enabled: !GC.isMobile() - function onIsCollapsedChanged() { - if (drawer.isCollapsed) { - const item = serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex) - if (item) { item.serverRadioButtonProperty.focus = false } - } - } - } - Connections { - target: ServersModel - function onDefaultServerIndexChanged(serverIndex) { - serversMenuContent.currentIndex = serverIndex - } - } - - clip: true - - delegate: Item { - id: menuContentDelegate - - property variant delegateData: model - property VerticalRadioButton serverRadioButtonProperty: serverRadioButton - - implicitWidth: serversMenuContent.width - implicitHeight: serverRadioButtonContent.implicitHeight - - onActiveFocusChanged: { - if (activeFocus) { - serverRadioButton.forceActiveFocus() - } - } - - ColumnLayout { - id: serverRadioButtonContent - - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - spacing: 0 - - RowLayout { - Layout.fillWidth: true - VerticalRadioButton { - id: serverRadioButton - - Layout.fillWidth: true - - text: name - descriptionText: serverDescription - - checked: index === serversMenuContent.currentIndex - checkable: !ConnectionController.isConnected - - ButtonGroup.group: serversRadioButtonGroup - - onClicked: { - if (ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) - return - } - - serversMenuContent.currentIndex = index - - ServersModel.defaultIndex = index - } - - MouseArea { - anchors.fill: serverRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - - Keys.onTabPressed: serverInfoButton.forceActiveFocus() - Keys.onEnterPressed: serverRadioButton.clicked() - Keys.onReturnPressed: serverRadioButton.clicked() - } - - ImageButtonType { - id: serverInfoButton - image: "qrc:/images/controls/settings.svg" - imageColor: AmneziaStyle.color.paleGray - - implicitWidth: 56 - implicitHeight: 56 - - z: 1 - - Keys.onTabPressed: { - if (serversMenuContent.focusItemIndex < serversMenuContent.count - 1) { - serversMenuContent.focusItemIndex++ - serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex).forceActiveFocus() - } else { - focusItem1.forceActiveFocus() - serversMenuContent.contentY = 0 - } - } - Keys.onEnterPressed: serverInfoButton.clicked() - Keys.onReturnPressed: serverInfoButton.clicked() - - onClicked: function() { - ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) - drawer.close() - } - } - } - - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 0 - Layout.rightMargin: 0 - } + // this item shouldn't be focused when drawer is closed + function onIsOpenedChanged() { + serversMenuContent.isFocusable = drawer.isOpened } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index 2b912f18..859d155e 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -16,18 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview.currentItem.mtuTextField.textField - - Item { - id: focusItem - onFocusChanged: { - if (activeFocus) { - fl.ensureVisible(focusItem) - } - } - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -39,229 +27,236 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.mtuTextField.textField } } - FlickableType { - id: fl + ListView { + id: listview + anchors.top: backButtonLayout.bottom - anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin + anchors.bottom: saveButton.top - Column { - id: content + width: parent.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + clip: true - ListView { - id: listview + property bool isFocusable: true - width: parent.width - height: listview.contentItem.height + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - clip: true - interactive: false + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } - model: AwgConfigModel + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } - delegate: Item { - id: delegateItem - implicitWidth: listview.width - implicitHeight: col.implicitHeight + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } - property alias mtuTextField: mtuTextField - property bool isSaveButtonEnabled: mtuTextField.errorText === "" && - junkPacketMaxSizeTextField.errorText === "" && - junkPacketMinSizeTextField.errorText === "" && - junkPacketCountTextField.errorText === "" + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } - ColumnLayout { - id: col + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: AwgConfigModel - anchors.leftMargin: 16 - anchors.rightMargin: 16 + delegate: Item { + id: delegateItem + implicitWidth: listview.width + implicitHeight: col.implicitHeight - spacing: 0 + property alias mtuTextField: mtuTextField + property bool isSaveButtonEnabled: mtuTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" - HeaderType { - Layout.fillWidth: true + ColumnLayout { + id: col - headerText: qsTr("AmneziaWG settings") - } + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - TextFieldWithHeaderType { - id: mtuTextField - Layout.fillWidth: true - Layout.topMargin: 40 + anchors.leftMargin: 16 + anchors.rightMargin: 16 - headerText: qsTr("MTU") - textFieldText: clientMtu - textField.validator: IntValidator { bottom: 576; top: 65535 } + spacing: 0 - textField.onEditingFinished: { - if (textFieldText !== clientMtu) { - clientMtu = textFieldText - } - } - checkEmptyText: true - KeyNavigation.tab: junkPacketCountTextField.textField - } + HeaderType { + Layout.fillWidth: true - TextFieldWithHeaderType { - id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 + headerText: qsTr("AmneziaWG settings") + } - headerText: "Jc - Junk packet count" - textFieldText: clientJunkPacketCount - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl + TextFieldWithHeaderType { + id: mtuTextField + Layout.fillWidth: true + Layout.topMargin: 40 - textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketCount) { - clientJunkPacketCount = textFieldText - } - } + headerText: qsTr("MTU") + textFieldText: clientMtu + textField.validator: IntValidator { bottom: 576; top: 65535 } - checkEmptyText: true - - KeyNavigation.tab: junkPacketMinSizeTextField.textField - } - - TextFieldWithHeaderType { - id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: "Jmin - Junk packet minimum size" - textFieldText: clientJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketMinSize) { - clientJunkPacketMinSize = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: junkPacketMaxSizeTextField.textField - } - - TextFieldWithHeaderType { - id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: "Jmax - Junk packet maximum size" - textFieldText: clientJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketMaxSize) { - clientJunkPacketMaxSize = textFieldText - } - } - - checkEmptyText: true - - Keys.onTabPressed: saveButton.forceActiveFocus() - } - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Server settings") - } - - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 8 - - enabled: false - - headerText: qsTr("Port") - textFieldText: port - } - - TextFieldWithHeaderType { - id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: false - - headerText: "S1 - Init packet junk size" - textFieldText: serverInitPacketJunkSize - } - - TextFieldWithHeaderType { - id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: false - - headerText: "S2 - Response packet junk size" - textFieldText: serverResponsePacketJunkSize - } - - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: false - - headerText: "H1 - Init packet magic header" - textFieldText: serverInitPacketMagicHeader - } - - TextFieldWithHeaderType { - id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: false - - headerText: "H2 - Response packet magic header" - textFieldText: serverResponsePacketMagicHeader - } - - TextFieldWithHeaderType { - id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - parentFlickable: fl - - enabled: false - - headerText: "H3 - Underload packet magic header" - textFieldText: serverUnderloadPacketMagicHeader - } - - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: false - - headerText: "H4 - Transport packet magic header" - textFieldText: serverTransportPacketMagicHeader + textField.onEditingFinished: { + if (textFieldText !== clientMtu) { + clientMtu = textFieldText } } + checkEmptyText: true + KeyNavigation.tab: junkPacketCountTextField.textField + } + + TextFieldWithHeaderType { + id: junkPacketCountTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: "Jc - Junk packet count" + textFieldText: clientJunkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== clientJunkPacketCount) { + clientJunkPacketCount = textFieldText + } + } + + checkEmptyText: true + + KeyNavigation.tab: junkPacketMinSizeTextField.textField + } + + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: "Jmin - Junk packet minimum size" + textFieldText: clientJunkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== clientJunkPacketMinSize) { + clientJunkPacketMinSize = textFieldText + } + } + + checkEmptyText: true + + KeyNavigation.tab: junkPacketMaxSizeTextField.textField + } + + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: "Jmax - Junk packet maximum size" + textFieldText: clientJunkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== clientJunkPacketMaxSize) { + clientJunkPacketMaxSize = textFieldText + } + } + + checkEmptyText: true + + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Server settings") + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 8 + + enabled: false + + headerText: qsTr("Port") + textFieldText: port + } + + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: false + + headerText: "S1 - Init packet junk size" + textFieldText: serverInitPacketJunkSize + } + + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: false + + headerText: "S2 - Response packet junk size" + textFieldText: serverResponsePacketJunkSize + } + + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: false + + headerText: "H1 - Init packet magic header" + textFieldText: serverInitPacketMagicHeader + } + + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: false + + headerText: "H2 - Response packet magic header" + textFieldText: serverResponsePacketMagicHeader + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + parentFlickable: fl + + enabled: false + + headerText: "H3 - Underload packet magic header" + textFieldText: serverUnderloadPacketMagicHeader + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: false + + headerText: "H4 - Transport packet magic header" + textFieldText: serverTransportPacketMagicHeader } } } @@ -283,7 +278,11 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 27ea66f9..3093e134 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtCore + import SortFilterProxyModel 0.2 import PageEnum 1.0 @@ -17,18 +19,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview.currentItem.portTextField.textField - - Item { - id: focusItem - onFocusChanged: { - if (activeFocus) { - fl.ensureVisible(focusItem) - } - } - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -40,341 +30,357 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.portTextField.textField } } - FlickableType { - id: fl + ListView { + id: listview + anchors.top: backButtonLayout.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight - Column { - id: content + width: parent.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + property bool isFocusable: true - ListView { - id: listview + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - width: parent.width - height: listview.contentItem.height + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } - clip: true - interactive: false + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } - model: AwgConfigModel + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } - delegate: Item { - id: delegateItem - implicitWidth: listview.width - implicitHeight: col.implicitHeight + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } - property alias portTextField: portTextField - property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } - ColumnLayout { - id: col + clip: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: AwgConfigModel - anchors.leftMargin: 16 - anchors.rightMargin: 16 + delegate: Item { + id: delegateItem + implicitWidth: listview.width + implicitHeight: col.implicitHeight - spacing: 0 + property alias portTextField: portTextField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() - HeaderType { - Layout.fillWidth: true + ColumnLayout { + id: col - headerText: qsTr("AmneziaWG settings") + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("AmneziaWG settings") + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 40 + + enabled: delegateItem.isEnabled + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: mtuTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("MTU") + textFieldText: mtu + textField.validator: IntValidator { bottom: 576; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText === "") { + textFieldText = "0" + } + if (textFieldText !== mtu) { + mtu = textFieldText + } + } + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketCountTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Jc - Junk packet count") + textFieldText: serverJunkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText === "") { + textFieldText = "0" } - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 40 + if (textFieldText !== serverJunkPacketCount) { + serverJunkPacketCount = textFieldText + } + } - enabled: delegateItem.isEnabled + checkEmptyText: true + } - headerText: qsTr("Port") - textFieldText: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - parentFlickable: fl + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 - textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText - } + headerText: qsTr("Jmin - Junk packet minimum size") + textFieldText: serverJunkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverJunkPacketMinSize) { + serverJunkPacketMinSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Jmax - Junk packet maximum size") + textFieldText: serverJunkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverJunkPacketMaxSize) { + serverJunkPacketMaxSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("S1 - Init packet junk size") + textFieldText: serverInitPacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverInitPacketJunkSize) { + serverInitPacketJunkSize = textFieldText + } + } + + checkEmptyText: true + + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } + } + + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("S2 - Response packet junk size") + textFieldText: serverResponsePacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverResponsePacketJunkSize) { + serverResponsePacketJunkSize = textFieldText + } + } + + checkEmptyText: true + + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } + } + + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("H1 - Init packet magic header") + textFieldText: serverInitPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverInitPacketMagicHeader) { + serverInitPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("H2 - Response packet magic header") + textFieldText: serverResponsePacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverResponsePacketMagicHeader) { + serverResponsePacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("H4 - Transport packet magic header") + textFieldText: serverTransportPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverTransportPacketMagicHeader) { + serverTransportPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("H3 - Underload packet magic header") + textFieldText: serverUnderloadPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== serverUnderloadPacketMagicHeader) { + serverUnderloadPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + BasicButtonType { + id: saveRestartButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + enabled: underloadPacketMagicHeaderTextField.errorText === "" && + transportPacketMagicHeaderTextField.errorText === "" && + responsePacketMagicHeaderTextField.errorText === "" && + initPacketMagicHeaderTextField.errorText === "" && + responsePacketJunkSizeTextField.errorText === "" && + initPacketJunkSizeTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" && + portTextField.errorText === "" + + text: qsTr("Save") + + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } + + clickedFunc: function() { + forceActiveFocus() + + if (delegateItem.isEnabled) { + if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text, + transportPacketMagicHeaderTextField.textField.text, + responsePacketMagicHeaderTextField.textField.text, + initPacketMagicHeaderTextField.textField.text)) { + PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique")) + return } - checkEmptyText: true - - KeyNavigation.tab: junkPacketCountTextField.textField - } - - TextFieldWithHeaderType { - id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jc - Junk packet count") - textFieldText: serverJunkPacketCount - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText === "") { - textFieldText = "0" - } - - if (textFieldText !== serverJunkPacketCount) { - serverJunkPacketCount = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: junkPacketMinSizeTextField.textField - } - - TextFieldWithHeaderType { - id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jmin - Junk packet minimum size") - textFieldText: serverJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverJunkPacketMinSize) { - serverJunkPacketMinSize = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: junkPacketMaxSizeTextField.textField - } - - TextFieldWithHeaderType { - id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jmax - Junk packet maximum size") - textFieldText: serverJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverJunkPacketMaxSize) { - serverJunkPacketMaxSize = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: initPacketJunkSizeTextField.textField - } - - TextFieldWithHeaderType { - id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("S1 - Init packet junk size") - textFieldText: serverInitPacketJunkSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverInitPacketJunkSize) { - serverInitPacketJunkSize = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: responsePacketJunkSizeTextField.textField - } - - TextFieldWithHeaderType { - id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("S2 - Response packet junk size") - textFieldText: serverResponsePacketJunkSize - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverResponsePacketJunkSize) { - serverResponsePacketJunkSize = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: initPacketMagicHeaderTextField.textField - } - - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("H1 - Init packet magic header") - textFieldText: serverInitPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverInitPacketMagicHeader) { - serverInitPacketMagicHeader = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: responsePacketMagicHeaderTextField.textField - } - - TextFieldWithHeaderType { - id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("H2 - Response packet magic header") - textFieldText: serverResponsePacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverResponsePacketMagicHeader) { - serverResponsePacketMagicHeader = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: transportPacketMagicHeaderTextField.textField - } - - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("H4 - Transport packet magic header") - textFieldText: serverTransportPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText !== serverTransportPacketMagicHeader) { - serverTransportPacketMagicHeader = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: underloadPacketMagicHeaderTextField.textField - } - - TextFieldWithHeaderType { - id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - parentFlickable: fl - - headerText: qsTr("H3 - Underload packet magic header") - textFieldText: serverUnderloadPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textFieldText !== serverUnderloadPacketMagicHeader) { - serverUnderloadPacketMagicHeader = textFieldText - } - } - - checkEmptyText: true - - KeyNavigation.tab: saveRestartButton - } - - BasicButtonType { - id: saveRestartButton - parentFlickable: fl - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - enabled: underloadPacketMagicHeaderTextField.errorText === "" && - transportPacketMagicHeaderTextField.errorText === "" && - responsePacketMagicHeaderTextField.errorText === "" && - initPacketMagicHeaderTextField.errorText === "" && - responsePacketJunkSizeTextField.errorText === "" && - initPacketJunkSizeTextField.errorText === "" && - junkPacketMaxSizeTextField.errorText === "" && - junkPacketMinSizeTextField.errorText === "" && - junkPacketCountTextField.errorText === "" && - portTextField.errorText === "" - - text: qsTr("Save") - - Keys.onTabPressed: lastItemTabClicked(focusItem) - - clickedFunc: function() { - forceActiveFocus() - - if (delegateItem.isEnabled) { - if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text, - transportPacketMagicHeaderTextField.textField.text, - responsePacketMagicHeaderTextField.textField.text, - initPacketMagicHeaderTextField.textField.text)) { - PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique")) - return - } - - if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), - parseInt(responsePacketJunkSizeTextField.textField.text))) { - PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) - return - } - } - - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(AwgConfigModel.getConfig()) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveRestartButton.forceActiveFocus() - } - } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), + parseInt(responsePacketJunkSizeTextField.textField.text))) { + PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) + return } } + + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(AwgConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveRestartButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 5089a764..fc43bd8e 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -16,13 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview.currentItem.trafficFromField.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -34,7 +27,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.trafficFromField.textField } } @@ -110,8 +102,6 @@ PageType { } } } - - KeyNavigation.tab: portTextField.textField } TextFieldWithHeaderType { @@ -130,8 +120,6 @@ PageType { port = textFieldText } } - - KeyNavigation.tab: cipherDropDown } DropDownType { @@ -143,7 +131,6 @@ PageType { headerText: qsTr("Cipher") drawerParent: root - KeyNavigation.tab: saveRestartButton listView: ListViewWithRadioButtonType { id: cipherListView @@ -161,7 +148,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { @@ -184,7 +171,6 @@ PageType { Layout.bottomMargin: 24 text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 30540a93..2b1f3f44 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -17,18 +17,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview.currentItem.vpnAddressSubnetTextField.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - onActiveFocusChanged: { - if (activeFocus) { - fl.ensureVisible(focusItem) - } - } - } - ColumnLayout { id: backButtonLayout @@ -40,7 +28,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.vpnAddressSubnetTextField.textField } } @@ -104,7 +91,6 @@ PageType { textFieldText: subnetAddress parentFlickable: fl - KeyNavigation.tab: transportProtoSelector textField.onEditingFinished: { if (textFieldText !== subnetAddress) { @@ -132,8 +118,6 @@ PageType { return transportProto === "tcp" ? 1 : 0 } - KeyNavigation.tab: portTextField.enabled ? portTextField.textField : autoNegotiateEncryprionSwitcher - onCurrentIndexChanged: { if (transportProto === "tcp" && currentIndex === 0) { transportProto = "udp" @@ -162,8 +146,6 @@ PageType { port = textFieldText } } - - KeyNavigation.tab: autoNegotiateEncryprionSwitcher } SwitcherType { @@ -181,10 +163,6 @@ PageType { autoNegotiateEncryprion = checked } } - - KeyNavigation.tab: hashDropDown.enabled ? - hashDropDown : - tlsAuthCheckBox } DropDownType { @@ -199,9 +177,6 @@ PageType { drawerParent: root parentFlickable: fl - KeyNavigation.tab: cipherDropDown.enabled ? - cipherDropDown : - tlsAuthCheckBox listView: ListViewWithRadioButtonType { id: hashListView @@ -224,7 +199,7 @@ PageType { clickedFunction: function() { hashDropDown.text = selectedText hash = hashDropDown.text - hashDropDown.close() + hashDropDown.closeTriggered() } Component.onCompleted: { @@ -252,8 +227,6 @@ PageType { drawerParent: root parentFlickable: fl - KeyNavigation.tab: tlsAuthCheckBox - listView: ListViewWithRadioButtonType { id: cipherListView @@ -275,7 +248,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { @@ -320,8 +293,6 @@ PageType { text: qsTr("TLS auth") checked: tlsAuth - KeyNavigation.tab: blockDnsCheckBox - onCheckedChanged: { if (checked !== tlsAuth) { console.log("tlsAuth changed to: " + checked) @@ -339,8 +310,6 @@ PageType { text: qsTr("Block DNS requests outside of VPN") checked: blockDns - KeyNavigation.tab: additionalClientCommandsSwitcher - onCheckedChanged: { if (checked !== blockDns) { blockDns = checked @@ -355,9 +324,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 parentFlickable: fl - KeyNavigation.tab: additionalClientCommandsTextArea.visible ? - additionalClientCommandsTextArea.textArea : - additionalServerCommandsSwitcher checked: additionalClientCommands !== "" @@ -376,7 +342,7 @@ PageType { Layout.topMargin: 16 visible: additionalClientCommandsSwitcher.checked - KeyNavigation.tab: additionalServerCommandsSwitcher + parentFlickable: fl textAreaText: additionalClientCommands @@ -394,9 +360,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 parentFlickable: fl - KeyNavigation.tab: additionalServerCommandsTextArea.visible ? - additionalServerCommandsTextArea.textArea : - saveRestartButton checked: additionalServerCommands !== "" @@ -419,7 +382,6 @@ PageType { textAreaText: additionalServerCommands placeholderText: qsTr("Commands:") parentFlickable: fl - KeyNavigation.tab: saveRestartButton textArea.onEditingFinished: { if (additionalServerCommands !== textAreaText) { additionalServerCommands = textAreaText @@ -436,7 +398,6 @@ PageType { text: qsTr("Save") parentFlickable: fl - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 24853afd..1a530780 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -19,13 +19,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: header @@ -37,7 +30,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listView } HeaderType { @@ -101,11 +93,9 @@ PageType { text: qsTr("Show connection options") clickedFunction: function() { - configContentDrawer.open() + configContentDrawer.openTriggered() } - KeyNavigation.tab: removeButton - MouseArea { anchors.fill: button cursorShape: Qt.PointingHandCursor @@ -120,31 +110,12 @@ PageType { expandedHeight: root.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() - } - } - parent: root anchors.fill: parent - expandedContent: Item { + expandedStateContent: Item { implicitHeight: configContentDrawer.expandedHeight - Connections { - target: configContentDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem1.forceActiveFocus() - } - } - - Item { - id: focusItem1 - KeyNavigation.tab: backButton1 - } - BackButtonType { id: backButton1 @@ -154,10 +125,8 @@ PageType { anchors.topMargin: 16 backButtonFunction: function() { - configContentDrawer.close() + configContentDrawer.closeTriggered() } - - KeyNavigation.tab: focusItem1 } FlickableType { @@ -226,7 +195,6 @@ PageType { text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunction: function() { var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 4d3b2c4e..44cbd1ce 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -16,15 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview.currentItem.focusItemId.enabled ? - listview.currentItem.focusItemId.textField : - focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -36,9 +27,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.focusItemId.enabled ? - listview.currentItem.focusItemId.textField : - focusItem } } @@ -114,8 +102,6 @@ PageType { port = textFieldText } } - - KeyNavigation.tab: cipherDropDown } DropDownType { @@ -129,9 +115,9 @@ PageType { headerText: qsTr("Cipher") drawerParent: root - KeyNavigation.tab: saveRestartButton listView: ListViewWithRadioButtonType { + id: cipherListView rootWidth: root.width @@ -147,7 +133,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { @@ -172,7 +158,6 @@ PageType { enabled: isPortEditable | isCipherEditable text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index 007de5ca..4f0f474c 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -150,8 +150,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { forceActiveFocus() var headerText = qsTr("Save settings?") diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index b5d08132..257bc675 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -16,13 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -34,7 +27,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview } } @@ -64,12 +56,12 @@ PageType { model: WireGuardConfigModel - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() - } - } + // activeFocusOnTab: true + // onActiveFocusChanged: { + // if (activeFocus) { + // listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() + // } + // } delegate: Item { id: delegateItem @@ -109,8 +101,6 @@ PageType { textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } - KeyNavigation.tab: saveButton - textField.onEditingFinished: { if (textFieldText !== port) { port = textFieldText @@ -120,6 +110,26 @@ PageType { checkEmptyText: true } + TextFieldWithHeaderType { + id: mtuTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("MTU") + textFieldText: mtu + textField.validator: IntValidator { bottom: 576; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText === "") { + textFieldText = "0" + } + if (textFieldText !== mtu) { + mtu = textFieldText + } + } + checkEmptyText: true + } + BasicButtonType { id: saveButton Layout.fillWidth: true @@ -130,8 +140,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - onClicked: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 20ee1da6..6d53fdd3 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -17,13 +17,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -35,7 +28,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview } } @@ -65,12 +57,12 @@ PageType { model: XrayConfigModel - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() - } - } + // activeFocusOnTab: true + // onActiveFocusChanged: { + // if (activeFocus) { + // listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() + // } + // } delegate: Item { property alias focusItemId: textFieldWithHeaderType.textField @@ -103,8 +95,6 @@ PageType { headerText: qsTr("Disguised as traffic from") textFieldText: site - KeyNavigation.tab: basicButton - textField.onEditingFinished: { if (textFieldText !== site) { var tmpText = textFieldText @@ -128,8 +118,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index bb3cbf96..cef29813 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -16,13 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -34,7 +27,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: removeButton } } @@ -72,8 +64,6 @@ PageType { text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: root.lastItemTabClicked() - clickedFunction: function() { var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var yesButtonText = qsTr("Continue") diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 9bdbf2db..2deb315c 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -16,8 +16,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - Connections { target: InstallController @@ -26,11 +24,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -42,7 +35,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview } } @@ -107,7 +99,6 @@ PageType { Layout.topMargin: 32 parentFlickable: fl - KeyNavigation.tab: portLabel.rightButton text: qsTr("Host") descriptionText: ServersModel.getProcessedServerData("hostName") @@ -136,7 +127,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - KeyNavigation.tab: usernameLabel.rightButton rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -160,7 +150,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - KeyNavigation.tab: passwordLabel.eyeButton rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -184,14 +173,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - eyeButton.KeyNavigation.tab: passwordLabel.rightButton - rightButton.Keys.onTabPressed: { - if (mountButton.visible) { - mountButton.forceActiveFocus() - } else { - detailedInstructionsButton.forceActiveFocus() - } - } rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -225,7 +206,6 @@ PageType { borderWidth: 1 parentFlickable: fl - KeyNavigation.tab: detailedInstructionsButton text: qsTr("Mount folder on device") @@ -290,7 +270,6 @@ PageType { text: qsTr("Detailed instructions") parentFlickable: fl - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { // Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 9d21963d..5eee9a1e 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -17,8 +17,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: listview - Connections { target: InstallController @@ -27,11 +25,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -43,7 +36,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview } } @@ -99,7 +91,6 @@ PageType { Layout.topMargin: 32 parentFlickable: fl - KeyNavigation.tab: portLabel.rightButton text: qsTr("Host") descriptionText: ServersModel.getProcessedServerData("hostName") @@ -128,7 +119,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - KeyNavigation.tab: usernameLabel.rightButton rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -152,7 +142,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - KeyNavigation.tab: passwordLabel.eyeButton rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -176,8 +165,6 @@ PageType { descriptionOnTop: true parentFlickable: fl - eyeButton.KeyNavigation.tab: passwordLabel.rightButton - rightButton.KeyNavigation.tab: changeSettingsButton rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -200,13 +187,7 @@ PageType { anchors.fill: parent expandedHeight: root.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } - - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { property string tempPort: port property string tempUsername: username property string tempPassword: password @@ -222,9 +203,6 @@ PageType { Connections { target: changeSettingsDrawer function onOpened() { - if (!GC.isMobile()) { - drawerFocusItem.forceActiveFocus() - } tempPort = port tempUsername = username tempPassword = password @@ -239,11 +217,6 @@ PageType { } } - Item { - id: drawerFocusItem - KeyNavigation.tab: portTextField.textField - } - HeaderType { Layout.fillWidth: true @@ -268,8 +241,6 @@ PageType { port = textFieldText } } - - KeyNavigation.tab: usernameTextField.textField } TextFieldWithHeaderType { @@ -290,8 +261,6 @@ PageType { username = textFieldText } } - - KeyNavigation.tab: passwordTextField.textField } TextFieldWithHeaderType { @@ -322,8 +291,6 @@ PageType { password = textFieldText } } - - KeyNavigation.tab: saveButton } BasicButtonType { @@ -334,7 +301,6 @@ PageType { Layout.bottomMargin: 24 text: qsTr("Change connection settings") - Keys.onTabPressed: lastItemTabClicked(drawerFocusItem) clickedFunc: function() { forceActiveFocus() @@ -356,7 +322,7 @@ PageType { tempPort = portTextField.textFieldText tempUsername = usernameTextField.textFieldText tempPassword = passwordTextField.textFieldText - changeSettingsDrawer.close() + changeSettingsDrawer.closeTriggered() } } } @@ -372,11 +338,10 @@ PageType { Layout.rightMargin: 16 text: qsTr("Change connection settings") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() - changeSettingsDrawer.open() + changeSettingsDrawer.openTriggered() } } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 946a77bb..249c70c7 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -17,8 +17,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - Connections { target: InstallController @@ -27,11 +25,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: backButtonLayout @@ -43,7 +36,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: websiteName.rightButton } } @@ -88,8 +80,6 @@ PageType { rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunction: function() { GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index bb5ca766..65c696c7 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -14,8 +14,6 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: header - FlickableType { id: fl anchors.top: parent.top @@ -39,8 +37,6 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("Settings") - - KeyNavigation.tab: account.rightButton } LabelWithButtonType { @@ -55,8 +51,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsServersList) } - - KeyNavigation.tab: connection.rightButton } DividerType {} @@ -72,8 +66,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsConnection) } - - KeyNavigation.tab: application.rightButton } DividerType {} @@ -89,8 +81,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsApplication) } - - KeyNavigation.tab: backup.rightButton } DividerType {} @@ -106,8 +96,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsBackup) } - - KeyNavigation.tab: about.rightButton } DividerType {} @@ -123,8 +111,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsAbout) } - KeyNavigation.tab: close - } DividerType {} @@ -138,8 +124,6 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/bug.svg" - // Keys.onTabPressed: lastItemTabClicked(header) - clickedFunction: function() { PageController.goToPage(PageEnum.PageDevMenu) } @@ -156,10 +140,8 @@ PageType { Layout.preferredHeight: about.height text: qsTr("Close application") + rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/x-circle.svg" - isLeftImageHoverEnabled: false - - Keys.onTabPressed: lastItemTabClicked(header) clickedFunction: function() { PageController.closeApplication() diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index cde9ee20..8501d2a4 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -14,19 +14,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - - onFocusChanged: { - if (focusItem.activeFocus) { - fl.contentY = 0 - } - } - } - BackButtonType { id: backButton @@ -35,21 +22,109 @@ PageType { anchors.right: parent.right anchors.topMargin: 20 - KeyNavigation.tab: telegramButton + onActiveFocusChanged: { + if(backButton.enabled && backButton.activeFocus) { + listView.positionViewAtBeginning() + } + } } - FlickableType { - id: fl + QtObject { + id: telegramGroup + + property string title: qsTr("Telegram group") + property string description: qsTr("To discuss features") + property string imageSource: "qrc:/images/controls/telegram.svg" + property var handler: function() { + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) + } + } + + QtObject { + id: mail + + property string title: qsTr("support@amnezia.org") + property string description: qsTr("For reviews and bug reports") + property string imageSource: "qrc:/images/controls/mail.svg" + property var handler: function() { + GC.copyToClipBoard(title) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + QtObject { + id: github + + property string title: qsTr("GitHub") + property string description: qsTr("Discover the source code") + property string imageSource: "qrc:/images/controls/github.svg" + property var handler: function() { + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) + } + } + + QtObject { + id: website + + property string title: qsTr("Website") + property string description: qsTr("Visit official website") + property string imageSource: "qrc:/images/controls/amnezia.svg" + property var handler: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + property list contacts: [ + telegramGroup, + mail, + github, + website + ] + + ListView { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.height + anchors.right: parent.right + anchors.left: parent.left - ColumnLayout { - id: content + property bool isFocusable: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + + model: contacts + + clip: true + + header: ColumnLayout { + width: listView.width Image { id: image @@ -96,81 +171,29 @@ PageType { text: qsTr("Contacts") } + } + + delegate: ColumnLayout { + width: listView.width LabelWithButtonType { id: telegramButton Layout.fillWidth: true - Layout.topMargin: 16 + Layout.topMargin: 6 - text: qsTr("Telegram group") - descriptionText: qsTr("To discuss features") - leftImageSource: "qrc:/images/controls/telegram.svg" + text: title + descriptionText: description + leftImageSource: imageSource - KeyNavigation.tab: mailButton - parentFlickable: fl - - clickedFunction: function() { - Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) - } + clickedFunction: handler } DividerType {} - LabelWithButtonType { - id: mailButton - Layout.fillWidth: true + } - text: qsTr("support@amnezia.org") - descriptionText: qsTr("For reviews and bug reports") - leftImageSource: "qrc:/images/controls/mail.svg" - - KeyNavigation.tab: githubButton - parentFlickable: fl - - clickedFunction: function() { - GC.copyToClipBoard(text) - PageController.showNotificationMessage(qsTr("Copied")) - } - - } - - DividerType {} - - LabelWithButtonType { - id: githubButton - Layout.fillWidth: true - - text: qsTr("GitHub") - leftImageSource: "qrc:/images/controls/github.svg" - - KeyNavigation.tab: websiteButton - parentFlickable: fl - - clickedFunction: function() { - Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) - } - - } - - DividerType {} - - LabelWithButtonType { - id: websiteButton - Layout.fillWidth: true - - text: qsTr("Website") - leftImageSource: "qrc:/images/controls/amnezia.svg" - - KeyNavigation.tab: checkUpdatesButton - parentFlickable: fl - - clickedFunction: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - - } - - DividerType {} + footer: ColumnLayout { + width: listView.width CaptionTextType { Layout.fillWidth: true @@ -196,6 +219,7 @@ PageType { BasicButtonType { id: checkUpdatesButton + Layout.alignment: Qt.AlignHCenter Layout.topMargin: 8 Layout.bottomMargin: 16 @@ -209,35 +233,30 @@ PageType { text: qsTr("Check for updates") - KeyNavigation.tab: privacyPolicyButton - parentFlickable: fl - clickedFunc: function() { Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") } } BasicButtonType { - id: privacyPolicyButton - Layout.alignment: Qt.AlignHCenter - Layout.bottomMargin: 16 - Layout.topMargin: -15 - implicitHeight: 25 + id: privacyPolicyButton - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.goldenApricot + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 + Layout.topMargin: -15 + implicitHeight: 25 - text: qsTr("Privacy Policy") + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.goldenApricot - Keys.onTabPressed: lastItemTabClicked() - parentFlickable: fl + text: qsTr("Privacy Policy") - clickedFunc: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") - } + clickedFunc: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml index 120313cd..dd38b0f4 100644 --- a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml +++ b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml @@ -31,72 +31,68 @@ PageType { id: containersRadioButtonGroup } - delegate: Item { + delegate: ColumnLayout { + id: content + implicitWidth: parent.width implicitHeight: content.implicitHeight - ColumnLayout { - id: content + RowLayout { + VerticalRadioButton { + id: containerRadioButton - anchors.fill: parent - - RowLayout { - VerticalRadioButton { - id: containerRadioButton - - Layout.fillWidth: true - Layout.leftMargin: 16 - - text: countryName - - ButtonGroup.group: containersRadioButtonGroup - - imageSource: "qrc:/images/controls/download.svg" - - checked: index === ApiCountryModel.currentIndex - - onClicked: { - if (index !== ApiCountryModel.currentIndex) { - PageController.showBusyIndicator(true) - var prevIndex = ApiCountryModel.currentIndex - ApiCountryModel.currentIndex = index - if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) { - ApiCountryModel.currentIndex = prevIndex - } - } - } - - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - - Keys.onEnterPressed: { - if (checkable) { - checked = true - } - containerRadioButton.clicked() - } - Keys.onReturnPressed: { - if (checkable) { - checked = true - } - containerRadioButton.clicked() - } - } - - Image { - Layout.rightMargin: 32 - Layout.alignment: Qt.AlignRight - - source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" - } - } - - DividerType { Layout.fillWidth: true + Layout.leftMargin: 16 + + text: countryName + + ButtonGroup.group: containersRadioButtonGroup + + imageSource: "qrc:/images/controls/download.svg" + + checked: index === ApiCountryModel.currentIndex + + onClicked: { + if (index !== ApiCountryModel.currentIndex) { + PageController.showBusyIndicator(true) + var prevIndex = ApiCountryModel.currentIndex + ApiCountryModel.currentIndex = index + if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) { + ApiCountryModel.currentIndex = prevIndex + } + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + + Keys.onEnterPressed: { + if (checkable) { + checked = true + } + containerRadioButton.clicked() + } + Keys.onReturnPressed: { + if (checkable) { + checked = true + } + containerRadioButton.clicked() + } } + + Image { + Layout.rightMargin: 32 + Layout.alignment: Qt.AlignRight + + source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" + } + } + + DividerType { + Layout.fillWidth: true } } } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 2d6c1d9b..73fdc551 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -15,8 +15,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - FlickableType { id: fl anchors.top: parent.top @@ -32,11 +30,6 @@ PageType { spacing: 0 - Item { - id: focusItem -// KeyNavigation.tab: backButton - } - LabelWithImageType { Layout.fillWidth: true Layout.margins: 16 diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index 2e8bda2f..91501224 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -21,8 +21,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - property bool pageEnabled Component.onCompleted: { @@ -66,11 +64,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: header @@ -82,7 +75,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: switcher } RowLayout { @@ -103,10 +95,6 @@ PageType { enabled: root.pageEnabled - KeyNavigation.tab: selector.enabled ? - selector : - searchField.textField - checked: AppSplitTunnelingModel.isTunnelingEnabled onToggled: { AppSplitTunnelingModel.toggleSplitTunneling(checked) @@ -130,8 +118,6 @@ PageType { enabled: Qt.platform.os === "android" && root.pageEnabled - KeyNavigation.tab: searchField.textField - listView: ListViewWithRadioButtonType { rootWidth: root.width @@ -141,7 +127,7 @@ PageType { clickedFunction: function() { selector.text = selectedText - selector.close() + selector.closeTriggered() if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[currentIndex].type) { AppSplitTunnelingModel.routeMode = root.routeModesModel[currentIndex].type } @@ -267,7 +253,6 @@ PageType { textFieldPlaceholderText: qsTr("application name") buttonImageSource: "qrc:/images/controls/plus.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) rightButtonClickedOnEnter: true clickedFunc: function() { @@ -281,7 +266,7 @@ PageType { AppSplitTunnelingController.addApp(fileName) } } else if (Qt.platform.os === "android"){ - installedAppDrawer.open() + installedAppDrawer.openTriggered() } PageController.showBusyIndicator(false) diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 0f85ac30..c5fc0bc1 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -14,19 +14,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - - onFocusChanged: { - if (focusItem.activeFocus) { - fl.contentY = 0 - } - } - } - BackButtonType { id: backButton @@ -34,8 +21,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart } FlickableType { @@ -77,8 +62,8 @@ PageType { } } - KeyNavigation.tab: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted ? - labelWithButtonNotification.rightButton : labelWithButtonLanguage.rightButton + // KeyNavigation.tab: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted ? + // labelWithButtonNotification.rightButton : labelWithButtonLanguage.rightButton parentFlickable: fl } @@ -95,7 +80,6 @@ PageType { descriptionText: qsTr("Enable notifications to show the VPN state in the status bar") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: labelWithButtonLanguage.rightButton parentFlickable: fl clickedFunction: function() { @@ -117,7 +101,6 @@ PageType { text: qsTr("Auto start") descriptionText: qsTr("Launch the application every time the device is starts") - KeyNavigation.tab: switcherAutoConnect parentFlickable: fl checked: SettingsController.isAutoStartEnabled() @@ -142,7 +125,6 @@ PageType { text: qsTr("Auto connect") descriptionText: qsTr("Connect to VPN on app start") - KeyNavigation.tab: switcherStartMinimized parentFlickable: fl checked: SettingsController.isAutoConnectEnabled() @@ -167,7 +149,6 @@ PageType { text: qsTr("Start minimized") descriptionText: qsTr("Launch application minimized") - KeyNavigation.tab: labelWithButtonLanguage.rightButton parentFlickable: fl checked: SettingsController.isStartMinimizedEnabled() @@ -190,11 +171,10 @@ PageType { descriptionText: LanguageModel.currentLanguageName rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: labelWithButtonLogging.rightButton parentFlickable: fl clickedFunction: function() { - selectLanguageDrawer.open() + selectLanguageDrawer.openTriggered() } } @@ -208,7 +188,6 @@ PageType { descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: labelWithButtonReset.rightButton parentFlickable: fl clickedFunction: function() { @@ -226,7 +205,6 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: lastItemTabClicked() parentFlickable: fl clickedFunction: function() { @@ -245,12 +223,12 @@ PageType { } if (!GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() + // root.defaultActiveFocusItem.forceActiveFocus() } } var noButtonFunction = function() { if (!GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() + // root.defaultActiveFocusItem.forceActiveFocus() } } @@ -267,11 +245,5 @@ PageType { width: root.width height: root.height - - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index abede9b3..d2dd4f2a 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -17,8 +17,6 @@ import "../Controls2/TextTypes" PageType { id: root - defaultActiveFocusItem: focusItem - Connections { target: SettingsController @@ -36,11 +34,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -48,8 +41,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: makeBackupButton } FlickableType { @@ -93,6 +84,8 @@ PageType { text: qsTr("Make a backup") + parentFlickable: fl + clickedFunc: function() { var fileName = "" if (GC.isMobile()) { @@ -111,8 +104,6 @@ PageType { PageController.showNotificationMessage(qsTr("Backup file saved")) } } - - KeyNavigation.tab: restoreBackupButton } BasicButtonType { @@ -129,6 +120,8 @@ PageType { text: qsTr("Restore from backup") + parentFlickable: fl + clickedFunc: function() { var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) @@ -136,8 +129,6 @@ PageType { restoreBackup(filePath) } } - - Keys.onTabPressed: lastItemTabClicked() } } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 31b1c764..d3743b96 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -12,15 +12,8 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: focusItem - property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -28,8 +21,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: amneziaDnsSwitch } FlickableType { @@ -67,8 +58,6 @@ PageType { SettingsController.toggleAmneziaDns(checked) } } - - KeyNavigation.tab: dnsServersButton.rightButton } DividerType {} @@ -81,11 +70,11 @@ PageType { descriptionText: qsTr("When AmneziaDNS is not used or installed") rightImageSource: "qrc:/images/controls/chevron-right.svg" + parentFlickable: fl + clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsDns) } - - KeyNavigation.tab: splitTunnelingButton.rightButton } DividerType {} @@ -98,19 +87,11 @@ PageType { descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") rightImageSource: "qrc:/images/controls/chevron-right.svg" + parentFlickable: fl + clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsSplitTunneling) } - - Keys.onTabPressed: { - if (splitTunnelingButton2.visible) { - return splitTunnelingButton2.rightButton.forceActiveFocus() - } else if (killSwitchSwitcher.visible) { - return killSwitchSwitcher.forceActiveFocus() - } else { - lastItemTabClicked() - } - } } DividerType { @@ -127,17 +108,11 @@ PageType { descriptionText: qsTr("Allows you to use the VPN only for certain Apps") rightImageSource: "qrc:/images/controls/chevron-right.svg" + parentFlickable: fl + clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) } - - Keys.onTabPressed: { - if (killSwitchSwitcher.visible) { - return killSwitchSwitcher.forceActiveFocus() - } else { - lastItemTabClicked() - } - } } DividerType { @@ -154,6 +129,8 @@ PageType { text: qsTr("KillSwitch") descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.") + parentFlickable: fl + checked: SettingsController.isKillSwitchEnabled() checkable: !ConnectionController.isConnected onCheckedChanged: { @@ -166,8 +143,6 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) } } - - Keys.onTabPressed: lastItemTabClicked() } DividerType { diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 1d7517d9..b6ade37a 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -14,13 +14,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: primaryDns.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -28,8 +21,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: root.defaultActiveFocusItem } FlickableType { @@ -80,8 +71,6 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } - - KeyNavigation.tab: secondaryDns.textField } TextFieldWithHeaderType { @@ -94,8 +83,6 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } - - KeyNavigation.tab: restoreDefaultButton } BasicButtonType { @@ -124,19 +111,17 @@ PageType { PageController.showNotificationMessage(qsTr("Settings have been reset")) if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() + // defaultActiveFocusItem.forceActiveFocus() } } var noButtonFunction = function() { if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() + // defaultActiveFocusItem.forceActiveFocus() } } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - - KeyNavigation.tab: saveButton } BasicButtonType { @@ -155,8 +140,6 @@ PageType { } PageController.showNotificationMessage(qsTr("Settings saved")) } - - Keys.onTabPressed: lastItemTabClicked(focusItem) } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 9abfc453..4b2d7d42 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -16,13 +16,6 @@ import "../Controls2/TextTypes" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -30,22 +23,122 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: switcher } - FlickableType { - id: fl + QtObject { + id: clientLogs + + property string title: qsTr("Client logs") + property string description: qsTr("AmneziaVPN logs") + property bool isVisible: true + property var openLogsHandler: function() { + SettingsController.openLogsFolder() + } + property var exportLogsHandler: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "AmneziaVPN.log" + } else { + fileName = SystemController.getFileName(qsTr("Save"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".log") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportLogsFile(fileName) + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs file saved")) + } + } + } + + QtObject { + id: serviceLogs + + property string title: qsTr("Service logs") + property string description: qsTr("AmneziaVPN-service logs") + property bool isVisible: !GC.isMobile() + property var openLogsHandler: function() { + SettingsController.openServiceLogsFolder() + } + property var exportLogsHandler: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "AmneziaVPN-service.log" + } else { + fileName = SystemController.getFileName(qsTr("Save"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service", + true, + ".log") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportServiceLogsFile(fileName) + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs file saved")) + } + } + } + + property list logTypes: [ + clientLogs, + serviceLogs + ] + + ListView { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.height + anchors.right: parent.right + anchors.left: parent.left - ColumnLayout { - id: content + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + + model: logTypes + spacing: 24 + snapMode: ListView.SnapOneItem + + reuseItems: true + + clip: true + + header: ColumnLayout { + id: headerContent + + width: listView.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right spacing: 0 HeaderType { @@ -60,6 +153,7 @@ PageType { SwitcherType { id: switcher + Layout.fillWidth: true Layout.topMargin: 16 Layout.leftMargin: 16 @@ -68,7 +162,7 @@ PageType { text: qsTr("Enable logs") checked: SettingsController.isLoggingEnabled - //KeyNavigation.tab: openFolderButton + onCheckedChanged: { if (checked !== SettingsController.isLoggingEnabled) { SettingsController.isLoggingEnabled = checked @@ -79,7 +173,6 @@ PageType { DividerType {} LabelWithButtonType { - // id: labelWithButton2 Layout.fillWidth: true Layout.topMargin: -8 @@ -87,8 +180,6 @@ PageType { leftImageSource: "qrc:/images/controls/trash.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 - clickedFunction: function() { var headerText = qsTr("Clear logs?") var yesButtonText = qsTr("Continue") @@ -99,19 +190,25 @@ PageType { SettingsController.clearLogs() PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } } + var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } + } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } + } + + delegate: ColumnLayout { + id: delegateContent + + width: listView.width + + spacing: 0 + + visible: isVisible ListItemTitleType { Layout.fillWidth: true @@ -119,7 +216,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Client logs") + text: title } ParagraphTextType { @@ -129,11 +226,11 @@ PageType { Layout.rightMargin: 16 color: AmneziaStyle.color.mutedGray - text: qsTr("AmneziaVPN logs") + + text: description } LabelWithButtonType { - // id: labelWithButton2 Layout.fillWidth: true Layout.topMargin: -8 Layout.bottomMargin: -8 @@ -142,17 +239,12 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 - - clickedFunction: function() { - SettingsController.openLogsFolder() - } + clickedFunction: openLogsHandler } DividerType {} LabelWithButtonType { - // id: labelWithButton2 Layout.fillWidth: true Layout.topMargin: -8 Layout.bottomMargin: -8 @@ -161,115 +253,10 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 - - clickedFunction: function() { - var fileName = "" - if (GC.isMobile()) { - fileName = "AmneziaVPN.log" - } else { - fileName = SystemController.getFileName(qsTr("Save"), - qsTr("Logs files (*.log)"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", - true, - ".log") - } - if (fileName !== "") { - PageController.showBusyIndicator(true) - SettingsController.exportLogsFile(fileName) - PageController.showBusyIndicator(false) - PageController.showNotificationMessage(qsTr("Logs file saved")) - } - } + clickedFunction: exportLogsHandler } DividerType {} - - ListItemTitleType { - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("Service logs") - } - - ParagraphTextType { - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: AmneziaStyle.color.mutedGray - text: qsTr("AmneziaVPN-service logs") - } - - LabelWithButtonType { - // id: labelWithButton2 - - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.topMargin: -8 - Layout.bottomMargin: -8 - - text: qsTr("Open logs folder") - leftImageSource: "qrc:/images/controls/folder-open.svg" - isSmallLeftImage: true - - // KeyNavigation.tab: labelWithButton3 - - clickedFunction: function() { - SettingsController.openServiceLogsFolder() - } - } - - DividerType { - visible: !GC.isMobile() - } - - LabelWithButtonType { - // id: labelWithButton2 - - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.topMargin: -8 - Layout.bottomMargin: -8 - - text: qsTr("Export logs") - leftImageSource: "qrc:/images/controls/save.svg" - isSmallLeftImage: true - - // KeyNavigation.tab: labelWithButton3 - - clickedFunction: function() { - var fileName = "" - if (GC.isMobile()) { - fileName = "AmneziaVPN-service.log" - } else { - fileName = SystemController.getFileName(qsTr("Save"), - qsTr("Logs files (*.log)"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service", - true, - ".log") - } - if (fileName !== "") { - PageController.showBusyIndicator(true) - SettingsController.exportServiceLogsFile(fileName) - PageController.showBusyIndicator(false) - PageController.showNotificationMessage(qsTr("Logs file saved")) - } - } - } - - DividerType { - visible: !GC.isMobile() - } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index e170a351..cd736d39 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -100,8 +100,6 @@ PageType { text: qsTr("Check the server for previously installed Amnezia services") descriptionText: qsTr("Add them to the application if they were not displayed") - KeyNavigation.tab: labelWithButton2 - clickedFunction: function() { PageController.showBusyIndicator(true) InstallController.scanServerForInstalledContainers() @@ -121,8 +119,6 @@ PageType { text: qsTr("Reboot server") textColor: AmneziaStyle.color.vibrantRed - KeyNavigation.tab: labelWithButton3 - clickedFunction: function() { var headerText = qsTr("Do you want to reboot the server?") var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") @@ -162,16 +158,6 @@ PageType { text: qsTr("Remove server from application") textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: { - if (content.isServerWithWriteAccess) { - labelWithButton4.forceActiveFocus() - } else { - labelWithButton5.visible ? - labelWithButton5.forceActiveFocus() : - lastItemTabClickedSignal() - } - } - clickedFunction: function() { var headerText = qsTr("Do you want to remove the server from application?") var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") @@ -210,10 +196,6 @@ PageType { text: qsTr("Clear server from Amnezia software") textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: labelWithButton5.visible ? - labelWithButton5.forceActiveFocus() : - root.lastItemTabClickedSignal() - clickedFunction: function() { var headerText = qsTr("Do you want to clear server from Amnezia software?") var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.") @@ -253,8 +235,6 @@ PageType { text: qsTr("Reset API config") textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: root.lastItemTabClickedSignal() - clickedFunction: function() { var headerText = qsTr("Do you want to reset API config?") var descriptionText = "" diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 95ae5c8a..e17cec8f 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -19,24 +19,26 @@ import "../Components" PageType { id: root - property int pageSettingsServerProtocols: 0 - property int pageSettingsServerServices: 1 - property int pageSettingsServerData: 2 - property int pageSettingsApiServerInfo: 3 - property int pageSettingsApiLanguageList: 4 + readonly property int pageSettingsServerProtocols: 0 + readonly property int pageSettingsServerServices: 1 + readonly property int pageSettingsServerData: 2 + readonly property int pageSettingsApiServerInfo: 3 + readonly property int pageSettingsApiLanguageList: 4 - defaultActiveFocusItem: focusItem + property var server Connections { target: PageController function onGoToPageSettingsServerServices() { - tabBar.currentIndex = root.pageSettingsServerServices + tabBar.setCurrentIndex(root.pageSettingsServerServices) } } SortFilterProxyModel { id: proxyServersModel + objectName: "proxyServersModel" + sourceModel: ServersModel filters: [ ValueFilter { @@ -44,147 +46,111 @@ PageType { value: true } ] - } - Item { - id: focusItem - KeyNavigation.tab: header + Component.onCompleted: { + root.server = proxyServersModel.get(0) + } } ColumnLayout { + objectName: "mainLayout" + anchors.fill: parent + anchors.topMargin: 20 - spacing: 16 + spacing: 4 - Repeater { - id: header - model: proxyServersModel + BackButtonType { + id: backButton + objectName: "backButton" - activeFocusOnTab: true - onFocusChanged: { - header.itemAt(0).focusItem.forceActiveFocus() + backButtonFunction: function() { + if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo + && ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + nestedStackView.currentIndex = root.pageSettingsApiLanguageList + } else { + PageController.closePage() + } + } + } + + HeaderType { + id: headerContent + objectName: "headerContent" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 10 + + actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" : "qrc:/images/controls/edit-3.svg" + + headerText: root.server.name + descriptionText: { + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + return ApiServicesModel.getSelectedServiceData("serviceDescription") + } else if (ServersModel.getProcessedServerData("isServerFromTelegramApi")) { + return root.server.serverDescription + } else if (ServersModel.isProcessedServerHasWriteAccess()) { + return root.server.credentialsLogin + " · " + root.server.hostName + } else { + return root.server.hostName + } } - delegate: ColumnLayout { - - property alias focusItem: backButton - - id: content - - Layout.topMargin: 20 - - BackButtonType { - id: backButton - KeyNavigation.tab: headerContent.actionButton - - backButtonFunction: function() { - if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && - ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { - nestedStackView.currentIndex = root.pageSettingsApiLanguageList - } else { - PageController.closePage() - } - } + actionButtonFunction: function() { + if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { + nestedStackView.currentIndex = root.pageSettingsApiServerInfo + } else { + serverNameEditDrawer.openTriggered() } + } + } + + DrawerType2 { + id: serverNameEditDrawer + objectName: "serverNameEditDrawer" + + parent: root + + anchors.fill: parent + expandedHeight: root.height * 0.35 + + expandedStateContent: ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: serverName - HeaderType { - id: headerContent Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" : "qrc:/images/controls/edit-3.svg" - - headerText: name - descriptionText: { - if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - return ApiServicesModel.getSelectedServiceData("serviceDescription") - } else if (ServersModel.getProcessedServerData("isServerFromTelegramApi")) { - return serverDescription - } else if (ServersModel.isProcessedServerHasWriteAccess()) { - return credentialsLogin + " · " + hostName - } else { - return hostName - } - } - - KeyNavigation.tab: tabBar - - actionButtonFunction: function() { - if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { - nestedStackView.currentIndex = root.pageSettingsApiServerInfo - } else { - serverNameEditDrawer.open() - } - } + headerText: qsTr("Server name") + textFieldText: root.server.name + textField.maximumLength: 30 + checkEmptyText: true } - DrawerType2 { - id: serverNameEditDrawer + BasicButtonType { + id: saveButton - parent: root + Layout.fillWidth: true - anchors.fill: parent - expandedHeight: root.height * 0.35 + text: qsTr("Save") - onClosed: { - if (!GC.isMobile()) { - headerContent.actionButton.forceActiveFocus() - } - } - - expandedContent: ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 32 - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - Connections { - target: serverNameEditDrawer - enabled: !GC.isMobile() - function onOpened() { - serverName.textField.forceActiveFocus() - } + clickedFunc: function() { + if (serverName.textFieldText === "") { + return } - Item { - id: focusItem1 - KeyNavigation.tab: serverName.textField - } - - TextFieldWithHeaderType { - id: serverName - - Layout.fillWidth: true - headerText: qsTr("Server name") - textFieldText: name - textField.maximumLength: 30 - checkEmptyText: true - - KeyNavigation.tab: saveButton - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - - text: qsTr("Save") - KeyNavigation.tab: focusItem1 - - clickedFunc: function() { - if (serverName.textFieldText === "") { - return - } - - if (serverName.textFieldText !== name) { - name = serverName.textFieldText - } - serverNameEditDrawer.close() - } + if (serverName.textFieldText !== root.server.name) { + ServersModel.setProcessedServerData("name", serverName.textFieldText); + root.server = proxyServersModel.get(0); } + serverNameEditDrawer.closeTriggered() } } } @@ -205,35 +171,27 @@ PageType { visible: !ServersModel.getProcessedServerData("isServerFromGatewayApi") - activeFocusOnTab: true - onFocusChanged: { - if (activeFocus) { - protocolsTab.forceActiveFocus() - } - } TabButtonType { id: protocolsTab visible: protocolsPage.installedProtocolsCount width: protocolsPage.installedProtocolsCount ? undefined : 0 - isSelected: tabBar.currentIndex === root.pageSettingsServerProtocols + isSelected: TabBar.tabBar.currentIndex === root.pageSettingsServerProtocols text: qsTr("Protocols") - KeyNavigation.tab: servicesTab - Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerProtocols - Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerProtocols + Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerProtocols) + Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerProtocols) } TabButtonType { id: servicesTab visible: servicesPage.installedServicesCount width: servicesPage.installedServicesCount ? undefined : 0 - isSelected: tabBar.currentIndex === root.pageSettingsServerServices + isSelected: TabBar.tabBar.currentIndex === root.pageSettingsServerServices text: qsTr("Services") - KeyNavigation.tab: dataTab - Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerServices - Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerServices + Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerServices) + Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerServices) } TabButtonType { @@ -241,24 +199,15 @@ PageType { isSelected: tabBar.currentIndex === root.pageSettingsServerData text: qsTr("Management") - Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerData - Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerData - Keys.onTabPressed: function() { - if (nestedStackView.currentIndex === root.pageSettingsServerProtocols) { - return protocolsPage - } else if (nestedStackView.currentIndex === root.pageSettingsServerProtocols) { - return servicesPage - } else { - return dataPage - } - } + Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerData) + Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerData) } } StackLayout { id: nestedStackView - Layout.preferredWidth: root.width - Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + + Layout.fillWidth: true currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? (ServersModel.getProcessedServerData("isCountrySelectionAvailable") ? @@ -267,36 +216,26 @@ PageType { PageSettingsServerProtocols { id: protocolsPage stackView: root.stackView - - onLastItemTabClickedSignal: lastItemTabClicked(focusItem) } PageSettingsServerServices { id: servicesPage stackView: root.stackView - - onLastItemTabClickedSignal: lastItemTabClicked(focusItem) } PageSettingsServerData { id: dataPage stackView: root.stackView - - onLastItemTabClickedSignal: lastItemTabClicked(focusItem) } PageSettingsApiServerInfo { id: apiInfoPage stackView: root.stackView - -// onLastItemTabClickedSignal: lastItemTabClicked(focusItem) } PageSettingsApiLanguageList { id: apiLanguageListPage stackView: root.stackView - -// onLastItemTabClickedSignal: lastItemTabClicked(focusItem) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index dcdf01af..ade94ebb 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -21,13 +21,6 @@ PageType { property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex()) - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: header @@ -39,7 +32,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: protocols } HeaderType { @@ -57,30 +49,36 @@ PageType { height: protocols.contentItem.height clip: true interactive: true - model: ProtocolsModel - property int currentFocusIndex: 0 - - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus() - } - } + property bool isFocusable: true Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus() - } else { - clearCacheButton.forceActiveFocus() - } + FocusController.nextKeyTabItem() } - delegate: Item { - property var focusItem: clientSettings.rightButton + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + model: ProtocolsModel + + delegate: Item { implicitWidth: protocols.width implicitHeight: delegateContent.implicitHeight @@ -160,109 +158,112 @@ PageType { } } } - } - LabelWithButtonType { - id: clearCacheButton + footer: ColumnLayout { + width: header.width - Layout.fillWidth: true + LabelWithButtonType { + id: clearCacheButton - visible: root.isClearCacheVisible - KeyNavigation.tab: removeButton + Layout.fillWidth: true - text: qsTr("Clear profile") + visible: root.isClearCacheVisible - clickedFunction: function() { - var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName()) - var descriptionText = qsTr("The connection configuration will be deleted for this device only") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") + text: qsTr("Clear profile") - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) - PageController.showNotificationMessage(message) - return + clickedFunction: function() { + var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("The connection configuration will be deleted for this device only") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) + PageController.showNotificationMessage(message) + return + } + + PageController.showBusyIndicator(true) + InstallController.clearCachedProfile() + PageController.showBusyIndicator(false) + } + var noButtonFunction = function() { + // if (!GC.isMobile()) { + // focusItem.forceActiveFocus() + // } + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - PageController.showBusyIndicator(true) - InstallController.clearCachedProfile() - PageController.showBusyIndicator(false) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() + MouseArea { + anchors.fill: clearCacheButton + cursorShape: Qt.PointingHandCursor + enabled: false } } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - MouseArea { - anchors.fill: clearCacheButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - visible: root.isClearCacheVisible - } - - LabelWithButtonType { - id: removeButton - - Layout.fillWidth: true - - visible: ServersModel.isProcessedServerHasWriteAccess() - Keys.onTabPressed: lastItemTabClicked(focusItem) - - text: qsTr("Remove ") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) - var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected - && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Cannot remove active container")) - } else - { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeProcessedContainer() - } + visible: root.isClearCacheVisible } - var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() + + LabelWithButtonType { + id: removeButton + + Layout.fillWidth: true + + visible: ServersModel.isProcessedServerHasWriteAccess() + + text: qsTr("Remove ") + textColor: AmneziaStyle.color.vibrantRed + + clickedFunction: function() { + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Cannot remove active container")) + } else + { + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeProcessedContainer() + } + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + focusItem.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false } } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - MouseArea { - anchors.fill: removeButton - cursorShape: Qt.PointingHandCursor - enabled: false + visible: ServersModel.isProcessedServerHasWriteAccess() + } } } - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - visible: ServersModel.isProcessedServerHasWriteAccess() - } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 0bc1fc7d..ba72957e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -21,53 +21,45 @@ PageType { property var installedProtocolsCount - onFocusChanged: settingsContainersListView.forceActiveFocus() - signal lastItemTabClickedSignal() + function resetView() { + settingsContainersListView.positionViewAtBeginning() + } - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + SettingsContainersListView { + id: settingsContainersListView - Column { - id: content + anchors.fill: parent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + Connections { + target: ServersModel - SettingsContainersListView { - id: settingsContainersListView - - Connections { - target: ServersModel - - function onProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() - } - } - - function updateContainersModelFilters() { - if (ServersModel.isProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() - } - root.installedProtocolsCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - sorters: [ - RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }, - RoleSorter { roleName: "installPageOrder"; sortOrder: Qt.AscendingOrder } - ] - } - - Component.onCompleted: updateContainersModelFilters() + function onProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() } } + + function updateContainersModelFilters() { + if (ServersModel.isProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + sorters: [ + RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }, + RoleSorter { roleName: "installPageOrder"; sortOrder: Qt.AscendingOrder } + ] + } + + Component.onCompleted: { + settingsContainersListView.isFocusable = true + settingsContainersListView.interactive = true + updateContainersModelFilters() + } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 440fd8db..a46d4051 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -21,52 +21,40 @@ PageType { property var installedServicesCount - onFocusChanged: settingsContainersListView.forceActiveFocus() - signal lastItemTabClickedSignal() + SettingsContainersListView { + id: settingsContainersListView - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.fill: parent - Column { - id: content + Connections { + target: ServersModel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - SettingsContainersListView { - id: settingsContainersListView - - Connections { - target: ServersModel - - function onProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() - } - } - - function updateContainersModelFilters() { - if (ServersModel.isProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() - } - root.installedServicesCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - sorters: [ - RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder } - ] - } - - Component.onCompleted: updateContainersModelFilters() + function onProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() } } + + function updateContainersModelFilters() { + if (ServersModel.isProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + sorters: [ + RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder } + ] + } + + Component.onCompleted: { + settingsContainersListView.isFocusable = true + settingsContainersListView.interactive = true + updateContainersModelFilters() + } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 102dd46f..1852f9c3 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -18,13 +18,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - ColumnLayout { id: header @@ -36,7 +29,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: servers } HeaderType { @@ -48,95 +40,70 @@ PageType { } } - FlickableType { - id: fl + ListView { + id: servers + objectName: "servers" + + width: parent.width anchors.top: header.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + anchors.left: parent.left + anchors.right: parent.right - Column { - id: col - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + height: 500 // servers.contentItem.height // TODO: calculate height - ListView { - id: servers - width: parent.width - height: servers.contentItem.height + property bool isFocusable: true - model: ServersModel + model: ServersModel - clip: true - interactive: false + clip: true + interactive: false - activeFocusOnTab: true - focus: true - Keys.onTabPressed: { - if (currentIndex < servers.count - 1) { - servers.incrementCurrentIndex() - } else { - servers.currentIndex = 0 - focusItem.forceActiveFocus() - root.lastItemTabClicked() - } + onVisibleChanged: { + if (visible) { + currentIndex = 0 + } + } - fl.ensureVisible(this.currentItem) - } + delegate: Item { + implicitWidth: servers.width + implicitHeight: delegateContent.implicitHeight - onVisibleChanged: { - if (visible) { - currentIndex = 0 - } - } + ColumnLayout { + id: delegateContent - delegate: Item { - implicitWidth: servers.width - implicitHeight: delegateContent.implicitHeight + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - onFocusChanged: { - if (focus) { - server.rightButton.forceActiveFocus() - } - } + LabelWithButtonType { + id: server + Layout.fillWidth: true - ColumnLayout { - id: delegateContent + text: name - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelWithButtonType { - id: server - Layout.fillWidth: true - - text: name - parentFlickable: fl - descriptionText: { - var servicesNameString = "" - var servicesName = ServersModel.getAllInstalledServicesName(index) - for (var i = 0; i < servicesName.length; i++) { - servicesNameString += servicesName[i] + " · " - } - - if (ServersModel.isServerFromApi(index)) { - return servicesNameString + serverDescription - } else { - return servicesNameString + hostName - } - } - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) - } + descriptionText: { + var servicesNameString = "" + var servicesName = ServersModel.getAllInstalledServicesName(index) + for (var i = 0; i < servicesName.length; i++) { + servicesNameString += servicesName[i] + " · " } - DividerType {} + if (ServersModel.isServerFromApi(index)) { + return servicesNameString + serverDescription + } else { + return servicesNameString + hostName + } + } + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ServersModel.processedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) } } + + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index f5fe285a..e61dc9f6 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -23,13 +23,6 @@ PageType { property var isServerFromTelegramApi: ServersModel.getDefaultServerData("isServerFromTelegramApi") - defaultActiveFocusItem: searchField.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - property bool pageEnabled Component.onCompleted: { @@ -99,7 +92,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: switcher } RowLayout { @@ -129,8 +121,6 @@ PageType { onToggled: { onToggledFunc() } Keys.onEnterPressed: { onToggledFunc() } Keys.onReturnPressed: { onToggledFunc() } - - KeyNavigation.tab: selector } } @@ -158,7 +148,7 @@ PageType { clickedFunction: function() { selector.text = selectedText - selector.close() + selector.closeTriggered() if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { SitesModel.routeMode = root.routeModesModel[currentIndex].type } @@ -179,124 +169,120 @@ PageType { } } } - - KeyNavigation.tab: { - return sites.count > 0 ? - sites : - searchField.textField - } } } - FlickableType { - id: fl + ListView { + id: sites + anchors.top: header.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin + width: parent.width + + height: 200 // TODO: Change to correct height enabled: root.pageEnabled - Column { - id: col - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + property bool isFocusable: true - ListView { - id: sites - width: parent.width - height: sites.contentItem.height + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - model: SortFilterProxyModel { - id: proxySitesModel - sourceModel: SitesModel - filters: [ - AnyOf { - RegExpFilter { - roleName: "url" - pattern: ".*" + searchField.textField.text + ".*" - caseSensitivity: Qt.CaseInsensitive - } - RegExpFilter { - roleName: "ip" - pattern: ".*" + searchField.textField.text + ".*" - caseSensitivity: Qt.CaseInsensitive + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + model: SortFilterProxyModel { + id: proxySitesModel + sourceModel: SitesModel + filters: [ + AnyOf { + RegExpFilter { + roleName: "url" + pattern: ".*" + searchField.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + RegExpFilter { + roleName: "ip" + pattern: ".*" + searchField.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + } + ] + } + + clip: true + + delegate: Item { + implicitWidth: sites.width + implicitHeight: delegateContent.implicitHeight + + // onActiveFocusChanged: { + // if (activeFocus) { + // site.rightButton.forceActiveFocus() + // } + // } + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: site + Layout.fillWidth: true + + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + var headerText = qsTr("Remove ") + url + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SitesController.removeSite(proxySitesModel.mapToSource(index)) + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() } } - ] - } - - clip: true - interactive: false - - activeFocusOnTab: true - focus: true - Keys.onTabPressed: { - if (currentIndex < this.count - 1) { - this.incrementCurrentIndex() - } else { - currentIndex = 0 - searchField.textField.forceActiveFocus() - } - - fl.ensureVisible(currentItem) - } - - delegate: Item { - implicitWidth: sites.width - implicitHeight: delegateContent.implicitHeight - - onActiveFocusChanged: { - if (activeFocus) { - site.rightButton.forceActiveFocus() - } - } - - ColumnLayout { - id: delegateContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelWithButtonType { - id: site - Layout.fillWidth: true - - text: url - descriptionText: ip - rightImageSource: "qrc:/images/controls/trash.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - var headerText = qsTr("Remove ") + url + "?" - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - SitesController.removeSite(proxySitesModel.mapToSource(index)) - if (!GC.isMobile()) { - site.rightButton.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - site.rightButton.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + var noButtonFunction = function() { + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() } } - DividerType {} + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } + + DividerType {} } - } } + Rectangle { anchors.fill: addSiteButton anchors.bottomMargin: -24 @@ -325,7 +311,6 @@ PageType { textFieldPlaceholderText: qsTr("website or IP") buttonImageSource: "qrc:/images/controls/plus.svg" - KeyNavigation.tab: GC.isMobile() ? focusItem : addSiteButtonImage clickedFunc: function() { PageController.showBusyIndicator(true) @@ -344,13 +329,11 @@ PageType { imageColor: AmneziaStyle.color.paleGray onClicked: function () { - moreActionsDrawer.open() + moreActionsDrawer.openTriggered() } Keys.onReturnPressed: addSiteButtonImage.clicked() Keys.onEnterPressed: addSiteButtonImage.clicked() - - Keys.onTabPressed: lastItemTabClicked(focusItem) } } @@ -360,38 +343,13 @@ PageType { anchors.fill: parent expandedHeight: parent.height * 0.4375 - onClosed: { - if (root.defaultActiveFocusItem && !GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() - } - } - - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: moreActionsDrawerContent anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - Connections { - target: moreActionsDrawer - - function onOpened() { - focusItem1.forceActiveFocus() - } - - function onActiveFocusChanged() { - if (!GC.isMobile()) { - focusItem1.forceActiveFocus() - } - } - } - - Item { - id: focusItem1 - KeyNavigation.tab: importSitesButton.rightButton - } - Header2Type { Layout.fillWidth: true Layout.margins: 16 @@ -407,10 +365,8 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - importSitesDrawer.open() + importSitesDrawer.openTriggered() } - - KeyNavigation.tab: exportSitesButton } DividerType {} @@ -420,8 +376,6 @@ PageType { Layout.fillWidth: true text: qsTr("Save site list") - KeyNavigation.tab: focusItem1 - clickedFunction: function() { var fileName = "" if (GC.isMobile()) { @@ -436,7 +390,7 @@ PageType { if (fileName !== "") { PageController.showBusyIndicator(true) SitesController.exportSites(fileName) - moreActionsDrawer.close() + moreActionsDrawer.closeTriggered() PageController.showBusyIndicator(false) } } @@ -452,28 +406,9 @@ PageType { anchors.fill: parent expandedHeight: parent.height * 0.4375 - onClosed: { - if (!GC.isMobile()) { - moreActionsDrawer.forceActiveFocus() - } - } - - expandedContent: Item { + expandedStateContent: Item { implicitHeight: importSitesDrawer.expandedHeight - Connections { - target: importSitesDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem2.forceActiveFocus() - } - } - - Item { - id: focusItem2 - KeyNavigation.tab: importSitesDrawerBackButton - } - BackButtonType { id: importSitesDrawerBackButton @@ -482,10 +417,8 @@ PageType { anchors.right: parent.right anchors.topMargin: 16 - KeyNavigation.tab: importSitesButton2 - backButtonFunction: function() { - importSitesDrawer.close() + importSitesDrawer.closeTriggered() } } @@ -516,7 +449,6 @@ PageType { Layout.fillWidth: true text: qsTr("Replace site list") - KeyNavigation.tab: importSitesButton3 clickedFunction: function() { var fileName = SystemController.getFileName(qsTr("Open sites file"), @@ -533,7 +465,6 @@ PageType { id: importSitesButton3 Layout.fillWidth: true text: qsTr("Add imported sites to existing ones") - KeyNavigation.tab: focusItem2 clickedFunction: function() { var fileName = SystemController.getFileName(qsTr("Open sites file"), @@ -548,8 +479,8 @@ PageType { PageController.showBusyIndicator(true) SitesController.importSites(fileName, replaceExistingSites) PageController.showBusyIndicator(false) - importSitesDrawer.close() - moreActionsDrawer.close() + importSitesDrawer.closeTriggered() + moreActionsDrawer.closeTriggered() } DividerType {} diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 75fd3d47..9e541ded 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -15,8 +15,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - FlickableType { id: fl anchors.top: parent.top @@ -32,15 +30,9 @@ PageType { spacing: 0 - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton Layout.topMargin: 20 -// KeyNavigation.tab: fileButton.rightButton } HeaderType { diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index 85a50393..6ecfdc99 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -14,8 +14,6 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: focusItem - FlickableType { id: fl anchors.top: parent.top @@ -31,15 +29,9 @@ PageType { spacing: 0 - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton Layout.topMargin: 20 -// KeyNavigation.tab: fileButton.rightButton } HeaderType { @@ -59,6 +51,8 @@ PageType { height: containers.contentItem.height spacing: 16 + property bool isFocusable: true + currentIndex: 1 interactive: false model: ApiServicesModel @@ -93,6 +87,9 @@ PageType { PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo) } } + + Keys.onEnterPressed: clicked() + Keys.onReturnPressed: clicked() } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 7c031997..ca0556a1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,32 +25,155 @@ PageType { } } - defaultActiveFocusItem: focusItem + QtObject { + id: amneziaVpn - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height - - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 0 - - Item { - id: focusItem - KeyNavigation.tab: textKey.textField + property string title: qsTr("VPN by Amnezia") + property string description: qsTr("Connect to classic paid and free VPN services from Amnezia") + property string imageSource: "qrc:/images/controls/amnezia.svg" + property bool isVisible: true + property var handler: function() { + PageController.showBusyIndicator(true) + var result = InstallController.fillAvailableServices() + PageController.showBusyIndicator(false) + if (result) { + PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) } + } + } + QtObject { + id: selfHostVpn + + property string title: qsTr("Self-hosted VPN") + property string description: qsTr("Configure Amnezia VPN on your own server") + property string imageSource: "qrc:/images/controls/server.svg" + property bool isVisible: true + property var handler: function() { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + } + } + + QtObject { + id: backupRestore + + property string title: qsTr("Restore from backup") + property string description: qsTr("") + property string imageSource: "qrc:/images/controls/archive-restore.svg" + property bool isVisible: true + property var handler: function() { + var filePath = SystemController.getFileName(qsTr("Open backup file"), + qsTr("Backup files (*.backup)")) + if (filePath !== "") { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } + } + } + + QtObject { + id: fileOpen + + property string title: qsTr("File with connection settings") + property string description: qsTr("") + property string imageSource: "qrc:/images/controls/folder-search-2.svg" + property bool isVisible: true + property var handler: function() { + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : + "Config files (*.vpn *.ovpn *.conf *.json)" + var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) + if (fileName !== "") { + if (ImportController.extractConfigFromFile(fileName)) { + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + } + } + + QtObject { + id: qrScan + + property string title: qsTr("QR code") + property string description: qsTr("") + property string imageSource: "qrc:/images/controls/scan-line.svg" + property bool isVisible: SettingsController.isCameraPresent() + property var handler: function() { + ImportController.startDecodingQr() + if (Qt.platform.os === "ios") { + PageController.goToPage(PageEnum.PageSetupWizardQrReader) + } + } + } + + QtObject { + id: siteLink + + property string title: qsTr("I have nothing") + property string description: qsTr("") + property string imageSource: "qrc:/images/controls/help-circle.svg" + property bool isVisible: true + property var handler: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + property list variants: [ + amneziaVpn, + selfHostVpn, + backupRestore, + fileOpen, + qrScan, + siteLink + ] + + ListView { + id: listView + + anchors.fill: parent + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + + model: variants + + clip: true + + header: ColumnLayout { + width: listView.width HeaderType { - property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() + id: moreButton + property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() + Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 @@ -60,7 +183,7 @@ PageType { actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonFunction: function() { - moreActionsDrawer.open() + moreActionsDrawer.openTriggered() } DrawerType2 { @@ -71,7 +194,7 @@ PageType { anchors.fill: parent expandedHeight: root.height * 0.5 - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -131,6 +254,8 @@ PageType { } ParagraphTextType { + objectName: "insertKeyLabel" + Layout.fillWidth: true Layout.topMargin: 32 Layout.rightMargin: 16 @@ -154,8 +279,6 @@ PageType { textField.text = "" textField.paste() } - - KeyNavigation.tab: continueButton } BasicButtonType { @@ -169,7 +292,6 @@ PageType { visible: textKey.textFieldText !== "" text: qsTr("Continue") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { if (ImportController.extractConfigFromData(textKey.textFieldText)) { @@ -188,142 +310,28 @@ PageType { color: AmneziaStyle.color.charcoalGray text: qsTr("Other connection options") } + } + + delegate: ColumnLayout { + width: listView.width CardWithIconsType { - id: apiInstalling + id: entry Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 Layout.bottomMargin: 16 - headerText: qsTr("VPN by Amnezia") - bodyText: qsTr("Connect to classic paid and free VPN services from Amnezia") + visible: isVisible + + headerText: title + bodyText: description rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/amnezia.svg" + leftImageSource: imageSource - onClicked: function() { - PageController.showBusyIndicator(true) - var result = InstallController.fillAvailableServices() - PageController.showBusyIndicator(false) - if (result) { - PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) - } - } - } - - CardWithIconsType { - id: manualInstalling - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - headerText: qsTr("Self-hosted VPN") - bodyText: qsTr("Configure Amnezia VPN on your own server") - - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/server.svg" - - onClicked: { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) - } - } - - CardWithIconsType { - id: backupRestore - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - visible: PageController.isStartPageVisible() - - headerText: qsTr("Restore from backup") - - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/archive-restore.svg" - - onClicked: { - var filePath = SystemController.getFileName(qsTr("Open backup file"), - qsTr("Backup files (*.backup)")) - if (filePath !== "") { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(filePath) - PageController.showBusyIndicator(false) - } - } - } - - CardWithIconsType { - id: openFile - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - headerText: qsTr("File with connection settings") - - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/folder-search-2.svg" - - onClicked: { - var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : - "Config files (*.vpn *.ovpn *.conf *.json)" - var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) - if (fileName !== "") { - if (ImportController.extractConfigFromFile(fileName)) { - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } - } - } - - CardWithIconsType { - id: scanQr - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - visible: SettingsController.isCameraPresent() - - headerText: qsTr("QR code") - - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/scan-line.svg" - - onClicked: { - ImportController.startDecodingQr() - if (Qt.platform.os === "ios") { - PageController.goToPage(PageEnum.PageSetupWizardQrReader) - } - } - } - - CardWithIconsType { - id: siteLink - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - visible: PageController.isStartPageVisible() - - headerText: qsTr("I have nothing") - - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/help-circle.svg" - - onClicked: { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + onClicked: { handler() } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index aced12b1..aa0b935f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -13,13 +13,6 @@ import "../Controls2/TextTypes" PageType { id: root - defaultActiveFocusItem: hostname.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -27,8 +20,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: hostname.textField } FlickableType { @@ -61,11 +52,11 @@ PageType { headerText: qsTr("Server IP address [:port]") textFieldPlaceholderText: qsTr("255.255.255.255:22") + parentFlickable: fl + textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: username.textField } TextFieldWithHeaderType { @@ -75,11 +66,11 @@ PageType { headerText: qsTr("SSH Username") textFieldPlaceholderText: "root" + parentFlickable: fl + textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: secretData.textField } TextFieldWithHeaderType { @@ -93,6 +84,8 @@ PageType { buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + parentFlickable: fl + clickedFunc: function() { hidePassword = !hidePassword } @@ -100,8 +93,6 @@ PageType { textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: continueButton } BasicButtonType { @@ -112,7 +103,7 @@ PageType { text: qsTr("Continue") - Keys.onTabPressed: lastItemTabClicked(focusItem) + parentFlickable: fl clickedFunc: function() { forceActiveFocus() @@ -153,6 +144,8 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/help-circle.svg" + parentFlickable: fl + onClicked: { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/starter-guide") } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 02a7c928..27df72b0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -17,7 +17,6 @@ PageType { id: root property bool isEasySetup: true - defaultActiveFocusItem: focusItem SortFilterProxyModel { id: proxyContainersModel @@ -34,14 +33,6 @@ PageType { } } - Item { - id: focusItem - implicitWidth: 1 - implicitHeight: 54 - - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -49,8 +40,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: continueButton } FlickableType { @@ -163,7 +152,7 @@ PageType { implicitWidth: parent.width text: qsTr("Continue") - KeyNavigation.tab: setupLaterButton + parentFlickable: fl clickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 6b4c0a1c..0de4da6b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -49,6 +49,32 @@ PageType { interactive: false model: proxyContainersModel + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + delegate: Item { implicitWidth: processedContainerListView.width implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height @@ -62,19 +88,12 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton Layout.topMargin: 20 Layout.rightMargin: -16 Layout.leftMargin: -16 - - KeyNavigation.tab: showDetailsButton } HeaderType { @@ -104,42 +123,19 @@ PageType { KeyNavigation.tab: transportProtoSelector clickedFunc: function() { - showDetailsDrawer.open() + showDetailsDrawer.openTriggered() } } DrawerType2 { id: showDetailsDrawer parent: root - onClosed: { - if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() - } - } anchors.fill: parent expandedHeight: parent.height * 0.9 - expandedContent: Item { - Connections { - target: showDetailsDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem2.forceActiveFocus() - } - } - + expandedStateContent: Item { implicitHeight: showDetailsDrawer.expandedHeight - Item { - id: focusItem2 - KeyNavigation.tab: showDetailsBackButton - onFocusChanged: { - if (focusItem2.activeFocus) { - fl.contentY = 0 - } - } - } - BackButtonType { id: showDetailsBackButton @@ -148,10 +144,8 @@ PageType { anchors.right: parent.right anchors.topMargin: 16 - KeyNavigation.tab: showDetailsCloseButton - backButtonFunction: function() { - showDetailsDrawer.close() + showDetailsDrawer.closeTriggered() } } @@ -205,10 +199,9 @@ PageType { parentFlickable: fl text: qsTr("Close") - Keys.onTabPressed: lastItemTabClicked(focusItem2) clickedFunc: function() { - showDetailsDrawer.close() + showDetailsDrawer.closeTriggered() } } } @@ -229,8 +222,6 @@ PageType { Layout.fillWidth: true rootWidth: root.width - - KeyNavigation.tab: (port.visible && port.enabled) ? port.textField : installButton } TextFieldWithHeaderType { @@ -242,8 +233,6 @@ PageType { headerText: qsTr("Port") textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } - - KeyNavigation.tab: installButton } Rectangle { @@ -259,8 +248,6 @@ PageType { text: qsTr("Install") - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { if (!port.textField.acceptableInput && ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && @@ -288,11 +275,6 @@ PageType { var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) transportProtoSelector.visible = protocolSelectorVisible transportProtoHeader.visible = protocolSelectorVisible - - if (port.visible && port.enabled) - defaultActiveFocusItem = port.textField - else - defaultActiveFocusItem = focusItem } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 48265f66..15c1170f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -15,13 +15,6 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel @@ -52,7 +45,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: containers } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index b12c7830..2d6790ba 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -14,8 +14,6 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem - ColumnLayout { id: content @@ -32,11 +30,6 @@ PageType { Layout.preferredHeight: 287 } - Item { - id: focusItem - KeyNavigation.tab: startButton - } - BasicButtonType { id: startButton Layout.fillWidth: true @@ -50,8 +43,6 @@ PageType { clickedFunc: function() { PageController.goToPage(PageEnum.PageSetupWizardConfigSource) } - - Keys.onTabPressed: lastItemTabClicked(focusItem) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index c4227df1..126a7c91 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -13,14 +13,6 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: textKey.textField - - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - FlickableType { id: fl anchors.top: parent.top @@ -39,7 +31,6 @@ PageType { BackButtonType { id: backButton Layout.topMargin: 20 - KeyNavigation.tab: textKey.textField } HeaderType { @@ -67,8 +58,6 @@ PageType { textField.text = "" textField.paste() } - - KeyNavigation.tab: continueButton } } } @@ -84,7 +73,6 @@ PageType { anchors.bottomMargin: 32 text: qsTr("Continue") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { if (ImportController.extractConfigFromData(textKey.textFieldText)) { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 92048f36..14096742 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -16,13 +16,6 @@ PageType { property bool showContent: false - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -30,8 +23,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: showContentButton } Connections { @@ -107,7 +98,8 @@ PageType { textColor: AmneziaStyle.color.goldenApricot text: showContent ? qsTr("Collapse content") : qsTr("Show content") - KeyNavigation.tab: connectButton + + parentFlickable: fl clickedFunc: function() { showContent = !showContent @@ -186,8 +178,6 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - Keys.onTabPressed: lastItemTabClicked(focusItem) - BasicButtonType { id: connectButton Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 995fa3e7..c238d6c4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -18,8 +18,6 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: clientNameTextField.textField - enum ConfigType { AmneziaConnection, OpenVpn, @@ -47,7 +45,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - shareConnectionDrawer.open() + shareConnectionDrawer.openTriggered() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -104,7 +102,7 @@ PageType { } function onExportErrorOccurred(error) { - shareConnectionDrawer.close() + shareConnectionDrawer.closeTriggered() PageController.showErrorMessage(error) } @@ -172,16 +170,6 @@ PageType { spacing: 0 - Item { - id: focusItem - KeyNavigation.tab: header.actionButton - onFocusChanged: { - if (focusItem.activeFocus) { - a.contentY = 0 - } - } - } - HeaderType { id: header Layout.fillWidth: true @@ -191,10 +179,17 @@ PageType { actionButtonImage: "qrc:/images/controls/more-vertical.svg" actionButtonFunction: function() { - shareFullAccessDrawer.open() + shareFullAccessDrawer.openTriggered() } - KeyNavigation.tab: connectionRadioButton + actionButton.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (actionButton.activeFocus) { + if (fl) { + fl.ensureVisible(moreButton) + } + } + } DrawerType2 { id: shareFullAccessDrawer @@ -203,13 +198,8 @@ PageType { anchors.fill: parent expandedHeight: root.height - onClosed: { - if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() - } - } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: shareFullAccessDrawerContent anchors.top: parent.top anchors.left: parent.left @@ -222,14 +212,6 @@ PageType { shareFullAccessDrawer.expandedHeight = shareFullAccessDrawerContent.implicitHeight + 32 } - Connections { - target: shareFullAccessDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem.forceActiveFocus() - } - } - Header2Type { Layout.fillWidth: true Layout.bottomMargin: 16 @@ -240,24 +222,17 @@ PageType { descriptionText: qsTr("Use for your own devices, or share with those you trust to manage the server.") } - Item { - id: focusItem - KeyNavigation.tab: shareFullAccessButton.rightButton - } - LabelWithButtonType { id: shareFullAccessButton Layout.fillWidth: true text: qsTr("Share") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: focusItem clickedFunction: function() { PageController.goToPage(PageEnum.PageShareFullAccess) - shareFullAccessDrawer.close() + shareFullAccessDrawer.closeTriggered() } - } } } @@ -288,8 +263,6 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Connection") - KeyNavigation.tab: usersRadioButton - onClicked: { accessTypeSelector.currentIndex = 0 if (!GC.isMobile()) { @@ -305,15 +278,12 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Users") - KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? clientNameTextField.textField : serverSelector - onClicked: { accessTypeSelector.currentIndex = 1 PageController.showBusyIndicator(true) ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) - focusItem.forceActiveFocus() } } } @@ -342,9 +312,6 @@ PageType { textField.maximumLength: 20 checkEmptyText: true - - KeyNavigation.tab: serverSelector - } DropDownType { @@ -390,7 +357,7 @@ PageType { serverSelector.severSelectorIndexChanged() } - serverSelector.close() + serverSelector.closeTriggered() } Component.onCompleted: { @@ -408,8 +375,6 @@ PageType { ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) } } - - KeyNavigation.tab: protocolSelector } DropDownType { @@ -450,7 +415,7 @@ PageType { clickedFunction: function() { handler() - protocolSelector.close() + protocolSelector.closeTriggered() } Connections { @@ -508,12 +473,6 @@ PageType { } } } - - KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? - exportTypeSelector : - isSearchBarVisible ? - searchTextField.textField : - usersHeader.actionButton } DropDownType { @@ -549,7 +508,7 @@ PageType { clickedFunction: function() { exportTypeSelector.text = selectedText exportTypeSelector.currentIndex = currentIndex - exportTypeSelector.close() + exportTypeSelector.closeTriggered() } Component.onCompleted: { @@ -557,9 +516,6 @@ PageType { exportTypeSelector.currentIndex = currentIndex } } - - KeyNavigation.tab: shareButton - } BasicButtonType { @@ -575,7 +531,6 @@ PageType { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) parentFlickable: a @@ -584,7 +539,6 @@ PageType { ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } } - } Header2Type { @@ -604,7 +558,6 @@ PageType { Keys.onTabPressed: clientsListView.model.count > 0 ? clientsListView.forceActiveFocus() : lastItemTabClicked(focusItem) - } RowLayout { @@ -622,11 +575,11 @@ PageType { target: root function onIsSearchBarVisibleChanged() { if (root.isSearchBarVisible) { - searchTextField.textField.forceActiveFocus() + // searchTextField.textField.forceActiveFocus() } else { searchTextField.textFieldText = "" if (!GC.isMobile()) { - usersHeader.actionButton.forceActiveFocus() + // usersHeader.actionButton.forceActiveFocus() } } } @@ -638,15 +591,15 @@ PageType { function navigateTo() { if (GC.isMobile()) { - focusItem.forceActiveFocus() + // focusItem.forceActiveFocus() return; } if (searchTextField.textFieldText === "") { root.isSearchBarVisible = false - usersHeader.actionButton.forceActiveFocus() + // usersHeader.actionButton.forceActiveFocus() } else { - closeSearchButton.forceActiveFocus() + // closeSearchButton.forceActiveFocus() } } @@ -663,9 +616,9 @@ PageType { Keys.onTabPressed: { if (!GC.isMobile()) { if (clientsListView.model.count > 0) { - clientsListView.forceActiveFocus() + // clientsListView.forceActiveFocus() } else { - lastItemTabClicked(focusItem) + // lastItemTabClicked(focusItem) } } } @@ -687,6 +640,32 @@ PageType { visible: accessTypeSelector.currentIndex === 1 + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + model: SortFilterProxyModel { id: proxyClientManagementModel sourceModel: ClientManagementModel @@ -698,44 +677,44 @@ PageType { } clip: true - interactive: false + // interactive: false - activeFocusOnTab: true - focus: true - Keys.onTabPressed: { - if (!GC.isMobile()) { - if (currentIndex < this.count - 1) { - this.incrementCurrentIndex() - currentItem.focusItem.forceActiveFocus() - } else { - this.currentIndex = 0 - lastItemTabClicked(focusItem) - } - } - } + // activeFocusOnTab: true + // focus: true + // Keys.onTabPressed: { + // if (!GC.isMobile()) { + // if (currentIndex < this.count - 1) { + // this.incrementCurrentIndex() + // // currentItem.focusItem.forceActiveFocus() + // } else { + // this.currentIndex = 0 + // // lastItemTabClicked(focusItem) + // } + // } + // } - onActiveFocusChanged: { - if (focus && !GC.isMobile()) { - currentIndex = 0 - currentItem.focusItem.forceActiveFocus() - } - } + // onActiveFocusChanged: { + // if (focus && !GC.isMobile()) { + // currentIndex = 0 + // // currentItem.focusItem.forceActiveFocus() + // } + // } - onCurrentIndexChanged: { - if (currentItem) { - if (currentItem.y < a.contentY) { - a.contentY = currentItem.y - } else if (currentItem.y + currentItem.height + clientsListView.y > a.contentY + a.height) { - a.contentY = currentItem.y + clientsListView.y + currentItem.height - a.height - } - } - } + // onCurrentIndexChanged: { + // if (currentItem) { + // if (currentItem.y < a.contentY) { + // a.contentY = currentItem.y + // } else if (currentItem.y + currentItem.height + clientsListView.y > a.contentY + a.height) { + // a.contentY = currentItem.y + clientsListView.y + currentItem.height - a.height + // } + // } + // } delegate: Item { implicitWidth: clientsListView.width implicitHeight: delegateContent.implicitHeight - property alias focusItem: clientFocusItem.rightButton + // property alias focusItem: clientFocusItem.rightButton ColumnLayout { id: delegateContent @@ -755,7 +734,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - clientInfoDrawer.open() + clientInfoDrawer.openTriggered() } } @@ -768,15 +747,15 @@ PageType { onClosed: { if (!GC.isMobile()) { - focusItem.forceActiveFocus() + // focusItem.forceActiveFocus() } } width: root.width height: root.height - expandedContent: ColumnLayout { - id: expandedContent + expandedStateContent: ColumnLayout { + id: expandedStateContent anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -785,14 +764,14 @@ PageType { anchors.rightMargin: 16 onImplicitHeightChanged: { - clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32 + clientInfoDrawer.expandedHeight = expandedStateContent.implicitHeight + 32 } Connections { target: clientInfoDrawer enabled: !GC.isMobile() function onOpened() { - focusItem1.forceActiveFocus() + // focusItem1.forceActiveFocus() } } @@ -846,11 +825,6 @@ PageType { text: qsTr("Allowed IPs: %1").arg(allowedIps) } - Item { - id: focusItem1 - KeyNavigation.tab: renameButton - } - BasicButtonType { id: renameButton Layout.fillWidth: true @@ -865,10 +839,8 @@ PageType { text: qsTr("Rename") - KeyNavigation.tab: revokeButton - clickedFunc: function() { - clientNameEditDrawer.open() + clientNameEditDrawer.openTriggered() } DrawerType2 { @@ -881,11 +853,11 @@ PageType { onClosed: { if (!GC.isMobile()) { - focusItem1.forceActiveFocus() + // focusItem1.forceActiveFocus() } } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -897,15 +869,10 @@ PageType { target: clientNameEditDrawer enabled: !GC.isMobile() function onOpened() { - clientNameEditor.textField.forceActiveFocus() + // clientNameEditor.textField.forceActiveFocus() } } - Item { - id: focusItem2 - KeyNavigation.tab: clientNameEditor.textField - } - TextFieldWithHeaderType { id: clientNameEditor Layout.fillWidth: true @@ -913,8 +880,6 @@ PageType { textFieldText: clientName textField.maximumLength: 20 checkEmptyText: true - - KeyNavigation.tab: saveButton } BasicButtonType { @@ -923,7 +888,6 @@ PageType { Layout.fillWidth: true text: qsTr("Save") - KeyNavigation.tab: focusItem2 clickedFunc: function() { if (clientNameEditor.textFieldText === "") { @@ -937,7 +901,7 @@ PageType { ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) - clientNameEditDrawer.close() + clientNameEditDrawer.closeTriggered() } } } @@ -958,7 +922,6 @@ PageType { borderWidth: 1 text: qsTr("Revoke") - KeyNavigation.tab: focusItem1 clickedFunc: function() { var headerText = qsTr("Revoke the config for a user - %1?").arg(clientName) @@ -967,12 +930,12 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - clientInfoDrawer.close() + clientInfoDrawer.closeTriggered() root.revokeConfig(index) } var noButtonFunction = function() { if (!GC.isMobile()) { - focusItem1.forceActiveFocus() + // focusItem1.forceActiveFocus() } } @@ -993,7 +956,7 @@ PageType { anchors.fill: parent onClosed: { if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() + // clientNameTextField.textField.forceActiveFocus() } } } @@ -1001,7 +964,7 @@ PageType { MouseArea { anchors.fill: parent onPressed: function(mouse) { - forceActiveFocus() + // forceActiveFocus() mouse.accepted = false } } diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 404ba563..8451835c 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -18,12 +18,7 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: focusItem - - Item { - id: focusItem - KeyNavigation.tab: backButton - } + // defaultActiveFocusItem: focusItem BackButtonType { id: backButton @@ -32,8 +27,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: serverSelector } FlickableType { @@ -85,8 +78,6 @@ PageType { descriptionText: qsTr("Server") headerText: qsTr("Server") - KeyNavigation.tab: shareButton - listView: ListViewWithRadioButtonType { id: serverSelectorListView @@ -113,7 +104,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text - serverSelector.close() + serverSelector.closeTriggered() } Component.onCompleted: { @@ -137,8 +128,6 @@ PageType { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { PageController.showBusyIndicator(true) @@ -153,7 +142,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - shareConnectionDrawer.open() + shareConnectionDrawer.openTriggered() shareConnectionDrawer.contentVisible = true PageController.showBusyIndicator(false) @@ -166,10 +155,5 @@ PageType { id: shareConnectionDrawer anchors.fill: parent - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 640c61ef..c08acb7b 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -15,12 +15,12 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: homeTabButton - property bool isControlsDisabled: false property bool isTabBarDisabled: false Connections { + objectName: "pageControllerConnection" + target: PageController function onGoToPageHome() { @@ -90,19 +90,11 @@ PageType { PageController.closePage() } } - - function onForceTabBarActiveFocus() { - homeTabButton.focus = true - tabBar.forceActiveFocus() - } - - function onForceStackActiveFocus() { - homeTabButton.focus = true - tabBarStackView.forceActiveFocus() - } } Connections { + objectName: "installControllerConnections" + target: InstallController function onInstallationErrorOccurred(error) { @@ -165,6 +157,8 @@ PageType { } Connections { + objectName: "connectionControllerConnections" + target: ConnectionController function onReconnectWithUpdatedContainer(message) { @@ -182,6 +176,8 @@ PageType { } Connections { + objectName: "importControllerConnections" + target: ImportController function onImportErrorOccurred(error, goToPageHome) { @@ -196,6 +192,8 @@ PageType { } Connections { + objectName: "settingsControllerConnections" + target: SettingsController function onLoggingDisableByWatcher() { @@ -218,6 +216,7 @@ PageType { StackViewType { id: tabBarStackView + objectName: "tabBarStackView" anchors.top: parent.top anchors.right: parent.right @@ -247,13 +246,28 @@ PageType { } Keys.onPressed: function(event) { - PageController.keyPressEvent(event.key) - event.accepted = true + console.debug(">>>> ", event.key, " Event is caught by StartPage") + switch (event.key) { + case Qt.Key_Tab: + case Qt.Key_Down: + case Qt.Key_Right: + FocusController.nextKeyTabItem() + break + case Qt.Key_Backtab: + case Qt.Key_Up: + case Qt.Key_Left: + FocusController.previousKeyTabItem() + break + default: + PageController.keyPressEvent(event.key) + event.accepted = true + } } } TabBar { id: tabBar + objectName: "tabBar" anchors.right: parent.right anchors.left: parent.left @@ -269,6 +283,8 @@ PageType { enabled: !root.isControlsDisabled && !root.isTabBarDisabled background: Shape { + objectName: "backgroundShape" + width: parent.width height: parent.height @@ -289,6 +305,8 @@ PageType { TabImageButtonType { id: homeTabButton + objectName: "homeTabButton" + isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" clickedFunc: function () { @@ -296,14 +314,11 @@ PageType { ServersModel.processedIndex = ServersModel.defaultIndex tabBar.currentIndex = 0 } - - KeyNavigation.tab: shareTabButton - Keys.onEnterPressed: this.clicked() - Keys.onReturnPressed: this.clicked() } TabImageButtonType { id: shareTabButton + objectName: "shareTabButton" Connections { target: ServersModel @@ -324,32 +339,30 @@ PageType { tabBarStackView.goToTabBarPage(PageEnum.PageShare) tabBar.currentIndex = 1 } - - KeyNavigation.tab: settingsTabButton } TabImageButtonType { id: settingsTabButton + objectName: "settingsTabButton" + isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" clickedFunc: function () { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) tabBar.currentIndex = 2 } - - KeyNavigation.tab: plusTabButton } TabImageButtonType { id: plusTabButton + objectName: "plusTabButton" + isSelected: tabBar.currentIndex === 3 image: "qrc:/images/controls/plus.svg" clickedFunc: function () { tabBarStackView.goToTabBarPage(PageEnum.PageSetupWizardConfigSource) tabBar.currentIndex = 3 } - - Keys.onTabPressed: PageController.forceStackActiveFocus() } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index fb99559f..8b73e62d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -15,6 +15,7 @@ import "Pages2" Window { id: root objectName: "mainWindow" + visible: true width: GC.screenWidth height: GC.screenHeight @@ -26,13 +27,39 @@ Window { color: AmneziaStyle.color.midnightBlack onClosing: function() { - console.debug("QML onClosing signal") PageController.closeWindow() } title: "AmneziaVPN" + Item { // This item is needed for focus handling + id: defaultFocusItem + objectName: "defaultFocusItem" + + focus: true + + Keys.onPressed: function(event) { + switch (event.key) { + case Qt.Key_Tab: + case Qt.Key_Down: + case Qt.Key_Right: + FocusController.nextKeyTabItem() + break + case Qt.Key_Backtab: + case Qt.Key_Up: + case Qt.Key_Left: + FocusController.previousKeyTabItem() + break + default: + PageController.keyPressEvent(event.key) + event.accepted = true + } + } + } + Connections { + objectName: "pageControllerConnections" + target: PageController function onRaiseMainWindow() { @@ -58,7 +85,7 @@ Window { } function onShowPassphraseRequestDrawer() { - privateKeyPassphraseDrawer.open() + privateKeyPassphraseDrawer.openTriggered() } function onGoToPageSettingsBackup() { @@ -72,6 +99,8 @@ Window { } Connections { + objectName: "settingsControllerConnections" + target: SettingsController function onChangeSettingsFinished(finishedMessage) { @@ -80,11 +109,15 @@ Window { } PageStart { + objectName: "pageStart" + width: root.width height: root.height } Item { + objectName: "popupNotificationItem" + anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom @@ -108,6 +141,8 @@ Window { } Item { + objectName: "popupErrorMessageItem" + anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom @@ -120,6 +155,8 @@ Window { } Item { + objectName: "privateKeyPassphraseDrawerItem" + anchors.fill: parent DrawerType2 { @@ -128,7 +165,7 @@ Window { anchors.fill: parent expandedHeight: root.height * 0.35 - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -167,8 +204,6 @@ Window { clickedFunc: function() { hidePassword = !hidePassword } - - KeyNavigation.tab: saveButton } BasicButtonType { @@ -186,7 +221,7 @@ Window { text: qsTr("Save") clickedFunc: function() { - privateKeyPassphraseDrawer.close() + privateKeyPassphraseDrawer.closeTriggered() PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) } } @@ -195,6 +230,8 @@ Window { } Item { + objectName: "questionDrawerItem" + anchors.fill: parent QuestionDrawer { @@ -205,6 +242,8 @@ Window { } Item { + objectName: "busyIndicatorItem" + anchors.fill: parent BusyIndicatorType { @@ -221,26 +260,26 @@ Window { questionDrawer.noButtonText = noButtonText questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.closeTriggered() if (yesButtonFunction && typeof yesButtonFunction === "function") { yesButtonFunction() } } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.closeTriggered() if (noButtonFunction && typeof noButtonFunction === "function") { noButtonFunction() } } - questionDrawer.open() + questionDrawer.openTriggered() } FileDialog { id: mainFileDialog + objectName: "mainFileDialog" property bool isSaveMode: false - objectName: "mainFileDialog" fileMode: isSaveMode ? FileDialog.SaveFile : FileDialog.OpenFile onAccepted: SystemController.fileDialogClosed(true)