From 02bbcd3a31393f910825e66cc16790f1008ab3bc Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Sep 2024 19:42:55 +0200 Subject: [PATCH 001/100] add focusController class --- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 2 + client/resources.qrc | 383 +++++++------- client/ui/controllers/focusController.cpp | 476 ++++++++++++++++++ client/ui/controllers/focusController.h | 52 ++ client/ui/controllers/pageController.cpp | 18 +- client/ui/controllers/pageController.h | 7 +- client/ui/qml/Components/ConnectButton.qml | 10 + .../ConnectionTypeSelectionDrawer.qml | 19 +- .../qml/Components/HomeContainersListView.qml | 99 ++-- .../Components/HomeSplitTunnelingDrawer.qml | 29 +- .../ui/qml/Components/InstalledAppsDrawer.qml | 2 +- client/ui/qml/Components/QuestionDrawer.qml | 17 +- .../qml/Components/SelectLanguageDrawer.qml | 34 +- client/ui/qml/Components/ServersListView.qml | 208 ++++++++ .../qml/Components/ShareConnectionDrawer.qml | 27 +- .../qml/Components/TransportProtoSelector.qml | 2 - client/ui/qml/Controls2/BackButtonType.qml | 20 +- client/ui/qml/Controls2/BasicButtonType.qml | 13 + client/ui/qml/Controls2/CardWithIconsType.qml | 12 +- client/ui/qml/Controls2/DrawerType2.qml | 217 +++++--- client/ui/qml/Controls2/DropDownType.qml | 145 +++--- client/ui/qml/Controls2/FlickableType.qml | 2 +- .../qml/Controls2/HorizontalRadioButton.qml | 12 + client/ui/qml/Controls2/ImageButtonType.qml | 19 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 44 +- .../Controls2/ListViewWithRadioButtonType.qml | 40 +- client/ui/qml/Controls2/PageType.qml | 25 +- client/ui/qml/Controls2/PopupType.qml | 10 +- client/ui/qml/Controls2/SwitcherType.qml | 10 + client/ui/qml/Controls2/TabButtonType.qml | 11 +- .../ui/qml/Controls2/TabImageButtonType.qml | 15 +- .../qml/Controls2/TextAreaWithFooterType.qml | 3 - .../qml/Controls2/TextFieldWithHeaderType.qml | 23 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 8 +- client/ui/qml/Pages2/PageDevMenu.qml | 11 - client/ui/qml/Pages2/PageHome.qml | 270 ++-------- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 53 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 13 - .../Pages2/PageProtocolOpenVpnSettings.qml | 40 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 19 +- .../PageProtocolShadowSocksSettings.qml | 16 +- .../Pages2/PageProtocolWireGuardSettings.qml | 42 +- .../qml/Pages2/PageProtocolXraySettings.qml | 22 +- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 8 - .../ui/qml/Pages2/PageServiceSftpSettings.qml | 14 +- .../Pages2/PageServiceSocksProxySettings.qml | 26 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 8 - client/ui/qml/Pages2/PageSettings.qml | 23 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 27 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 7 - .../Pages2/PageSettingsAppSplitTunneling.qml | 14 - .../ui/qml/Pages2/PageSettingsApplication.qml | 48 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 11 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 13 - client/ui/qml/Pages2/PageSettingsDns.qml | 19 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 57 ++- .../ui/qml/Pages2/PageSettingsServerData.qml | 4 - .../ui/qml/Pages2/PageSettingsServerInfo.qml | 26 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 9 - .../ui/qml/Pages2/PageSettingsServersList.qml | 143 +++--- .../qml/Pages2/PageSettingsSplitTunneling.qml | 62 +-- .../Pages2/PageSetupWizardApiServiceInfo.qml | 8 - .../Pages2/PageSetupWizardApiServicesList.qml | 8 - .../Pages2/PageSetupWizardConfigSource.qml | 89 +++- .../qml/Pages2/PageSetupWizardCredentials.qml | 17 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 13 +- .../PageSetupWizardProtocolSettings.qml | 43 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 8 - client/ui/qml/Pages2/PageSetupWizardStart.qml | 9 - .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 11 - .../qml/Pages2/PageSetupWizardViewConfig.qml | 10 - client/ui/qml/Pages2/PageShare.qml | 146 +++--- client/ui/qml/Pages2/PageShareFullAccess.qml | 18 +- client/ui/qml/Pages2/PageStart.qml | 52 +- client/ui/qml/main2.qml | 28 +- 76 files changed, 1906 insertions(+), 1576 deletions(-) create mode 100644 client/ui/controllers/focusController.cpp create mode 100644 client/ui/controllers/focusController.h create mode 100644 client/ui/qml/Components/ServersListView.qml 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..58e33c7e --- /dev/null +++ b/client/ui/controllers/focusController.cpp @@ -0,0 +1,476 @@ +#include "focusController.h" + +#include +#include +#include +#include +#include +#include + + +bool isVisible(QObject* item) +{ + const auto res = item->property("visible").toBool(); + // qDebug() << "==>> " << (res ? "VISIBLE" : "NOT visible") << item; + return res; +} + +bool isFocusable(QObject* item) +{ + const auto res = item->property("isFocusable").toBool(); + // qDebug() << "==>> " << (res ? "FOCUSABLE" : "NOT focusable") << item; + return res; +} + +QRectF getItemCoordsOnScene(QQuickItem* item) // TODO: remove? +{ + if (!item) return {}; + return item->mapRectToScene(item->childrenRect()); +} + +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 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()) { + qInfo() << "The item is not visible: " << item; + return false; + } + + QRectF itemRect{}; // TODO: ListView couln't get into list because it's children's rect is too large + // if (isListView(item)) { + // itemRect = QRectF(item->x(), item->y(), item->width(), item->height()); + // } else { + 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; + return true; +} + +bool isEnabled(QObject* obj) +{ + const auto item = qobject_cast(obj); + return item && item->isEnabled(); +} + +QQuickItem* getPageOfItem(QQuickItem* item) // TODO: remove? +{ + if(!item) { + qWarning() << "item is null"; + return {}; + } + const auto pagePattern = QString::fromLatin1("Page"); + QString className{item->metaObject()->className()}; + qDebug() << "=====================>> Item: " << item << " with name: " << item->metaObject()->className(); + const auto isPage = className.contains(pagePattern, Qt::CaseSensitive); + if(isPage) { + return item; + } else { + return getPageOfItem(item->parentItem()); + } +} + +QList getSubChain(QObject* item) +{ + QList res; + if (!item) { + qDebug() << "null top item"; + return res; + } + const auto children = item->children(); + for(const auto child : children) { + if (child + && isFocusable(child) + && (isOnTheScene(child)) + && isEnabled(child) + ) { + res.append(child); + // qDebug() << "==>> [*** added ***] " << qobject_cast(child); + } else { + // qDebug() << "==>> [** skipped **] " << qobject_cast(child); + res.append(getSubChain(child)); + } + } + return res; +} + +template +void printItems(const T& items, QObject* current_item) +{ + qDebug() << "**********************************************"; + 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; + } + qDebug() << "**********************************************"; +} + +/*! + * \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 +{ +public: + explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); + ~ListViewFocusController(); + + void incrementIndex(); + void decrementCurrentIndex(); + void positionViewAtIndex(); + void focusNextItem(); + void focusPreviousItem(); + void resetFocusChain(); + bool isListViewLastFocusItem(); + bool isDelegateLastFocusItem(); + +private: + int size() const; + int currentIndex() const; + QQuickItem* itemAtIndex(const int index); + QQuickItem* currentDelegate(); + QQuickItem* focusedItem(); + + QQuickItem* m_listView; + QList m_focusChain; + QQuickItem* m_focusedItem; + qsizetype m_focusedItemIndex; + qsizetype m_delegateIndex; +}; + +ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* parent) + : QObject{parent} + , m_listView{listView} + , m_focusChain{} + , m_focusedItem{nullptr} + , m_focusedItemIndex{-1} + , m_delegateIndex{0} +{ +} + +ListViewFocusController::~ListViewFocusController() +{ + +} + +void ListViewFocusController::positionViewAtIndex() +{ + QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", + Q_ARG(int, m_delegateIndex), // Index + Q_ARG(int, 2)); // PositionMode (0 = Visible) +} + +int ListViewFocusController::size() const +{ + return m_listView->property("count").toInt(); +} + +int ListViewFocusController::currentIndex() const +{ + return m_delegateIndex; +} + +void ListViewFocusController::incrementIndex() +{ + m_delegateIndex++; +} + +void ListViewFocusController::decrementCurrentIndex() +{ + m_delegateIndex--; +} + +QQuickItem* ListViewFocusController::itemAtIndex(const int index) +{ + QQuickItem* item{nullptr}; + + QMetaObject::invokeMethod(m_listView, "itemAtIndex", + Q_RETURN_ARG(QQuickItem*, item), + Q_ARG(int, index)); + + return item; +} + +QQuickItem* ListViewFocusController::currentDelegate() +{ + return itemAtIndex(m_delegateIndex); +} + +QQuickItem* ListViewFocusController::focusedItem() +{ + return m_focusedItem; +} + +void ListViewFocusController::focusNextItem() +{ + if (m_focusChain.empty()) { + qWarning() << "Empty focusChain with current delegate: " << currentDelegate(); + m_focusChain = getSubChain(currentDelegate()); + } + m_focusedItemIndex++; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + m_focusedItem->forceActiveFocus(); +} + +void ListViewFocusController::focusPreviousItem() +{ + // TODO: implement +} + +void ListViewFocusController::resetFocusChain() +{ + m_focusChain.clear(); + m_focusedItem = nullptr; + m_focusedItemIndex = -1; +} + +bool ListViewFocusController::isDelegateLastFocusItem() +{ + return m_focusedItem && (m_focusedItem == m_focusChain.last()); +} + +bool ListViewFocusController::isListViewLastFocusItem() +{ + return (m_delegateIndex == size() - 1) && isDelegateLastFocusItem(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) + : QObject{parent} + , m_engine{engine} + , m_focusChain{} + , m_focusedItem{nullptr} + , m_focusedItemIndex{-1} + , m_rootItem{nullptr} + , m_lvfc{nullptr} +{ + connect(this, &FocusController::rootItemChanged, this, &FocusController::reload); +} + +void FocusController::resetFocus() +{ + reload(); + if (m_focusChain.empty()) { + qWarning() << "There is no focusable elements"; + return; + } + if(m_focusedItemIndex == -1) { + m_focusedItemIndex = 0; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + m_focusedItem->forceActiveFocus(); + } +} + +void FocusController::nextKeyTabItem() +{ + if (m_lvfc) { + focusNextListViewItem(); // Need to go on first element by default? + return; + } + + reload(); + + if(m_focusChain.empty()) { + qWarning() << "There are no items to navigate"; + return; + } + + if (m_focusedItemIndex == (m_focusChain.size() - 1)) { + qDebug() << "Last focus index. Making it zero"; + m_focusedItemIndex = 0; + } else { + qDebug() << "Incrementing focus index"; + m_focusedItemIndex++; + } + + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + + if(m_focusedItem == nullptr) { + qWarning() << "Failed to get item to focus on"; + return; + } + + if(isListView(m_focusedItem)) { + qDebug() << "===>> Found ListView Item: " << m_focusedItem; // TODO: remove? + m_lvfc = new ListViewFocusController(m_focusedItem, this); + focusNextListViewItem(); + return; + } + + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); + + printItems(m_focusChain, m_focusedItem); +} + +void FocusController::focusNextListViewItem() +{ + m_lvfc->focusNextItem(); + + if (m_lvfc->isListViewLastFocusItem()) { + delete m_lvfc; + m_lvfc = nullptr; + } else if (m_lvfc->isDelegateLastFocusItem()) { + m_lvfc->resetFocusChain(); + m_lvfc->incrementIndex(); + m_lvfc->positionViewAtIndex(); + } +} + +void FocusController::focusPreviousListViewItem() +{ + // TODO: implement +} + +void FocusController::previousKeyTabItem() +{ + reload(); + + if(m_focusChain.empty()) { + return; + } + + if (m_focusedItemIndex <= 0) { + m_focusedItemIndex = m_focusChain.size() - 1; + } else { + m_focusedItemIndex--; + } + + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); + + qDebug() << "--> Current focus was changed to " << m_focusedItem; +} + +void FocusController::nextKeyUpItem() +{ + qDebug() << "nextKeyUpItem" << "triggered"; +} + +void FocusController::nextKeyDownItem() +{ + qDebug() << "nextKeyDownItem" << "triggered"; +} + +void FocusController::nextKeyLeftItem() +{ + qDebug() << "nextKeyLeftItem" << "triggered"; +} + +void FocusController::nextKeyRightItem() +{ + qDebug() << "nextKeyRightItem" << "triggered"; +} + +void FocusController::reload() +{ + m_focusChain.clear(); + + QObjectList rootObjects; + + const auto rootItem = m_rootItem; + + if (rootItem != nullptr) { + qDebug() << "*** root item: " << rootItem; + rootObjects << qobject_cast(rootItem); + } else { + qDebug() << "*** root item is null"; + rootObjects = m_engine->rootObjects(); + } + + if(rootObjects.empty()) { + qWarning() << "Empty focus chain detected!"; + emit focusChainChanged(); + return; + } + + for(const auto object : rootObjects) { + m_focusChain.append(getSubChain(object)); + } + + std::sort(m_focusChain.begin(), m_focusChain.end(), isLess); + + printItems(m_focusChain, m_focusedItem); + + emit focusChainChanged(); + + if (m_focusChain.empty()) { + m_focusedItemIndex = -1; + qWarning() << "reloaded to empty focus chain"; + return; + } + + QQuickWindow* window = qobject_cast(rootObjects[0]); + if (!window) { + window = qobject_cast(rootObjects[0])->window(); + } + + if (!window) { + qCritical() << "Couldn't get the current window"; + return; + } + + // qDebug() << "==> Active Focused Item: " << window->activeFocusItem(); + // qDebug() << "--> Active Focused Object: " << window->focusObject(); + // qDebug() << ">>> Current Focused Item: " << m_focused_item; + + m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem()); + + if(m_focusedItemIndex == -1) { + qDebug() << "===>> No focus item in chain. Moving focus to begin..."; + // m_focused_item_index = 0; // if not in focus chain current + return; + } + + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + + m_focusedItem->forceActiveFocus(); +} + +void FocusController::setRootItem(QQuickItem* item) +{ + m_rootItem = item; +} diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h new file mode 100644 index 00000000..f593fc8d --- /dev/null +++ b/client/ui/controllers/focusController.h @@ -0,0 +1,52 @@ +#ifndef FOCUSCONTROLLER_H +#define FOCUSCONTROLLER_H + +#include + +class QQuickItem; +class QQmlApplicationEngine; +class ListViewFocusController; + +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(); + +signals: + void nextTabItemChanged(QObject* item); + void previousTabItemChanged(QObject* item); + void nextKeyUpItemChanged(QObject* item); + void nextKeyDownItemChanged(QObject* item); + void nextKeyLeftItemChanged(QObject* item); + void nextKeyRightItemChanged(QObject* item); + void focusChainChanged(); + void rootItemChanged(); + +public slots: + void resetFocus(); + void reload(); + void setRootItem(QQuickItem* item); + +private: + void focusNextListViewItem(); + void focusPreviousListViewItem(); + + QQmlApplicationEngine* 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 + qsizetype m_focusedItemIndex; // Active focus item's index in focus chain + QQuickItem* m_rootItem; + + ListViewFocusController* m_lvfc; // ListView focus manager +}; + +#endif // FOCUSCONTROLLER_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/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index fa18703b..c3032eab 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -16,6 +16,16 @@ 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() + } + implicitWidth: 190 implicitHeight: 190 diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 23fe0d2a..1b43a628 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 @@ -61,8 +48,6 @@ DrawerType2 { PageController.goToPage(PageEnum.PageSetupWizardCredentials) root.close() } - - KeyNavigation.tab: qrCode.rightButton } DividerType {} @@ -78,8 +63,6 @@ DrawerType2 { PageController.goToPage(PageEnum.PageSetupWizardConfigSource) root.close() } - - KeyNavigation.tab: focusItem } DividerType {} diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index b0e074d0..9c3d3857 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -20,52 +20,71 @@ ListView { 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() - } - } + console.debug("--> Tab is pressed on HomeContainersListView: ", objectName) + FocusController.nextKeyTabItem() } - onVisibleChanged: { - if (visible) { - currentFocusIndex = 0 - focusItem.forceActiveFocus() - } - } - - Item { - id: focusItem + Keys.onBacktabPressed: { + console.debug("--> Shift+Tab is pressed on HomeContainersListView: ", objectName) + FocusController.previousKeyTabItem() } - onCurrentFocusIndexChanged: { - if (parentFlickable) { - parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex)) - } - } + // 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 +94,6 @@ ListView { implicitWidth: rootWidth implicitHeight: content.implicitHeight - onActiveFocusChanged: { - if (activeFocus) { - containerRadioButton.forceActiveFocus() - } - } - ColumnLayout { id: content @@ -111,13 +124,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..b37b0b81 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,7 +46,7 @@ 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 + // KeyNavigation.tab: siteBasedSplitTunnelingSwitch.visible ? siteBasedSplitTunnelingSwitch.rightButton : focusItem clickedFunction: function() { // PageController.goToPage(PageEnum.PageSettingsSplitTunneling) @@ -80,13 +67,13 @@ DrawerType2 { descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: appSplitTunnelingSwitch.visible ? - appSplitTunnelingSwitch.rightButton : - focusItem + // KeyNavigation.tab: appSplitTunnelingSwitch.visible ? + // appSplitTunnelingSwitch.rightButton : + // focusItem clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsSplitTunneling) - root.close() + root.closeTriggered() } } @@ -103,11 +90,11 @@ DrawerType2 { descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: focusItem + // 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..0ee48100 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 diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index a0e86dbc..09e8638a 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 @@ -37,7 +37,11 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - focusItem.forceActiveFocus() + FocusController.setRoot(root) + } + + function onClosed() { + FocusController.setRoot(null) } } @@ -59,11 +63,6 @@ DrawerType2 { text: descriptionText } - Item { - id: focusItem - KeyNavigation.tab: yesButton - } - BasicButtonType { id: yesButton Layout.fillWidth: true @@ -78,8 +77,6 @@ DrawerType2 { yesButtonFunction() } } - - KeyNavigation.tab: noButton } BasicButtonType { @@ -102,8 +99,6 @@ DrawerType2 { noButtonFunction() } } - - KeyNavigation.tab: focusItem } } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 4d9d7f0e..3124445e 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 @@ -24,13 +24,12 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - focusItem.forceActiveFocus() + FocusController.setRoot(root) } - } - Item { - id: focusItem - KeyNavigation.tab: backButton + function onClosed() { + FocusController.setRoot(null) + } } ColumnLayout { @@ -44,8 +43,7 @@ DrawerType2 { BackButtonType { id: backButton backButtonImage: "qrc:/images/controls/arrow-left.svg" - backButtonFunction: function() { root.close() } - KeyNavigation.tab: listView + backButtonFunction: function() { root.closeTriggered() } } } @@ -97,15 +95,15 @@ DrawerType2 { } } - Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } else { - listViewFocusItem.forceActiveFocus() - focusItem.forceActiveFocus() - } - } + // Keys.onTabPressed: { + // if (currentFocusIndex < this.count - 1) { + // currentFocusIndex += 1 + // this.itemAtIndex(currentFocusIndex).forceActiveFocus() + // } else { + // listViewFocusItem.forceActiveFocus() + // focusItem.forceActiveFocus() + // } + // } Item { id: listViewFocusItem @@ -195,7 +193,7 @@ DrawerType2 { onClicked: { listView.currentIndex = index LanguageModel.changeLanguage(languageIndex) - root.close() + root.closeTriggered() } } } diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml new file mode 100644 index 00000000..76f3f267 --- /dev/null +++ b/client/ui/qml/Components/ServersListView.qml @@ -0,0 +1,208 @@ +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 + } + + readonly property bool isFocusable: true + + // Keys.onTabPressed: { + // FocusController.nextKeyTabItem() + // } + + // activeFocusOnTab: true + // focus: true + + property int focusItemIndex: 0 + + // onFocusItemIndexChanged: { + // console.debug("===>> root onFocusItemIndexChanged") + + // // const focusedElement = root.itemAtIndex(focusItemIndex) + // // if (focusedElement) { + // // if (focusedElement.y + focusedElement.height > root.height) { + // // root.contentY = focusedElement.y + focusedElement.height - root.height + // // } else { + // // root.contentY = 0 + // // } + // // } + // } + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + // Connections { + // target: drawer + // enabled: !GC.isMobile() + // function onIsCollapsedChanged() { + // if (drawer.isCollapsedStateActive) { + // const item = root.itemAtIndex(root.focusItemIndex) + // if (item) { item.serverRadioButtonProperty.focus = false } + // } + // } + // } + + 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.onTabPressed: serverInfoButton.forceActiveFocus() + Keys.onEnterPressed: serverRadioButton.clicked() + Keys.onReturnPressed: serverRadioButton.clicked() + } + + ImageButtonType { + id: serverInfoButton + objectName: "serverInfoButton" + + // signal keyTabOnLastElement + + // isFocusable: false + + image: "qrc:/images/controls/settings.svg" + imageColor: AmneziaStyle.color.paleGray + + implicitWidth: 56 + implicitHeight: 56 + + z: 1 + + // onActiveFocusChanged: { + // console.debug("===>> serverInfoButton::activeFocusChanged") + + // if (activeFocus) { + // if (currentIndex === root.count - 1) { + // console.log("---> Latest element") + // keyTabOnLastElement() + // } + + // console.log("--->>", currentIndex) + // // serverRadioButton.forceActiveFocus() + // } + // } + + // onKeyTabOnLastElement: { + // console.log("*** Signal emmited! ***") + // FocusController.nextKeyTabItem() + // } + + // Keys.onTabPressed: { + // console.log("===>> serverInfoButton::Keys.onTabPressed") + // if (root.focusItemIndex < root.count - 1) { + // root.focusItemIndex++ + // root.itemAtIndex(root.focusItemIndex).forceActiveFocus() + // } else { + // FocusController.nextKeyTabItem() + // root.contentY = 0 + // } + // } + Keys.onEnterPressed: serverInfoButton.clicked() + Keys.onReturnPressed: serverInfoButton.clicked() + + onClicked: function() { + console.debug("===>> onClicked serverInfoButton") + + 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/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index d2bf28ab..261cd742 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -36,7 +36,7 @@ DrawerType2 { configFileName = "amnezia_config" } - expandedContent: Item { + expandedStateContent: Item { implicitHeight: root.expandedHeight Connections { @@ -57,8 +57,6 @@ DrawerType2 { anchors.rightMargin: 16 headerText: root.headerText - - KeyNavigation.tab: shareButton } FlickableType { @@ -86,8 +84,6 @@ DrawerType2 { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - KeyNavigation.tab: copyConfigTextButton - clickedFunc: function() { var fileName = "" if (GC.isMobile()) { @@ -124,8 +120,6 @@ DrawerType2 { Keys.onReturnPressed: { copyConfigTextButton.clicked() } Keys.onEnterPressed: { copyConfigTextButton.clicked() } - - KeyNavigation.tab: copyNativeConfigStringButton.visible ? copyNativeConfigStringButton : showSettingsButton } BasicButtonType { @@ -166,8 +160,6 @@ DrawerType2 { clickedFunc: function() { configContentDrawer.open() } - - KeyNavigation.tab: header } DrawerType2 { @@ -184,24 +176,11 @@ DrawerType2 { } } - 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() { @@ -232,8 +211,6 @@ DrawerType2 { anchors.topMargin: 16 backButtonFunction: function() { configContentDrawer.close() } - - KeyNavigation.tab: focusItem } FlickableType { 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..41aac9e7 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -4,23 +4,27 @@ import Qt5Compat.GraphicalEffects import Style 1.0 -Item { +FocusScope { id: root property string backButtonImage: "qrc:/images/controls/arrow-left.svg" property var backButtonFunction + // property bool isFocusable: true + + // Keys.onTabPressed: { + // FocusController.nextKeyTabItem() + // } + + // Keys.onBacktabPressed: { + // FocusController.previousKeyTabItem() + // } + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight visible: backButtonImage !== "" - onActiveFocusChanged: { - if (activeFocus) { - backButton.forceActiveFocus() - } - } - RowLayout { id: content @@ -35,6 +39,8 @@ Item { implicitWidth: 40 implicitHeight: 40 + // focus: true + onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 828c32bc..e925035b 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -35,12 +35,25 @@ Button { property alias buttonTextLabel: buttonText + property bool isFocusable: true + + Keys.onTabPressed: { + console.debug("--> Tab is pressed on BasicButtonType: ", objectName) + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + console.debug("--> Shift+Tab is pressed on ", objectName) + FocusController.previousKeyTabItem() + } + implicitHeight: 56 hoverEnabled: true focusPolicy: Qt.TabFocus onFocusChanged: { + console.debug("===>> BUTTON: active.focus: ", root.activeFocus, " parentFlickable: ", root.parentFlickable ) if (root.activeFocus) { if (root.parentFlickable) { root.parentFlickable.ensureVisible(this) diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml index fea65116..f5d2bea2 100644 --- a/client/ui/qml/Controls2/CardWithIconsType.qml +++ b/client/ui/qml/Controls2/CardWithIconsType.qml @@ -25,10 +25,13 @@ Button { property real textOpacity: 1.0 + property alias focusItem: rightImage + hoverEnabled: true background: Rectangle { id: backgroundRect + anchors.fill: parent radius: 16 @@ -44,8 +47,10 @@ Button { anchors.right: parent.right implicitHeight: content.implicitHeight + RowLayout { id: content + anchors.fill: parent Image { @@ -61,6 +66,7 @@ Button { } ColumnLayout { + ListItemTitleType { text: root.headerText visible: text !== "" @@ -123,6 +129,7 @@ Button { Rectangle { id: rightImageBackground + anchors.fill: parent radius: 12 color: "transparent" @@ -131,10 +138,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..df07c52f 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,43 @@ 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() { + console.debug("===>> onCloseTopDrawer function") + if (depthIndex === PageController.getDrawerDepth()) { - if (isCollapsed) { + if (isCollapsedStateActive()) { return } aboutToHide() - drawerContent.state = root.drawerCollapsed + drawerContent.state = root.drawerCollapsedStateName depthIndex = 0 closed() } @@ -61,30 +75,62 @@ Item { Connections { target: root - function onClose() { - if (isCollapsed) { + function onCloseTriggered() { + console.debug("***>> onClose root connection") + + if (isCollapsedStateActive()) { return } aboutToHide() - drawerContent.state = root.drawerCollapsed - depthIndex = 0 - PageController.setDrawerDepth(PageController.getDrawerDepth() - 1) closed() } - function onOpen() { - if (isExpanded) { + function onClosed() { + console.debug("***>> onClosed root connection") + + drawerContent.state = root.drawerCollapsedStateName + + if (root.isCollapsedStateActive()) { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } + + depthIndex = 0 + PageController.decrementDrawerDepth() + FocusController.setRootItem(null) + } + + function onOpenTriggered() { + console.debug("===>> onOpen root connection") + + 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 + + console.debug("===>> onOpened root connection") + + if (isExpandedStateActive()) { + console.error("new state - extended") + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + depthIndex = PageController.incrementDrawerDepth() + FocusController.setRootItem(root) + console.debug("===>> Root item has changed to ", root) } } @@ -102,18 +148,18 @@ Item { MouseArea { id: emptyArea anchors.fill: parent - enabled: root.isExpanded - visible: enabled + onClicked: { - root.close() + console.debug("===>> onClicked emptyArea") + 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 +171,46 @@ 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() + console.debug("===>> onReleased dragArea") + + 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() + console.debug("===>> onEntered dragArea") + + root.cursorEntered() } onExited: { - root.exited() + console.debug("===>> onExited dragArea") + + root.cursorExited() } onPressedChanged: { + console.debug("===>> onPressedChanged dragArea") + root.pressed(pressed, entered) } onClicked: { - if (root.isCollapsed) { - root.open() + console.debug("===>> onClicked dragArea") + + if (isCollapsedStateActive()) { + root.openTriggered() } } } Rectangle { id: drawerContentBackground + objectName: "drawerContentBackground" anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top } height: root.height @@ -174,53 +231,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 +312,8 @@ Item { } }, Transition { - from: root.drawerExpanded - to: root.drawerCollapsed + from: root.drawerExpandedStateName + to: root.drawerCollapsedStateName PropertyAnimation { target: drawerContent properties: "y" @@ -241,7 +325,7 @@ Item { Loader { id: collapsedLoader - sourceComponent: root.collapsedContent + sourceComponent: root.collapsedStateContent anchors.right: parent.right anchors.left: parent.left @@ -250,8 +334,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..8b683d94 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -45,33 +45,51 @@ 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) - } - } - } + // function popupClosedFunc() { + // if (!GC.isMobile()) { + // this.forceActiveFocus() + // } + // } + + // property var parentFlickable + // onFocusChanged: { + // if (root.activeFocus) { + // if (root.parentFlickable) { + // root.parentFlickable.ensureVisible(root) + // } + // } + // } 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 { @@ -153,6 +171,8 @@ Item { } ImageButtonType { + // isFocusable: false + Layout.rightMargin: 16 implicitWidth: 40 @@ -173,7 +193,7 @@ Item { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() } else { - menu.open() + menu.openTriggered() } } } @@ -186,27 +206,14 @@ Item { anchors.fill: parent expandedHeight: drawerParent.height * drawerHeight - onClosed: { - root.popupClosedFunc() - } + // 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 +225,43 @@ Item { BackButtonType { id: backButton backButtonImage: root.headerBackButtonImage - backButtonFunction: function() { menu.close() } - KeyNavigation.tab: listViewLoader.item + backButtonFunction: function() { menu.closeTriggered() } + // 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 - Column { - id: col - anchors.top: parent.top + spacing: 16 + + Header2Type { anchors.left: parent.left anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 - spacing: 16 + headerText: root.headerText - Header2Type { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 + width: parent.width + } - headerText: root.headerText + Loader { + id: listViewLoader + sourceComponent: root.listView - width: parent.width - } - - Loader { - id: listViewLoader - sourceComponent: root.listView - - onLoaded: { - listViewLoader.item.parentFlickable = flickable - listViewLoader.item.lastItemTabClicked = function() { - focusItem.forceActiveFocus() - } - } + onLoaded: { + // listViewLoader.item.parentFlickable = flickable + // FocusController.reload() + // listViewLoader.item.lastItemTabClicked = function() { + // focusItem.forceActiveFocus() + // } } } } } } - - Keys.onEnterPressed: { - if (menu.isClosed) { - menu.open() - } - } - - Keys.onReturnPressed: { - if (menu.isClosed) { - menu.open() - } - } } diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index bcd14487..db857ea9 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -7,7 +7,7 @@ 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 } diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 1ac1cd30..b87d9b84 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -27,6 +27,18 @@ RadioButton { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight + property bool isFocusable: true + + Keys.onTabPressed: { + console.debug("--> Tab is pressed on BasicButtonType: ", objectName) + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + console.debug("--> Shift+Tab is pressed on ", objectName) + FocusController.previousKeyTabItem() + } + 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..8770f454 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -24,22 +24,23 @@ 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.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..5e05c30e 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -41,27 +41,37 @@ 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() + } + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } + // onFocusChanged: { + // if (root.activeFocus) { + // if (root.parentFlickable) { + // root.parentFlickable.ensureVisible(root) + // } + // } + // } - Connections { - target: rightImage - function onFocusChanged() { - if (rightImage.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - } + // Connections { + // target: rightImage + // function onFocusChanged() { + // if (rightImage.activeFocus) { + // if (root.parentFlickable) { + // root.parentFlickable.ensureVisible(root) + // } + // } + // } + // } MouseArea { anchors.fill: parent diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index f7b777a7..ed5fa6d9 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -33,40 +33,14 @@ ListView { 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() - } - } - - onVisibleChanged: { - if (visible) { - focusItem.forceActiveFocus() - } - } - - onCurrentFocusIndexChanged: { - if (parentFlickable) { - parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex)) - } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() } ButtonGroup { @@ -83,12 +57,6 @@ ListView { implicitWidth: rootWidth implicitHeight: content.implicitHeight - onActiveFocusChanged: { - if (activeFocus) { - radioButton.forceActiveFocus() - } - } - ColumnLayout { id: content diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 0a2a2998..abe7c8d6 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -9,30 +9,12 @@ 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 @@ -49,11 +31,10 @@ Item { // Set a timer to set focus after a short delay Timer { id: timer - interval: 100 // Milliseconds + interval: 1000 // Milliseconds // TODO: return to 500 onTriggered: { - if (defaultActiveFocusItem) { - defaultActiveFocusItem.forceActiveFocus() - } + console.debug("===>> Page creation completed") + FocusController.resetFocus() } 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..e5019fe5 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -28,11 +28,11 @@ Popup { } onOpened: { - focusItem.forceActiveFocus() + FocusController.setRoot(root) } onClosed: { - PageController.forceStackActiveFocus() + FocusController.setRoot(null) } background: Rectangle { @@ -72,11 +72,6 @@ Popup { } } - Item { - id: focusItem - KeyNavigation.tab: closeButton - } - BasicButtonType { id: closeButton visible: closeButtonVisible @@ -92,7 +87,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..b96f40e4 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -35,6 +35,16 @@ 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() + } + hoverEnabled: enabled ? true : false focusPolicy: Qt.TabFocus diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index d57ff3a0..d3dc0d1b 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -17,10 +17,19 @@ TabButton { property bool isSelected: false + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + 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..3e0b5aed 100644 --- a/client/ui/qml/Controls2/TabImageButtonType.qml +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -14,13 +14,23 @@ TabButton { property bool isSelected: false + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + property string borderFocusedColor: AmneziaStyle.color.paleGray property int borderFocusedWidth: 1 property var clickedFunc hoverEnabled: true - focusPolicy: Qt.TabFocus + // focusPolicy: Qt.TabFocus icon.source: image icon.color: isSelected ? selectedColor : defaultColor @@ -41,8 +51,9 @@ TabButton { cursorShape: Qt.PointingHandCursor enabled: false } - + Keys.onEnterPressed: { + console.log("$$$$$$$$$ ENTER PRESSED INSIDE TABIMAGEBUTTONTYPE") 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..875eed9f 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -84,7 +84,16 @@ Item { TextField { id: textField - activeFocusOnTab: false + // 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 +218,9 @@ Item { clickedFunc() } - if (KeyNavigation.tab) { - KeyNavigation.tab.forceActiveFocus(); - } + // if (KeyNavigation.tab) { + // KeyNavigation.tab.forceActiveFocus(); + // } } Keys.onReturnPressed: { @@ -219,8 +228,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..4320103b 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -28,8 +28,14 @@ RadioButton { property string imageSource property bool showImage + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + 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..aa2f0501 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,47 @@ PageType { Keys.onEnterPressed: splitTunnelingButton.clicked() Keys.onReturnPressed: splitTunnelingButton.clicked() - KeyNavigation.tab: drawer - onClicked: { - homeSplitTunnelingDrawer.open() + homeSplitTunnelingDrawer.openTriggered() } HomeSplitTunnelingDrawer { id: homeSplitTunnelingDrawer + objectName: "homeSplitTunnelingDrawer" + parent: root onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } + console.log(objectName, " was closed...") + FocusController.setRootItem(null) } } } } } - 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 +173,8 @@ PageType { } RowLayout { + objectName: "rowLayout" + Layout.topMargin: 14 Layout.leftMargin: 24 Layout.rightMargin: 24 @@ -188,9 +183,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 +195,8 @@ PageType { } } - function onExited() { - if (drawer.isCollapsed) { + function onCursorExited() { + if (drawer.isCollapsedStateActive) { collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor collapsedButtonHeader.opacity = 1 } else { @@ -208,7 +205,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 +216,8 @@ PageType { Header1TextType { id: collapsedButtonHeader + objectName: "collapsedButtonHeader" + Layout.maximumWidth: drawer.width - 48 - 18 - 12 maximumLineCount: 2 @@ -227,8 +226,6 @@ PageType { text: ServersModel.defaultServerName horizontalAlignment: Qt.AlignHCenter - KeyNavigation.tab: tabBar - Behavior on opacity { PropertyAnimation { duration: 200 } } @@ -236,10 +233,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 +252,19 @@ PageType { Keys.onEnterPressed: collapsedButtonChevron.clicked() Keys.onReturnPressed: collapsedButtonChevron.clicked() - Keys.onTabPressed: lastItemTabClicked() - onClicked: { - if (drawer.isCollapsed) { - drawer.open() + console.debug("onClicked collapsedButtonChevron") + if (drawer.isCollapsedStateActive()) { + drawer.openTriggered() + FocusController.setRootItem(drawer) } } } } RowLayout { + objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16 @@ -316,6 +315,7 @@ PageType { ColumnLayout { id: serversMenuHeader + objectName: "serversMenuHeader" anchors.top: collapsed.bottom anchors.right: parent.right @@ -327,13 +327,9 @@ PageType { visible: !ServersModel.isDefaultServerFromApi - Item { - id: focusItem1 - KeyNavigation.tab: containersDropDown - } - DropDownType { id: containersDropDown + objectName: "containersDropDown" rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonBackgroundColor: AmneziaStyle.color.paleGray @@ -350,22 +346,23 @@ PageType { 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 + + // isFocusable: false // TODO: this is a workaround. Need to remove it Connections { + objectName: "rowLayoutConnections" + target: ServersModel function onDefaultServerIndexChanged() { @@ -407,169 +404,12 @@ PageType { ButtonGroup { id: serversRadioButtonGroup + objectName: "serversRadioButtonGroup" } - ListView { + ServersListView { id: 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() - - 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 - } - } - } + objectName: "serversMenuContent" } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 27ea66f9..d03b3580 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -17,18 +17,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,7 +28,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: listview.currentItem.portTextField.textField } } @@ -114,8 +101,26 @@ PageType { } checkEmptyText: true + } - KeyNavigation.tab: junkPacketCountTextField.textField + 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 { @@ -139,8 +144,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: junkPacketMinSizeTextField.textField } TextFieldWithHeaderType { @@ -160,8 +163,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: junkPacketMaxSizeTextField.textField } TextFieldWithHeaderType { @@ -181,8 +182,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: initPacketJunkSizeTextField.textField } TextFieldWithHeaderType { @@ -202,8 +201,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: responsePacketJunkSizeTextField.textField } TextFieldWithHeaderType { @@ -223,8 +220,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: initPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -244,8 +239,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: responsePacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -265,8 +258,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: transportPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -286,8 +277,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: underloadPacketMagicHeaderTextField.textField } TextFieldWithHeaderType { @@ -307,8 +296,6 @@ PageType { } checkEmptyText: true - - KeyNavigation.tab: saveRestartButton } BasicButtonType { @@ -332,8 +319,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 5089a764..29df03ef 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 diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 30540a93..75fdd18d 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 @@ -252,8 +227,6 @@ PageType { drawerParent: root parentFlickable: fl - KeyNavigation.tab: tlsAuthCheckBox - listView: ListViewWithRadioButtonType { id: cipherListView @@ -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 diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 24853afd..3cb02435 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 { @@ -104,8 +96,6 @@ PageType { configContentDrawer.open() } - KeyNavigation.tab: removeButton - MouseArea { anchors.fill: button cursorShape: Qt.PointingHandCursor @@ -129,7 +119,7 @@ PageType { parent: root anchors.fill: parent - expandedContent: Item { + expandedStateContent: Item { implicitHeight: configContentDrawer.expandedHeight Connections { @@ -140,11 +130,6 @@ PageType { } } - Item { - id: focusItem1 - KeyNavigation.tab: backButton1 - } - BackButtonType { id: backButton1 @@ -156,8 +141,6 @@ PageType { backButtonFunction: function() { configContentDrawer.close() } - - KeyNavigation.tab: focusItem1 } FlickableType { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 4d3b2c4e..dae96b5a 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 diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index b5d08132..ab50f444 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 diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 20ee1da6..d74aabad 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 diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index bb3cbf96..c50f9a9b 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 } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 9bdbf2db..8513f111 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,7 +173,7 @@ PageType { descriptionOnTop: true parentFlickable: fl - eyeButton.KeyNavigation.tab: passwordLabel.rightButton + rightButton.Keys.onTabPressed: { if (mountButton.visible) { mountButton.forceActiveFocus() @@ -225,7 +214,6 @@ PageType { borderWidth: 1 parentFlickable: fl - KeyNavigation.tab: detailedInstructionsButton text: qsTr("Mount folder on device") diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 9d21963d..cadfee09 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 @@ -206,7 +193,7 @@ PageType { } } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { property string tempPort: port property string tempUsername: username property string tempPassword: password @@ -239,11 +226,6 @@ PageType { } } - Item { - id: drawerFocusItem - KeyNavigation.tab: portTextField.textField - } - HeaderType { Layout.fillWidth: true @@ -268,8 +250,6 @@ PageType { port = textFieldText } } - - KeyNavigation.tab: usernameTextField.textField } TextFieldWithHeaderType { @@ -290,8 +270,6 @@ PageType { username = textFieldText } } - - KeyNavigation.tab: passwordTextField.textField } TextFieldWithHeaderType { @@ -322,8 +300,6 @@ PageType { password = textFieldText } } - - KeyNavigation.tab: saveButton } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 946a77bb..a1e41f29 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 } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index bb5ca766..6e291ff0 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -14,9 +14,7 @@ import "../Config" PageType { id: root - defaultActiveFocusItem: header - - FlickableType { + FlickableType { // TODO: refactor either replace with ListView or Repeater id: fl anchors.top: parent.top anchors.bottom: parent.bottom @@ -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,9 @@ 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) + // isLeftImageHoverEnabled: false clickedFunction: function() { PageController.closeApplication() diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index cde9ee20..5407e75e 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -14,18 +14,16 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem + // Item { + // id: focusItem + // KeyNavigation.tab: backButton - Item { - id: focusItem - KeyNavigation.tab: backButton - - onFocusChanged: { - if (focusItem.activeFocus) { - fl.contentY = 0 - } - } - } + // onFocusChanged: { + // if (focusItem.activeFocus) { + // fl.contentY = 0 + // } + // } + // } BackButtonType { id: backButton @@ -34,8 +32,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: telegramButton } FlickableType { @@ -106,7 +102,6 @@ PageType { descriptionText: qsTr("To discuss features") leftImageSource: "qrc:/images/controls/telegram.svg" - KeyNavigation.tab: mailButton parentFlickable: fl clickedFunction: function() { @@ -124,7 +119,6 @@ PageType { descriptionText: qsTr("For reviews and bug reports") leftImageSource: "qrc:/images/controls/mail.svg" - KeyNavigation.tab: githubButton parentFlickable: fl clickedFunction: function() { @@ -143,7 +137,6 @@ PageType { text: qsTr("GitHub") leftImageSource: "qrc:/images/controls/github.svg" - KeyNavigation.tab: websiteButton parentFlickable: fl clickedFunction: function() { @@ -161,7 +154,6 @@ PageType { text: qsTr("Website") leftImageSource: "qrc:/images/controls/amnezia.svg" - KeyNavigation.tab: checkUpdatesButton parentFlickable: fl clickedFunction: function() { @@ -209,7 +201,6 @@ PageType { text: qsTr("Check for updates") - KeyNavigation.tab: privacyPolicyButton parentFlickable: fl clickedFunc: function() { 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..4751aa71 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 diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 0f85ac30..dfccabcf 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -14,18 +14,15 @@ import "../Components" PageType { id: root - defaultActiveFocusItem: focusItem + // Item { + // id: focusItem - Item { - id: focusItem - KeyNavigation.tab: backButton - - onFocusChanged: { - if (focusItem.activeFocus) { - fl.contentY = 0 - } - } - } + // onFocusChanged: { + // if (focusItem.activeFocus) { + // fl.contentY = 0 + // } + // } + // } BackButtonType { id: backButton @@ -35,7 +32,7 @@ PageType { anchors.right: parent.right anchors.topMargin: 20 - KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart + // KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart } FlickableType { @@ -77,8 +74,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 +92,7 @@ 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 + // KeyNavigation.tab: labelWithButtonLanguage.rightButton parentFlickable: fl clickedFunction: function() { @@ -117,7 +114,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 +138,6 @@ PageType { text: qsTr("Auto connect") descriptionText: qsTr("Connect to VPN on app start") - KeyNavigation.tab: switcherStartMinimized parentFlickable: fl checked: SettingsController.isAutoConnectEnabled() @@ -167,7 +162,6 @@ PageType { text: qsTr("Start minimized") descriptionText: qsTr("Launch application minimized") - KeyNavigation.tab: labelWithButtonLanguage.rightButton parentFlickable: fl checked: SettingsController.isStartMinimizedEnabled() @@ -190,11 +184,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 +201,6 @@ PageType { descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: labelWithButtonReset.rightButton parentFlickable: fl clickedFunction: function() { @@ -245,12 +237,12 @@ PageType { } if (!GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() + // root.defaultActiveFocusItem.forceActiveFocus() } } var noButtonFunction = function() { if (!GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() + // root.defaultActiveFocusItem.forceActiveFocus() } } @@ -268,10 +260,10 @@ PageType { width: root.width height: root.height - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } + // onClosed: { + // if (!GC.isMobile()) { + // focusItem.forceActiveFocus() + // } + // } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index abede9b3..35d45589 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -17,7 +17,7 @@ import "../Controls2/TextTypes" PageType { id: root - defaultActiveFocusItem: focusItem + // defaultActiveFocusItem: focusItem Connections { target: SettingsController @@ -36,11 +36,6 @@ PageType { } } - Item { - id: focusItem - KeyNavigation.tab: backButton - } - BackButtonType { id: backButton @@ -48,8 +43,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: makeBackupButton } FlickableType { @@ -111,8 +104,6 @@ PageType { PageController.showNotificationMessage(qsTr("Backup file saved")) } } - - KeyNavigation.tab: restoreBackupButton } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 31b1c764..5991b713 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 {} @@ -84,8 +73,6 @@ PageType { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsDns) } - - KeyNavigation.tab: splitTunnelingButton.rightButton } DividerType {} diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 1d7517d9..cede6c74 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 { diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 9abfc453..bea366a6 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,8 +23,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: switcher } FlickableType { @@ -68,12 +59,21 @@ PageType { text: qsTr("Enable logs") checked: SettingsController.isLoggingEnabled - //KeyNavigation.tab: openFolderButton + onCheckedChanged: { if (checked !== SettingsController.isLoggingEnabled) { SettingsController.isLoggingEnabled = checked } } + + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } } DividerType {} @@ -87,8 +87,6 @@ PageType { leftImageSource: "qrc:/images/controls/trash.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 - clickedFunction: function() { var headerText = qsTr("Clear logs?") var yesButtonText = qsTr("Continue") @@ -104,9 +102,9 @@ PageType { } } var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } + // if (!GC.isMobile()) { + // focusItem.forceActiveFocus() + // } } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) @@ -142,8 +140,6 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 - clickedFunction: function() { SettingsController.openLogsFolder() } @@ -161,7 +157,14 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } clickedFunction: function() { var fileName = "" @@ -221,7 +224,14 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } clickedFunction: function() { SettingsController.openServiceLogsFolder() @@ -245,7 +255,14 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" isSmallLeftImage: true - // KeyNavigation.tab: labelWithButton3 + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } clickedFunction: function() { var fileName = "" diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index e170a351..0568c5f4 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?") diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 95ae5c8a..4f6ab934 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -25,7 +25,7 @@ PageType { property int pageSettingsApiServerInfo: 3 property int pageSettingsApiLanguageList: 4 - defaultActiveFocusItem: focusItem + // defaultActiveFocusItem: focusItem Connections { target: PageController @@ -46,11 +46,6 @@ PageType { ] } - Item { - id: focusItem - KeyNavigation.tab: header - } - ColumnLayout { anchors.fill: parent @@ -75,7 +70,6 @@ PageType { BackButtonType { id: backButton - KeyNavigation.tab: headerContent.actionButton backButtonFunction: function() { if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && @@ -108,13 +102,11 @@ PageType { } } - KeyNavigation.tab: tabBar - actionButtonFunction: function() { if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { nestedStackView.currentIndex = root.pageSettingsApiServerInfo } else { - serverNameEditDrawer.open() + serverNameEditDrawer.openTriggered() } } } @@ -133,7 +125,7 @@ PageType { } } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -149,11 +141,6 @@ PageType { } } - Item { - id: focusItem1 - KeyNavigation.tab: serverName.textField - } - TextFieldWithHeaderType { id: serverName @@ -162,8 +149,6 @@ PageType { textFieldText: name textField.maximumLength: 30 checkEmptyText: true - - KeyNavigation.tab: saveButton } BasicButtonType { @@ -172,7 +157,6 @@ PageType { Layout.fillWidth: true text: qsTr("Save") - KeyNavigation.tab: focusItem1 clickedFunc: function() { if (serverName.textFieldText === "") { @@ -182,7 +166,7 @@ PageType { if (serverName.textFieldText !== name) { name = serverName.textFieldText } - serverNameEditDrawer.close() + serverNameEditDrawer.closeTriggered() } } } @@ -219,7 +203,6 @@ PageType { isSelected: tabBar.currentIndex === root.pageSettingsServerProtocols text: qsTr("Protocols") - KeyNavigation.tab: servicesTab Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerProtocols Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerProtocols } @@ -231,7 +214,6 @@ PageType { isSelected: tabBar.currentIndex === root.pageSettingsServerServices text: qsTr("Services") - KeyNavigation.tab: dataTab Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerServices Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerServices } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index dcdf01af..73e1e2c6 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 { @@ -168,7 +160,6 @@ PageType { Layout.fillWidth: true visible: root.isClearCacheVisible - KeyNavigation.tab: removeButton text: qsTr("Clear profile") diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 102dd46f..6f43c3fe 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,88 @@ 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 + 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() + // } - activeFocusOnTab: true - focus: true - Keys.onTabPressed: { - if (currentIndex < servers.count - 1) { - servers.incrementCurrentIndex() - } else { - servers.currentIndex = 0 - focusItem.forceActiveFocus() - root.lastItemTabClicked() - } + // fl.ensureVisible(this.currentItem) + // } - fl.ensureVisible(this.currentItem) - } + onVisibleChanged: { + if (visible) { + currentIndex = 0 + } + } - onVisibleChanged: { - if (visible) { - currentIndex = 0 - } - } + delegate: Item { + implicitWidth: servers.width + implicitHeight: delegateContent.implicitHeight - delegate: Item { - implicitWidth: servers.width - implicitHeight: delegateContent.implicitHeight + // onFocusChanged: { + // if (focus) { + // server.rightButton.forceActiveFocus() + // } + // } - onFocusChanged: { - if (focus) { - server.rightButton.forceActiveFocus() - } - } + ColumnLayout { + id: delegateContent - ColumnLayout { - id: delegateContent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + LabelWithButtonType { + id: server + Layout.fillWidth: true - 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) - } + text: name + // parentFlickable: fl + 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..4bd4d0a4 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 } } @@ -180,11 +170,11 @@ PageType { } } - KeyNavigation.tab: { - return sites.count > 0 ? - sites : - searchField.textField - } + // KeyNavigation.tab: { + // return sites.count > 0 ? + // sites : + // searchField.textField + // } } } @@ -325,7 +315,7 @@ PageType { textFieldPlaceholderText: qsTr("website or IP") buttonImageSource: "qrc:/images/controls/plus.svg" - KeyNavigation.tab: GC.isMobile() ? focusItem : addSiteButtonImage + // KeyNavigation.tab: GC.isMobile() ? focusItem : addSiteButtonImage clickedFunc: function() { PageController.showBusyIndicator(true) @@ -344,7 +334,7 @@ PageType { imageColor: AmneziaStyle.color.paleGray onClicked: function () { - moreActionsDrawer.open() + moreActionsDrawer.openTriggered() } Keys.onReturnPressed: addSiteButtonImage.clicked() @@ -361,12 +351,12 @@ PageType { expandedHeight: parent.height * 0.4375 onClosed: { - if (root.defaultActiveFocusItem && !GC.isMobile()) { - root.defaultActiveFocusItem.forceActiveFocus() - } + // if (root.defaultActiveFocusItem && !GC.isMobile()) { + // root.defaultActiveFocusItem.forceActiveFocus() + // } } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: moreActionsDrawerContent anchors.top: parent.top @@ -387,11 +377,6 @@ PageType { } } - Item { - id: focusItem1 - KeyNavigation.tab: importSitesButton.rightButton - } - Header2Type { Layout.fillWidth: true Layout.margins: 16 @@ -407,10 +392,8 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - importSitesDrawer.open() + importSitesDrawer.openTriggered() } - - KeyNavigation.tab: exportSitesButton } DividerType {} @@ -420,8 +403,6 @@ PageType { Layout.fillWidth: true text: qsTr("Save site list") - KeyNavigation.tab: focusItem1 - clickedFunction: function() { var fileName = "" if (GC.isMobile()) { @@ -436,7 +417,7 @@ PageType { if (fileName !== "") { PageController.showBusyIndicator(true) SitesController.exportSites(fileName) - moreActionsDrawer.close() + moreActionsDrawer.closeTriggered() PageController.showBusyIndicator(false) } } @@ -458,7 +439,7 @@ PageType { } } - expandedContent: Item { + expandedStateContent: Item { implicitHeight: importSitesDrawer.expandedHeight Connections { @@ -469,11 +450,6 @@ PageType { } } - Item { - id: focusItem2 - KeyNavigation.tab: importSitesDrawerBackButton - } - BackButtonType { id: importSitesDrawerBackButton @@ -482,10 +458,8 @@ PageType { anchors.right: parent.right anchors.topMargin: 16 - KeyNavigation.tab: importSitesButton2 - backButtonFunction: function() { - importSitesDrawer.close() + importSitesDrawer.closeTriggered() } } @@ -516,7 +490,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 +506,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 +520,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..82e2e7e1 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 { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 7c031997..e5030336 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,7 +25,7 @@ PageType { } } - defaultActiveFocusItem: focusItem + // defaultActiveFocusItem: focusItem FlickableType { id: fl @@ -42,15 +42,11 @@ PageType { spacing: 0 - Item { - id: focusItem - KeyNavigation.tab: textKey.textField - } - - 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 +56,16 @@ PageType { actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonFunction: function() { - moreActionsDrawer.open() + moreActionsDrawer.openTriggered() + } + + actionButton.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (actionButton.activeFocus) { + if (fl) { + fl.ensureVisible(moreButton) + } + } } DrawerType2 { @@ -71,7 +76,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 @@ -154,8 +159,6 @@ PageType { textField.text = "" textField.paste() } - - KeyNavigation.tab: continueButton } BasicButtonType { @@ -166,10 +169,18 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } + visible: textKey.textFieldText !== "" text: qsTr("Continue") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { if (ImportController.extractConfigFromData(textKey.textFieldText)) { @@ -203,6 +214,15 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/amnezia.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (focusItem.activeFocus) { + if (fl) { + fl.ensureVisible(apiInstalling) + } + } + } + onClicked: function() { PageController.showBusyIndicator(true) var result = InstallController.fillAvailableServices() @@ -227,6 +247,15 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/server.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (focusItem.activeFocus) { + if (fl) { + fl.ensureVisible(manualInstalling) + } + } + } + onClicked: { PageController.goToPage(PageEnum.PageSetupWizardCredentials) } @@ -247,6 +276,15 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/archive-restore.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (focusItem.activeFocus) { + if (fl) { + fl.ensureVisible(backupRestore) + } + } + } + onClicked: { var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) @@ -271,6 +309,13 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-search-2.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (fl) { + fl.ensureVisible(openFile) + } + } + onClicked: { var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : "Config files (*.vpn *.ovpn *.conf *.json)" @@ -298,6 +343,15 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/scan-line.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (focusItem.activeFocus) { + if (fl) { + fl.ensureVisible(scanQr) + } + } + } + onClicked: { ImportController.startDecodingQr() if (Qt.platform.os === "ios") { @@ -321,6 +375,15 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/help-circle.svg" + focusItem.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (focusItem.activeFocus) { + if (fl) { + fl.ensureVisible(siteLink) + } + } + } + onClicked: { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index aced12b1..5139ad5a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -13,12 +13,7 @@ import "../Controls2/TextTypes" PageType { id: root - defaultActiveFocusItem: hostname.textField - - Item { - id: focusItem - KeyNavigation.tab: backButton - } + // defaultActiveFocusItem: hostname.textField BackButtonType { id: backButton @@ -27,8 +22,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - KeyNavigation.tab: hostname.textField } FlickableType { @@ -64,8 +57,6 @@ PageType { textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: username.textField } TextFieldWithHeaderType { @@ -78,8 +69,6 @@ PageType { textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: secretData.textField } TextFieldWithHeaderType { @@ -100,8 +89,6 @@ PageType { textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } - - KeyNavigation.tab: continueButton } BasicButtonType { @@ -112,8 +99,6 @@ PageType { text: qsTr("Continue") - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { forceActiveFocus() if (!isCredentialsFilled()) { 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..de8275f1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -62,19 +62,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 { @@ -113,13 +106,13 @@ PageType { parent: root onClosed: { if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() + // defaultActiveFocusItem.forceActiveFocus() } } anchors.fill: parent expandedHeight: parent.height * 0.9 - expandedContent: Item { + expandedStateContent: Item { Connections { target: showDetailsDrawer enabled: !GC.isMobile() @@ -130,15 +123,15 @@ PageType { implicitHeight: showDetailsDrawer.expandedHeight - Item { - id: focusItem2 - KeyNavigation.tab: showDetailsBackButton - onFocusChanged: { - if (focusItem2.activeFocus) { - fl.contentY = 0 - } - } - } + // Item { + // id: focusItem2 + // KeyNavigation.tab: showDetailsBackButton + // onFocusChanged: { + // if (focusItem2.activeFocus) { + // fl.contentY = 0 + // } + // } + // } BackButtonType { id: showDetailsBackButton @@ -148,8 +141,6 @@ PageType { anchors.right: parent.right anchors.topMargin: 16 - KeyNavigation.tab: showDetailsCloseButton - backButtonFunction: function() { showDetailsDrawer.close() } @@ -230,7 +221,7 @@ PageType { Layout.fillWidth: true rootWidth: root.width - KeyNavigation.tab: (port.visible && port.enabled) ? port.textField : installButton + // 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 { @@ -289,10 +278,10 @@ PageType { transportProtoSelector.visible = protocolSelectorVisible transportProtoHeader.visible = protocolSelectorVisible - if (port.visible && port.enabled) - defaultActiveFocusItem = port.textField - else - defaultActiveFocusItem = focusItem + // 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..86880713 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 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 92048f36..dffaa8c6 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,6 @@ PageType { textColor: AmneziaStyle.color.goldenApricot text: showContent ? qsTr("Collapse content") : qsTr("Show content") - KeyNavigation.tab: connectButton clickedFunc: function() { showContent = !showContent diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 995fa3e7..cef516df 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. 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,18 @@ PageType { actionButtonImage: "qrc:/images/controls/more-vertical.svg" actionButtonFunction: function() { - shareFullAccessDrawer.open() + shareFullAccessDrawer.openTriggered() } - KeyNavigation.tab: connectionRadioButton + // KeyNavigation.tab: connectionRadioButton + actionButton.onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (actionButton.activeFocus) { + if (fl) { + fl.ensureVisible(moreButton) + } + } + } DrawerType2 { id: shareFullAccessDrawer @@ -205,11 +201,11 @@ PageType { expandedHeight: root.height onClosed: { if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() + // clientNameTextField.textField.forceActiveFocus() } } - expandedContent: ColumnLayout { + expandedStateContent: ColumnLayout { id: shareFullAccessDrawerContent anchors.top: parent.top anchors.left: parent.left @@ -226,7 +222,7 @@ PageType { target: shareFullAccessDrawer enabled: !GC.isMobile() function onOpened() { - focusItem.forceActiveFocus() + // focusItem.forceActiveFocus() } } @@ -240,10 +236,10 @@ 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 - } + // Item { + // id: focusItem + // // KeyNavigation.tab: shareFullAccessButton.rightButton + // } LabelWithButtonType { id: shareFullAccessButton @@ -251,11 +247,11 @@ PageType { text: qsTr("Share") rightImageSource: "qrc:/images/controls/chevron-right.svg" - KeyNavigation.tab: focusItem + // KeyNavigation.tab: focusItem clickedFunction: function() { PageController.goToPage(PageEnum.PageShareFullAccess) - shareFullAccessDrawer.close() + shareFullAccessDrawer.closeTriggered() } } @@ -288,12 +284,12 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Connection") - KeyNavigation.tab: usersRadioButton + // KeyNavigation.tab: usersRadioButton onClicked: { accessTypeSelector.currentIndex = 0 if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() + // clientNameTextField.textField.forceActiveFocus() } } } @@ -305,7 +301,7 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Users") - KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? clientNameTextField.textField : serverSelector + // KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? clientNameTextField.textField : serverSelector onClicked: { accessTypeSelector.currentIndex = 1 @@ -313,7 +309,7 @@ PageType { ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) - focusItem.forceActiveFocus() + // focusItem.forceActiveFocus() } } } @@ -343,7 +339,7 @@ PageType { checkEmptyText: true - KeyNavigation.tab: serverSelector + // KeyNavigation.tab: serverSelector } @@ -390,7 +386,7 @@ PageType { serverSelector.severSelectorIndexChanged() } - serverSelector.close() + serverSelector.closeTriggered() } Component.onCompleted: { @@ -409,7 +405,7 @@ PageType { } } - KeyNavigation.tab: protocolSelector + // KeyNavigation.tab: protocolSelector } DropDownType { @@ -450,7 +446,7 @@ PageType { clickedFunction: function() { handler() - protocolSelector.close() + protocolSelector.closeTriggered() } Connections { @@ -509,11 +505,11 @@ PageType { } } - KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? - exportTypeSelector : - isSearchBarVisible ? - searchTextField.textField : - usersHeader.actionButton + // KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? + // exportTypeSelector : searchTextField.textField + // isSearchBarVisible ? + // searchTextField.textField : + // usersHeader.actionButton } DropDownType { @@ -549,7 +545,7 @@ PageType { clickedFunction: function() { exportTypeSelector.text = selectedText exportTypeSelector.currentIndex = currentIndex - exportTypeSelector.close() + exportTypeSelector.closeTriggered() } Component.onCompleted: { @@ -558,7 +554,7 @@ PageType { } } - KeyNavigation.tab: shareButton + // KeyNavigation.tab: shareButton } @@ -622,11 +618,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 +634,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 +659,9 @@ PageType { Keys.onTabPressed: { if (!GC.isMobile()) { if (clientsListView.model.count > 0) { - clientsListView.forceActiveFocus() + // clientsListView.forceActiveFocus() } else { - lastItemTabClicked(focusItem) + // lastItemTabClicked(focusItem) } } } @@ -706,10 +702,10 @@ PageType { if (!GC.isMobile()) { if (currentIndex < this.count - 1) { this.incrementCurrentIndex() - currentItem.focusItem.forceActiveFocus() + // currentItem.focusItem.forceActiveFocus() } else { this.currentIndex = 0 - lastItemTabClicked(focusItem) + // lastItemTabClicked(focusItem) } } } @@ -717,7 +713,7 @@ PageType { onActiveFocusChanged: { if (focus && !GC.isMobile()) { currentIndex = 0 - currentItem.focusItem.forceActiveFocus() + // currentItem.focusItem.forceActiveFocus() } } @@ -735,7 +731,7 @@ PageType { implicitWidth: clientsListView.width implicitHeight: delegateContent.implicitHeight - property alias focusItem: clientFocusItem.rightButton + // property alias focusItem: clientFocusItem.rightButton ColumnLayout { id: delegateContent @@ -755,7 +751,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - clientInfoDrawer.open() + clientInfoDrawer.openTriggered() } } @@ -768,15 +764,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 +781,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 +842,6 @@ PageType { text: qsTr("Allowed IPs: %1").arg(allowedIps) } - Item { - id: focusItem1 - KeyNavigation.tab: renameButton - } - BasicButtonType { id: renameButton Layout.fillWidth: true @@ -865,10 +856,10 @@ PageType { text: qsTr("Rename") - KeyNavigation.tab: revokeButton + // KeyNavigation.tab: revokeButton clickedFunc: function() { - clientNameEditDrawer.open() + clientNameEditDrawer.openTriggered() } DrawerType2 { @@ -881,11 +872,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 +888,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 @@ -914,7 +900,7 @@ PageType { textField.maximumLength: 20 checkEmptyText: true - KeyNavigation.tab: saveButton + // KeyNavigation.tab: saveButton } BasicButtonType { @@ -923,7 +909,7 @@ PageType { Layout.fillWidth: true text: qsTr("Save") - KeyNavigation.tab: focusItem2 + // KeyNavigation.tab: focusItem2 clickedFunc: function() { if (clientNameEditor.textFieldText === "") { @@ -937,7 +923,7 @@ PageType { ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) - clientNameEditDrawer.close() + clientNameEditDrawer.closeTriggered() } } } @@ -958,7 +944,7 @@ PageType { borderWidth: 1 text: qsTr("Revoke") - KeyNavigation.tab: focusItem1 + // KeyNavigation.tab: focusItem1 clickedFunc: function() { var headerText = qsTr("Revoke the config for a user - %1?").arg(clientName) @@ -967,12 +953,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 +979,7 @@ PageType { anchors.fill: parent onClosed: { if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() + // clientNameTextField.textField.forceActiveFocus() } } } @@ -1001,7 +987,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..70451b83 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 @@ -137,8 +128,6 @@ PageType { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { PageController.showBusyIndicator(true) @@ -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..9f37458c 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() { @@ -91,18 +91,20 @@ PageType { } } - function onForceTabBarActiveFocus() { - homeTabButton.focus = true - tabBar.forceActiveFocus() - } + // function onForceTabBarActiveFocus() { + // homeTabButton.focus = true + // tabBar.forceActiveFocus() + // } - function onForceStackActiveFocus() { - homeTabButton.focus = true - tabBarStackView.forceActiveFocus() - } + // function onForceStackActiveFocus() { + // homeTabButton.focus = true + // tabBarStackView.forceActiveFocus() + // } } Connections { + objectName: "installControllerConnections" + target: InstallController function onInstallationErrorOccurred(error) { @@ -165,6 +167,8 @@ PageType { } Connections { + objectName: "connectionControllerConnections" + target: ConnectionController function onReconnectWithUpdatedContainer(message) { @@ -182,6 +186,8 @@ PageType { } Connections { + objectName: "importControllerConnections" + target: ImportController function onImportErrorOccurred(error, goToPageHome) { @@ -196,6 +202,8 @@ PageType { } Connections { + objectName: "settingsControllerConnections" + target: SettingsController function onLoggingDisableByWatcher() { @@ -218,6 +226,7 @@ PageType { StackViewType { id: tabBarStackView + objectName: "tabBarStackView" anchors.top: parent.top anchors.right: parent.right @@ -254,6 +263,7 @@ PageType { TabBar { id: tabBar + objectName: "tabBar" anchors.right: parent.right anchors.left: parent.left @@ -269,6 +279,8 @@ PageType { enabled: !root.isControlsDisabled && !root.isTabBarDisabled background: Shape { + objectName: "backgroundShape" + width: parent.width height: parent.height @@ -289,21 +301,25 @@ PageType { TabImageButtonType { id: homeTabButton + objectName: "homeTabButton" + isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" clickedFunc: function () { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.processedIndex = ServersModel.defaultIndex tabBar.currentIndex = 0 + FocusController.setRootItem(null) // TODO: move to do it automaticaly } - KeyNavigation.tab: shareTabButton - Keys.onEnterPressed: this.clicked() - Keys.onReturnPressed: this.clicked() + // KeyNavigation.tab: shareTabButton + // Keys.onEnterPressed: this.clicked() + // Keys.onReturnPressed: this.clicked() } TabImageButtonType { id: shareTabButton + objectName: "shareTabButton" Connections { target: ServersModel @@ -325,11 +341,13 @@ PageType { tabBar.currentIndex = 1 } - KeyNavigation.tab: settingsTabButton + // KeyNavigation.tab: settingsTabButton } TabImageButtonType { id: settingsTabButton + objectName: "settingsTabButton" + isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" clickedFunc: function () { @@ -337,11 +355,13 @@ PageType { tabBar.currentIndex = 2 } - KeyNavigation.tab: plusTabButton + // KeyNavigation.tab: plusTabButton } TabImageButtonType { id: plusTabButton + objectName: "plusTabButton" + isSelected: tabBar.currentIndex === 3 image: "qrc:/images/controls/plus.svg" clickedFunc: function () { @@ -349,7 +369,7 @@ PageType { tabBar.currentIndex = 3 } - Keys.onTabPressed: PageController.forceStackActiveFocus() + // Keys.onTabPressed: PageController.forceStackActiveFocus() } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index fb99559f..18b69795 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -33,6 +33,8 @@ Window { title: "AmneziaVPN" Connections { + objectName: "pageControllerConnections" + target: PageController function onRaiseMainWindow() { @@ -72,6 +74,8 @@ Window { } Connections { + objectName: "settingsControllerConnections" + target: SettingsController function onChangeSettingsFinished(finishedMessage) { @@ -80,11 +84,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 +116,8 @@ Window { } Item { + objectName: "popupErrorMessageItem" + anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom @@ -120,6 +130,8 @@ Window { } Item { + objectName: "privateKeyPassphraseDrawerItem" + anchors.fill: parent DrawerType2 { @@ -128,7 +140,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 @@ -168,7 +180,7 @@ Window { hidePassword = !hidePassword } - KeyNavigation.tab: saveButton + // KeyNavigation.tab: saveButton } BasicButtonType { @@ -195,6 +207,8 @@ Window { } Item { + objectName: "questionDrawerItem" + anchors.fill: parent QuestionDrawer { @@ -205,6 +219,8 @@ Window { } Item { + objectName: "busyIndicatorItem" + anchors.fill: parent BusyIndicatorType { @@ -221,26 +237,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) From cecee3769e20379ca6959a7900fa7b63cb51f0e8 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 19 Sep 2024 15:00:01 +0200 Subject: [PATCH 002/100] add more key handlers --- client/ui/controllers/focusController.cpp | 54 +++++++------------ client/ui/qml/Components/ConnectButton.qml | 16 ++++++ .../qml/Components/HomeContainersListView.qml | 8 +++ .../Components/HomeSplitTunnelingDrawer.qml | 12 +---- client/ui/qml/Controls2/BasicButtonType.qml | 20 +++++-- client/ui/qml/Controls2/FlickableType.qml | 1 + .../qml/Controls2/HorizontalRadioButton.qml | 18 ++++++- client/ui/qml/Controls2/ImageButtonType.qml | 16 ++++++ .../ui/qml/Controls2/LabelWithButtonType.qml | 50 +++++++++++------ .../Controls2/ListViewWithRadioButtonType.qml | 16 ++++++ client/ui/qml/Controls2/SwitcherType.qml | 17 ++++++ client/ui/qml/Controls2/TabButtonType.qml | 16 ++++++ .../ui/qml/Controls2/TabImageButtonType.qml | 30 ++++++++--- .../ui/qml/Controls2/VerticalRadioButton.qml | 20 +++++++ client/ui/qml/Pages2/PageHome.qml | 10 ++++ 15 files changed, 228 insertions(+), 76 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 58e33c7e..7e2f79d3 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -18,7 +18,6 @@ bool isVisible(QObject* item) bool isFocusable(QObject* item) { const auto res = item->property("isFocusable").toBool(); - // qDebug() << "==>> " << (res ? "FOCUSABLE" : "NOT focusable") << item; return res; } @@ -60,28 +59,23 @@ bool isOnTheScene(QObject* object) return false; } - QRectF itemRect{}; // TODO: ListView couln't get into list because it's children's rect is too large - // if (isListView(item)) { - // itemRect = QRectF(item->x(), item->y(), item->width(), item->height()); - // } else { - itemRect = item->mapRectToScene(item->childrenRect()); - // } + 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; - return true; + 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; } bool isEnabled(QObject* obj) @@ -98,7 +92,6 @@ QQuickItem* getPageOfItem(QQuickItem* item) // TODO: remove? } const auto pagePattern = QString::fromLatin1("Page"); QString className{item->metaObject()->className()}; - qDebug() << "=====================>> Item: " << item << " with name: " << item->metaObject()->className(); const auto isPage = className.contains(pagePattern, Qt::CaseSensitive); if(isPage) { return item; @@ -122,9 +115,7 @@ QList getSubChain(QObject* item) && isEnabled(child) ) { res.append(child); - // qDebug() << "==>> [*** added ***] " << qobject_cast(child); } else { - // qDebug() << "==>> [** skipped **] " << qobject_cast(child); res.append(getSubChain(child)); } } @@ -134,14 +125,12 @@ QList getSubChain(QObject* item) template void printItems(const T& items, QObject* current_item) { - qDebug() << "**********************************************"; 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; + // qDebug() << prefix << " Item: " << i << " with coords: " << coords; // Uncomment to visualize tab transitions } - qDebug() << "**********************************************"; } /*! @@ -308,7 +297,7 @@ void FocusController::resetFocus() void FocusController::nextKeyTabItem() { if (m_lvfc) { - focusNextListViewItem(); // Need to go on first element by default? + focusNextListViewItem(); return; } @@ -335,7 +324,6 @@ void FocusController::nextKeyTabItem() } if(isListView(m_focusedItem)) { - qDebug() << "===>> Found ListView Item: " << m_focusedItem; // TODO: remove? m_lvfc = new ListViewFocusController(m_focusedItem, this); focusNextListViewItem(); return; @@ -387,22 +375,22 @@ void FocusController::previousKeyTabItem() void FocusController::nextKeyUpItem() { - qDebug() << "nextKeyUpItem" << "triggered"; + previousKeyTabItem(); } void FocusController::nextKeyDownItem() { - qDebug() << "nextKeyDownItem" << "triggered"; + nextKeyTabItem(); } void FocusController::nextKeyLeftItem() { - qDebug() << "nextKeyLeftItem" << "triggered"; + previousKeyTabItem(); } void FocusController::nextKeyRightItem() { - qDebug() << "nextKeyRightItem" << "triggered"; + nextKeyTabItem(); } void FocusController::reload() @@ -414,10 +402,8 @@ void FocusController::reload() const auto rootItem = m_rootItem; if (rootItem != nullptr) { - qDebug() << "*** root item: " << rootItem; rootObjects << qobject_cast(rootItem); } else { - qDebug() << "*** root item is null"; rootObjects = m_engine->rootObjects(); } @@ -453,14 +439,10 @@ void FocusController::reload() return; } - // qDebug() << "==> Active Focused Item: " << window->activeFocusItem(); - // qDebug() << "--> Active Focused Object: " << window->focusObject(); - // qDebug() << ">>> Current Focused Item: " << m_focused_item; - m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem()); if(m_focusedItemIndex == -1) { - qDebug() << "===>> No focus item in chain. Moving focus to begin..."; + qInfo() << "No focus item in chain. Moving focus to begin..."; // m_focused_item_index = 0; // if not in focus chain current return; } diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index c3032eab..b90891a0 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -25,7 +25,23 @@ Button { 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/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 9c3d3857..5d3569f5 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -46,6 +46,14 @@ ListView { FocusController.previousKeyTabItem() } + Keys.onRightPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onLeftPressed: { + FocusController.previousKeyTabItem() + } + // activeFocusOnTab: true // onActiveFocusChanged: { // console.log("===========================") diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index b37b0b81..67696c33 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -46,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.close() } } @@ -67,10 +65,6 @@ 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.closeTriggered() @@ -90,8 +84,6 @@ 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.closeTriggered() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index e925035b..2f1e3fbb 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -38,14 +38,28 @@ Button { property bool isFocusable: true Keys.onTabPressed: { - console.debug("--> Tab is pressed on BasicButtonType: ", objectName) FocusController.nextKeyTabItem() } - + Keys.onBacktabPressed: { - console.debug("--> Shift+Tab is pressed on ", objectName) FocusController.previousKeyTabItem() } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } implicitHeight: 56 diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index db857ea9..2151e520 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -11,6 +11,7 @@ Flickable { } 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/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index b87d9b84..89cc1658 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -30,15 +30,29 @@ RadioButton { property bool isFocusable: true Keys.onTabPressed: { - console.debug("--> Tab is pressed on BasicButtonType: ", objectName) FocusController.nextKeyTabItem() } Keys.onBacktabPressed: { - console.debug("--> Shift+Tab is pressed on ", objectName) 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 8770f454..d5f646a7 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -38,6 +38,22 @@ Button { 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() diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 5e05c30e..087415f7 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -50,28 +50,44 @@ Item { 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 - // onFocusChanged: { - // if (root.activeFocus) { - // if (root.parentFlickable) { - // root.parentFlickable.ensureVisible(root) - // } - // } - // } + onFocusChanged: { + if (root.activeFocus) { + if (root.parentFlickable) { + root.parentFlickable.ensureVisible(root) + } + } + } - // Connections { - // target: rightImage - // function onFocusChanged() { - // if (rightImage.activeFocus) { - // if (root.parentFlickable) { - // root.parentFlickable.ensureVisible(root) - // } - // } - // } - // } + Connections { + target: rightImage + function onFocusChanged() { + if (rightImage.activeFocus) { + if (root.parentFlickable) { + root.parentFlickable.ensureVisible(root) + } + } + } + } MouseArea { anchors.fill: parent diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index ed5fa6d9..9024af91 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -43,6 +43,22 @@ ListView { FocusController.previousKeyTabItem() } + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + ButtonGroup { id: buttonGroup } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b96f40e4..3569a7d0 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -45,10 +45,27 @@ Switch { 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) { diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index d3dc0d1b..0e48d975 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -26,6 +26,22 @@ TabButton { Keys.onBacktabPressed: { FocusController.previousKeyTabItem() } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } implicitHeight: 48 diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml index 3e0b5aed..b49ad8eb 100644 --- a/client/ui/qml/Controls2/TabImageButtonType.qml +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -16,13 +16,29 @@ TabButton { property bool isFocusable: true - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } + 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 @@ -30,7 +46,6 @@ TabButton { property var clickedFunc hoverEnabled: true - // focusPolicy: Qt.TabFocus icon.source: image icon.color: isSelected ? selectedColor : defaultColor @@ -53,7 +68,6 @@ TabButton { } Keys.onEnterPressed: { - console.log("$$$$$$$$$ ENTER PRESSED INSIDE TABIMAGEBUTTONTYPE") if (root.clickedFunc && typeof root.clickedFunc === "function") { root.clickedFunc() } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 4320103b..dcf0f1d9 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -34,6 +34,26 @@ RadioButton { 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 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index aa2f0501..2d52b044 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -410,6 +410,16 @@ PageType { ServersListView { id: serversMenuContent objectName: "serversMenuContent" + + isFocusable: false + + Connections { + target: drawer + + function onIsOpenedChanged() { + serversMenuContent.isFocusable = drawer.isOpened + } + } } } } From 01e31b4b4d6e137653307cc95a15b213f8dc79ba Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 19 Sep 2024 20:27:48 +0200 Subject: [PATCH 003/100] add focus navigation to qml --- client/ui/controllers/focusController.cpp | 21 ++++-- client/ui/qml/Components/QuestionDrawer.qml | 4 +- .../Components/SettingsContainersListView.qml | 16 ++--- client/ui/qml/Controls2/BackButtonType.qml | 12 ---- client/ui/qml/Controls2/BasicButtonType.qml | 1 - client/ui/qml/Controls2/CardWithIconsType.qml | 18 +++++ client/ui/qml/Controls2/PageType.qml | 3 +- client/ui/qml/Controls2/PopupType.qml | 4 +- .../qml/Controls2/TextFieldWithHeaderType.qml | 3 +- .../Pages2/PageProtocolAwgClientSettings.qml | 2 - .../qml/Pages2/PageProtocolCloakSettings.qml | 1 - client/ui/qml/Pages2/PageSettings.qml | 3 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 12 ---- .../ui/qml/Pages2/PageSettingsApplication.qml | 13 ---- client/ui/qml/Pages2/PageSettingsBackup.qml | 8 +-- .../ui/qml/Pages2/PageSettingsConnection.qml | 28 +++----- client/ui/qml/Pages2/PageSettingsLogging.qml | 20 +++--- .../ui/qml/Pages2/PageSettingsServersList.qml | 24 +------ .../Pages2/PageSetupWizardApiServicesList.qml | 5 ++ .../Pages2/PageSetupWizardConfigSource.qml | 67 +++---------------- .../qml/Pages2/PageSetupWizardCredentials.qml | 12 +++- .../qml/Pages2/PageSetupWizardViewConfig.qml | 2 + client/ui/qml/Pages2/PageStart.qml | 20 ------ 23 files changed, 103 insertions(+), 196 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 7e2f79d3..3f5d042f 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -41,6 +41,11 @@ bool isLess(QObject* item1, QObject* 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 isListView(QObject* item) { return item->inherits("QQuickListView"); @@ -129,7 +134,7 @@ void printItems(const T& items, QObject* current_item) QQuickItem* i = qobject_cast(item); QPointF coords {getItemCenterPointOnScene(i)}; QString prefix = current_item == i ? "==>" : " "; - // qDebug() << prefix << " Item: " << i << " with coords: " << coords; // Uncomment to visualize tab transitions + qDebug() << prefix << " Item: " << i << " with coords: " << coords; } } @@ -236,12 +241,12 @@ QQuickItem* ListViewFocusController::focusedItem() void ListViewFocusController::focusNextItem() { if (m_focusChain.empty()) { - qWarning() << "Empty focusChain with current delegate: " << currentDelegate(); + qDebug() << "Empty focusChain with current delegate: " << currentDelegate(); m_focusChain = getSubChain(currentDelegate()); } m_focusedItemIndex++; m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(); + m_focusedItem->forceActiveFocus(); } void ListViewFocusController::focusPreviousItem() @@ -287,6 +292,7 @@ void FocusController::resetFocus() qWarning() << "There is no focusable elements"; return; } + if(m_focusedItemIndex == -1) { m_focusedItemIndex = 0; m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); @@ -370,7 +376,7 @@ void FocusController::previousKeyTabItem() m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); m_focusedItem->forceActiveFocus(Qt::TabFocusReason); - qDebug() << "--> Current focus was changed to " << m_focusedItem; + qDebug() << "===>> Current focus was changed to " << m_focusedItem; } void FocusController::nextKeyUpItem() @@ -439,7 +445,8 @@ void FocusController::reload() return; } - m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem()); + m_focusedItemIndex = m_focusChain.indexOf(m_focusedItem); + // m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem()); if(m_focusedItemIndex == -1) { qInfo() << "No focus item in chain. Moving focus to begin..."; @@ -447,9 +454,9 @@ void FocusController::reload() return; } - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + // m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(); + // m_focusedItem->forceActiveFocus(); } void FocusController::setRootItem(QQuickItem* item) diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 09e8638a..0dbd2eab 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -37,11 +37,11 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - FocusController.setRoot(root) + FocusController.setRootItem(root) } function onClosed() { - FocusController.setRoot(null) + FocusController.setRootItem(null) } } diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 769e1abd..d6db36af 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -23,14 +23,14 @@ ListView { interactive: false activeFocusOnTab: true - Keys.onTabPressed: { - if (currentIndex < this.count - 1) { - this.incrementCurrentIndex() - } else { - currentIndex = 0 - lastItemTabClickedSignal() - } - } + // Keys.onTabPressed: { + // if (currentIndex < this.count - 1) { + // this.incrementCurrentIndex() + // } else { + // currentIndex = 0 + // lastItemTabClickedSignal() + // } + // } onCurrentIndexChanged: { if (visible) { diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 41aac9e7..40136ad5 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -10,16 +10,6 @@ FocusScope { property string backButtonImage: "qrc:/images/controls/arrow-left.svg" property var backButtonFunction - // property bool isFocusable: true - - // Keys.onTabPressed: { - // FocusController.nextKeyTabItem() - // } - - // Keys.onBacktabPressed: { - // FocusController.previousKeyTabItem() - // } - implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -39,8 +29,6 @@ FocusScope { implicitWidth: 40 implicitHeight: 40 - // focus: true - onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 2f1e3fbb..aa8103e9 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -67,7 +67,6 @@ Button { focusPolicy: Qt.TabFocus onFocusChanged: { - console.debug("===>> BUTTON: active.focus: ", root.activeFocus, " parentFlickable: ", root.parentFlickable ) if (root.activeFocus) { if (root.parentFlickable) { root.parentFlickable.ensureVisible(this) diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml index f5d2bea2..482b5217 100644 --- a/client/ui/qml/Controls2/CardWithIconsType.qml +++ b/client/ui/qml/Controls2/CardWithIconsType.qml @@ -27,6 +27,8 @@ Button { property alias focusItem: rightImage + property FlickableType parentFlickable + hoverEnabled: true background: Rectangle { @@ -42,6 +44,22 @@ 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 diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index abe7c8d6..977c18ba 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -31,9 +31,8 @@ Item { // Set a timer to set focus after a short delay Timer { id: timer - interval: 1000 // Milliseconds // TODO: return to 500 + interval: 500 // Milliseconds onTriggered: { - console.debug("===>> Page creation completed") FocusController.resetFocus() } repeat: false // Stop the timer after one trigger diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index e5019fe5..5bed7350 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -28,11 +28,11 @@ Popup { } onOpened: { - FocusController.setRoot(root) + FocusController.setRootItem(root) } onClosed: { - FocusController.setRoot(null) + FocusController.setRootItem(null) } background: Rectangle { diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 875eed9f..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,7 @@ Item { TextField { id: textField - // activeFocusOnTab: false + property bool isFocusable: true Keys.onTabPressed: { diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index 2b912f18..8685a954 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -174,7 +174,6 @@ PageType { checkEmptyText: true - Keys.onTabPressed: saveButton.forceActiveFocus() } Header2TextType { @@ -283,7 +282,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 29df03ef..c0dbc2ee 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -171,7 +171,6 @@ PageType { Layout.bottomMargin: 24 text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 6e291ff0..65c696c7 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -14,7 +14,7 @@ import "../Config" PageType { id: root - FlickableType { // TODO: refactor either replace with ListView or Repeater + FlickableType { id: fl anchors.top: parent.top anchors.bottom: parent.bottom @@ -142,7 +142,6 @@ PageType { text: qsTr("Close application") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/x-circle.svg" - // isLeftImageHoverEnabled: false clickedFunction: function() { PageController.closeApplication() diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 5407e75e..7de813e3 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -14,17 +14,6 @@ import "../Components" PageType { id: root - // Item { - // id: focusItem - // KeyNavigation.tab: backButton - - // onFocusChanged: { - // if (focusItem.activeFocus) { - // fl.contentY = 0 - // } - // } - // } - BackButtonType { id: backButton @@ -223,7 +212,6 @@ PageType { text: qsTr("Privacy Policy") - Keys.onTabPressed: lastItemTabClicked() parentFlickable: fl clickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index dfccabcf..4dea8c65 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -14,16 +14,6 @@ import "../Components" PageType { id: root - // Item { - // id: focusItem - - // onFocusChanged: { - // if (focusItem.activeFocus) { - // fl.contentY = 0 - // } - // } - // } - BackButtonType { id: backButton @@ -31,8 +21,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - // KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart } FlickableType { @@ -92,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() { diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 35d45589..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 @@ -86,6 +84,8 @@ PageType { text: qsTr("Make a backup") + parentFlickable: fl + clickedFunc: function() { var fileName = "" if (GC.isMobile()) { @@ -120,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)")) @@ -127,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 5991b713..d3743b96 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -70,6 +70,8 @@ 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) } @@ -85,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 { @@ -114,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 { @@ -141,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: { @@ -153,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/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index bea366a6..33074dfa 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -23,10 +23,20 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 + + onFocusChanged: { + console.debug("MOVE THIS LOGIC TO CPP!") + if (activeFocus) { + if (fl) { + fl.ensureVisible(this) + } + } + } } FlickableType { id: fl + anchors.top: backButton.bottom anchors.bottom: parent.bottom contentHeight: content.height @@ -51,6 +61,7 @@ PageType { SwitcherType { id: switcher + Layout.fillWidth: true Layout.topMargin: 16 Layout.leftMargin: 16 @@ -66,14 +77,7 @@ PageType { } } - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } - } - } + parentFlickable: fl } DividerType {} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 6f43c3fe..1852f9c3 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -52,25 +52,13 @@ PageType { height: 500 // servers.contentItem.height // TODO: calculate height + property bool isFocusable: true + model: ServersModel 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() - // } - - // fl.ensureVisible(this.currentItem) - // } - onVisibleChanged: { if (visible) { currentIndex = 0 @@ -81,12 +69,6 @@ PageType { implicitWidth: servers.width implicitHeight: delegateContent.implicitHeight - // onFocusChanged: { - // if (focus) { - // server.rightButton.forceActiveFocus() - // } - // } - ColumnLayout { id: delegateContent @@ -99,7 +81,7 @@ PageType { Layout.fillWidth: true text: name - // parentFlickable: fl + descriptionText: { var servicesNameString = "" var servicesName = ServersModel.getAllInstalledServicesName(index) diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index 82e2e7e1..6ecfdc99 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -51,6 +51,8 @@ PageType { height: containers.contentItem.height spacing: 16 + property bool isFocusable: true + currentIndex: 1 interactive: false model: ApiServicesModel @@ -85,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 e5030336..e98b8055 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,8 +25,6 @@ PageType { } } - // defaultActiveFocusItem: focusItem - FlickableType { id: fl anchors.top: parent.top @@ -136,6 +134,8 @@ PageType { } ParagraphTextType { + objectName: "insertKeyLabel" + Layout.fillWidth: true Layout.topMargin: 32 Layout.rightMargin: 16 @@ -155,6 +155,8 @@ PageType { headerText: qsTr("Insert key") buttonText: qsTr("Insert") + parentFlickable: fl + clickedFunc: function() { textField.text = "" textField.paste() @@ -169,14 +171,7 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } - } - } + parentFlickable: fl visible: textKey.textFieldText !== "" @@ -214,14 +209,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/amnezia.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (focusItem.activeFocus) { - if (fl) { - fl.ensureVisible(apiInstalling) - } - } - } + parentFlickable: fl onClicked: function() { PageController.showBusyIndicator(true) @@ -247,14 +235,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/server.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (focusItem.activeFocus) { - if (fl) { - fl.ensureVisible(manualInstalling) - } - } - } + parentFlickable: fl onClicked: { PageController.goToPage(PageEnum.PageSetupWizardCredentials) @@ -276,14 +257,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/archive-restore.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (focusItem.activeFocus) { - if (fl) { - fl.ensureVisible(backupRestore) - } - } - } + parentFlickable: fl onClicked: { var filePath = SystemController.getFileName(qsTr("Open backup file"), @@ -309,12 +283,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-search-2.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (fl) { - fl.ensureVisible(openFile) - } - } + parentFlickable: fl onClicked: { var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : @@ -343,14 +312,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/scan-line.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (focusItem.activeFocus) { - if (fl) { - fl.ensureVisible(scanQr) - } - } - } + parentFlickable: fl onClicked: { ImportController.startDecodingQr() @@ -375,14 +337,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/help-circle.svg" - focusItem.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (focusItem.activeFocus) { - if (fl) { - fl.ensureVisible(siteLink) - } - } - } + parentFlickable: fl onClicked: { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 5139ad5a..aa0b935f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -13,8 +13,6 @@ import "../Controls2/TextTypes" PageType { id: root - // defaultActiveFocusItem: hostname.textField - BackButtonType { id: backButton @@ -54,6 +52,8 @@ 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, '') } @@ -66,6 +66,8 @@ PageType { headerText: qsTr("SSH Username") textFieldPlaceholderText: "root" + parentFlickable: fl + textField.onFocusChanged: { textField.text = textField.text.replace(/^\s+|\s+$/g, '') } @@ -82,6 +84,8 @@ PageType { buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + parentFlickable: fl + clickedFunc: function() { hidePassword = !hidePassword } @@ -99,6 +103,8 @@ PageType { text: qsTr("Continue") + parentFlickable: fl + clickedFunc: function() { forceActiveFocus() if (!isCredentialsFilled()) { @@ -138,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/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index dffaa8c6..52c12c56 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -99,6 +99,8 @@ PageType { text: showContent ? qsTr("Collapse content") : qsTr("Show content") + parentFlickable: fl + clickedFunc: function() { showContent = !showContent } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 9f37458c..abd03809 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -90,16 +90,6 @@ PageType { PageController.closePage() } } - - // function onForceTabBarActiveFocus() { - // homeTabButton.focus = true - // tabBar.forceActiveFocus() - // } - - // function onForceStackActiveFocus() { - // homeTabButton.focus = true - // tabBarStackView.forceActiveFocus() - // } } Connections { @@ -311,10 +301,6 @@ PageType { tabBar.currentIndex = 0 FocusController.setRootItem(null) // TODO: move to do it automaticaly } - - // KeyNavigation.tab: shareTabButton - // Keys.onEnterPressed: this.clicked() - // Keys.onReturnPressed: this.clicked() } TabImageButtonType { @@ -340,8 +326,6 @@ PageType { tabBarStackView.goToTabBarPage(PageEnum.PageShare) tabBar.currentIndex = 1 } - - // KeyNavigation.tab: settingsTabButton } TabImageButtonType { @@ -354,8 +338,6 @@ PageType { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) tabBar.currentIndex = 2 } - - // KeyNavigation.tab: plusTabButton } TabImageButtonType { @@ -368,8 +350,6 @@ PageType { tabBarStackView.goToTabBarPage(PageEnum.PageSetupWizardConfigSource) tabBar.currentIndex = 3 } - - // Keys.onTabPressed: PageController.forceStackActiveFocus() } } } From 78a4caa47eb6cbb819f0d650864861cc6b268645 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Sep 2024 00:22:14 +0200 Subject: [PATCH 004/100] fixed language selector --- .../qml/Components/SelectLanguageDrawer.qml | 255 ++++++++++-------- client/ui/qml/Controls2/DrawerType2.qml | 23 -- 2 files changed, 138 insertions(+), 140 deletions(-) diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 3124445e..337c5987 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -24,11 +24,11 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - FocusController.setRoot(root) + FocusController.setRootItem(root) } function onClosed() { - FocusController.setRoot(null) + FocusController.setRootItem(null) } } @@ -42,166 +42,187 @@ 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.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 + model: LanguageModel + currentIndex: LanguageModel.currentLanguageIndex - headerText: qsTr("Choose language") - } + ButtonGroup { + id: buttonGroup + } - ListView { - id: listView + // activeFocusOnTab: true + // onActiveFocusChanged: { + // if (activeFocus) { + // this.currentFocusIndex = 0 + // this.itemAtIndex(currentFocusIndex).forceActiveFocus() + // } + // } - Layout.fillWidth: true - height: listView.contentItem.height + // Keys.onTabPressed: { + // if (currentFocusIndex < this.count - 1) { + // currentFocusIndex += 1 + // this.itemAtIndex(currentFocusIndex).forceActiveFocus() + // } else { + // listViewFocusItem.forceActiveFocus() + // focusItem.forceActiveFocus() + // } + // } - clip: true - interactive: false + // Item { + // id: listViewFocusItem // TODO: delete? + // Keys.onTabPressed: { + // root.forceActiveFocus() + // } + // } - model: LanguageModel - currentIndex: LanguageModel.currentLanguageIndex + // onVisibleChanged: { + // if (visible) { + // listViewFocusItem.forceActiveFocus() + // focusItem.forceActiveFocus() + // } + // } - ButtonGroup { - id: buttonGroup - } + delegate: Item { + implicitWidth: root.width + implicitHeight: delegateContent.implicitHeight - property int currentFocusIndex: 0 + // onActiveFocusChanged: { + // if (activeFocus) { + // radioButton.forceActiveFocus() + // } + // } - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - this.itemAtIndex(currentFocusIndex).forceActiveFocus() - } - } + ColumnLayout { + id: delegateContent - // Keys.onTabPressed: { - // if (currentFocusIndex < this.count - 1) { - // currentFocusIndex += 1 - // this.itemAtIndex(currentFocusIndex).forceActiveFocus() - // } else { - // listViewFocusItem.forceActiveFocus() - // focusItem.forceActiveFocus() - // } - // } + 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.closeTriggered() - } + 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/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml index df07c52f..5e480327 100644 --- a/client/ui/qml/Controls2/DrawerType2.qml +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -56,8 +56,6 @@ Item { target: PageController function onCloseTopDrawer() { - console.debug("===>> onCloseTopDrawer function") - if (depthIndex === PageController.getDrawerDepth()) { if (isCollapsedStateActive()) { return @@ -76,8 +74,6 @@ Item { target: root function onCloseTriggered() { - console.debug("***>> onClose root connection") - if (isCollapsedStateActive()) { return } @@ -88,8 +84,6 @@ Item { } function onClosed() { - console.debug("***>> onClosed root connection") - drawerContent.state = root.drawerCollapsedStateName if (root.isCollapsedStateActive()) { @@ -105,8 +99,6 @@ Item { } function onOpenTriggered() { - console.debug("===>> onOpen root connection") - if (root.isExpandedStateActive()) { return } @@ -119,10 +111,7 @@ Item { function onOpened() { drawerContent.state = root.drawerExpandedStateName - console.debug("===>> onOpened root connection") - if (isExpandedStateActive()) { - console.error("new state - extended") if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } @@ -130,7 +119,6 @@ Item { depthIndex = PageController.incrementDrawerDepth() FocusController.setRootItem(root) - console.debug("===>> Root item has changed to ", root) } } @@ -150,7 +138,6 @@ Item { anchors.fill: parent onClicked: { - console.debug("===>> onClicked emptyArea") root.closeTriggered() } } @@ -171,8 +158,6 @@ Item { /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { - console.debug("===>> onReleased dragArea") - if (isCollapsedStateActive() && drawerContent.y < dragArea.drag.maximumY) { root.openTriggered() return @@ -184,24 +169,16 @@ Item { } onEntered: { - console.debug("===>> onEntered dragArea") - root.cursorEntered() } onExited: { - console.debug("===>> onExited dragArea") - root.cursorExited() } onPressedChanged: { - console.debug("===>> onPressedChanged dragArea") - root.pressed(pressed, entered) } onClicked: { - console.debug("===>> onClicked dragArea") - if (isCollapsedStateActive()) { root.openTriggered() } From f189f7b6af3d96ab07617e8eceb3ce33b779467c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 24 Sep 2024 20:08:36 +0200 Subject: [PATCH 005/100] add reverse focus change to FocusController --- client/ui/controllers/focusController.cpp | 237 ++++++++++++++-------- client/ui/controllers/focusController.h | 29 ++- 2 files changed, 160 insertions(+), 106 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 3f5d042f..16133341 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -60,12 +60,12 @@ bool isOnTheScene(QObject* object) } if (!item->isVisible()) { - qInfo() << "The item is not visible: " << item; + // 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"; @@ -118,7 +118,7 @@ QList getSubChain(QObject* item) && isFocusable(child) && (isOnTheScene(child)) && isEnabled(child) - ) { + ) { res.append(child); } else { res.append(getSubChain(child)); @@ -153,13 +153,17 @@ public: ~ListViewFocusController(); void incrementIndex(); - void decrementCurrentIndex(); + void decrementIndex(); void positionViewAtIndex(); void focusNextItem(); void focusPreviousItem(); void resetFocusChain(); + bool isListViewFirstFocusItem(); + bool isDelegateFirstFocusItem(); bool isListViewLastFocusItem(); bool isDelegateLastFocusItem(); + bool isReturnNeeded(); + void viewToBegin(); private: int size() const; @@ -173,6 +177,7 @@ private: QQuickItem* m_focusedItem; qsizetype m_focusedItemIndex; qsizetype m_delegateIndex; + bool m_isReturnNeeded; }; ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* parent) @@ -182,6 +187,7 @@ ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* , m_focusedItem{nullptr} , m_focusedItemIndex{-1} , m_delegateIndex{0} + , m_isReturnNeeded{false} { } @@ -212,7 +218,7 @@ void ListViewFocusController::incrementIndex() m_delegateIndex++; } -void ListViewFocusController::decrementCurrentIndex() +void ListViewFocusController::decrementIndex() { m_delegateIndex--; } @@ -241,11 +247,17 @@ QQuickItem* ListViewFocusController::focusedItem() void ListViewFocusController::focusNextItem() { if (m_focusChain.empty()) { - qDebug() << "Empty focusChain with current delegate: " << currentDelegate(); + qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; m_focusChain = getSubChain(currentDelegate()); } + if (m_focusChain.empty()) { + qWarning() << "No elements found. Returning from ListView..."; + m_isReturnNeeded = true; + return; + } m_focusedItemIndex++; m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + qDebug() << "==>> Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex; m_focusedItem->forceActiveFocus(); } @@ -261,16 +273,36 @@ void ListViewFocusController::resetFocusChain() m_focusedItemIndex = -1; } +bool ListViewFocusController::isDelegateFirstFocusItem() +{ + return m_focusedItem && (m_focusedItem == m_focusChain.first()); +} + bool ListViewFocusController::isDelegateLastFocusItem() { return m_focusedItem && (m_focusedItem == m_focusChain.last()); } +bool ListViewFocusController::isListViewFirstFocusItem() +{ + return (m_delegateIndex == 0) && isDelegateFirstFocusItem(); +} + bool ListViewFocusController::isListViewLastFocusItem() { return (m_delegateIndex == size() - 1) && isDelegateLastFocusItem(); } +bool ListViewFocusController::isReturnNeeded() +{ + return m_isReturnNeeded; +} + +void ListViewFocusController::viewToBegin() +{ + QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning", Qt::AutoConnection); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) @@ -279,35 +311,31 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) , m_focusChain{} , m_focusedItem{nullptr} , m_focusedItemIndex{-1} - , m_rootItem{nullptr} + , m_rootObjects{} + , m_defaultFocusItem{QSharedPointer()} , m_lvfc{nullptr} { - connect(this, &FocusController::rootItemChanged, this, &FocusController::reload); + // connect(this, &FocusController::rootItemChanged, this, &FocusController::onReload); + QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url){ + qDebug() << "===>> () CREATED " << object << " : " << url; + QQuickItem* newDefaultFocusItem = object->findChild("defaultFocusItem"); + if(newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { + m_defaultFocusItem.reset(newDefaultFocusItem); + qDebug() << "===>> [] NEW DEFAULT FOCUS ITEM " << m_defaultFocusItem; + } + }); } -void FocusController::resetFocus() -{ - reload(); - if (m_focusChain.empty()) { - qWarning() << "There is no focusable elements"; - return; - } - - if(m_focusedItemIndex == -1) { - m_focusedItemIndex = 0; - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(); - } -} - -void FocusController::nextKeyTabItem() +void FocusController::nextItem(bool isForwardOrder) { if (m_lvfc) { - focusNextListViewItem(); + isForwardOrder ? focusNextListViewItem() : focusPreviousListViewItem(); + qDebug() << "===>> [handling the ListView]"; + return; } - reload(); + reload(isForwardOrder); if(m_focusChain.empty()) { qWarning() << "There are no items to navigate"; @@ -325,29 +353,49 @@ void FocusController::nextKeyTabItem() m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); if(m_focusedItem == nullptr) { - qWarning() << "Failed to get item to focus on"; + qWarning() << "Failed to get item to focus on. Setting focus on default"; + m_focusedItem = m_defaultFocusItem.get(); return; } if(isListView(m_focusedItem)) { + qDebug() << "===>> [Found ListView]"; m_lvfc = new ListViewFocusController(m_focusedItem, this); - focusNextListViewItem(); + if(isForwardOrder) { + m_lvfc->viewToBegin(); + focusNextListViewItem(); + } else { + focusPreviousListViewItem(); + } return; } + qDebug() << "===>> Focused Item: " << m_focusedItem; m_focusedItem->forceActiveFocus(Qt::TabFocusReason); printItems(m_focusChain, m_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() { m_lvfc->focusNextItem(); - if (m_lvfc->isListViewLastFocusItem()) { + if (m_lvfc->isListViewLastFocusItem() || m_lvfc->isReturnNeeded()) { + qDebug() << "===>> [Last item in ListView was reached]"; delete m_lvfc; m_lvfc = nullptr; } else if (m_lvfc->isDelegateLastFocusItem()) { + qDebug() << "===>> [End of delegate elements was reached. Going to the next delegate]"; m_lvfc->resetFocusChain(); m_lvfc->incrementIndex(); m_lvfc->positionViewAtIndex(); @@ -356,110 +404,121 @@ void FocusController::focusNextListViewItem() void FocusController::focusPreviousListViewItem() { - // TODO: implement + m_lvfc->focusPreviousItem(); + + if (m_lvfc->isListViewFirstFocusItem() || m_lvfc->isReturnNeeded()) { + delete m_lvfc; + m_lvfc = nullptr; + } else if (m_lvfc->isDelegateFirstFocusItem()) { + m_lvfc->resetFocusChain(); + m_lvfc->decrementIndex(); + m_lvfc->positionViewAtIndex(); + } +} + +void FocusController::nextKeyTabItem() +{ + nextItem(true); } void FocusController::previousKeyTabItem() { - reload(); - - if(m_focusChain.empty()) { - return; - } - - if (m_focusedItemIndex <= 0) { - m_focusedItemIndex = m_focusChain.size() - 1; - } else { - m_focusedItemIndex--; - } - - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(Qt::TabFocusReason); - - qDebug() << "===>> Current focus was changed to " << m_focusedItem; + nextItem(false); } void FocusController::nextKeyUpItem() { - previousKeyTabItem(); + nextItem(false); } void FocusController::nextKeyDownItem() { - nextKeyTabItem(); + nextItem(true); } void FocusController::nextKeyLeftItem() { - previousKeyTabItem(); + nextItem(false); } void FocusController::nextKeyRightItem() { - nextKeyTabItem(); + nextItem(true); } -void FocusController::reload() +void FocusController::setFocusOnDefaultItem() { - m_focusChain.clear(); + qDebug() << "===>> Setting focus on DEFAULT FOCUS ITEM..."; + m_defaultFocusItem->forceActiveFocus(); +} - QObjectList rootObjects; +void FocusController::reload(bool isForwardOrder) +{ + m_focusChain.clear(); - const auto rootItem = m_rootItem; + QObject* rootObject = (m_rootObjects.empty() + ? m_engine->rootObjects().value(0) + : m_rootObjects.top()); - if (rootItem != nullptr) { - rootObjects << qobject_cast(rootItem); - } else { - rootObjects = m_engine->rootObjects(); - } - - if(rootObjects.empty()) { - qWarning() << "Empty focus chain detected!"; - emit focusChainChanged(); + if(!rootObject) { + qCritical() << "No ROOT OBJECT found!"; + m_focusedItemIndex = -1; + resetRootObject(); + setFocusOnDefaultItem(); return; } - for(const auto object : rootObjects) { - m_focusChain.append(getSubChain(object)); - } + qDebug() << "===>> ROOT OBJECTS: " << rootObject; - std::sort(m_focusChain.begin(), m_focusChain.end(), isLess); + m_focusChain.append(getSubChain(rootObject)); - printItems(m_focusChain, m_focusedItem); - - emit focusChainChanged(); + std::sort(m_focusChain.begin(), m_focusChain.end(), isForwardOrder? isLess : isMore); if (m_focusChain.empty()) { + qWarning() << "Focus chain is empty!"; m_focusedItemIndex = -1; - qWarning() << "reloaded to empty focus chain"; - return; - } - - QQuickWindow* window = qobject_cast(rootObjects[0]); - if (!window) { - window = qobject_cast(rootObjects[0])->window(); - } - - if (!window) { - qCritical() << "Couldn't get the current window"; + resetRootObject(); + setFocusOnDefaultItem(); return; } m_focusedItemIndex = m_focusChain.indexOf(m_focusedItem); - // m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem()); if(m_focusedItemIndex == -1) { - qInfo() << "No focus item in chain. Moving focus to begin..."; - // m_focused_item_index = 0; // if not in focus chain current + qInfo() << "No focus item in chain."; + setFocusOnDefaultItem(); + return; + } +} + +void FocusController::pushRootObject(QObject* object) +{ + m_rootObjects.push(object); + qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); +} + +void FocusController::dropRootObject(QObject* object) +{ + if (m_rootObjects.empty()) { + qDebug() << "ROOT OBJECT is already NULL"; + return; } - // m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - - // m_focusedItem->forceActiveFocus(); + if (m_rootObjects.top() == object) { + m_rootObjects.pop(); + if(m_rootObjects.size()) { + qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); + } else { + qDebug() << "===>> ROOT OBJECT is changed to NULL"; + } + } else { + qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object; + } } -void FocusController::setRootItem(QQuickItem* item) +void FocusController::resetRootObject() { - m_rootItem = item; + m_rootObjects.clear(); + qDebug() << "===>> ROOT OBJECT IS RESETED"; } diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h index f593fc8d..38869a69 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -2,6 +2,9 @@ #define FOCUSCONTROLLER_H #include +#include +#include + class QQuickItem; class QQmlApplicationEngine; @@ -20,31 +23,23 @@ public: Q_INVOKABLE void nextKeyDownItem(); Q_INVOKABLE void nextKeyLeftItem(); Q_INVOKABLE void nextKeyRightItem(); - -signals: - void nextTabItemChanged(QObject* item); - void previousTabItemChanged(QObject* item); - void nextKeyUpItemChanged(QObject* item); - void nextKeyDownItemChanged(QObject* item); - void nextKeyLeftItemChanged(QObject* item); - void nextKeyRightItemChanged(QObject* item); - void focusChainChanged(); - void rootItemChanged(); - -public slots: - void resetFocus(); - void reload(); - void setRootItem(QQuickItem* item); + Q_INVOKABLE void setFocusOnDefaultItem(); + Q_INVOKABLE void resetRootObject(); + Q_INVOKABLE void pushRootObject(QObject* object); + Q_INVOKABLE void dropRootObject(QObject* object); private: + void nextItem(bool isForwardOrder); void focusNextListViewItem(); void focusPreviousListViewItem(); + void reload(bool isForwardOrder); - QQmlApplicationEngine* m_engine; // Pointer to engine to get root object + 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 qsizetype m_focusedItemIndex; // Active focus item's index in focus chain - QQuickItem* m_rootItem; + QStack m_rootObjects; + QSharedPointer m_defaultFocusItem; ListViewFocusController* m_lvfc; // ListView focus manager }; From b4f4ec4ac96c0db3ea18e0819c76c7aafffc364c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Sep 2024 23:47:20 +0200 Subject: [PATCH 006/100] add default focus item --- client/ui/qml/main2.qml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 18b69795..83a6667f 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -32,6 +32,37 @@ Window { title: "AmneziaVPN" + Item { + id: defaultFocusItem + objectName: "defaultFocusItem" + + focus: 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() + } + } + Connections { objectName: "pageControllerConnections" From 3c655d0051444e81a71e4b2eacdaafa04b127573 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Sep 2024 23:50:58 +0200 Subject: [PATCH 007/100] update transitions --- client/ui/qml/Components/QuestionDrawer.qml | 4 ++-- .../ui/qml/Components/SelectLanguageDrawer.qml | 4 ++-- client/ui/qml/Controls2/DrawerType2.qml | 4 ++-- client/ui/qml/Controls2/PageType.qml | 16 ++-------------- client/ui/qml/Controls2/PopupType.qml | 4 ++-- client/ui/qml/Pages2/PageStart.qml | 1 - 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 0dbd2eab..0a49358a 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -37,11 +37,11 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - FocusController.setRootItem(root) + FocusController.pushRootObject(root) } function onClosed() { - FocusController.setRootItem(null) + FocusController.dropRootObject(root) } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 337c5987..4ca472cf 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -24,11 +24,11 @@ DrawerType2 { target: root enabled: !GC.isMobile() function onOpened() { - FocusController.setRootItem(root) + FocusController.pushRootObject(root) } function onClosed() { - FocusController.setRootItem(null) + FocusController.dropRootObject(root) } } diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml index 5e480327..04a7635c 100644 --- a/client/ui/qml/Controls2/DrawerType2.qml +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -95,7 +95,7 @@ Item { depthIndex = 0 PageController.decrementDrawerDepth() - FocusController.setRootItem(null) + FocusController.dropRootObject(root) } function onOpenTriggered() { @@ -118,7 +118,7 @@ Item { } depthIndex = PageController.incrementDrawerDepth() - FocusController.setRootItem(root) + FocusController.pushRootObject(root) } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 977c18ba..724d3b0d 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -15,25 +15,13 @@ Item { } } -// 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: 500 // Milliseconds onTriggered: { - FocusController.resetFocus() + 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 5bed7350..61221567 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -28,11 +28,11 @@ Popup { } onOpened: { - FocusController.setRootItem(root) + FocusController.pushRootObject(root) } onClosed: { - FocusController.setRootItem(null) + FocusController.dropRootObject(root) } background: Rectangle { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index abd03809..01e623b6 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -299,7 +299,6 @@ PageType { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.processedIndex = ServersModel.defaultIndex tabBar.currentIndex = 0 - FocusController.setRootItem(null) // TODO: move to do it automaticaly } } From 75f189e25694e0a6563eca09b8e696324321a23c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Sep 2024 23:54:04 +0200 Subject: [PATCH 008/100] update pages --- .../qml/Components/HomeContainersListView.qml | 22 +- .../ui/qml/Components/InstalledAppsDrawer.qml | 2 + client/ui/qml/Components/ServersListView.qml | 74 +----- .../Components/SettingsContainersListView.qml | 29 +- client/ui/qml/Controls2/DropDownType.qml | 51 ++-- .../Controls2/ListViewWithRadioButtonType.qml | 122 ++++----- client/ui/qml/Pages2/PageHome.qml | 6 +- .../Pages2/PageSettingsApiLanguageList.qml | 116 ++++---- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 250 ++++++++---------- .../Pages2/PageSettingsServerProtocols.qml | 76 +++--- .../qml/Pages2/PageSettingsServerServices.qml | 72 +++-- .../qml/Pages2/PageSettingsSplitTunneling.qml | 227 +++++++--------- client/ui/qml/Pages2/PageShare.qml | 64 ++--- 13 files changed, 443 insertions(+), 668 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 5d3569f5..8ddccb5a 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -17,8 +17,6 @@ ListView { property var rootWidth property var selectedText - property bool a: true - width: rootWidth height: contentItem.height // TODO: It should be fixed size, not content item height @@ -30,28 +28,34 @@ ListView { // property int currentFocusIndex: 0 - snapMode: ListView.SnapToItem + // snapMode: ListView.SnapToItem // ScrollBar.vertical: ScrollBar {} property bool isFocusable: true Keys.onTabPressed: { - console.debug("--> Tab is pressed on HomeContainersListView: ", objectName) FocusController.nextKeyTabItem() } Keys.onBacktabPressed: { - console.debug("--> Shift+Tab is pressed on HomeContainersListView: ", objectName) FocusController.previousKeyTabItem() } - Keys.onRightPressed: { - FocusController.nextKeyTabItem() + Keys.onUpPressed: { + FocusController.nextKeyUpItem() } - + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + Keys.onLeftPressed: { - FocusController.previousKeyTabItem() + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() } // activeFocusOnTab: true diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index 0ee48100..cb8186a9 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -69,6 +69,8 @@ DrawerType2 { clip: true interactive: true + property bool isFocusable: true + model: SortFilterProxyModel { id: proxyInstalledAppsModel sourceModel: installedAppsModel diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 76f3f267..b3663cbd 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -33,43 +33,7 @@ ListView { policy: root.height >= root.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn } - readonly property bool isFocusable: true - - // Keys.onTabPressed: { - // FocusController.nextKeyTabItem() - // } - - // activeFocusOnTab: true - // focus: true - - property int focusItemIndex: 0 - - // onFocusItemIndexChanged: { - // console.debug("===>> root onFocusItemIndexChanged") - - // // const focusedElement = root.itemAtIndex(focusItemIndex) - // // if (focusedElement) { - // // if (focusedElement.y + focusedElement.height > root.height) { - // // root.contentY = focusedElement.y + focusedElement.height - root.height - // // } else { - // // root.contentY = 0 - // // } - // // } - // } - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() - - // Connections { - // target: drawer - // enabled: !GC.isMobile() - // function onIsCollapsedChanged() { - // if (drawer.isCollapsedStateActive) { - // const item = root.itemAtIndex(root.focusItemIndex) - // if (item) { item.serverRadioButtonProperty.focus = false } - // } - // } - // } + property bool isFocusable: true Connections { target: ServersModel @@ -135,7 +99,6 @@ ListView { enabled: false } - // Keys.onTabPressed: serverInfoButton.forceActiveFocus() Keys.onEnterPressed: serverRadioButton.clicked() Keys.onReturnPressed: serverRadioButton.clicked() } @@ -144,10 +107,6 @@ ListView { id: serverInfoButton objectName: "serverInfoButton" - // signal keyTabOnLastElement - - // isFocusable: false - image: "qrc:/images/controls/settings.svg" imageColor: AmneziaStyle.color.paleGray @@ -156,41 +115,10 @@ ListView { z: 1 - // onActiveFocusChanged: { - // console.debug("===>> serverInfoButton::activeFocusChanged") - - // if (activeFocus) { - // if (currentIndex === root.count - 1) { - // console.log("---> Latest element") - // keyTabOnLastElement() - // } - - // console.log("--->>", currentIndex) - // // serverRadioButton.forceActiveFocus() - // } - // } - - // onKeyTabOnLastElement: { - // console.log("*** Signal emmited! ***") - // FocusController.nextKeyTabItem() - // } - - // Keys.onTabPressed: { - // console.log("===>> serverInfoButton::Keys.onTabPressed") - // if (root.focusItemIndex < root.count - 1) { - // root.focusItemIndex++ - // root.itemAtIndex(root.focusItemIndex).forceActiveFocus() - // } else { - // FocusController.nextKeyTabItem() - // root.contentY = 0 - // } - // } Keys.onEnterPressed: serverInfoButton.clicked() Keys.onReturnPressed: serverInfoButton.clicked() onClicked: function() { - console.debug("===>> onClicked serverInfoButton") - ServersModel.processedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) drawer.closeTriggered() diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index d6db36af..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/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 8b683d94..48471873 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -54,20 +54,25 @@ Item { FocusController.nextKeyTabItem() } - // function popupClosedFunc() { - // if (!GC.isMobile()) { - // this.forceActiveFocus() - // } - // } + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } - // property var parentFlickable - // onFocusChanged: { - // if (root.activeFocus) { - // if (root.parentFlickable) { - // root.parentFlickable.ensureVisible(root) - // } - // } - // } + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight @@ -171,8 +176,6 @@ Item { } ImageButtonType { - // isFocusable: false - Layout.rightMargin: 16 implicitWidth: 40 @@ -206,10 +209,6 @@ Item { anchors.fill: parent expandedHeight: drawerParent.height * drawerHeight - // onClosed: { - // root.popupClosedFunc() - // } - expandedStateContent: Item { id: container implicitHeight: menu.expandedHeight @@ -226,7 +225,11 @@ Item { id: backButton backButtonImage: root.headerBackButtonImage backButtonFunction: function() { menu.closeTriggered() } - // KeyNavigation.tab: listViewLoader.item + onActiveFocusChanged: { + if(activeFocus) { + root.listView.positionViewAtBeginning() + } + } } } @@ -252,14 +255,6 @@ Item { Loader { id: listViewLoader sourceComponent: root.listView - - onLoaded: { - // listViewLoader.item.parentFlickable = flickable - // FocusController.reload() - // listViewLoader.item.lastItemTabClicked = function() { - // focusItem.forceActiveFocus() - // } - } } } } diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 9024af91..230f37eb 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -26,12 +26,6 @@ ListView { height: root.contentItem.height clip: true - interactive: false - - property FlickableType parentFlickable - property var lastItemTabClicked - - property int currentFocusIndex: 0 property bool isFocusable: true @@ -69,84 +63,80 @@ ListView { radioButton.clicked() } - delegate: Item { + delegate: ColumnLayout { + id: content + implicitWidth: rootWidth - implicitHeight: content.implicitHeight + // implicitHeight: content.implicitHeight - ColumnLayout { - id: content + RadioButton { + id: radioButton - anchors.fill: parent + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight - RadioButton { - id: radioButton + hoverEnabled: true - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + 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 - hoverEnabled: true - - 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/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 2d52b044..5ef33ded 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -126,7 +126,6 @@ PageType { onClosed: { console.log(objectName, " was closed...") - FocusController.setRootItem(null) } } } @@ -257,7 +256,6 @@ PageType { console.debug("onClicked collapsedButtonChevron") if (drawer.isCollapsedStateActive()) { drawer.openTriggered() - FocusController.setRootItem(drawer) } } } @@ -340,6 +338,8 @@ PageType { rootButtonTextTopMargin: 8 rootButtonTextBottomMargin: 8 + enabled: drawer.isOpened + text: ServersModel.defaultServerDefaultContainerName textColor: AmneziaStyle.color.midnightBlack headerText: qsTr("VPN protocol") @@ -358,8 +358,6 @@ PageType { rootWidth: root.width height: 500 // TODO: make calculated - // isFocusable: false // TODO: this is a workaround. Need to remove it - Connections { objectName: "rowLayoutConnections" 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/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 4f6ab934..01d559f8 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,131 +46,110 @@ PageType { value: true } ] + + 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 - - 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 - } - } - - actionButtonFunction: function() { - if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { - nestedStackView.currentIndex = root.pageSettingsApiServerInfo - } else { - serverNameEditDrawer.openTriggered() - } - } + 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() - } - } - - expandedStateContent: 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 } - TextFieldWithHeaderType { - id: serverName - - Layout.fillWidth: true - headerText: qsTr("Server name") - textFieldText: name - textField.maximumLength: 30 - checkEmptyText: true - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - - text: qsTr("Save") - - clickedFunc: function() { - if (serverName.textFieldText === "") { - return - } - - if (serverName.textFieldText !== name) { - name = serverName.textFieldText - } - serverNameEditDrawer.closeTriggered() - } + if (serverName.textFieldText !== root.server.name) { + root.server.name = serverName.textFieldText // TODO(CyAn84): set value to the model } + serverNameEditDrawer.closeTriggered() } } } @@ -189,33 +170,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") - 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") - 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 { @@ -223,24 +198,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") ? @@ -249,36 +215,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/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/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 4bd4d0a4..f3ae3227 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -169,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 @@ -315,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) @@ -339,8 +334,6 @@ PageType { Keys.onReturnPressed: addSiteButtonImage.clicked() Keys.onEnterPressed: addSiteButtonImage.clicked() - - Keys.onTabPressed: lastItemTabClicked(focusItem) } } @@ -350,12 +343,6 @@ PageType { anchors.fill: parent expandedHeight: parent.height * 0.4375 - onClosed: { - // if (root.defaultActiveFocusItem && !GC.isMobile()) { - // root.defaultActiveFocusItem.forceActiveFocus() - // } - } - expandedStateContent: ColumnLayout { id: moreActionsDrawerContent @@ -363,20 +350,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - Connections { - target: moreActionsDrawer - - function onOpened() { - focusItem1.forceActiveFocus() - } - - function onActiveFocusChanged() { - if (!GC.isMobile()) { - focusItem1.forceActiveFocus() - } - } - } - Header2Type { Layout.fillWidth: true Layout.margins: 16 @@ -433,23 +406,9 @@ PageType { anchors.fill: parent expandedHeight: parent.height * 0.4375 - onClosed: { - if (!GC.isMobile()) { - moreActionsDrawer.forceActiveFocus() - } - } - expandedStateContent: Item { implicitHeight: importSitesDrawer.expandedHeight - Connections { - target: importSitesDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem2.forceActiveFocus() - } - } - BackButtonType { id: importSitesDrawerBackButton diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index cef516df..3cb8f660 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -236,11 +236,6 @@ 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 @@ -253,7 +248,6 @@ PageType { PageController.goToPage(PageEnum.PageShareFullAccess) shareFullAccessDrawer.closeTriggered() } - } } } @@ -694,38 +688,38 @@ 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 From f3df9eb5f51697550eed8d4282db68dc30c1d36f Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 13 Oct 2024 21:19:15 +0200 Subject: [PATCH 009/100] add ListViewFocusController --- client/ui/controllers/focusController.cpp | 324 +-------------- client/ui/controllers/focusController.h | 6 + .../controllers/listViewFocusController.cpp | 385 ++++++++++++++++++ .../ui/controllers/listViewFocusController.h | 74 ++++ 4 files changed, 478 insertions(+), 311 deletions(-) create mode 100644 client/ui/controllers/listViewFocusController.cpp create mode 100644 client/ui/controllers/listViewFocusController.h diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 16133341..27be7d2a 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -1,310 +1,11 @@ #include "focusController.h" +#include "listViewFocusController.h" + #include #include -#include -#include -#include -#include -bool isVisible(QObject* item) -{ - const auto res = item->property("visible").toBool(); - // qDebug() << "==>> " << (res ? "VISIBLE" : "NOT visible") << item; - return res; -} - -bool isFocusable(QObject* item) -{ - const auto res = item->property("isFocusable").toBool(); - return res; -} - -QRectF getItemCoordsOnScene(QQuickItem* item) // TODO: remove? -{ - if (!item) return {}; - return item->mapRectToScene(item->childrenRect()); -} - -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 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; -} - -bool isEnabled(QObject* obj) -{ - const auto item = qobject_cast(obj); - return item && item->isEnabled(); -} - -QQuickItem* getPageOfItem(QQuickItem* item) // TODO: remove? -{ - if(!item) { - qWarning() << "item is null"; - return {}; - } - const auto pagePattern = QString::fromLatin1("Page"); - QString className{item->metaObject()->className()}; - const auto isPage = className.contains(pagePattern, Qt::CaseSensitive); - if(isPage) { - return item; - } else { - return getPageOfItem(item->parentItem()); - } -} - -QList getSubChain(QObject* item) -{ - QList res; - if (!item) { - qDebug() << "null top item"; - return res; - } - const auto children = item->children(); - for(const auto child : children) { - if (child - && isFocusable(child) - && (isOnTheScene(child)) - && isEnabled(child) - ) { - res.append(child); - } else { - res.append(getSubChain(child)); - } - } - return res; -} - -template -void printItems(const T& 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; - } -} - -/*! - * \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 -{ -public: - explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); - ~ListViewFocusController(); - - void incrementIndex(); - void decrementIndex(); - void positionViewAtIndex(); - void focusNextItem(); - void focusPreviousItem(); - void resetFocusChain(); - bool isListViewFirstFocusItem(); - bool isDelegateFirstFocusItem(); - bool isListViewLastFocusItem(); - bool isDelegateLastFocusItem(); - bool isReturnNeeded(); - void viewToBegin(); - -private: - int size() const; - int currentIndex() const; - QQuickItem* itemAtIndex(const int index); - QQuickItem* currentDelegate(); - QQuickItem* focusedItem(); - - QQuickItem* m_listView; - QList m_focusChain; - QQuickItem* m_focusedItem; - qsizetype m_focusedItemIndex; - qsizetype m_delegateIndex; - bool m_isReturnNeeded; -}; - -ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* parent) - : QObject{parent} - , m_listView{listView} - , m_focusChain{} - , m_focusedItem{nullptr} - , m_focusedItemIndex{-1} - , m_delegateIndex{0} - , m_isReturnNeeded{false} -{ -} - -ListViewFocusController::~ListViewFocusController() -{ - -} - -void ListViewFocusController::positionViewAtIndex() -{ - QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", - Q_ARG(int, m_delegateIndex), // Index - Q_ARG(int, 2)); // PositionMode (0 = Visible) -} - -int ListViewFocusController::size() const -{ - return m_listView->property("count").toInt(); -} - -int ListViewFocusController::currentIndex() const -{ - return m_delegateIndex; -} - -void ListViewFocusController::incrementIndex() -{ - m_delegateIndex++; -} - -void ListViewFocusController::decrementIndex() -{ - m_delegateIndex--; -} - -QQuickItem* ListViewFocusController::itemAtIndex(const int index) -{ - QQuickItem* item{nullptr}; - - QMetaObject::invokeMethod(m_listView, "itemAtIndex", - Q_RETURN_ARG(QQuickItem*, item), - Q_ARG(int, index)); - - return item; -} - -QQuickItem* ListViewFocusController::currentDelegate() -{ - return itemAtIndex(m_delegateIndex); -} - -QQuickItem* ListViewFocusController::focusedItem() -{ - return m_focusedItem; -} - -void ListViewFocusController::focusNextItem() -{ - if (m_focusChain.empty()) { - qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = getSubChain(currentDelegate()); - } - if (m_focusChain.empty()) { - qWarning() << "No elements found. Returning from ListView..."; - m_isReturnNeeded = true; - return; - } - m_focusedItemIndex++; - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - qDebug() << "==>> Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex; - m_focusedItem->forceActiveFocus(); -} - -void ListViewFocusController::focusPreviousItem() -{ - // TODO: implement -} - -void ListViewFocusController::resetFocusChain() -{ - m_focusChain.clear(); - m_focusedItem = nullptr; - m_focusedItemIndex = -1; -} - -bool ListViewFocusController::isDelegateFirstFocusItem() -{ - return m_focusedItem && (m_focusedItem == m_focusChain.first()); -} - -bool ListViewFocusController::isDelegateLastFocusItem() -{ - return m_focusedItem && (m_focusedItem == m_focusChain.last()); -} - -bool ListViewFocusController::isListViewFirstFocusItem() -{ - return (m_delegateIndex == 0) && isDelegateFirstFocusItem(); -} - -bool ListViewFocusController::isListViewLastFocusItem() -{ - return (m_delegateIndex == size() - 1) && isDelegateLastFocusItem(); -} - -bool ListViewFocusController::isReturnNeeded() -{ - return m_isReturnNeeded; -} - -void ListViewFocusController::viewToBegin() -{ - QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning", Qt::AutoConnection); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) : QObject{parent} , m_engine{engine} @@ -315,13 +16,11 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) , m_defaultFocusItem{QSharedPointer()} , m_lvfc{nullptr} { - // connect(this, &FocusController::rootItemChanged, this, &FocusController::onReload); QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url){ - qDebug() << "===>> () CREATED " << object << " : " << url; QQuickItem* newDefaultFocusItem = object->findChild("defaultFocusItem"); if(newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { m_defaultFocusItem.reset(newDefaultFocusItem); - qDebug() << "===>> [] NEW DEFAULT FOCUS ITEM " << m_defaultFocusItem; + qDebug() << "===>> NEW DEFAULT FOCUS ITEM " << m_defaultFocusItem; } }); } @@ -363,8 +62,11 @@ void FocusController::nextItem(bool isForwardOrder) m_lvfc = new ListViewFocusController(m_focusedItem, this); if(isForwardOrder) { m_lvfc->viewToBegin(); + m_lvfc->nextElement(); focusNextListViewItem(); } else { + m_lvfc->viewToEnd(); + m_lvfc->previousElement(); focusPreviousListViewItem(); } return; @@ -390,15 +92,15 @@ void FocusController::focusNextListViewItem() { m_lvfc->focusNextItem(); - if (m_lvfc->isListViewLastFocusItem() || m_lvfc->isReturnNeeded()) { + if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { qDebug() << "===>> [Last item in ListView was reached]"; delete m_lvfc; m_lvfc = nullptr; - } else if (m_lvfc->isDelegateLastFocusItem()) { + } else if (m_lvfc->isLastFocusItemInDelegate()) { qDebug() << "===>> [End of delegate elements was reached. Going to the next delegate]"; m_lvfc->resetFocusChain(); - m_lvfc->incrementIndex(); - m_lvfc->positionViewAtIndex(); + m_lvfc->nextElement(); + m_lvfc->viewAtCurrentIndex(); } } @@ -406,13 +108,13 @@ void FocusController::focusPreviousListViewItem() { m_lvfc->focusPreviousItem(); - if (m_lvfc->isListViewFirstFocusItem() || m_lvfc->isReturnNeeded()) { + if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { delete m_lvfc; m_lvfc = nullptr; - } else if (m_lvfc->isDelegateFirstFocusItem()) { + } else if (m_lvfc->isFirstFocusItemInDelegate()) { m_lvfc->resetFocusChain(); m_lvfc->decrementIndex(); - m_lvfc->positionViewAtIndex(); + m_lvfc->viewAtCurrentIndex(); } } diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h index 38869a69..e6368e98 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -10,6 +10,12 @@ 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 diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp new file mode 100644 index 00000000..a4b13fb4 --- /dev/null +++ b/client/ui/controllers/listViewFocusController.cpp @@ -0,0 +1,385 @@ +#include "listViewFocusController.h" + +#include +#include +#include +#include +#include + + +bool isVisible(QObject* item) +{ + const auto res = item->property("visible").toBool(); + // qDebug() << "==>> " << (res ? "VISIBLE" : "NOT visible") << item; + 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 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; +} + +bool isEnabled(QObject* obj) +{ + const auto item = qobject_cast(obj); + return item && item->isEnabled(); +} + +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; +} + +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() +{ + switch(m_currentSection) { + case Section::Default: + case Section::Header: { + QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); + break; + } + case Section::Delegate: { + QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", + Q_ARG(int, m_delegateIndex), // Index + Q_ARG(int, 2)); // PositionMode (0 = Visible) + break; + } + case Section::Footer: { + 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::nextElement() +{ + qDebug() << "===>> Current section: " << m_currentSectionString.at(static_cast(m_currentSection)); + switch(m_currentSection) { + case Section::Default: { + if(m_header) { + m_currentSection = Section::Header; + break; + } + } + case Section::Header: { + if (size() > 0) { + m_currentSection = Section::Delegate; + break; + } + } + case Section::Delegate: + if (m_delegateIndex < (size() - 1)) { + m_delegateIndex++; + break; + } else if (m_footer) { + m_currentSection = Section::Footer; + break; + } + case Section::Footer: { + m_isReturnNeeded = true; + m_currentSection = Section::Default; + break; + } + default: { + qCritical() << "Current section is invalid!"; + break; + } + } + +} + +void ListViewFocusController::previousElement() +{ + switch(m_currentSection) { + case Section::Default: { + if(m_footer) { + m_currentSection = Section::Footer; + break; + } + } + case Section::Footer: { + if (size() > 0) { + m_currentSection = Section::Delegate; + m_focusedItemIndex = size() - 1; // workarount to default value == -1 + break; + } + } + case Section::Delegate: { + if (m_delegateIndex > 0) { + m_delegateIndex--; + break; + } else if (m_header) { + m_currentSection = Section::Header; + break; + } + } + 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) +{ + QQuickItem* item{nullptr}; + + QMetaObject::invokeMethod(m_listView, "itemAtIndex", + Q_RETURN_ARG(QQuickItem*, item), + Q_ARG(int, index)); + + return item; +} + +QQuickItem* ListViewFocusController::currentDelegate() +{ + 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() +{ + return m_focusedItem; +} + +void ListViewFocusController::focusNextItem() +{ + if (m_isReturnNeeded) { + return; + } + + if (m_focusChain.empty()) { + qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; + m_focusChain = getSubChain(currentDelegate()); + } + if (m_focusChain.empty()) { + qWarning() << "No elements found. Returning from ListView..."; + nextElement(); + 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(); +} + +void ListViewFocusController::focusPreviousItem() +{ + if (m_isReturnNeeded) { + return; + } + + if (m_focusChain.empty()) { + qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; + m_focusChain = getSubChain(currentDelegate()); + } + if (m_focusChain.empty()) { + qWarning() << "No elements found. Returning from ListView..."; + previousElement(); + focusPreviousItem(); + return; + } + m_focusedItemIndex--; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + qDebug() << "==>> Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex; + m_focusedItem->forceActiveFocus(); +} + +void ListViewFocusController::resetFocusChain() +{ + m_focusChain.clear(); + m_focusedItem = nullptr; + m_focusedItemIndex = -1; +} + +bool ListViewFocusController::isFirstFocusItemInDelegate() +{ + return m_focusedItem && (m_focusedItem == m_focusChain.first()); +} + +bool ListViewFocusController::isLastFocusItemInDelegate() +{ + return m_focusedItem && (m_focusedItem == m_focusChain.last()); +} + +bool ListViewFocusController::isFirstFocusItemInListView() +{ + return (m_delegateIndex == 0) && isFirstFocusItemInDelegate(); +} + +bool ListViewFocusController::isLastFocusItemInListView() +{ + bool isLastSection = (m_footer && m_currentSection == Section::Footer) + || (!m_footer && (m_currentSection == Section::Delegate) && (m_delegateIndex == size() - 1)) + || (m_header && (m_currentSection == Section::Header) && (size() <= 0) && !m_footer); + return isLastSection && isLastFocusItemInDelegate(); +} + +bool ListViewFocusController::isReturnNeeded() +{ + return m_isReturnNeeded; +} + +void ListViewFocusController::viewToBegin() +{ + QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning", Qt::AutoConnection); +} + +void ListViewFocusController::viewToEnd() +{ + QMetaObject::invokeMethod(m_listView, "positionViewAtEnd", Qt::AutoConnection); +} diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h new file mode 100644 index 00000000..e7d83bbf --- /dev/null +++ b/client/ui/controllers/listViewFocusController.h @@ -0,0 +1,74 @@ +#ifndef LISTVIEWFOCUSCONTROLLER_H +#define LISTVIEWFOCUSCONTROLLER_H + +#include +#include +#include +#include + + +bool isListView(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 nextElement(); + void previousElement(); + void decrementIndex(); + void focusNextItem(); + void focusPreviousItem(); + void resetFocusChain(); + bool isFirstFocusItemInListView(); + bool isFirstFocusItemInDelegate(); + bool isLastFocusItemInListView(); + bool isLastFocusItemInDelegate(); + bool isReturnNeeded(); + void viewToBegin(); + void viewToEnd(); + void viewAtCurrentIndex(); + +private: + enum class Section { + Default, + Header, + Delegate, + Footer, + }; + + int size() const; + int currentIndex() const; + QQuickItem* itemAtIndex(const int index); + QQuickItem* currentDelegate(); + QQuickItem* focusedItem(); + + 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 From 89ac585e07ddab7a2f7512eefefbc8dc77c030bd Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 17 Oct 2024 03:52:57 +0200 Subject: [PATCH 010/100] fix ListView navigation --- client/ui/controllers/focusController.cpp | 108 ++++++++++++++---- client/ui/controllers/focusController.h | 9 +- .../controllers/listViewFocusController.cpp | 69 +++-------- .../ui/controllers/listViewFocusController.h | 7 +- client/ui/qml/main2.qml | 5 +- 5 files changed, 117 insertions(+), 81 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 27be7d2a..e70391a8 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -6,6 +6,67 @@ #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} @@ -25,16 +86,16 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) }); } -void FocusController::nextItem(bool isForwardOrder) +void FocusController::nextItem(Direction direction) { if (m_lvfc) { - isForwardOrder ? focusNextListViewItem() : focusPreviousListViewItem(); + direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); qDebug() << "===>> [handling the ListView]"; return; } - reload(isForwardOrder); + reload(direction); if(m_focusChain.empty()) { qWarning() << "There are no items to navigate"; @@ -60,13 +121,13 @@ void FocusController::nextItem(bool isForwardOrder) if(isListView(m_focusedItem)) { qDebug() << "===>> [Found ListView]"; m_lvfc = new ListViewFocusController(m_focusedItem, this); - if(isForwardOrder) { + if(direction == Direction::Forward) { m_lvfc->viewToBegin(); - m_lvfc->nextElement(); + m_lvfc->nextDelegate(); focusNextListViewItem(); } else { m_lvfc->viewToEnd(); - m_lvfc->previousElement(); + m_lvfc->previousDelegate(); focusPreviousListViewItem(); } return; @@ -90,62 +151,67 @@ void FocusController::nextItem(bool isForwardOrder) void FocusController::focusNextListViewItem() { - m_lvfc->focusNextItem(); - if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { - qDebug() << "===>> [Last item in ListView was reached]"; + qDebug() << "===>> [Last item in ListView was reached. Going to the NEXT element after ListView]"; delete m_lvfc; m_lvfc = nullptr; + 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->nextElement(); + m_lvfc->nextDelegate(); m_lvfc->viewAtCurrentIndex(); } + + m_lvfc->focusNextItem(); } void FocusController::focusPreviousListViewItem() { - m_lvfc->focusPreviousItem(); - if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { + qDebug() << "===>> [First item in ListView was reached. Going to the PREVIOUS element after ListView]"; delete m_lvfc; m_lvfc = nullptr; + nextItem(Direction::Backward); + return; } else if (m_lvfc->isFirstFocusItemInDelegate()) { m_lvfc->resetFocusChain(); - m_lvfc->decrementIndex(); + m_lvfc->previousDelegate(); m_lvfc->viewAtCurrentIndex(); } + + m_lvfc->focusPreviousItem(); } void FocusController::nextKeyTabItem() { - nextItem(true); + nextItem(Direction::Forward); } void FocusController::previousKeyTabItem() { - nextItem(false); + nextItem(Direction::Backward); } void FocusController::nextKeyUpItem() { - nextItem(false); + nextItem(Direction::Backward); } void FocusController::nextKeyDownItem() { - nextItem(true); + nextItem(Direction::Forward); } void FocusController::nextKeyLeftItem() { - nextItem(false); + nextItem(Direction::Backward); } void FocusController::nextKeyRightItem() { - nextItem(true); + nextItem(Direction::Forward); } void FocusController::setFocusOnDefaultItem() @@ -154,7 +220,7 @@ void FocusController::setFocusOnDefaultItem() m_defaultFocusItem->forceActiveFocus(); } -void FocusController::reload(bool isForwardOrder) +void FocusController::reload(Direction direction) { m_focusChain.clear(); @@ -174,7 +240,7 @@ void FocusController::reload(bool isForwardOrder) m_focusChain.append(getSubChain(rootObject)); - std::sort(m_focusChain.begin(), m_focusChain.end(), isForwardOrder? isLess : isMore); + std::sort(m_focusChain.begin(), m_focusChain.end(), direction == Direction::Forward ? isLess : isMore); if (m_focusChain.empty()) { qWarning() << "Focus chain is empty!"; diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h index e6368e98..a4851032 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -35,10 +35,15 @@ public: Q_INVOKABLE void dropRootObject(QObject* object); private: - void nextItem(bool isForwardOrder); + enum class Direction { + Forward, + Backward, + }; + + void nextItem(Direction direction); void focusNextListViewItem(); void focusPreviousListViewItem(); - void reload(bool isForwardOrder); + void reload(Direction direction); QSharedPointer m_engine; // Pointer to engine to get root object QList m_focusChain; // List of current objects to be focused diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index a4b13fb4..b899dfe6 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -10,7 +10,6 @@ bool isVisible(QObject* item) { const auto res = item->property("visible").toBool(); - // qDebug() << "==>> " << (res ? "VISIBLE" : "NOT visible") << item; return res; } @@ -39,50 +38,13 @@ bool isMore(QObject* item1, QObject* item2) return !isLess(item1, item2); } -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; -} - bool isEnabled(QObject* obj) { const auto item = qobject_cast(obj); return item && item->isEnabled(); } -QList getSubChain(QObject* object) +QList getItemsChain(QObject* object) { QList res; if (!object) { @@ -95,12 +57,12 @@ QList getSubChain(QObject* object) for(const auto child : children) { if (child && isFocusable(child) - && isOnTheScene(child) && isEnabled(child) + && isVisible(child) ) { res.append(child); } else { - res.append(getSubChain(child)); + res.append(getItemsChain(child)); } } return res; @@ -175,13 +137,13 @@ int ListViewFocusController::currentIndex() const return m_delegateIndex; } -void ListViewFocusController::nextElement() +void ListViewFocusController::nextDelegate() { - qDebug() << "===>> Current section: " << m_currentSectionString.at(static_cast(m_currentSection)); switch(m_currentSection) { case Section::Default: { if(m_header) { m_currentSection = Section::Header; + viewToBegin(); break; } } @@ -197,6 +159,7 @@ void ListViewFocusController::nextElement() break; } else if (m_footer) { m_currentSection = Section::Footer; + viewToEnd(); break; } case Section::Footer: { @@ -209,10 +172,9 @@ void ListViewFocusController::nextElement() break; } } - } -void ListViewFocusController::previousElement() +void ListViewFocusController::previousDelegate() { switch(m_currentSection) { case Section::Default: { @@ -224,7 +186,6 @@ void ListViewFocusController::previousElement() case Section::Footer: { if (size() > 0) { m_currentSection = Section::Delegate; - m_focusedItemIndex = size() - 1; // workarount to default value == -1 break; } } @@ -298,16 +259,17 @@ QQuickItem* ListViewFocusController::focusedItem() void ListViewFocusController::focusNextItem() { if (m_isReturnNeeded) { + qDebug() << "===>> RETURN IS NEEDED..."; return; } if (m_focusChain.empty()) { qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = getSubChain(currentDelegate()); + m_focusChain = getItemsChain(currentDelegate()); } if (m_focusChain.empty()) { - qWarning() << "No elements found. Returning from ListView..."; - nextElement(); + qWarning() << "No elements found in the delegate. Going to next delegate..."; + nextDelegate(); focusNextItem(); return; } @@ -325,14 +287,17 @@ void ListViewFocusController::focusPreviousItem() if (m_focusChain.empty()) { qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = getSubChain(currentDelegate()); + m_focusChain = getItemsChain(currentDelegate()); } if (m_focusChain.empty()) { - qWarning() << "No elements found. Returning from ListView..."; - previousElement(); + 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; diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index e7d83bbf..66c31228 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -7,7 +7,8 @@ #include -bool isListView(QObject* item); +bool isEnabled(QObject* item); +bool isFocusable(QObject* item); bool isMore(QObject* item1, QObject* item2); bool isLess(QObject* item1, QObject* item2); QList getSubChain(QObject* object); @@ -29,8 +30,8 @@ public: explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); ~ListViewFocusController(); - void nextElement(); - void previousElement(); + void nextDelegate(); + void previousDelegate(); void decrementIndex(); void focusNextItem(); void focusPreviousItem(); diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 83a6667f..f1a87ba9 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 @@ -32,7 +33,7 @@ Window { title: "AmneziaVPN" - Item { + Item { // This item is needed for focus handling id: defaultFocusItem objectName: "defaultFocusItem" @@ -210,8 +211,6 @@ Window { clickedFunc: function() { hidePassword = !hidePassword } - - // KeyNavigation.tab: saveButton } BasicButtonType { From 852e90e3175d640e4967453534bb48aa0440e7d1 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 17 Oct 2024 04:10:40 +0200 Subject: [PATCH 011/100] update CardType for using with focus navigation --- client/ui/qml/Controls2/CardType.qml | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) 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 From 063851445a597cce996ad4515a4efb173dbe13fa Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 17 Oct 2024 04:14:18 +0200 Subject: [PATCH 012/100] remove useless key navigation --- client/ui/qml/Controls2/HeaderType.qml | 2 -- .../Pages2/PageProtocolShadowSocksSettings.qml | 1 - .../ui/qml/Pages2/PageProtocolXraySettings.qml | 2 -- .../Pages2/PageSettingsAppSplitTunneling.qml | 1 - .../ui/qml/Pages2/PageSettingsApplication.qml | 7 ------- .../Pages2/PageSetupWizardProtocolSettings.qml | 17 ++--------------- client/ui/qml/Pages2/PageSetupWizardTextKey.qml | 1 - .../ui/qml/Pages2/PageSetupWizardViewConfig.qml | 2 -- 8 files changed, 2 insertions(+), 31 deletions(-) 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/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index dae96b5a..aa04eb29 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -158,7 +158,6 @@ PageType { enabled: isPortEditable | isCipherEditable text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index d74aabad..6d53fdd3 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -118,8 +118,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index 4751aa71..2e86a0dc 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -253,7 +253,6 @@ PageType { textFieldPlaceholderText: qsTr("application name") buttonImageSource: "qrc:/images/controls/plus.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) rightButtonClickedOnEnter: true clickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 4dea8c65..c5fc0bc1 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -205,7 +205,6 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" textColor: AmneziaStyle.color.vibrantRed - Keys.onTabPressed: lastItemTabClicked() parentFlickable: fl clickedFunction: function() { @@ -246,11 +245,5 @@ PageType { width: root.width height: root.height - - // onClosed: { - // if (!GC.isMobile()) { - // focusItem.forceActiveFocus() - // } - // } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index de8275f1..3cf52c3f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -104,23 +104,10 @@ PageType { DrawerType2 { id: showDetailsDrawer parent: root - onClosed: { - if (!GC.isMobile()) { - // defaultActiveFocusItem.forceActiveFocus() - } - } anchors.fill: parent expandedHeight: parent.height * 0.9 expandedStateContent: Item { - Connections { - target: showDetailsDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem2.forceActiveFocus() - } - } - implicitHeight: showDetailsDrawer.expandedHeight // Item { @@ -196,7 +183,7 @@ PageType { parentFlickable: fl text: qsTr("Close") - Keys.onTabPressed: lastItemTabClicked(focusItem2) + // Keys.onTabPressed: lastItemTabClicked(focusItem2) clickedFunc: function() { showDetailsDrawer.close() @@ -248,7 +235,7 @@ PageType { text: qsTr("Install") - Keys.onTabPressed: lastItemTabClicked(focusItem) + // Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { if (!port.textField.acceptableInput && diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 86880713..126a7c91 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -73,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 52c12c56..14096742 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -178,8 +178,6 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - Keys.onTabPressed: lastItemTabClicked(focusItem) - BasicButtonType { id: connectButton Layout.fillWidth: true From ada3f9a7faa5de13a23c7fa8479f6caf8014432b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 19 Oct 2024 17:09:57 +0200 Subject: [PATCH 013/100] remove useless slots, logs, Drawer open and close --- client/ui/qml/Components/QuestionDrawer.qml | 12 ------------ .../ui/qml/Components/SelectLanguageDrawer.qml | 12 ------------ .../ui/qml/Components/ShareConnectionDrawer.qml | 14 -------------- client/ui/qml/Pages2/PageHome.qml | 8 +++----- .../qml/Pages2/PageProtocolOpenVpnSettings.qml | 1 - client/ui/qml/Pages2/PageProtocolRaw.qml | 15 --------------- .../PageProtocolWireGuardClientSettings.qml | 2 -- .../qml/Pages2/PageProtocolWireGuardSettings.qml | 2 -- client/ui/qml/Pages2/PageServiceDnsSettings.qml | 2 -- client/ui/qml/Pages2/PageServiceSftpSettings.qml | 9 --------- .../qml/Pages2/PageServiceSocksProxySettings.qml | 11 ----------- .../qml/Pages2/PageServiceTorWebsiteSettings.qml | 2 -- client/ui/qml/Pages2/PageSettingsDns.qml | 2 -- client/ui/qml/Pages2/PageSettingsServerData.qml | 16 ---------------- .../ui/qml/Pages2/PageSettingsServerProtocol.qml | 10 ---------- client/ui/qml/Pages2/PageShare.qml | 16 ---------------- client/ui/qml/main2.qml | 1 - 17 files changed, 3 insertions(+), 132 deletions(-) diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 0a49358a..0c14e52d 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -33,18 +33,6 @@ DrawerType2 { root.expandedHeight = content.implicitHeight + 32 } - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - FocusController.pushRootObject(root) - } - - function onClosed() { - FocusController.dropRootObject(root) - } - } - Header2TextType { Layout.fillWidth: true Layout.topMargin: 16 diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 4ca472cf..678ecf64 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -20,18 +20,6 @@ DrawerType2 { root.expandedHeight = container.implicitHeight } - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - FocusController.pushRootObject(root) - } - - function onClosed() { - FocusController.dropRootObject(root) - } - } - ColumnLayout { id: backButtonLayout diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 261cd742..dd794a03 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -39,14 +39,6 @@ DrawerType2 { expandedStateContent: Item { implicitHeight: root.expandedHeight - Connections { - target: root - enabled: !GC.isMobile() - function onOpened() { - header.forceActiveFocus() - } - } - Header2Type { id: header anchors.top: parent.top @@ -170,12 +162,6 @@ DrawerType2 { anchors.fill: parent expandedHeight: parent.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - header.forceActiveFocus() - } - } - expandedStateContent: Item { id: configContentContainer diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 5ef33ded..f5ac8ae3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -123,10 +123,6 @@ PageType { objectName: "homeSplitTunnelingDrawer" parent: root - - onClosed: { - console.log(objectName, " was closed...") - } } } } @@ -145,12 +141,14 @@ PageType { Component.onCompleted: { drawer.expandedHeight = implicitHeight } + Connections { objectName: "drawerConnections" target: drawer enabled: !GC.isMobile() } + ColumnLayout { id: collapsed objectName: "collapsedColumnLayout" @@ -253,7 +251,6 @@ PageType { Keys.onReturnPressed: collapsedButtonChevron.clicked() onClicked: { - console.debug("onClicked collapsedButtonChevron") if (drawer.isCollapsedStateActive()) { drawer.openTriggered() } @@ -414,6 +411,7 @@ PageType { Connections { target: drawer + // this item shouldn't be focused when drawer is closed function onIsOpenedChanged() { serversMenuContent.isFocusable = drawer.isOpened } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 75fdd18d..3df23ac0 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -398,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 3cb02435..14aa5194 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -110,26 +110,12 @@ PageType { expandedHeight: root.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - defaultActiveFocusItem.forceActiveFocus() - } - } - parent: root anchors.fill: parent expandedStateContent: Item { implicitHeight: configContentDrawer.expandedHeight - Connections { - target: configContentDrawer - enabled: !GC.isMobile() - function onOpened() { - focusItem1.forceActiveFocus() - } - } - BackButtonType { id: backButton1 @@ -209,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/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 ab50f444..257bc675 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -140,8 +140,6 @@ PageType { text: qsTr("Save") - Keys.onTabPressed: lastItemTabClicked(focusItem) - onClicked: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index c50f9a9b..cef29813 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -64,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 8513f111..2deb315c 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -174,14 +174,6 @@ PageType { parentFlickable: fl - rightButton.Keys.onTabPressed: { - if (mountButton.visible) { - mountButton.forceActiveFocus() - } else { - detailedInstructionsButton.forceActiveFocus() - } - } - rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -278,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 cadfee09..4c94913e 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -187,12 +187,6 @@ PageType { anchors.fill: parent expandedHeight: root.height * 0.9 - onClosed: { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } - expandedStateContent: ColumnLayout { property string tempPort: port property string tempUsername: username @@ -209,9 +203,6 @@ PageType { Connections { target: changeSettingsDrawer function onOpened() { - if (!GC.isMobile()) { - drawerFocusItem.forceActiveFocus() - } tempPort = port tempUsername = username tempPassword = password @@ -310,7 +301,6 @@ PageType { Layout.bottomMargin: 24 text: qsTr("Change connection settings") - Keys.onTabPressed: lastItemTabClicked(drawerFocusItem) clickedFunc: function() { forceActiveFocus() @@ -348,7 +338,6 @@ PageType { Layout.rightMargin: 16 text: qsTr("Change connection settings") - Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index a1e41f29..249c70c7 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -80,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/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index cede6c74..b6ade37a 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -140,8 +140,6 @@ PageType { } PageController.showNotificationMessage(qsTr("Settings saved")) } - - Keys.onTabPressed: lastItemTabClicked(focusItem) } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 0568c5f4..cd736d39 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -158,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.") @@ -206,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.") @@ -249,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/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 73e1e2c6..ab0cd291 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -61,15 +61,6 @@ PageType { } } - Keys.onTabPressed: { - if (currentFocusIndex < this.count - 1) { - currentFocusIndex += 1 - protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus() - } else { - clearCacheButton.forceActiveFocus() - } - } - delegate: Item { property var focusItem: clientSettings.rightButton @@ -210,7 +201,6 @@ PageType { Layout.fillWidth: true visible: ServersModel.isProcessedServerHasWriteAccess() - Keys.onTabPressed: lastItemTabClicked(focusItem) text: qsTr("Remove ") textColor: AmneziaStyle.color.vibrantRed diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 3cb8f660..7bf31067 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -199,11 +199,6 @@ PageType { anchors.fill: parent expandedHeight: root.height - onClosed: { - if (!GC.isMobile()) { - // clientNameTextField.textField.forceActiveFocus() - } - } expandedStateContent: ColumnLayout { id: shareFullAccessDrawerContent @@ -218,14 +213,6 @@ PageType { shareFullAccessDrawer.expandedHeight = shareFullAccessDrawerContent.implicitHeight + 32 } - Connections { - target: shareFullAccessDrawer - enabled: !GC.isMobile() - function onOpened() { - // focusItem.forceActiveFocus() - } - } - Header2Type { Layout.fillWidth: true Layout.bottomMargin: 16 @@ -295,8 +282,6 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Users") - // KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? clientNameTextField.textField : serverSelector - onClicked: { accessTypeSelector.currentIndex = 1 PageController.showBusyIndicator(true) @@ -565,7 +550,6 @@ PageType { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - Keys.onTabPressed: lastItemTabClicked(focusItem) parentFlickable: a diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index f1a87ba9..e71fae37 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -27,7 +27,6 @@ Window { color: AmneziaStyle.color.midnightBlack onClosing: function() { - console.debug("QML onClosing signal") PageController.closeWindow() } From 626b9e1e763e8b9e6b7ab94c41b85c25f5eb1da7 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 19 Oct 2024 22:20:35 +0200 Subject: [PATCH 014/100] fix reverse focus move on listView --- client/ui/controllers/listViewFocusController.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index b899dfe6..f07c069c 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -109,6 +109,7 @@ void ListViewFocusController::viewAtCurrentIndex() { switch(m_currentSection) { case Section::Default: + [[fallthrough]]; case Section::Header: { QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); break; @@ -146,12 +147,14 @@ void ListViewFocusController::nextDelegate() viewToBegin(); break; } + [[fallthrough]]; } case Section::Header: { if (size() > 0) { m_currentSection = Section::Delegate; break; } + [[fallthrough]]; } case Section::Delegate: if (m_delegateIndex < (size() - 1)) { @@ -162,6 +165,7 @@ void ListViewFocusController::nextDelegate() viewToEnd(); break; } + [[fallthrough]]; case Section::Footer: { m_isReturnNeeded = true; m_currentSection = Section::Default; @@ -182,12 +186,15 @@ void ListViewFocusController::previousDelegate() 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) { @@ -197,6 +204,7 @@ void ListViewFocusController::previousDelegate() m_currentSection = Section::Header; break; } + [[fallthrough]]; } case Section::Header: { m_isReturnNeeded = true; From 2c9fa10b8b02db3c54b9449dda060bcb4de09427 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 20 Oct 2024 01:27:02 +0200 Subject: [PATCH 015/100] fix drawer radio buttons selection --- .../qml/Components/SelectLanguageDrawer.qml | 62 +++++++------------ .../Controls2/ListViewWithRadioButtonType.qml | 2 + client/ui/qml/Pages2/PageStart.qml | 4 ++ 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 678ecf64..a48515cc 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -65,6 +65,30 @@ DrawerType2 { 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: LanguageModel currentIndex: LanguageModel.currentLanguageIndex @@ -72,48 +96,10 @@ DrawerType2 { id: buttonGroup } - // activeFocusOnTab: true - // onActiveFocusChanged: { - // if (activeFocus) { - // this.currentFocusIndex = 0 - // this.itemAtIndex(currentFocusIndex).forceActiveFocus() - // } - // } - - // Keys.onTabPressed: { - // if (currentFocusIndex < this.count - 1) { - // currentFocusIndex += 1 - // this.itemAtIndex(currentFocusIndex).forceActiveFocus() - // } else { - // listViewFocusItem.forceActiveFocus() - // focusItem.forceActiveFocus() - // } - // } - - // Item { - // id: listViewFocusItem // TODO: delete? - // Keys.onTabPressed: { - // root.forceActiveFocus() - // } - // } - - // onVisibleChanged: { - // if (visible) { - // listViewFocusItem.forceActiveFocus() - // focusItem.forceActiveFocus() - // } - // } - delegate: Item { implicitWidth: root.width implicitHeight: delegateContent.implicitHeight - // onActiveFocusChanged: { - // if (activeFocus) { - // radioButton.forceActiveFocus() - // } - // } - ColumnLayout { id: delegateContent diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 230f37eb..dda20313 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -77,6 +77,8 @@ ListView { hoverEnabled: true + property bool isFocusable: true + indicator: Rectangle { width: parent.width - 1 height: parent.height diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 01e623b6..14cc6896 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -246,6 +246,10 @@ PageType { } Keys.onPressed: function(event) { + if(event.key === Qt.Key_Tab) { + FocusController.nextKeyTabItem() + } + PageController.keyPressEvent(event.key) event.accepted = true } From 9cfa4c1389ca088c8608af36698a28b4e430c465 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 20 Oct 2024 21:44:44 +0200 Subject: [PATCH 016/100] fix drawer layout and focus move --- client/ui/qml/Controls2/DropDownType.qml | 5 ++-- .../Controls2/ListViewWithRadioButtonType.qml | 24 +++++++++++++++++++ .../qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 48471873..787890cb 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -226,7 +226,7 @@ Item { backButtonImage: root.headerBackButtonImage backButtonFunction: function() { menu.closeTriggered() } onActiveFocusChanged: { - if(activeFocus) { + if(backButton.enabled && backButton.activeFocus) { root.listView.positionViewAtBeginning() } } @@ -235,9 +235,10 @@ Item { Column { id: col - anchors.top: parent.top + anchors.top: header.bottom anchors.left: parent.left anchors.right: parent.right + anchors.topMargin: 16 spacing: 16 diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index dda20313..2ced4335 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -79,6 +79,30 @@ ListView { 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 { width: parent.width - 1 height: parent.height diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index f3ae3227..e61dc9f6 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -148,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 } From c9622110682e6c2241d18d6a31aebe8f12b8b0c7 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 21 Oct 2024 16:19:57 +0200 Subject: [PATCH 017/100] fix PageSetupWizardProtocolSettings focus move --- .../PageSetupWizardProtocolSettings.qml | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 3cf52c3f..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 @@ -97,7 +123,7 @@ PageType { KeyNavigation.tab: transportProtoSelector clickedFunc: function() { - showDetailsDrawer.open() + showDetailsDrawer.openTriggered() } } @@ -110,16 +136,6 @@ PageType { expandedStateContent: Item { implicitHeight: showDetailsDrawer.expandedHeight - // Item { - // id: focusItem2 - // KeyNavigation.tab: showDetailsBackButton - // onFocusChanged: { - // if (focusItem2.activeFocus) { - // fl.contentY = 0 - // } - // } - // } - BackButtonType { id: showDetailsBackButton @@ -129,7 +145,7 @@ PageType { anchors.topMargin: 16 backButtonFunction: function() { - showDetailsDrawer.close() + showDetailsDrawer.closeTriggered() } } @@ -183,10 +199,9 @@ PageType { parentFlickable: fl text: qsTr("Close") - // Keys.onTabPressed: lastItemTabClicked(focusItem2) clickedFunc: function() { - showDetailsDrawer.close() + showDetailsDrawer.closeTriggered() } } } @@ -207,8 +222,6 @@ PageType { Layout.fillWidth: true rootWidth: root.width - - // KeyNavigation.tab: (port.visible && port.enabled) ? port.textField : installButton } TextFieldWithHeaderType { @@ -235,8 +248,6 @@ PageType { text: qsTr("Install") - // Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { if (!port.textField.acceptableInput && ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && @@ -264,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 } } } From db5d289edcbf560ec94dad84da7c29decb5371fc Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 21 Oct 2024 16:47:46 +0200 Subject: [PATCH 018/100] fix back navigation on default focus item --- client/ui/qml/main2.qml | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index e71fae37..2ab4db69 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -38,28 +38,22 @@ Window { focus: 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() + 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 + } } } From dac45a9f7f37c6ff517eedd798896759ac463d45 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 21 Oct 2024 20:43:05 +0200 Subject: [PATCH 019/100] fix crashes after ListView navigation --- client/ui/controllers/focusController.cpp | 19 +++++++++++++++---- client/ui/controllers/focusController.h | 1 + 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index e70391a8..682f4b0e 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -88,6 +88,8 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) void FocusController::nextItem(Direction direction) { + reload(direction); + if (m_lvfc) { direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); qDebug() << "===>> [handling the ListView]"; @@ -95,15 +97,13 @@ void FocusController::nextItem(Direction direction) return; } - reload(direction); - if(m_focusChain.empty()) { qWarning() << "There are no items to navigate"; return; } if (m_focusedItemIndex == (m_focusChain.size() - 1)) { - qDebug() << "Last focus index. Making it zero"; + qDebug() << "Last focus index. Making it zero..."; m_focusedItemIndex = 0; } else { qDebug() << "Incrementing focus index"; @@ -222,7 +222,7 @@ void FocusController::setFocusOnDefaultItem() void FocusController::reload(Direction direction) { - m_focusChain.clear(); + m_focusChain.clear(); QObject* rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) @@ -232,6 +232,7 @@ void FocusController::reload(Direction direction) qCritical() << "No ROOT OBJECT found!"; m_focusedItemIndex = -1; resetRootObject(); + resetListView(); setFocusOnDefaultItem(); return; } @@ -246,6 +247,7 @@ void FocusController::reload(Direction direction) qWarning() << "Focus chain is empty!"; m_focusedItemIndex = -1; resetRootObject(); + resetListView(); setFocusOnDefaultItem(); return; } @@ -254,11 +256,20 @@ void FocusController::reload(Direction direction) if(m_focusedItemIndex == -1) { qInfo() << "No focus item in chain."; + resetListView(); setFocusOnDefaultItem(); return; } } +void FocusController::resetListView() +{ + if(m_lvfc) { + delete m_lvfc; + m_lvfc = nullptr; + } +} + void FocusController::pushRootObject(QObject* object) { m_rootObjects.push(object); diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h index a4851032..472c328b 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -44,6 +44,7 @@ private: void focusNextListViewItem(); void focusPreviousListViewItem(); void reload(Direction direction); + void resetListView(); QSharedPointer m_engine; // Pointer to engine to get root object QList m_focusChain; // List of current objects to be focused From 21755cbd5404c04df895e9a716379ea205d8c980 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 22 Oct 2024 00:25:05 +0200 Subject: [PATCH 020/100] fix protocol settings focus move --- .../Pages2/PageProtocolAwgClientSettings.qml | 415 ++++++------ .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 629 +++++++++--------- .../qml/Pages2/PageSettingsServerProtocol.qml | 202 +++--- 3 files changed, 644 insertions(+), 602 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index 8685a954..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,228 +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 - - } - - 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 } } } @@ -282,6 +278,11 @@ PageType { text: qsTr("Save") + 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 d03b3580..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 @@ -31,335 +33,354 @@ PageType { } } - 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 - } - - 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 } - parentFlickable: fl - - textField.onEditingFinished: { - if (textFieldText === "") { - textFieldText = "0" - } - - if (textFieldText !== serverJunkPacketCount) { - serverJunkPacketCount = textFieldText - } - } - - checkEmptyText: true - } - - 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 - } - - 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 - } - - 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 - } - - 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 - } - - 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 - } - - 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 - } - - 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 - } - - 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 - } - - 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") - - 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/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index ab0cd291..ade94ebb 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -49,21 +49,36 @@ PageType { height: protocols.contentItem.height clip: true interactive: true - model: ProtocolsModel - property int currentFocusIndex: 0 + property bool isFocusable: true - activeFocusOnTab: true - onActiveFocusChanged: { - if (activeFocus) { - this.currentFocusIndex = 0 - protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus() - } + Keys.onTabPressed: { + 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 @@ -143,107 +158,112 @@ PageType { } } } - } - LabelWithButtonType { - id: clearCacheButton + footer: ColumnLayout { + width: header.width - Layout.fillWidth: true + LabelWithButtonType { + id: clearCacheButton - visible: root.isClearCacheVisible + 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() - - 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() - } } } From 766e1c92fcf610498a7afcb543257509cb961020 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 22 Oct 2024 23:58:53 +0200 Subject: [PATCH 021/100] fix focus on users on page share --- client/ui/qml/Pages2/PageShare.qml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 7bf31067..47ca08f4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -661,6 +661,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 From b6c59b08a1654ff9e8834fc4053dc7aef29edc1b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 22 Oct 2024 23:59:12 +0200 Subject: [PATCH 022/100] clean up page share --- client/ui/qml/Pages2/PageShare.qml | 31 ++---------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 47ca08f4..c238d6c4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -45,7 +45,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - shareConnectionDrawer. + shareConnectionDrawer.openTriggered() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -182,7 +182,6 @@ PageType { shareFullAccessDrawer.openTriggered() } - // KeyNavigation.tab: connectionRadioButton actionButton.onFocusChanged: { console.debug("MOVE THIS LOGIC TO CPP!") if (actionButton.activeFocus) { @@ -229,7 +228,6 @@ PageType { text: qsTr("Share") rightImageSource: "qrc:/images/controls/chevron-right.svg" - // KeyNavigation.tab: focusItem clickedFunction: function() { PageController.goToPage(PageEnum.PageShareFullAccess) @@ -265,12 +263,10 @@ PageType { implicitWidth: (root.width - 32) / 2 text: qsTr("Connection") - // KeyNavigation.tab: usersRadioButton - onClicked: { accessTypeSelector.currentIndex = 0 if (!GC.isMobile()) { - // clientNameTextField.textField.forceActiveFocus() + clientNameTextField.textField.forceActiveFocus() } } } @@ -288,7 +284,6 @@ PageType { ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) - // focusItem.forceActiveFocus() } } } @@ -317,9 +312,6 @@ PageType { textField.maximumLength: 20 checkEmptyText: true - - // KeyNavigation.tab: serverSelector - } DropDownType { @@ -383,8 +375,6 @@ PageType { ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) } } - - // KeyNavigation.tab: protocolSelector } DropDownType { @@ -483,12 +473,6 @@ PageType { } } } - - // KeyNavigation.tab: accessTypeSelector.currentIndex === 0 ? - // exportTypeSelector : searchTextField.textField - // isSearchBarVisible ? - // searchTextField.textField : - // usersHeader.actionButton } DropDownType { @@ -532,9 +516,6 @@ PageType { exportTypeSelector.currentIndex = currentIndex } } - - // KeyNavigation.tab: shareButton - } BasicButtonType { @@ -558,7 +539,6 @@ PageType { ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } } - } Header2Type { @@ -578,7 +558,6 @@ PageType { Keys.onTabPressed: clientsListView.model.count > 0 ? clientsListView.forceActiveFocus() : lastItemTabClicked(focusItem) - } RowLayout { @@ -860,8 +839,6 @@ PageType { text: qsTr("Rename") - // KeyNavigation.tab: revokeButton - clickedFunc: function() { clientNameEditDrawer.openTriggered() } @@ -903,8 +880,6 @@ PageType { textFieldText: clientName textField.maximumLength: 20 checkEmptyText: true - - // KeyNavigation.tab: saveButton } BasicButtonType { @@ -913,7 +888,6 @@ PageType { Layout.fillWidth: true text: qsTr("Save") - // KeyNavigation.tab: focusItem2 clickedFunc: function() { if (clientNameEditor.textFieldText === "") { @@ -948,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) From c77e01fb37547cea98843916ecb7a1e8805afc50 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 24 Oct 2024 07:33:10 +0200 Subject: [PATCH 023/100] fix server rename --- client/ui/models/servers_model.cpp | 18 ++++++++++++++++++ client/ui/models/servers_model.h | 2 ++ .../ui/qml/Pages2/PageSettingsServerInfo.qml | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) 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/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 01d559f8..e17cec8f 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -147,7 +147,8 @@ PageType { } if (serverName.textFieldText !== root.server.name) { - root.server.name = serverName.textFieldText // TODO(CyAn84): set value to the model + ServersModel.setProcessedServerData("name", serverName.textFieldText); + root.server = proxyServersModel.get(0); } serverNameEditDrawer.closeTriggered() } From 7c3d08d80e632e9f58dd406f1e49c77d7447cabc Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 24 Oct 2024 14:36:22 +0200 Subject: [PATCH 024/100] fix page share default server selection --- client/ui/qml/Controls2/ListViewWithRadioButtonType.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 2ced4335..8409e964 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -59,7 +59,7 @@ ListView { function triggerCurrentItem() { var item = root.itemAtIndex(currentIndex) - var radioButton = item.children[0].children[0] + var radioButton = item.children[0] radioButton.clicked() } From 42645a98f80001abaacf80f8297ac44c3dfd6fc0 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 24 Oct 2024 22:09:59 +0200 Subject: [PATCH 025/100] refactor about page for correct focus move --- client/ui/qml/Pages2/PageSettingsAbout.qml | 209 ++++++++++++--------- 1 file changed, 123 insertions(+), 86 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 7de813e3..86034ae9 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -21,20 +21,109 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 + + 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 @@ -81,77 +170,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 - 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" - - 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" - - 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" - - parentFlickable: fl - - clickedFunction: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - - } - - DividerType {} + footer: ColumnLayout { + width: listView.width CaptionTextType { Layout.fillWidth: true @@ -190,33 +231,29 @@ PageType { text: qsTr("Check for updates") - 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 + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 + Layout.topMargin: -15 + implicitHeight: 25 - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.goldenApricot + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.goldenApricot - text: qsTr("Privacy Policy") + text: qsTr("Privacy Policy") - parentFlickable: fl - - clickedFunc: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") - } + clickedFunc: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") + } } } } From 3d7209ee7a43d3bb271f78b26ddeaab78f8efe9b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 25 Oct 2024 03:42:22 +0200 Subject: [PATCH 026/100] fix focus move on list views with header and-or footer --- .../controllers/listViewFocusController.cpp | 57 ++++++++++++++++--- .../ui/controllers/listViewFocusController.h | 3 + 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index f07c069c..70801a65 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -142,7 +142,7 @@ void ListViewFocusController::nextDelegate() { switch(m_currentSection) { case Section::Default: { - if(m_header) { + if(hasHeader()) { m_currentSection = Section::Header; viewToBegin(); break; @@ -160,7 +160,7 @@ void ListViewFocusController::nextDelegate() if (m_delegateIndex < (size() - 1)) { m_delegateIndex++; break; - } else if (m_footer) { + } else if (hasFooter()) { m_currentSection = Section::Footer; viewToEnd(); break; @@ -182,7 +182,7 @@ void ListViewFocusController::previousDelegate() { switch(m_currentSection) { case Section::Default: { - if(m_footer) { + if(hasFooter()) { m_currentSection = Section::Footer; break; } @@ -200,7 +200,7 @@ void ListViewFocusController::previousDelegate() if (m_delegateIndex > 0) { m_delegateIndex--; break; - } else if (m_header) { + } else if (hasHeader()) { m_currentSection = Section::Header; break; } @@ -331,15 +331,54 @@ bool ListViewFocusController::isLastFocusItemInDelegate() bool ListViewFocusController::isFirstFocusItemInListView() { - return (m_delegateIndex == 0) && isFirstFocusItemInDelegate(); + 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::hasHeader() +{ + return m_header && !getItemsChain(m_header).isEmpty(); +} + +bool ListViewFocusController::hasFooter() +{ + return m_footer && !getItemsChain(m_footer).isEmpty(); } bool ListViewFocusController::isLastFocusItemInListView() { - bool isLastSection = (m_footer && m_currentSection == Section::Footer) - || (!m_footer && (m_currentSection == Section::Delegate) && (m_delegateIndex == size() - 1)) - || (m_header && (m_currentSection == Section::Header) && (size() <= 0) && !m_footer); - return isLastSection && isLastFocusItemInDelegate(); + 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() diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index 66c31228..508efa6e 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -59,6 +59,9 @@ private: QQuickItem* currentDelegate(); QQuickItem* focusedItem(); + bool hasHeader(); + bool hasFooter(); + QQuickItem* m_listView; QList m_focusChain; Section m_currentSection; From 163399816f77827ed1baf4f64d31880f15d54644 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 25 Oct 2024 16:55:46 +0200 Subject: [PATCH 027/100] minor fixes --- client/ui/controllers/focusController.cpp | 10 ++++------ client/ui/qml/Pages2/PageSettingsAbout.qml | 3 +++ client/ui/qml/Pages2/PageShareFullAccess.qml | 4 ++-- client/ui/qml/Pages2/PageStart.qml | 18 ++++++++++++++---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 682f4b0e..66738030 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -153,8 +153,7 @@ void FocusController::focusNextListViewItem() { if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { qDebug() << "===>> [Last item in ListView was reached. Going to the NEXT element after ListView]"; - delete m_lvfc; - m_lvfc = nullptr; + resetListView(); nextItem(Direction::Forward); return; } else if (m_lvfc->isLastFocusItemInDelegate()) { @@ -171,8 +170,7 @@ void FocusController::focusPreviousListViewItem() { if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { qDebug() << "===>> [First item in ListView was reached. Going to the PREVIOUS element after ListView]"; - delete m_lvfc; - m_lvfc = nullptr; + resetListView(); nextItem(Direction::Backward); return; } else if (m_lvfc->isFirstFocusItemInDelegate()) { @@ -279,7 +277,7 @@ void FocusController::pushRootObject(QObject* object) void FocusController::dropRootObject(QObject* object) { if (m_rootObjects.empty()) { - qDebug() << "ROOT OBJECT is already NULL"; + qDebug() << "ROOT OBJECT is already DEFAULT"; return; } @@ -289,7 +287,7 @@ void FocusController::dropRootObject(QObject* object) if(m_rootObjects.size()) { qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); } else { - qDebug() << "===>> ROOT OBJECT is changed to NULL"; + qDebug() << "===>> ROOT OBJECT is changed to DEFAULT"; } } else { qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object; diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 86034ae9..8501d2a4 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -83,6 +83,7 @@ PageType { ListView { id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom anchors.right: parent.right @@ -218,6 +219,7 @@ PageType { BasicButtonType { id: checkUpdatesButton + Layout.alignment: Qt.AlignHCenter Layout.topMargin: 8 Layout.bottomMargin: 16 @@ -238,6 +240,7 @@ PageType { BasicButtonType { id: privacyPolicyButton + Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 16 Layout.topMargin: -15 diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 70451b83..8451835c 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -104,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: { @@ -142,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) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 14cc6896..c08acb7b 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -246,12 +246,22 @@ PageType { } Keys.onPressed: function(event) { - if(event.key === Qt.Key_Tab) { + 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 } - - PageController.keyPressEvent(event.key) - event.accepted = true } } From 2e896ed34f67770f8a510636f6347d7833e53a29 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 26 Oct 2024 18:01:14 +0200 Subject: [PATCH 028/100] fix server list back button handler --- client/ui/qml/Components/ServersListView.qml | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index b3663cbd..9270c0e6 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -115,9 +115,6 @@ ListView { z: 1 - Keys.onEnterPressed: serverInfoButton.clicked() - Keys.onReturnPressed: serverInfoButton.clicked() - onClicked: function() { ServersModel.processedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) From e4d21dc4d7fb1ea8194782b5a1d1b88f0d797f50 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 27 Oct 2024 20:16:11 +0100 Subject: [PATCH 029/100] fix spawn signals on switch --- client/ui/qml/Controls2/SwitcherType.qml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index 3569a7d0..77b657e7 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -159,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 } } From 88958c042f715270a3674cedb7be857904737b2a Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 28 Oct 2024 18:41:22 +0100 Subject: [PATCH 030/100] fix share details drawer --- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index dd794a03..eb746d37 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -150,7 +150,7 @@ DrawerType2 { text: qsTr("Show connection settings") clickedFunc: function() { - configContentDrawer.open() + configContentDrawer.openTriggered() } } @@ -196,7 +196,7 @@ DrawerType2 { anchors.right: parent.right anchors.topMargin: 16 - backButtonFunction: function() { configContentDrawer.close() } + backButtonFunction: function() { configContentDrawer.closeTriggered() } } FlickableType { From f020bdb6e8cea8a6fb7c18133e100438c0578770 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 30 Oct 2024 05:58:54 +0100 Subject: [PATCH 031/100] fix drawer open close usage --- client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml | 4 ++-- client/ui/qml/Components/HomeSplitTunnelingDrawer.qml | 2 +- client/ui/qml/Components/InstalledAppsDrawer.qml | 4 ++-- client/ui/qml/Pages2/PageProtocolCloakSettings.qml | 2 +- client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml | 4 ++-- client/ui/qml/Pages2/PageProtocolRaw.qml | 4 ++-- client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml | 2 +- client/ui/qml/Pages2/PageServiceSocksProxySettings.qml | 4 ++-- client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml | 4 ++-- client/ui/qml/main2.qml | 4 ++-- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 1b43a628..c9124d81 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -46,7 +46,7 @@ DrawerType2 { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardCredentials) - root.close() + root.closeTriggered() } } @@ -61,7 +61,7 @@ DrawerType2 { clickedFunction: function() { PageController.goToPage(PageEnum.PageSetupWizardConfigSource) - root.close() + root.closeTriggered() } } diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index 67696c33..097274a4 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -48,7 +48,7 @@ DrawerType2 { clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsSplitTunneling) - root.close() + root.closeTriggered() } } diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index cb8186a9..ca579491 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -43,7 +43,7 @@ DrawerType2 { BackButtonType { backButtonImage: "qrc:/images/controls/arrow-left.svg" backButtonFunction: function() { - root.close() + root.closeTriggered() } } @@ -157,7 +157,7 @@ DrawerType2 { PageController.showBusyIndicator(true) AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo()) PageController.showBusyIndicator(false) - root.close() + root.closeTriggered() } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index c0dbc2ee..fc43bd8e 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -148,7 +148,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 3df23ac0..2b1f3f44 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -199,7 +199,7 @@ PageType { clickedFunction: function() { hashDropDown.text = selectedText hash = hashDropDown.text - hashDropDown.close() + hashDropDown.closeTriggered() } Component.onCompleted: { @@ -248,7 +248,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 14aa5194..1a530780 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -93,7 +93,7 @@ PageType { text: qsTr("Show connection options") clickedFunction: function() { - configContentDrawer.open() + configContentDrawer.openTriggered() } MouseArea { @@ -125,7 +125,7 @@ PageType { anchors.topMargin: 16 backButtonFunction: function() { - configContentDrawer.close() + configContentDrawer.closeTriggered() } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index aa04eb29..44cbd1ce 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -133,7 +133,7 @@ PageType { clickedFunction: function() { cipherDropDown.text = selectedText cipher = cipherDropDown.text - cipherDropDown.close() + cipherDropDown.closeTriggered() } Component.onCompleted: { diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 4c94913e..5eee9a1e 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -322,7 +322,7 @@ PageType { tempPort = portTextField.textFieldText tempUsername = usernameTextField.textFieldText tempPassword = passwordTextField.textFieldText - changeSettingsDrawer.close() + changeSettingsDrawer.closeTriggered() } } } @@ -341,7 +341,7 @@ PageType { clickedFunc: function() { forceActiveFocus() - changeSettingsDrawer.open() + changeSettingsDrawer.openTriggered() } } } diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index 2e86a0dc..91501224 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -127,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 } @@ -266,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/main2.qml b/client/ui/qml/main2.qml index 2ab4db69..8b73e62d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -85,7 +85,7 @@ Window { } function onShowPassphraseRequestDrawer() { - privateKeyPassphraseDrawer.open() + privateKeyPassphraseDrawer.openTriggered() } function onGoToPageSettingsBackup() { @@ -221,7 +221,7 @@ Window { text: qsTr("Save") clickedFunc: function() { - privateKeyPassphraseDrawer.close() + privateKeyPassphraseDrawer.closeTriggered() PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) } } From dc6c1cdf49c51a64da5f44cdfe4db3d78a5c1d1b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 30 Oct 2024 07:46:22 +0100 Subject: [PATCH 032/100] refactor listViewFocusController --- .../controllers/listViewFocusController.cpp | 70 +++++++++---------- .../ui/controllers/listViewFocusController.h | 26 ++++--- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 70801a65..38e7acc0 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -105,22 +105,25 @@ ListViewFocusController::~ListViewFocusController() } -void ListViewFocusController::viewAtCurrentIndex() +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; } @@ -140,11 +143,13 @@ int ListViewFocusController::currentIndex() const 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; - viewToBegin(); + viewAtCurrentIndex(); break; } [[fallthrough]]; @@ -152,6 +157,7 @@ void ListViewFocusController::nextDelegate() case Section::Header: { if (size() > 0) { m_currentSection = Section::Delegate; + viewAtCurrentIndex(); break; } [[fallthrough]]; @@ -159,16 +165,18 @@ void ListViewFocusController::nextDelegate() case Section::Delegate: if (m_delegateIndex < (size() - 1)) { m_delegateIndex++; + viewAtCurrentIndex(); break; } else if (hasFooter()) { m_currentSection = Section::Footer; - viewToEnd(); + viewAtCurrentIndex(); break; } [[fallthrough]]; case Section::Footer: { m_isReturnNeeded = true; m_currentSection = Section::Default; + viewAtCurrentIndex(); break; } default: { @@ -223,7 +231,7 @@ void ListViewFocusController::decrementIndex() m_delegateIndex--; } -QQuickItem* ListViewFocusController::itemAtIndex(const int index) +QQuickItem* ListViewFocusController::itemAtIndex(const int index) const { QQuickItem* item{nullptr}; @@ -234,7 +242,7 @@ QQuickItem* ListViewFocusController::itemAtIndex(const int index) return item; } -QQuickItem* ListViewFocusController::currentDelegate() +QQuickItem* ListViewFocusController::currentDelegate() const { QQuickItem* result{nullptr}; @@ -259,7 +267,7 @@ QQuickItem* ListViewFocusController::currentDelegate() return result; } -QQuickItem* ListViewFocusController::focusedItem() +QQuickItem* ListViewFocusController::focusedItem() const { return m_focusedItem; } @@ -267,7 +275,7 @@ QQuickItem* ListViewFocusController::focusedItem() void ListViewFocusController::focusNextItem() { if (m_isReturnNeeded) { - qDebug() << "===>> RETURN IS NEEDED..."; + qDebug() << "===>> [ RETURN IS NEEDED... ]"; return; } @@ -283,8 +291,8 @@ void ListViewFocusController::focusNextItem() } m_focusedItemIndex++; m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - qDebug() << "==>> Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex; - m_focusedItem->forceActiveFocus(); + qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]"; + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); } void ListViewFocusController::focusPreviousItem() @@ -308,8 +316,8 @@ void ListViewFocusController::focusPreviousItem() } m_focusedItemIndex--; m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - qDebug() << "==>> Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex; - m_focusedItem->forceActiveFocus(); + qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]"; + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); } void ListViewFocusController::resetFocusChain() @@ -319,17 +327,27 @@ void ListViewFocusController::resetFocusChain() m_focusedItemIndex = -1; } -bool ListViewFocusController::isFirstFocusItemInDelegate() +bool ListViewFocusController::isFirstFocusItemInDelegate() const { return m_focusedItem && (m_focusedItem == m_focusChain.first()); } -bool ListViewFocusController::isLastFocusItemInDelegate() +bool ListViewFocusController::isLastFocusItemInDelegate() const { return m_focusedItem && (m_focusedItem == m_focusChain.last()); } -bool ListViewFocusController::isFirstFocusItemInListView() +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: { @@ -350,17 +368,7 @@ bool ListViewFocusController::isFirstFocusItemInListView() } } -bool ListViewFocusController::hasHeader() -{ - return m_header && !getItemsChain(m_header).isEmpty(); -} - -bool ListViewFocusController::hasFooter() -{ - return m_footer && !getItemsChain(m_footer).isEmpty(); -} - -bool ListViewFocusController::isLastFocusItemInListView() +bool ListViewFocusController::isLastFocusItemInListView() const { switch (m_currentSection) { case Section::Default: { @@ -381,17 +389,7 @@ bool ListViewFocusController::isLastFocusItemInListView() } } -bool ListViewFocusController::isReturnNeeded() +bool ListViewFocusController::isReturnNeeded() const { return m_isReturnNeeded; } - -void ListViewFocusController::viewToBegin() -{ - QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning", Qt::AutoConnection); -} - -void ListViewFocusController::viewToEnd() -{ - QMetaObject::invokeMethod(m_listView, "positionViewAtEnd", Qt::AutoConnection); -} diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index 508efa6e..a94b4237 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -25,7 +25,7 @@ void printItems(const QList& items, QObject* current_item); */ class ListViewFocusController : public QObject { - // Q_OBJECT + Q_OBJECT public: explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); ~ListViewFocusController(); @@ -36,14 +36,11 @@ public: void focusNextItem(); void focusPreviousItem(); void resetFocusChain(); - bool isFirstFocusItemInListView(); - bool isFirstFocusItemInDelegate(); - bool isLastFocusItemInListView(); - bool isLastFocusItemInDelegate(); - bool isReturnNeeded(); - void viewToBegin(); - void viewToEnd(); - void viewAtCurrentIndex(); + bool isFirstFocusItemInListView() const; + bool isFirstFocusItemInDelegate() const; + bool isLastFocusItemInListView() const; + bool isLastFocusItemInDelegate() const; + bool isReturnNeeded() const; private: enum class Section { @@ -55,12 +52,13 @@ private: int size() const; int currentIndex() const; - QQuickItem* itemAtIndex(const int index); - QQuickItem* currentDelegate(); - QQuickItem* focusedItem(); + void viewAtCurrentIndex() const; + QQuickItem* itemAtIndex(const int index) const; + QQuickItem* currentDelegate() const; + QQuickItem* focusedItem() const; - bool hasHeader(); - bool hasFooter(); + bool hasHeader() const; + bool hasFooter() const; QQuickItem* m_listView; QList m_focusChain; From 5e9202f6acc2de18ac6c6be3ff802d39e946894c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 30 Oct 2024 17:00:57 +0100 Subject: [PATCH 033/100] refactor focusController to make the logic more straightforward --- client/ui/controllers/focusController.cpp | 319 ++++++++++++---------- client/ui/controllers/focusController.h | 11 +- 2 files changed, 178 insertions(+), 152 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 66738030..b5558f33 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -72,7 +72,6 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) , m_engine{engine} , m_focusChain{} , m_focusedItem{nullptr} - , m_focusedItemIndex{-1} , m_rootObjects{} , m_defaultFocusItem{QSharedPointer()} , m_lvfc{nullptr} @@ -81,106 +80,15 @@ FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) QQuickItem* newDefaultFocusItem = object->findChild("defaultFocusItem"); if(newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { m_defaultFocusItem.reset(newDefaultFocusItem); - qDebug() << "===>> NEW DEFAULT FOCUS ITEM " << m_defaultFocusItem; + qDebug() << "===>> NEW DEFAULT FOCUS ITEM: " << m_defaultFocusItem; } }); + + QObject::connect(this, &FocusController::focusedItemChanged, this, [this]() { + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); + }); } -void FocusController::nextItem(Direction direction) -{ - reload(direction); - - if (m_lvfc) { - direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); - qDebug() << "===>> [handling the ListView]"; - - return; - } - - if(m_focusChain.empty()) { - qWarning() << "There are no items to navigate"; - return; - } - - if (m_focusedItemIndex == (m_focusChain.size() - 1)) { - qDebug() << "Last focus index. Making it zero..."; - m_focusedItemIndex = 0; - } else { - qDebug() << "Incrementing focus index"; - m_focusedItemIndex++; - } - - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - - if(m_focusedItem == nullptr) { - qWarning() << "Failed to get item to focus on. Setting focus on default"; - m_focusedItem = m_defaultFocusItem.get(); - return; - } - - if(isListView(m_focusedItem)) { - qDebug() << "===>> [Found ListView]"; - m_lvfc = new ListViewFocusController(m_focusedItem, this); - if(direction == Direction::Forward) { - m_lvfc->viewToBegin(); - m_lvfc->nextDelegate(); - focusNextListViewItem(); - } else { - m_lvfc->viewToEnd(); - m_lvfc->previousDelegate(); - focusPreviousListViewItem(); - } - return; - } - - qDebug() << "===>> Focused Item: " << m_focusedItem; - m_focusedItem->forceActiveFocus(Qt::TabFocusReason); - - printItems(m_focusChain, m_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() -{ - if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { - qDebug() << "===>> [Last item in ListView was reached. Going to the NEXT element after ListView]"; - resetListView(); - 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->viewAtCurrentIndex(); - } - - m_lvfc->focusNextItem(); -} - -void FocusController::focusPreviousListViewItem() -{ - if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { - qDebug() << "===>> [First item in ListView was reached. Going to the PREVIOUS element after ListView]"; - resetListView(); - nextItem(Direction::Backward); - return; - } else if (m_lvfc->isFirstFocusItemInDelegate()) { - m_lvfc->resetFocusChain(); - m_lvfc->previousDelegate(); - m_lvfc->viewAtCurrentIndex(); - } - - m_lvfc->focusPreviousItem(); -} void FocusController::nextKeyTabItem() { @@ -212,70 +120,36 @@ 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..."; - m_defaultFocusItem->forceActiveFocus(); -} - -void FocusController::reload(Direction direction) -{ - m_focusChain.clear(); - - QObject* rootObject = (m_rootObjects.empty() - ? m_engine->rootObjects().value(0) - : m_rootObjects.top()); - - if(!rootObject) { - qCritical() << "No ROOT OBJECT found!"; - m_focusedItemIndex = -1; - resetRootObject(); - resetListView(); - setFocusOnDefaultItem(); - 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!"; - m_focusedItemIndex = -1; - resetRootObject(); - resetListView(); - setFocusOnDefaultItem(); - return; - } - - m_focusedItemIndex = m_focusChain.indexOf(m_focusedItem); - - if(m_focusedItemIndex == -1) { - qInfo() << "No focus item in chain."; - resetListView(); - setFocusOnDefaultItem(); - return; - } -} - -void FocusController::resetListView() -{ - if(m_lvfc) { - delete m_lvfc; - m_lvfc = nullptr; - } + 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"; @@ -284,6 +158,8 @@ void FocusController::dropRootObject(QObject* object) if (m_rootObjects.top() == object) { m_rootObjects.pop(); + dropListView(); + setFocusOnDefaultItem(); if(m_rootObjects.size()) { qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); } else { @@ -296,6 +172,153 @@ void FocusController::dropRootObject(QObject* 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) { + 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 index 472c328b..54e0a05c 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -29,10 +29,11 @@ public: 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 resetRootObject(); Q_INVOKABLE void pushRootObject(QObject* object); Q_INVOKABLE void dropRootObject(QObject* object); + Q_INVOKABLE void resetRootObject(); private: enum class Direction { @@ -40,20 +41,22 @@ private: Backward, }; + void reload(Direction direction); void nextItem(Direction direction); void focusNextListViewItem(); void focusPreviousListViewItem(); - void reload(Direction direction); - void resetListView(); + 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 - qsizetype m_focusedItemIndex; // Active focus item's index in focus chain QStack m_rootObjects; QSharedPointer m_defaultFocusItem; ListViewFocusController* m_lvfc; // ListView focus manager + +signals: + void focusedItemChanged(); }; #endif // FOCUSCONTROLLER_H From a92f706524b28c6579270eb485ead2762ee87df5 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 1 Nov 2024 08:28:27 +0100 Subject: [PATCH 034/100] fix focus on notification --- client/ui/qml/Controls2/PageType.qml | 2 +- client/ui/qml/Controls2/PopupType.qml | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 724d3b0d..edeb07fe 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -18,7 +18,7 @@ Item { // Set a timer to set focus after a short delay Timer { id: timer - interval: 500 // Milliseconds + interval: 200 // Milliseconds onTriggered: { FocusController.resetRootObject() FocusController.setFocusOnDefaultItem() diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 61221567..5f635593 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,7 +29,7 @@ Popup { } onOpened: { - FocusController.pushRootObject(root) + timer.start() } onClosed: { @@ -42,6 +43,17 @@ Popup { radius: 4 } + Timer { + id: timer + interval: 400 // Milliseconds + onTriggered: { + FocusController.pushRootObject(root) + FocusController.setFocusItem(closeButton) + } + repeat: false // Stop the timer after one trigger + running: !GC.isMobile() // Start the timer + } + contentItem: Item { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight From 45b8235a819a84a06cee6440217c6e6bfa88944f Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 1 Nov 2024 22:32:57 +0100 Subject: [PATCH 035/100] update config page for scrolling with tab --- .../Pages2/PageSetupWizardConfigSource.qml | 312 +++++++++--------- 1 file changed, 151 insertions(+), 161 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index e98b8055..ca0556a1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,20 +25,149 @@ PageType { } } - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height + QtObject { + id: amneziaVpn - ColumnLayout { - id: content + 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) + } + } + } - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + QtObject { + id: selfHostVpn - spacing: 0 + 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 { id: moreButton @@ -57,15 +186,6 @@ PageType { moreActionsDrawer.openTriggered() } - actionButton.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (actionButton.activeFocus) { - if (fl) { - fl.ensureVisible(moreButton) - } - } - } - DrawerType2 { id: moreActionsDrawer @@ -155,8 +275,6 @@ PageType { headerText: qsTr("Insert key") buttonText: qsTr("Insert") - parentFlickable: fl - clickedFunc: function() { textField.text = "" textField.paste() @@ -171,8 +289,6 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - parentFlickable: fl - visible: textKey.textFieldText !== "" text: qsTr("Continue") @@ -194,154 +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 - parentFlickable: fl - - 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" - - parentFlickable: fl - - 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" - - parentFlickable: fl - - 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" - - parentFlickable: fl - - 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" - - parentFlickable: fl - - 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" - - parentFlickable: fl - - onClicked: { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + onClicked: { handler() } } } } From 416421cba7c1123ea4a7ebf1c00968d0a131bf99 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 1 Nov 2024 22:33:28 +0100 Subject: [PATCH 036/100] fix crash on return with esc key --- client/ui/controllers/focusController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index b5558f33..f206620d 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -213,7 +213,7 @@ void FocusController::nextItem(Direction direction) reload(direction); - if (m_lvfc) { + if (m_lvfc && isListView(m_focusedItem)) { direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); qDebug() << "===>> Handling the [ ListView ]..."; From ed6fc27e522fbdacb7cbf6264a80f6128a1963a8 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 1 Nov 2024 23:10:01 +0100 Subject: [PATCH 037/100] fix focus navigation in dynamic delegate of list view --- client/ui/controllers/listViewFocusController.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 38e7acc0..21326597 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -279,10 +279,8 @@ void ListViewFocusController::focusNextItem() return; } - if (m_focusChain.empty()) { - qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = getItemsChain(currentDelegate()); - } + m_focusChain = getItemsChain(currentDelegate()); + if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; nextDelegate(); From 942805cca20b49657edab05db676a3f19701e6eb Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 2 Nov 2024 01:44:49 +0100 Subject: [PATCH 038/100] fix focus move on qr code on share page --- .../qml/Components/ShareConnectionDrawer.qml | 90 +++++++++++++++++-- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index eb746d37..7e8db993 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -51,20 +51,50 @@ DrawerType2 { headerText: root.headerText } - 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 @@ -72,6 +102,8 @@ 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" @@ -99,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 @@ -118,6 +152,8 @@ DrawerType2 { id: copyNativeConfigStringButton Layout.fillWidth: true Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: false @@ -139,6 +175,8 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -265,6 +303,10 @@ DrawerType2 { } } } + } + + delegate: ColumnLayout { + width: listView.width Rectangle { id: qrCodeContainer @@ -272,6 +314,8 @@ DrawerType2 { Layout.fillWidth: true Layout.preferredHeight: width Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: ExportController.qrCodesCount > 0 @@ -283,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 @@ -309,6 +379,8 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 visible: ExportController.qrCodesCount > 0 From d956be901dff2eab21decb1be368646bd4ebd092 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 3 Nov 2024 17:48:51 +0100 Subject: [PATCH 039/100] refactor page logging settings for focus navigation --- client/ui/qml/Pages2/PageSettingsLogging.qml | 280 ++++++++----------- 1 file changed, 123 insertions(+), 157 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 33074dfa..4b2d7d42 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -23,30 +23,122 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 + } - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } + 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")) } } } - FlickableType { - id: fl + 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 { @@ -76,14 +168,11 @@ PageType { SettingsController.isLoggingEnabled = checked } } - - parentFlickable: fl } DividerType {} LabelWithButtonType { - // id: labelWithButton2 Layout.fillWidth: true Layout.topMargin: -8 @@ -101,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 @@ -121,7 +216,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Client logs") + text: title } ParagraphTextType { @@ -131,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 @@ -144,15 +239,12 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" isSmallLeftImage: true - clickedFunction: function() { - SettingsController.openLogsFolder() - } + clickedFunction: openLogsHandler } DividerType {} LabelWithButtonType { - // id: labelWithButton2 Layout.fillWidth: true Layout.topMargin: -8 Layout.bottomMargin: -8 @@ -161,136 +253,10 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" isSmallLeftImage: true - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } - } - } - - 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 - - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } - } - } - - 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 - - onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (activeFocus) { - if (fl) { - fl.ensureVisible(this) - } - } - } - - 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() - } } } } From 0620b4536a88d3b59b270dea445cecef9e12d59e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 4 Nov 2024 00:11:01 +0100 Subject: [PATCH 040/100] update popup --- client/ui/qml/Controls2/PopupType.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 5f635593..d067fc12 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -47,8 +47,10 @@ Popup { id: timer interval: 400 // Milliseconds onTriggered: { - FocusController.pushRootObject(root) - FocusController.setFocusItem(closeButton) + if (!GC.isMobile()) { + FocusController.setFocusItem(closeButton) + FocusController.pushRootObject(root) + } } repeat: false // Stop the timer after one trigger running: !GC.isMobile() // Start the timer From 940806a6faaa940af415480d1ff50e81ced7f168 Mon Sep 17 00:00:00 2001 From: albexk Date: Tue, 29 Oct 2024 16:19:13 +0300 Subject: [PATCH 041/100] Bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5e64e32..62ab3477 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.2.3 +project(${PROJECT} VERSION 4.8.3.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2069) +set(APP_ANDROID_VERSION_CODE 2070) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 65870ad5c938c84ae9f4a1169689432f05e48e39 Mon Sep 17 00:00:00 2001 From: albexk Date: Tue, 29 Oct 2024 17:31:36 +0300 Subject: [PATCH 042/100] Add mandatory requirement for android.software.leanback. --- client/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 179def86..019e999f 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -11,7 +11,7 @@ - + - + From 53e766f6d36aa7cfc80164e4620302243d43bbde Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 2 Nov 2024 00:54:24 +0300 Subject: [PATCH 046/100] Fix connection check for AWG/WG --- .../vpn/protocol/wireguard/Wireguard.kt | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index e93834f4..80cab96d 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -1,11 +1,12 @@ package org.amnezia.vpn.protocol.wireguard import android.net.VpnService.Builder -import java.io.IOException -import java.util.Locale +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay -import kotlinx.coroutines.withContext +import kotlinx.coroutines.launch import org.amnezia.awg.GoBackend import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.ProtocolState.CONNECTED @@ -27,6 +28,8 @@ open class Wireguard : Protocol() { private var tunnelHandle: Int = -1 protected open val ifName: String = "amn0" + private lateinit var scope: CoroutineScope + private var statusJob: Job? = null override val statistics: Statistics get() { @@ -49,46 +52,17 @@ open class Wireguard : Protocol() { override fun internalInit() { if (!isInitialized) loadSharedLibrary(context, "wg-go") + if (this::scope.isInitialized) { + scope.cancel() + } + scope = CoroutineScope(Dispatchers.IO) } override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { val wireguardConfig = parseConfig(config) - val startTime = System.currentTimeMillis() start(wireguardConfig, vpnBuilder, protect) - waitForConnection(startTime) - state.value = CONNECTED } - private suspend fun waitForConnection(startTime: Long) { - Log.d(TAG, "Waiting for connection") - withContext(Dispatchers.IO) { - val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0) - try { - delay(1000) - var log = getLogcat(time) - Log.v(TAG, "First waiting log: $log") - // check that there is a connection log, - // to avoid infinite connection - if (!log.contains("Attaching to interface")) { - Log.w(TAG, "Logs do not contain a connection log") - return@withContext - } - while (!log.contains("Received handshake response")) { - delay(1000) - log = getLogcat(time) - } - } catch (e: IOException) { - Log.e(TAG, "Failed to get logcat: $e") - } - } - } - - private fun getLogcat(time: String): String = - ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time) - .redirectErrorStream(true) - .start() - .inputStream.reader().readText() - protected open fun parseConfig(config: JSONObject): WireguardConfig { val configData = config.getJSONObject("wireguard_config_data") return WireguardConfig.build { @@ -178,6 +152,43 @@ open class Wireguard : Protocol() { tunnelHandle = -1 throw VpnStartException("Protect VPN interface: permission not granted or revoked") } + launchStatusJob() + } + + private fun launchStatusJob() { + Log.d(TAG, "Launch status job") + statusJob = scope.launch { + while (true) { + val lastHandshake = getLastHandshake() + Log.v(TAG, "lastHandshake=$lastHandshake") + if (lastHandshake == 0L) { + delay(1000) + continue + } + if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED + else if (lastHandshake == -1L) state.value = DISCONNECTED + statusJob = null + break + } + } + } + + private fun getLastHandshake(): Long { + if (tunnelHandle == -1) { + Log.e(TAG, "Trying to get config of a non-existent tunnel") + return -1 + } + val config = GoBackend.awgGetConfig(tunnelHandle) + if (config == null) { + Log.e(TAG, "Failed to get tunnel config") + return -2 + } + val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong() + if (lastHandshake == null) { + Log.e(TAG, "Failed to get last_handshake_time_sec") + return -2 + } + return lastHandshake } override fun stopVpn() { @@ -185,6 +196,8 @@ open class Wireguard : Protocol() { Log.w(TAG, "Tunnel already down") return } + statusJob?.cancel() + statusJob = null val handleToClose = tunnelHandle tunnelHandle = -1 GoBackend.awgTurnOff(handleToClose) From 4e1862a8029b3710ace1b73a52163532aecbadf8 Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 6 Nov 2024 09:57:39 +0400 Subject: [PATCH 047/100] chore: minor fixes (#1235) --- client/ui/controllers/installController.cpp | 1 - client/ui/qml/Controls2/BusyIndicatorType.qml | 2 +- client/ui/qml/Controls2/CardType.qml | 2 +- client/ui/qml/Controls2/DrawerType2.qml | 2 +- client/ui/qml/Controls2/PopupType.qml | 2 +- client/ui/qml/Controls2/TopCloseButtonType.qml | 2 +- client/ui/qml/Modules/Style/AmneziaStyle.qml | 4 ++++ client/ui/qml/Pages2/PageHome.qml | 4 ++-- client/ui/qml/Pages2/PageSettingsApiServerInfo.qml | 8 ++++---- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 306e7f38..ae0804cb 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -848,7 +848,6 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::authData, authData); - newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc)); m_serversModel->editServer(newServerConfig, serverIndex); if (reloadServiceConfig) { diff --git a/client/ui/qml/Controls2/BusyIndicatorType.qml b/client/ui/qml/Controls2/BusyIndicatorType.qml index 55af280f..480f25c1 100644 --- a/client/ui/qml/Controls2/BusyIndicatorType.qml +++ b/client/ui/qml/Controls2/BusyIndicatorType.qml @@ -14,7 +14,7 @@ Popup { visible: false Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + color: AmneziaStyle.color.translucentMidnightBlack } background: Rectangle { diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 50f84dbf..f584a8fc 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -19,7 +19,7 @@ RadioButton { property string textColor: AmneziaStyle.color.midnightBlack - property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot property string selectedBorderColor: AmneziaStyle.color.goldenApricot property string defaultBodredColor: AmneziaStyle.color.transparent property int borderWidth: 0 diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml index 6647bc88..c4b584c1 100644 --- a/client/ui/qml/Controls2/DrawerType2.qml +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -92,7 +92,7 @@ Item { id: background anchors.fill: parent - color: root.isCollapsed ? AmneziaStyle.color.transparent : Qt.rgba(14/255, 14/255, 17/255, 0.8) + color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack Behavior on color { PropertyAnimation { duration: 200 } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index bd4aa4fb..7a6a770e 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -24,7 +24,7 @@ Popup { Overlay.modal: Rectangle { visible: root.closeButtonVisible - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + color: AmneziaStyle.color.translucentMidnightBlack } onOpened: { diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index 1bd7fef6..3a652da6 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -14,7 +14,7 @@ Popup { visible: false Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + color: AmneziaStyle.color.translucentMidnightBlack } background: Rectangle { diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml index c0038246..1abfbe3a 100644 --- a/client/ui/qml/Modules/Style/AmneziaStyle.qml +++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml @@ -22,5 +22,9 @@ QtObject { readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12) readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08) readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05) + readonly property color translucentMidnightBlack: Qt.rgba(14/255, 14/255, 17/255, 0.8) + readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3) + readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) + readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8074337a..5689e4d4 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -316,8 +316,8 @@ PageType { rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonBackgroundColor: AmneziaStyle.color.paleGray - rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) - rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) + rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray + rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray rootButtonHoveredBorderColor: AmneziaStyle.color.transparent rootButtonDefaultBorderColor: AmneziaStyle.color.transparent rootButtonTextTopMargin: 8 diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index f23e36d9..2d6c1d9b 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -132,8 +132,8 @@ PageType { implicitHeight: 32 defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite textColor: AmneziaStyle.color.vibrantRed text: qsTr("Reload API config") @@ -172,8 +172,8 @@ PageType { implicitHeight: 32 defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite textColor: AmneziaStyle.color.vibrantRed text: qsTr("Remove from application") From d02a6dfb2214cc7ce8511ab570d6701fb970dd32 Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 13 Nov 2024 17:48:49 +0300 Subject: [PATCH 048/100] fix: add a workaround to open files on Android TV due to lack of SAF --- client/android/AndroidManifest.xml | 7 + .../src/org/amnezia/vpn/AmneziaActivity.kt | 120 +++++++++++------- .../src/org/amnezia/vpn/TvFilePicker.kt | 41 ++++++ 3 files changed, 119 insertions(+), 49 deletions(-) create mode 100644 client/android/src/org/amnezia/vpn/TvFilePicker.kt diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 6dfd0983..96f60f53 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -91,6 +91,13 @@ android:exported="false" android:theme="@style/Translucent" /> + + - Log.v(TAG, "Save file to $uri") - try { - contentResolver.openOutputStream(uri)?.use { os -> - os.bufferedWriter().use { it.write(data) } + try { + startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( + onSuccess = { + it?.data?.let { uri -> + Log.v(TAG, "Save file to $uri") + try { + contentResolver.openOutputStream(uri)?.use { os -> + os.bufferedWriter().use { it.write(data) } + } + } catch (e: IOException) { + Log.e(TAG, "Failed to save file $uri: $e") + // todo: send error to Qt } - } catch (e: IOException) { - Log.e(TAG, "Failed to save file $uri: $e") - // todo: send error to Qt } } - } - )) + )) + } catch (_: ActivityNotFoundException) { + Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show() + } } } } @@ -543,55 +547,73 @@ class AmneziaActivity : QtActivity() { fun openFile(filter: String?) { Log.v(TAG, "Open file with filter: $filter") mainScope.launch { - val mimeTypes = if (!filter.isNullOrEmpty()) { - val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE) - val mime = MimeTypeMap.getSingleton() - extensionRegex.findAll(filter).map { - it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*" - }.toSet() - } else emptySet() + val intent = if (!isOnTv()) { + val mimeTypes = if (!filter.isNullOrEmpty()) { + val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE) + val mime = MimeTypeMap.getSingleton() + extensionRegex.findAll(filter).map { + it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*" + }.toSet() + } else emptySet() - Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - Log.v(TAG, "File mimyType filter: $mimeTypes") - if ("*/*" in mimeTypes) { - type = "*/*" - } else { - when (mimeTypes.size) { - 1 -> type = mimeTypes.first() + Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + Log.v(TAG, "File mimyType filter: $mimeTypes") + if ("*/*" in mimeTypes) { + type = "*/*" + } else { + when (mimeTypes.size) { + 1 -> type = mimeTypes.first() - in 2..Int.MAX_VALUE -> { - type = "*/*" - putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) + in 2..Int.MAX_VALUE -> { + type = "*/*" + putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) + } + + else -> type = "*/*" } - - else -> type = "*/*" } } - }.also { - if (packageManager.resolveActivity(it, PackageManager.MATCH_DEFAULT_ONLY) == null) { - Log.w(TAG, "Not found activity for ACTION_OPEN_DOCUMENT intent") - it.action = Intent.ACTION_GET_CONTENT - } + } else { + Intent(this@AmneziaActivity, TvFilePicker::class.java) + } - try { - startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( - onAny = { - val uri = it?.data?.toString() ?: "" - Log.v(TAG, "Open file: $uri") - mainScope.launch { - qtInitialized.await() - QtAndroidController.onFileOpened(uri) - } + try { + startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler( + onAny = { + if (isOnTv() && it?.hasExtra("activityNotFound") == true) { + showNoFileBrowserAlertDialog() } - )) - } catch (_: ActivityNotFoundException) { - Toast.makeText(this@AmneziaActivity, resources.getText(R.string.tvNoFileBrowser), Toast.LENGTH_LONG).show() + val uri = it?.data?.toString() ?: "" + Log.v(TAG, "Open file: $uri") + mainScope.launch { + qtInitialized.await() + QtAndroidController.onFileOpened(uri) + } + } + )) + } catch (_: ActivityNotFoundException) { + showNoFileBrowserAlertDialog() + mainScope.launch { + qtInitialized.await() + QtAndroidController.onFileOpened("") } } } } + private fun showNoFileBrowserAlertDialog() { + AlertDialog.Builder(this) + .setMessage(R.string.tvNoFileBrowser) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> + try { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect"))) + } catch (_: Throwable) {} + } + .show() + } + @Suppress("unused") fun getFd(fileName: String): Int = try { Log.v(TAG, "Get fd for $fileName") diff --git a/client/android/src/org/amnezia/vpn/TvFilePicker.kt b/client/android/src/org/amnezia/vpn/TvFilePicker.kt new file mode 100644 index 00000000..f3048509 --- /dev/null +++ b/client/android/src/org/amnezia/vpn/TvFilePicker.kt @@ -0,0 +1,41 @@ +package org.amnezia.vpn + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.result.contract.ActivityResultContracts +import org.amnezia.vpn.util.Log + +private const val TAG = "TvFilePicker" + +class TvFilePicker : ComponentActivity() { + + private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { + setResult(RESULT_OK, Intent().apply { data = it }) + finish() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Log.v(TAG, "onCreate") + getFile() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + Log.v(TAG, "onNewIntent") + getFile() + } + + private fun getFile() { + try { + Log.v(TAG, "getFile") + fileChooseResultLauncher.launch("*/*") + } catch (_: ActivityNotFoundException) { + Log.w(TAG, "Activity not found") + setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) }) + finish() + } + } +} From 51092e91cffd48f9f596abf74daebfe95f122ca4 Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 18 Nov 2024 20:59:22 +0300 Subject: [PATCH 049/100] fix: change the banner format for TV --- .../android/res/mipmap-anydpi-v26/ic_banner.xml | 5 ----- client/android/res/mipmap-hdpi/ic_banner.png | Bin 0 -> 15410 bytes client/android/res/mipmap-mdpi/ic_banner.png | Bin 0 -> 10138 bytes .../res/mipmap-xhdpi/ic_banner_foreground.png | Bin 12414 -> 0 bytes .../android/res/values/ic_banner_background.xml | 4 ---- 5 files changed, 9 deletions(-) delete mode 100644 client/android/res/mipmap-anydpi-v26/ic_banner.xml create mode 100644 client/android/res/mipmap-hdpi/ic_banner.png create mode 100644 client/android/res/mipmap-mdpi/ic_banner.png delete mode 100644 client/android/res/mipmap-xhdpi/ic_banner_foreground.png delete mode 100644 client/android/res/values/ic_banner_background.xml diff --git a/client/android/res/mipmap-anydpi-v26/ic_banner.xml b/client/android/res/mipmap-anydpi-v26/ic_banner.xml deleted file mode 100644 index cf3108b3..00000000 --- a/client/android/res/mipmap-anydpi-v26/ic_banner.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/android/res/mipmap-hdpi/ic_banner.png b/client/android/res/mipmap-hdpi/ic_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..a444777fe7aa7779fe972b82961c45237222d803 GIT binary patch literal 15410 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 AHvj+t literal 0 HcmV?d00001 diff --git a/client/android/res/mipmap-mdpi/ic_banner.png b/client/android/res/mipmap-mdpi/ic_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..b9ad1db7c74a60f1f128609882ce86158ed93424 GIT binary patch literal 10138 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 literal 0 HcmV?d00001 diff --git a/client/android/res/mipmap-xhdpi/ic_banner_foreground.png b/client/android/res/mipmap-xhdpi/ic_banner_foreground.png deleted file mode 100644 index 1c21902ec06a1849c6bf83b934cc6dca4fbe444e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12414 zcmcJ01yft!*EN(PgurYRcRV#qOE4en zS8_?bSIBfgg84r|j+`0L?{d+7z%OF2Lpk=B-F(VqgtUZ;6EjHV=I{L1UdZ;U4)*sC z?yIYqyUx&HV8H+Pi_mM;?`FmGr9(FHeE?eMJIp!23q?f!Ojl>;ysxkC$xvYaz{FAo zIs=~Mu#-g&*KHfR$P7d6Hu>0I&aLxHe;C$_-lKWA8Bpay8w%Z{!X#q4RDskGLqBPp zx(Nj2^-hpgn0P3%l^40KGyoa=hY2-lG|k%4+KJCLf#GI`V+O*)!e}*-+)^~4SpIOI zG7H_jX^mOe+1HvVH55J@+|SCL&~USiobzjelgWjS_8oDyMxrYciUwZZ~O_H z3AOqxIiFJNj2<}<1sgvVfON{%9MS5=Y=_0|Z)r_6=k*^_h3DgM;#vU1r{GGEP7yPW z?;k+JuV#sy7rI9-nto!i7Zb0aR5|R6dTTsR0bqnKkB^VfSg}RI^I6)PyEGJBzfOF@ zdmLE<=F8WZo&&3>^EHhpKV*g6e z$ckI-0mBRk@yIc+%;5!}y0q6V&Qtq^Dhwdwctk9#B;jTOFw>0}viT7Hja^%3Tu-Nz zT6Nr^8va+2j1PtKp+D4HBcytsAgj@Fw|5`UggF2gOPj1Ld-xR|6}$Z5>z}0-oKXno z^R;IKCLxT1>6chcsId(UD>P?i9DwAByJ3;%GZEdN7T*#~U=!*5bnEzY(MOKly#qw( zy?Yi(n_Ef7rf#>dst8t?&z=$gQ1L$Lx_NdY9Z*Ec7z{a4a%s^a%I#W7-vsGdeCn@^C?=gDw=Vrs9t2R%gE#!tMZYx!<)Z zG%L?j?-VSfhlU94tw!Bsn`H>TBA)Fjq|*qVV;pE>3!TECDx+-5S}ReqF}?0Sp*EWW zF*;Gs5`^}F$Fhg9I2BhzG3o5WJt>Vlslg?{7-Mf;j-E)eifd1t9r9KTu5NzRcV-*v zkAe&bvPHh5bU(q~sCHR;cJ$sw*>Mun$5>$B7pV*hag>~{lUI=aMRb67 z{YPDuOLS|_j}fGF&Y~2QLY)`TMxE*^C4Jav{RBf9JyXDr`Y9G5X?ja-sw=MCm)#lx z0eNso1!w2Hk0L%V3juq&HFy5>nN+s-<8akdH7rIWN@9-~)e|r-Vn7Uoh}lbB1H7Fo zqVwJ}X@-9z>q&}Hss0jXpE74Y(B!S_$qIk%-2j1kz0E3J&mBhNi!R!(i;_wUw;NmR z&F~PQg%L_Gi#b=8}4t^Y;baRa3iO*?=8~ zU4Q)}w>`IsF#DNwHEFXbp27E#RGZE4H5I!N4&%iR>}zmnfq2`eb&JtCDJ@4y?>@H( z(jk;dd0L3xngH(2ZQ9o=41yNQgcw%(Melo%;L{mSXX{n6)b;BG^zB&4QBg_(k6GM{rG6VAopwZQ_(C<`a8IEdn|I&u}L=? zGYLz`xh~l#4a|l?#hfvIK+ys?glfuK%uuUWu5tTGNw~WkPrI9ZcJETyJ)4t^&Qah) z=AT38YG1U=#Ukr3B(2<~Kp=mMJ#Kf|vWd>*Nay@}XV@=r-O!TYj73;WM0x{>m1OZ5 zE_QIkC#%AZAg^1~DRSWH{%}I8C&{Rwi#8`%XK%9YFzL+p3Z8iZ_W$RFf&J@>j-xBB zVD5)^83wHNE&Bx}Ws`e{?w(*7rV^RNMWRH3&cR#Yw_U8;)5!(xSiU9_Yl>Ft%yj0a zSJ{0x9$9@wJf&q~V^wn{ht~~x8pe@#!(c_6!rYP0F*0Ce*cc7x{jKO#+>zOzI>*zE zzIulkmAOBmb^Di-Gky+e`M1aDUUe2e`a%yJ6ywJtuWPfp(M{G2S6q)FN3HZbG%)br zFV}UYM`T@Y-}VivtUZ7Y-no`O`bzv>lALThb|b%Ddg0kWy85o&AC^56Cd=zD1q2y> zSASS^(;HO@H~WrET!L9)z#leB!-;w;k$xwa04?7sx4RG4NV08HXO1@vMpuS}YOQp5 z5lQ372dx(vlBp{BvbsfJz_tjhp1i%i9VZzLDe<}e?L7|xk?7qi0F~-^oTJbZl%3Mr z<4{AJZHJ4it*$MoC?=ZS4?)jN?0j>V6vJlIKSF=^q*HVQ z_Se?ZMp*^gKW)8sP?QT}J6cT-s1^wGZcSRivCMD?2`l3yF0e5V{!|W)F;P7OpJHKt z84A7ctMC_~ZA@+^DDZ*g%Bt1Q zT5h&E;z5^MAH^(Yqw=cL25<(LQ!NvXJ}ZYt=;aJ2=h=?k-O<9n&WZSlCP~zr*_sCi z1}W0bn0#xHG7aG&o$J5cNzv)n6oG;^L z79%z{<0b=-)<9{L`DZ2{--c_Mt}e$Ry8eo%jt|8%1hH>QaFT+G?iChV_C>@wIqhR{ zvU?U6-&ZV(m)NN54EtHm;2)m=Bj}-n-D5ihTRX%XhOx*`9?$m47ZbrzSEl~Af;yNvd zTeXY?*GloLrsmv#3YqH@LvhhdRi_lJDa zWZ^wl_Zq|myBwnZ+Pj3f4{I|R)W=Qhz0Z@;y3@Z(-To)VMH8#nf)bS;nKYdM#n(EZ z(1uTgg4S|+3Rwa~qsda3jilv^>8Xn2zk^0A5zttB)VZ-MvK(%rlGDf`GJT=3)F7Ah zszWC}{IgQEglQ1_m)G~=+4_m#Miq)b9A!K?z_%DJ!>Ej9GVKEIFg_RIf3Ds^q5Vb& zoopi(;CGT7@UsE#V3*#sARh!P_qj%A#RK1y=H}fy%7$`!*eUEc7df&U4C03AJ&Y^t z$Q13=XAEV{n+BDwJm{@aunD`rIX3=?>*4JKCw7P0_smz8+f8Y$FV){zflTj}9PW$m zD1LDF*bHCyR%2Cf%y}}``uMq13a;|tg1NSrspQ(abF7X2G zH8|Et!~4qci4)%NhYhhZ>9-SJF16}&3aEvOti|l;C~)wek?Y@~hcO7W>nS;l@c8!d z;&SysR1&M_7S8}NN!!eQGKZdwGpjuLkMU#EI=0bCrMGdC;fstsXnEx(!8$i={nZcU z>vJpc)6EC?>ESPeDEw!iE$U9emerw0*$G?~8Tg?MPcQ4)>8T0rx^UK7sai0&{abz8 zkWE6*j9iy^Y*{BUSH2l0q_D6s@bvtgzfc2e$YwXhBe$2o-O(fVN07~+r!K`qI_14;Bs)+Q3`2LX-9@(9n^U=4r9uKDWM<;Un1~pfxu=%`aRqo|8svw6w z;C6Gy3qYel?1DKa3|^@m?Aj(SpB5CXS99|5(+N(+N^LaEGeDnv?~f5j&))*N{FZD%RJ@1QeuN#l;)J$|}d9%xe<)PAlKJz5XsNyv|-2Pr^=cw_)#W zw_LjP_Yg+SJIPJZdsrez_uSPEC9Qp;=KOPa;vqiq-#Lrpg|tjG6zz{}c|)sIEZTrV zcsUVvq%DtNpY26)A?wp_O$ccRTW<=h&Dc=69ZU|oTpF*jvwd=c+rbmSM7kcmwBO)s z0Qy2SL*+xmMBPiZ>H8yMxCQ6u&doZov;8bn^T9X3g-LK_eog;D!DQ}@bPEgSP|inB zipsG6D*{SRk%p>a-TK=NycT{bPNCZNQH79#(@k}KUnb)!L4ZN0gzfoa29@2wv=N@( zw2)|e&Tl)3L3_q&g;Ph3G*mI4Y zh%ZT@KreUNvR zps9T;y!4v3T3W&OC~{n{TnQK0vD0c9P-0A=@hyDs@tJ%K#1q2xfGc+InZ9v$MOBL? zyW?@49gC->9YCThSi|5R~&ePv81Y(@%n z7Jf|DDbWD=S_Nth1@YO)mtNltg4Ujjptwu{Rtu0e~_69Wwm+#hM3_QE6? z9hWkt-}i3p>ET8o+Kdl%VlL)9!u@Phs{8^2{$j#SVXWgg<{Ii_r~+&mTj}t$e}24} zII){3kI33t?ec9}^h#Ujx-O$r65oTqbX1fB$CtqvsH_3Zhdhs@nGEGz^bW!Skz7k)}0LAX||qQy>-xo zyuJ^dd|fmmBdR+^Z53BP1r6GK7vN%PjQ$HLza}yaT?1w+laG|~caek+auMc}IP`Dy z!b3ilNh^ucU9{l{InBKNSZeU9k+)yp?x)zYBtlKo|$># zgwRE~`bN7B?4rMX4WJ&HN}>XQl~@?+2w86+6)&Q{WHra~g>b91YUxTxzJ91o~%PlEntRA0_VS8XCzE2a|m$? zoIS|4qqXwkP>!)V=U!AFTsDX@?9>P5<=9>9j4ejo7>r2Vg(AXki{BP1$kOp2bit2l zG$vT9z;>HQdgH@h!#6>|VMt(2ly^~YE|`iK6kYIj6zX7V(Gm6Thp{w(`D5 z#&}}PpC-rEA_OQ@RoIdPbqPa4qa|cz-!N#`5T-DwQT|S;#NoCZ5_R||ySEUC%%YdU zVie!l9^ewD4j1V5ehb)Jd>s}3`VY6TRuZz)K1#d}1W|8sT9gtk6Q*!JVUy2cxCQ6b zIFy37f&rgZsT8+vHr*15QqZupSruP|d8(@;H<*CNnVajL!-C%DTZk#o40e2@DWFId zy|zmmF7Qg}L;v}T$H{Wrg@}1;x%cV1m~A`d+vl|J=B>nqq>s5kwxXfIwR_zf4(nm0 zH!(MnAd{LEyYY+WoweO2*Cr@7UOmkTX+e7W0x1F%)l-?YlSU<&tAZnKzbF0L!gY1M zK!7#Tn&&(BW6n42pR>zm(R_!;!jMtY{|=Dy_|>|z{+(KZSGqu9ycO!JcX$}QhFCM;q7ZTW~KXi2sE3dzuz^0`b+x0{2W(&Th&@rd78W1Qs#9I1Egx7@i zp)B+UQC1f^??Uz#Sh3M?li-y__IR;lq>hHo0xY#^-(c^~bhHBQ{Q<0YqLI znrM`YIEgXw?w_DYv+Uc8eUo;(!)Nf}{g)QTle3pKUCPmiO}9c?35y^?ou?KGB^WQd zdz}3h7bta1*+U;?pdX@2AFJKcWUIB81NEDv89=RVHwqx~eMB z9Y$9H6|AbL1d;!H>@-pLn!=oC7|g44q2YesVeE3yE*IPB}}2J-T{oTs*rDT}V&_rNoGd1vS zzmgDe+RmdGhFJ_k?{O~WVG$C_T_vj8Rh|y7gx0G88qtT0O4{m#&0A?uN*+w#bTA<9 zk3X?<#QdDh*b~YfM?0e8xc4{bO{73+x_n)|;4TAL@JlxjO0$TVgWnpBHJxEKA*aPu zRAl5JiXgnlkNWCW4pog|kuI%Q&%WkY`*bFR%oKBJ2NiGa?^1* zdgCpQ$f!O10QROK3%Zw@I=8A!xa(=G1~tR8kHZn4vbT}_#hNLO8Vi#OIYc=Wx37S2gaO$?7N*oM{M&neB_i{O z?tG-egJD2gd+av>L@+K$vvC(4!$u6Ud5)@J&-}mrwo1mK8N=u~qcnn9&Yz+Z?ARpPtp0eOFz=L9aUKkjDI*wz2L)Jo+H;nG^nS;`|y=1g?dWZ znu~<5p=9!NA+QV;>vj#YV4+dbyfuLIYgV(;_K1`^xM$x+L6B?)-7O-Xzi~H@M=^RH zDR<6a0R8~k&_-z@cdiKExC*GIWS>OSEp)el6r^TaODbt;`bB6VvZV7s3yzP^PeJe@ zx1uhlypW1jbpDnc*32xfDA>t!!M0(AMl-Xdq~@TDtsD-~dsxT@@JiUfhg+oSlVuQT);ono_@%JAP@MHWu46`; z1IRS$c2y-BqyiD$G`6n4V*ahdpGF0kRLZ4Bt5EwEYALVJT{VU47n3=nrB*ICmB6Xm ztw{r2a&h>P-JU59x97&Slkp5gdMV;xNb~owT?56P=pu{cbN0N@bO|;Rj{6A`GNpcU z`ZlXPC7iTx%iS4#aYB%bA6wsPL;B!(cWxysE2|1|-Ws_1)r!=sUQ3$$WM%U zvzX1?sT!g%2N*uqb~-^SDZ zl}pLgX3DVMGRX8eOeb(x;lFHHVWug~_eCB185(6E?f3|!9#sZP5USQlOW2M_xL7%{ z+@t`HRMcV3Pl&++Rc$AG$xfq}5uX+;OYOHgT9eqR9 z`IQEE-(Lex5xa^P)p)p2DnGmCEUz9ZwXs11hy=S9Bd;;bTyfBmu}pa5F6 z@Mk+OK{glr%SueH^EIn+c7RCfx6%8HYQs9Bto&zz!3C|%&0rUEF_~YFvLZFd$b%si zf%VAErR6<{9bjGjn=hxL3#6u+nkjY|;LrbbIsD>p$p!sx8#+NIHm8T`+={-23QlCy z#IjbZEnb2Gri9$zA0E9^0c#Rd}wH8xLr;SAmQgv$nIa#%&r~;jrpY<8?PrG z&4pR%`XTEk&+}qWv-Qe+!!=CMHb`iZP9khtsTcZlHH!1EnpcI0A0MkUr-;@Hh&S&o z$$@haEc734i15evcu}aW<48I+x`kPhgwj~1UnhdCzJ9&W+b`o`p`ij&Klgm2>sr~j z(j0-UEockIp?XwjQDWKAb1YNh02*3%d^VfRn9|z%xAmw50< zuSKLtK%-prLe>pn(rCm?ji5E4c{NF|P5-jHLD^lWC5wu2asqu?ng_Al*|`+fTOpoz z$3hNF&QT`9cIFf5e+KqeQ=y5wlFTEW=np*`wYrF577LQ(qip6`mxcehOHafFB($dU zS%g9q=sU&2+vqbe8=Cc^ZmNFJt&}f9^vouZY>5@6tz(Z!17yDPkk2Un{`oI4ZTc{{ z`4Y*|FNuqLZW%24p5~&f^nS&A0;y}IiMpzdprqPTw{A`zs?UtS z)X`PUCdd`^R`*}OB+hzDJva2j(Y(aekrw6<4Y6)C{Ubfr=J4glvLS;Gri3PI>gCDN zB_*N_)|0ax`)&ahP{GJYDJGC1ra34!88DLA-lA92 z$rTgjfb=eyHZ^a#YTJUkF4vm6S-Q(YN3`_&Sn0{m&8#K=cUu`Cu~P8AE+F!ekw!IS*a{?wk4!kLaD^^s z0qexOLVR|MI4lobWl-X&L;@aPt`Nh`o$$%)sP-q)@p7Amrw>KeiSGelEQexLNQD7)9HKJf7Xoqyg;D=eW6)#= zXA;L4O5>XMP0pNpoXliXnyt#VlP95&y)}RC%e@Aq8LfnIscPYmbqhLrXHp#lT7JWj zy_t7JMaBAZwt|ItkVWIj}L2V9g9w%=J(GPPs`CkvNWtERuevb0zOf$T)K))mfG$3e$;;z zZRcq&Sj$F_CVuhhT*xJYp?(lO2*FqDg7v?m4(d&vCTFGci?#4XTf&P$WLZ?CRIK_; zY!OlibG|&c4!F=CyO`Ouo7?&ELLbzGc#Xg5z zrkeJ|x8_;~4cdsu2qX0RkN#yLpBU&*N)36{U*I~1-0tQHU#tzvJ7Ev$?n*ajRbG3P zZPJlxt5}?FJDfja7^I!j515)7E4j_nzIrRo%dOaBEz{h~L*!Qg8sgh4xRw#>@x4bE zt;D2K>jxigGD5};ba|zVjrR#ba!u2itU6L%Ewu-< z;#W~862BUtR-&9&TTv&MP_?NlErUO`Z`5n+^snaShtUUdF`3X%&`V{{WQ{tMA`%i@ z_Om`oaLa=>BH}OCP_0YJGU`0BlV>gdS;v?piHtRP+roVmX?2d2*3yq7Ms3vlelC% zjpiM078F{6>BDn?A`u$9%hdWe--0tv05ED@1uk=BzB&yTLpuG`zf%F-ni7h@PWejw zs^Rc>T@?6GXCW)EE2Yy+SozT zGU~Im^mLu?2stSe8sM_MR|aNyH#c@8yG-5Obot-MxGw|a+@Gbzlh1f8B=3Q2p=KkVs z+oE#xkcLmK@9y4^urSDYU?wO?p_Csk*kfbu&?%s$r-oybmi_a1m&etj$-Rt0AHi=E zWSP>(wtBH3m&=){sK=H)07lJii6&%BT0aV(Gm})Ca9QE^FmswF|FuPKdp)YB`b}JF z->Uqku_JG%KQM=CHl@(~FH8>d(V>s4qZ zEWdB2<8_`l{I&Pb*87|@1N>QVpb3g$R>-$>__4g`TcDf8W6E~5BtoNpbM&Oj(M%)r z9TwVQQ>MnVrs7;CynGN}EH!;vH+}hs{qM53p=7yU$mO55v zf;41V>$iBj{QTsR#6oxzC)TywrS(9%ht9~ba^d;cGai_!OS4_}p1)4DKhR6qor~*! zv#gwZq|8_5CR+<5in{B%{rv9n{x7xHcYf(iD={)aqS+Bbw4GHB05_Mi1NwGsCP2cI z-AT9un&9Z?mBx15%R0ttXI3g%J2+MZ8ftIu+4szCNs}v&msMKOir8FvCm~n5Sq97L z#l-_Vq?eW-b;R*tx{{!T#keM8_4nY#JI4*pKfEpM4taC<^Q`13zC7#^HZ~eWu>S|i~e<6Jik;Q_&sb%@3{ViG}ASs3zB?wJoDOa5`>h3oJKJA z&T1?_{s$CI8{LnrX!iq}AwE-sg}#cce||v0CC(I>Jqa7OKgu4hG*rBD3`r0?R;uFg zN|CM0M-k2w^iQ9?3eLS3#s$Q)IHn6yWUPai&v$|+02e;M*dY;fG5Pb)7yt0IP*&>! z2FZ&I_wLg6dvj$WO66!z0%gjKx}&o`N+uIXDQW3Xc^j|?TD38Q z)RU8&9~j2k>wXCQ%q1m8Qj=OS54y~Hk7~8zmSzSd2}WgpWlyr;j+v)GNJI*tQn4)r zuCLx56-rjv6AKpTzmMIvHy%fC*Wv2h8|rRo?V|FVUZJ|i#%Qo}enI;4b=bQ3Dy%h& zu7bc;Ge%3$D(x?7q&c-9 zQ*op8>(@Ocf`y&<7tx5EFIIg|o+z-E)A>=)nju}O_0!oM&ZY(D<4S;uiSs^vn|ctP z6^c8j=zXnuPEOQ$$hqVweYn~3YvOi!-CMTEurm+I=8_5cqnV3;`h47ZOogq|39)U# zJ^K3X;&_Re&7TW(^!Q`i=4% zi^B~V?keJJab&?uJV4JMmb`5&ROqYXO>K<{15>_#|9SlgD)xc_MS&jTXIR&CuU2K`Kdbm^x&6NH&^sG8-I-wkQ*Q6z3MY<$3q z#t0whoN{Ly-#?rwp;CRGY%1867K_6EO#`9sH@wDAbnWS9%m1u0{E)hGS}t)q9KI?d z&l|O;FK_gL@1k1}R!Jj%-S(jUB&!@MTo1Dhzn|FYK$j0QbsLXGAR&r^jQ;ctoxVp66u(3}>XCTy2KFCTg(lL>4*O0}eXNLvQyhXm3w2 z5VpKdMDYkkS)_HTnu)IzA#rO#l!d?Q2PW=2&G=wopH972mkkk%#s*H%;Hz1ylgFpH z1Y-8q5dE*s-AQSc4uW;@W*IeSJkBsafQX9sE;~($y--wmde8zDDFwxoz*!5zbIl7| zqD$VdhkhWM(o9SGRzSIzq3m>!+TxvX;|dy3T!10%uFtdLeLm}9z z1d~wZ^<51LiSv{_qiv2^sKX}D)!7+GXCM3)7=DK292Oq_jI7iEuEMv&1WkR06eZt! z!HD=nJbOD|n+a3CZOqzT7?%3|En}dizya*N0qeQYy39Cr6G|6MRBuh7XZVFs; z`zDrF@0FnZpO?%FIyjDfMu1ZMGF<%hv44I4XMTUcKK^NnaHIeKzkaDavahT&5-Lk@ SS0UajK$4SEmaG#u4f%g&wd%tF diff --git a/client/android/res/values/ic_banner_background.xml b/client/android/res/values/ic_banner_background.xml deleted file mode 100644 index fa6f91c7..00000000 --- a/client/android/res/values/ic_banner_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #1E1E1F - \ No newline at end of file From 392cacaabc9ae9354401fea76d0f7b9633772c83 Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 18 Nov 2024 21:03:43 +0300 Subject: [PATCH 050/100] refactor: make TvFilePicker activity more sustainable --- .../src/org/amnezia/vpn/AmneziaActivity.kt | 50 +++++++++++++------ .../src/org/amnezia/vpn/TvFilePicker.kt | 4 ++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 0dd3d319..b43a22c5 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -32,9 +32,9 @@ import android.widget.Toast import androidx.annotation.MainThread import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat -import java.io.FileNotFoundException import java.io.IOException import kotlin.LazyThreadSafetyMode.NONE +import kotlin.coroutines.CoroutineContext import kotlin.text.RegexOption.IGNORE_CASE import AppListProvider import kotlinx.coroutines.CompletableDeferred @@ -584,7 +584,9 @@ class AmneziaActivity : QtActivity() { if (isOnTv() && it?.hasExtra("activityNotFound") == true) { showNoFileBrowserAlertDialog() } - val uri = it?.data?.toString() ?: "" + val uri = it?.data?.apply { + grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION) + }.toString() ?: "" Log.v(TAG, "Open file: $uri") mainScope.launch { qtInitialized.await() @@ -615,30 +617,43 @@ class AmneziaActivity : QtActivity() { } @Suppress("unused") - fun getFd(fileName: String): Int = try { + fun getFd(fileName: String): Int { Log.v(TAG, "Get fd for $fileName") - pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r") - pfd?.fd ?: -1 - } catch (e: FileNotFoundException) { - Log.e(TAG, "Failed to get fd: $e") - -1 + return blockingCall { + try { + pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r") + pfd?.fd ?: -1 + } catch (e: Exception) { + Log.e(TAG, "Failed to get fd: $e") + -1 + } + } } @Suppress("unused") fun closeFd() { Log.v(TAG, "Close fd") - pfd?.close() - pfd = null + mainScope.launch { + pfd?.close() + pfd = null + } } @Suppress("unused") fun getFileName(uri: String): String { - contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor -> - if (cursor.moveToFirst() && !cursor.isNull(0)) { - return cursor.getString(0) + Log.v(TAG, "Get file name for uri: $uri") + return blockingCall { + try { + contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor -> + if (cursor.moveToFirst() && !cursor.isNull(0)) { + return@blockingCall cursor.getString(0) ?: "" + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to get file name: $e") } + "" } - return "" } @Suppress("unused") @@ -848,6 +863,13 @@ class AmneziaActivity : QtActivity() { /** * Utils methods */ + private fun blockingCall( + context: CoroutineContext = Dispatchers.Main.immediate, + block: suspend () -> T + ) = runBlocking { + mainScope.async(context) { block() }.await() + } + companion object { private fun actionCodeToString(actionCode: Int): String = when (actionCode) { diff --git a/client/android/src/org/amnezia/vpn/TvFilePicker.kt b/client/android/src/org/amnezia/vpn/TvFilePicker.kt index f3048509..1ac275eb 100644 --- a/client/android/src/org/amnezia/vpn/TvFilePicker.kt +++ b/client/android/src/org/amnezia/vpn/TvFilePicker.kt @@ -36,6 +36,10 @@ class TvFilePicker : ComponentActivity() { Log.w(TAG, "Activity not found") setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) }) finish() + } catch (e: Exception) { + Log.e(TAG, "Failed to get file: $e") + setResult(RESULT_CANCELED) + finish() } } } From 2987e03a8608b2a3af3c67a5c0c44307fe0007e9 Mon Sep 17 00:00:00 2001 From: albexk Date: Sun, 24 Nov 2024 21:18:35 +0300 Subject: [PATCH 051/100] fix: add the touch emulation method for Android TV --- .../src/org/amnezia/vpn/AmneziaActivity.kt | 48 +++++++++++++++++++ .../platforms/android/android_controller.cpp | 5 ++ client/platforms/android/android_controller.h | 1 + client/ui/controllers/systemController.cpp | 7 +++ client/ui/controllers/systemController.h | 2 + 5 files changed, 63 insertions(+) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index b43a22c5..8d873859 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -23,9 +23,12 @@ import android.os.Looper import android.os.Message import android.os.Messenger import android.os.ParcelFileDescriptor +import android.os.SystemClock import android.provider.OpenableColumns import android.provider.Settings import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup import android.view.WindowManager.LayoutParams import android.webkit.MimeTypeMap import android.widget.Toast @@ -800,6 +803,50 @@ class AmneziaActivity : QtActivity() { } } + // method to workaround Qt's problem with calling the keyboard on TVs + @Suppress("unused") + fun sendTouch(x: Float, y: Float) { + Log.v(TAG, "Send touch: $x, $y") + blockingCall { + findQtWindow(window.decorView)?.let { + Log.v(TAG, "Send touch to $it") + it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN)) + it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP)) + } + } + } + + private fun findQtWindow(view: View): View? { + Log.v(TAG, "findQtWindow: process $view") + if (view::class.simpleName == "QtWindow") return view + else if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val result = findQtWindow(view.getChildAt(i)) + if (result != null) return result + } + return null + } else return null + } + + private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent = + MotionEvent.obtain( + eventTime, + eventTime, + action, + 1, + arrayOf(MotionEvent.PointerProperties().apply { + id = 0 + toolType = MotionEvent.TOOL_TYPE_FINGER + }), + arrayOf(MotionEvent.PointerCoords().apply { + this.x = x + this.y = y + pressure = 1f + size = 1f + }), + 0, 0, 1.0f, 1.0f, 0, 0, 0,0 + ) + // workaround for a bug in Qt that causes the mouse click event not to be handled // also disable right-click, as it causes the application to crash private var lastButtonState = 0 @@ -849,6 +896,7 @@ class AmneziaActivity : QtActivity() { } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + Log.v(TAG, "dispatchTouch: $ev") if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { return handleMouseEvent(ev) { super.dispatchTouchEvent(it) } } diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 6ea8bea5..d9195f87 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -304,6 +304,11 @@ bool AndroidController::requestAuthentication() return result; } +void AndroidController::sendTouch(float x, float y) +{ + callActivityMethod("sendTouch", "(FF)V", x, y); +} + // Moving log processing to the Android side jclass AndroidController::log; jmethodID AndroidController::logDebug; diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index c2e082ea..5707771e 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -51,6 +51,7 @@ public: bool isNotificationPermissionGranted(); void requestNotificationPermission(); bool requestAuthentication(); + void sendTouch(float x, float y); static bool initLogging(); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 6de66769..3463d5f9 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -159,3 +159,10 @@ bool SystemController::isAuthenticated() return true; #endif } + +void SystemController::sendTouch(float x, float y) +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->sendTouch(x, y); +#endif +} diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index ddc3476a..f80d2fdb 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -22,6 +22,8 @@ public slots: void setQmlRoot(QObject *qmlRoot); bool isAuthenticated(); + void sendTouch(float x, float y); + signals: void fileDialogClosed(const bool isAccepted); From 3eeab2f2e75414bb7ae99a792db75687337e582b Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 25 Nov 2024 11:46:33 +0300 Subject: [PATCH 052/100] fix: null uri processing --- client/android/src/org/amnezia/vpn/AmneziaActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 8d873859..c6db5e29 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -589,7 +589,7 @@ class AmneziaActivity : QtActivity() { } val uri = it?.data?.apply { grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION) - }.toString() ?: "" + }?.toString() ?: "" Log.v(TAG, "Open file: $uri") mainScope.launch { qtInitialized.await() From 4d2d7e4071f428f4dfe06853a691f5855c3baa46 Mon Sep 17 00:00:00 2001 From: albexk Date: Sun, 24 Nov 2024 21:18:35 +0300 Subject: [PATCH 053/100] fix: add the touch emulation method for Android TV --- .../src/org/amnezia/vpn/AmneziaActivity.kt | 56 +++++++++++++++++++ .../platforms/android/android_controller.cpp | 5 ++ client/platforms/android/android_controller.h | 1 + client/ui/controllers/systemController.cpp | 7 +++ client/ui/controllers/systemController.h | 2 + 5 files changed, 71 insertions(+) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index b2c2ff71..7406181e 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -20,8 +20,11 @@ import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger +import android.os.SystemClock import android.provider.Settings import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup import android.view.WindowManager.LayoutParams import android.webkit.MimeTypeMap import android.widget.Toast @@ -30,6 +33,7 @@ import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import java.io.IOException import kotlin.LazyThreadSafetyMode.NONE +import kotlin.coroutines.CoroutineContext import kotlin.text.RegexOption.IGNORE_CASE import AppListProvider import kotlinx.coroutines.CompletableDeferred @@ -721,6 +725,50 @@ class AmneziaActivity : QtActivity() { } } + // method to workaround Qt's problem with calling the keyboard on TVs + @Suppress("unused") + fun sendTouch(x: Float, y: Float) { + Log.v(TAG, "Send touch: $x, $y") + blockingCall { + findQtWindow(window.decorView)?.let { + Log.v(TAG, "Send touch to $it") + it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN)) + it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP)) + } + } + } + + private fun findQtWindow(view: View): View? { + Log.v(TAG, "findQtWindow: process $view") + if (view::class.simpleName == "QtWindow") return view + else if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val result = findQtWindow(view.getChildAt(i)) + if (result != null) return result + } + return null + } else return null + } + + private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent = + MotionEvent.obtain( + eventTime, + eventTime, + action, + 1, + arrayOf(MotionEvent.PointerProperties().apply { + id = 0 + toolType = MotionEvent.TOOL_TYPE_FINGER + }), + arrayOf(MotionEvent.PointerCoords().apply { + this.x = x + this.y = y + pressure = 1f + size = 1f + }), + 0, 0, 1.0f, 1.0f, 0, 0, 0,0 + ) + // workaround for a bug in Qt that causes the mouse click event not to be handled // also disable right-click, as it causes the application to crash private var lastButtonState = 0 @@ -770,6 +818,7 @@ class AmneziaActivity : QtActivity() { } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + Log.v(TAG, "dispatchTouch: $ev") if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { return handleMouseEvent(ev) { super.dispatchTouchEvent(it) } } @@ -784,6 +833,13 @@ class AmneziaActivity : QtActivity() { /** * Utils methods */ + private fun blockingCall( + context: CoroutineContext = Dispatchers.Main.immediate, + block: suspend () -> T + ) = runBlocking { + mainScope.async(context) { block() }.await() + } + companion object { private fun actionCodeToString(actionCode: Int): String = when (actionCode) { diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 2790eb1b..11c4ca4e 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -287,6 +287,11 @@ bool AndroidController::requestAuthentication() return result; } +void AndroidController::sendTouch(float x, float y) +{ + callActivityMethod("sendTouch", "(FF)V", x, y); +} + // Moving log processing to the Android side jclass AndroidController::log; jmethodID AndroidController::logDebug; diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 759c9c3f..a7a2bde4 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -48,6 +48,7 @@ public: bool isNotificationPermissionGranted(); void requestNotificationPermission(); bool requestAuthentication(); + void sendTouch(float x, float y); static bool initLogging(); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 4598bff1..cd8f74ff 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -134,3 +134,10 @@ bool SystemController::isAuthenticated() return true; #endif } + +void SystemController::sendTouch(float x, float y) +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->sendTouch(x, y); +#endif +} diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index d2ee6f63..a3c0559c 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -20,6 +20,8 @@ public slots: void setQmlRoot(QObject *qmlRoot); bool isAuthenticated(); + void sendTouch(float x, float y); + signals: void fileDialogClosed(const bool isAccepted); From 09b1f322ba0978f0355b5f48a7a21fd8f203c804 Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 25 Nov 2024 13:56:14 +0300 Subject: [PATCH 054/100] fix: hide UI elements that use file saving --- client/ui/qml/Pages2/PageSettings.qml | 5 ++++- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 5 ++++- client/ui/qml/Pages2/PageStart.qml | 12 +++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 65c696c7..604d3074 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -87,6 +87,7 @@ PageType { LabelWithButtonType { id: backup + visible: !SettingsController.isOnTv() Layout.fillWidth: true text: qsTr("Backup") @@ -98,7 +99,9 @@ PageType { } } - DividerType {} + DividerType { + visible: !SettingsController.isOnTv() + } LabelWithButtonType { id: about diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index e61dc9f6..d74f3003 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -373,6 +373,7 @@ PageType { LabelWithButtonType { id: exportSitesButton + visible: !SettingsController.isOnTv() Layout.fillWidth: true text: qsTr("Save site list") @@ -396,7 +397,9 @@ PageType { } } - DividerType {} + DividerType { + visible: !SettingsController.isOnTv() + } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index c08acb7b..83e7626d 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -324,14 +324,16 @@ PageType { target: ServersModel function onModelReset() { - var hasServerWithWriteAccess = ServersModel.hasServerWithWriteAccess() - shareTabButton.visible = hasServerWithWriteAccess - shareTabButton.width = hasServerWithWriteAccess ? undefined : 0 + if (!SettingsController.isOnTv()) { + var hasServerWithWriteAccess = ServersModel.hasServerWithWriteAccess() + shareTabButton.visible = hasServerWithWriteAccess + shareTabButton.width = hasServerWithWriteAccess ? undefined : 0 + } } } - visible: ServersModel.hasServerWithWriteAccess() - width: ServersModel.hasServerWithWriteAccess() ? undefined : 0 + visible: !SettingsController.isOnTv() && ServersModel.hasServerWithWriteAccess() + width: !SettingsController.isOnTv() && ServersModel.hasServerWithWriteAccess() ? undefined : 0 isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" From c9205afbd4c842a8683c8e25cd7eb83abbcc61de Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 25 Nov 2024 14:08:25 +0300 Subject: [PATCH 055/100] chore: bump version code --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62ab3477..98f3be14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2070) +set(APP_ANDROID_VERSION_CODE 2072) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 87d00116e11265102cf76c15b02c7c0771df79dc Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 6 Dec 2024 22:57:50 +0100 Subject: [PATCH 056/100] add `ScrollBarType` --- client/resources.qrc | 1 + client/ui/qml/Components/HomeContainersListView.qml | 2 +- client/ui/qml/Components/InstalledAppsDrawer.qml | 5 +---- client/ui/qml/Components/ServersListView.qml | 2 +- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 +--- client/ui/qml/Controls2/FlickableType.qml | 2 +- client/ui/qml/Controls2/ScrollBarType.qml | 11 +++++++++++ client/ui/qml/Pages2/PageSettingsAbout.qml | 4 +--- client/ui/qml/Pages2/PageSettingsLogging.qml | 4 +--- 9 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 client/ui/qml/Controls2/ScrollBarType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 5edd1e8a..38f0e79a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -151,6 +151,7 @@ ui/qml/Controls2/PageType.qml ui/qml/Controls2/PopupType.qml ui/qml/Controls2/ProgressBarType.qml + ui/qml/Controls2/ScrollBarType.qml ui/qml/Controls2/StackViewType.qml ui/qml/Controls2/SwitcherType.qml ui/qml/Controls2/TabButtonType.qml diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 8ddccb5a..3dc31300 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -30,7 +30,7 @@ ListView { // snapMode: ListView.SnapToItem - // ScrollBar.vertical: ScrollBar {} + ScrollBar.vertical: ScrollBarType {} property bool isFocusable: true diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index ca579491..5835e1c6 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -81,10 +81,7 @@ DrawerType2 { } } - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: ScrollBar.AlwaysOn - } + ScrollBar.vertical: ScrollBarType {} ButtonGroup { id: buttonGroup diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 9270c0e6..1711a458 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -27,7 +27,7 @@ ListView { model: ServersModel currentIndex: ServersModel.defaultIndex - ScrollBar.vertical: ScrollBar { + ScrollBar.vertical: ScrollBarType { id: scrollBar objectName: "scrollBar" policy: root.height >= root.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 7e8db993..d3bbaa17 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -85,9 +85,7 @@ DrawerType2 { FocusController.nextKeyRightItem() } - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } + ScrollBar.vertical: ScrollBarType {} model: 1 diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index 2151e520..b3e5cabf 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -25,7 +25,7 @@ Flickable { Keys.onUpPressed: scrollBar.decrease() Keys.onDownPressed: scrollBar.increase() - ScrollBar.vertical: ScrollBar { + ScrollBar.vertical: ScrollBarType { id: scrollBar policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn } diff --git a/client/ui/qml/Controls2/ScrollBarType.qml b/client/ui/qml/Controls2/ScrollBarType.qml new file mode 100644 index 00000000..d91431ab --- /dev/null +++ b/client/ui/qml/Controls2/ScrollBarType.qml @@ -0,0 +1,11 @@ +import QtQuick +import QtQuick.Controls + +import "./" +import "../Controls2" + +ScrollBar { + id: root + + policy: ScrollBar.AsNeeded +} diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 8501d2a4..6145da8d 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -115,9 +115,7 @@ PageType { FocusController.nextKeyRightItem() } - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } + ScrollBar.vertical: ScrollBarType {} model: contacts diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 4b2d7d42..9d85173e 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -122,9 +122,7 @@ PageType { FocusController.nextKeyRightItem() } - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } + ScrollBar.vertical: ScrollBarType {} model: logTypes spacing: 24 From 39cbe6de2848bdcc81cb9ea0a88542fc64e76dc6 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 9 Dec 2024 01:28:57 +0100 Subject: [PATCH 057/100] update initial config page --- .../Pages2/PageSetupWizardConfigSource.qml | 212 +++++++++--------- 1 file changed, 105 insertions(+), 107 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index ca0556a1..82fec030 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,108 +25,6 @@ PageType { } } - QtObject { - id: amneziaVpn - - 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 @@ -158,14 +56,14 @@ PageType { FocusController.nextKeyRightItem() } - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } + ScrollBar.vertical: ScrollBarType {} model: variants clip: true + reuseItems: true + header: ColumnLayout { width: listView.width @@ -316,8 +214,6 @@ PageType { width: listView.width CardWithIconsType { - id: entry - Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 @@ -335,4 +231,106 @@ PageType { } } } + + property list variants: [ + amneziaVpn, + selfHostVpn, + backupRestore, + fileOpen, + qrScan, + siteLink + ] + + QtObject { + id: amneziaVpn + + 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()) + } + } } From f331c11f518c32c1bc98d2355593e8ed1ffdf85e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 9 Dec 2024 01:30:11 +0100 Subject: [PATCH 058/100] refactor credentials setup page to handle the focus navigation --- .../qml/Pages2/PageSetupWizardCredentials.qml | 216 ++++++++++++------ 1 file changed, 151 insertions(+), 65 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index aa0b935f..d3d29bfd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -20,99 +20,125 @@ PageType { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } } - FlickableType { - id: fl + 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 - anchors.rightMargin: 16 - anchors.leftMargin: 16 + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } - spacing: 16 + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBarType {} + + header: ColumnLayout { + width: listView.width HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 headerText: qsTr("Configure your server") } + } + + model: inputFields + spacing: 16 + clip: true + reuseItems: true + + delegate: ColumnLayout { + property alias textField: _textField.textField + + width: listView.width TextFieldWithHeaderType { - id: hostname + id: _textField Layout.fillWidth: true - headerText: qsTr("Server IP address [:port]") - textFieldPlaceholderText: qsTr("255.255.255.255:22") + Layout.leftMargin: 16 + Layout.rightMargin: 16 - parentFlickable: fl + property bool hidePassword: hideText - textField.onFocusChanged: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - } - } + headerText: title + textField.echoMode: hideText ? TextInput.Password : TextInput.Normal + buttonImageSource: imageSource + textFieldPlaceholderText: placeholderText + textField.text: textFieldText - TextFieldWithHeaderType { - id: username + rightButtonClickedOnEnter: true - Layout.fillWidth: true - headerText: qsTr("SSH Username") - textFieldPlaceholderText: "root" - - parentFlickable: fl - - textField.onFocusChanged: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - } - } - - TextFieldWithHeaderType { - id: secretData - - property bool hidePassword: true - - Layout.fillWidth: true - headerText: qsTr("Password or SSH private key") - textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal - buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") - : "" - - parentFlickable: fl - - clickedFunc: function() { - hidePassword = !hidePassword + clickedFunc: function () { + clickedHandler } textField.onFocusChanged: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') + var _currentIndex = listView.currentIndex + var _currentItem = listView.itemAtIndex(_currentIndex).children[0] + listView.model[_currentIndex].textFieldText = _currentItem.textFieldText.replace(/^\s+|\s+$/g, '') } } + } + + footer: ColumnLayout { + width: listView.width BasicButtonType { id: continueButton Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Continue") - parentFlickable: fl - clickedFunc: function() { - forceActiveFocus() - if (!isCredentialsFilled()) { + if (!root.isCredentialsFilled()) { return } InstallController.setShouldCreateServer(true) - InstallController.setProcessedServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textFieldText + var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textFieldText + var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textFieldText + + InstallController.setProcessedServerCredentials(_hostname, _username, _secretData) PageController.showBusyIndicator(true) var isConnectionOpened = InstallController.checkSshConnection() @@ -127,7 +153,10 @@ PageType { LabelTextType { Layout.fillWidth: true - Layout.topMargin: 12 + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 text: qsTr("All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties") } @@ -136,6 +165,8 @@ PageType { id: siteLink Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 Layout.bottomMargin: 16 headerText: qsTr("How to run your VPN server") @@ -144,8 +175,6 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/help-circle.svg" - parentFlickable: fl - onClicked: { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/starter-guide") } @@ -156,21 +185,78 @@ PageType { function isCredentialsFilled() { var hasEmptyField = false - if (hostname.textFieldText === "") { - hostname.errorText = qsTr("Ip address cannot be empty") + var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0] + if (_hostname.textFieldText === "") { + _hostname.errorText = qsTr("Ip address cannot be empty") hasEmptyField = true - } else if (!hostname.textField.acceptableInput) { - hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") + } else if (!_hostname.textField.acceptableInput) { + _hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") } - if (username.textFieldText === "") { - username.errorText = qsTr("Login cannot be empty") + var _username = listView.itemAtIndex(vars.usernameIndex).children[0] + if (_username.textFieldText === "") { + _username.errorText = qsTr("Login cannot be empty") hasEmptyField = true } - if (secretData.textFieldText === "") { - secretData.errorText = qsTr("Password/private key cannot be empty") + + var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0] + if (_secretData.textFieldText === "") { + _secretData.errorText = qsTr("Password/private key cannot be empty") hasEmptyField = true } + return !hasEmptyField } + + property list inputFields: [ + hostname, + username, + secretData + ] + + QtObject { + id: hostname + + property string title: qsTr("Server IP address [:port]") + readonly property string placeholderText: qsTr("255.255.255.255:22") + property string textFieldText: "" + property bool hideText: false + property string imageSource: "" + readonly property var clickedHandler: function() { + console.debug(">>> Server IP address text field was clicked!!!") + clicked() + } + } + + QtObject { + id: username + + property string title: qsTr("SSH Username") + readonly property string placeholderText: "root" + property string textFieldText: "" + property bool hideText: false + property string imageSource: "" + readonly property var clickedHandler: undefined + } + + QtObject { + id: secretData + + property string title: qsTr("Password or SSH private key") + readonly property string placeholderText: "" + property string textFieldText: "" + property bool hideText: true + property string imageSource: textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + readonly property var clickedHandler: function() { + hideText = !hideText + } + } + + QtObject { + id: vars + + readonly property int hostnameIndex: 0 + readonly property int usernameIndex: 1 + readonly property int secretDataIndex: 2 + } } From 3cc70790bdeb4ed117e1496f89584b2fe21ee4e2 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 9 Dec 2024 01:32:11 +0100 Subject: [PATCH 059/100] add `setDelegateIndex` method to `listViewFocusController` --- client/ui/controllers/listViewFocusController.cpp | 12 +++++++++--- client/ui/controllers/listViewFocusController.h | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 21326597..c26167d1 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -141,6 +141,12 @@ int ListViewFocusController::currentIndex() const return m_delegateIndex; } +void ListViewFocusController::setDelegateIndex(int index) +{ + m_delegateIndex = index; + m_listView->setProperty("currentIndex", index); +} + void ListViewFocusController::nextDelegate() { const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; @@ -164,7 +170,7 @@ void ListViewFocusController::nextDelegate() } case Section::Delegate: if (m_delegateIndex < (size() - 1)) { - m_delegateIndex++; + setDelegateIndex(m_delegateIndex + 1); viewAtCurrentIndex(); break; } else if (hasFooter()) { @@ -199,14 +205,14 @@ void ListViewFocusController::previousDelegate() case Section::Footer: { if (size() > 0) { m_currentSection = Section::Delegate; - m_delegateIndex = size() - 1; + setDelegateIndex(size() - 1); break; } [[fallthrough]]; } case Section::Delegate: { if (m_delegateIndex > 0) { - m_delegateIndex--; + setDelegateIndex(m_delegateIndex - 1); break; } else if (hasHeader()) { m_currentSection = Section::Header; diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index a94b4237..df14b6be 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -52,6 +52,7 @@ private: int size() const; int currentIndex() const; + void setDelegateIndex(int index); void viewAtCurrentIndex() const; QQuickItem* itemAtIndex(const int index) const; QQuickItem* currentDelegate() const; From ea394477085b8a14b4660b1ac5e6d3889b1b409e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 9 Dec 2024 01:35:10 +0100 Subject: [PATCH 060/100] fix focus behavior on new page/popup --- client/ui/qml/Controls2/PageType.qml | 5 +++-- client/ui/qml/Controls2/PopupType.qml | 10 ++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index edeb07fe..c2ed5197 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -10,7 +10,7 @@ Item { property StackView stackView: StackView.view onVisibleChanged: { - if (visible && !GC.isMobile()) { + if (visible) { timer.start() } } @@ -20,10 +20,11 @@ Item { id: timer interval: 200 // Milliseconds onTriggered: { + console.debug(">>> PageType timer triggered") FocusController.resetRootObject() FocusController.setFocusOnDefaultItem() } repeat: false // Stop the timer after one trigger - running: !GC.isMobile() // Start the timer + running: true // Start the timer } } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index d067fc12..dfb6f273 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -45,15 +45,13 @@ Popup { Timer { id: timer - interval: 400 // Milliseconds + interval: 200 // Milliseconds onTriggered: { - if (!GC.isMobile()) { - FocusController.setFocusItem(closeButton) - FocusController.pushRootObject(root) - } + FocusController.pushRootObject(root) + FocusController.setFocusItem(closeButton) } repeat: false // Stop the timer after one trigger - running: !GC.isMobile() // Start the timer + running: true // Start the timer } contentItem: Item { From 882c288d9253373913f594be069ff5df3fbcaf01 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 9 Dec 2024 01:39:16 +0100 Subject: [PATCH 061/100] make minor fixes and clean up --- .../qml/Components/ShareConnectionDrawer.qml | 1 + client/ui/qml/Controls2/BasicButtonType.qml | 2 +- client/ui/qml/Controls2/DropDownType.qml | 5 --- client/ui/qml/Pages2/PageHome.qml | 17 -------- .../Pages2/PageProtocolAwgClientSettings.qml | 1 - client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 + client/ui/qml/Pages2/PageShare.qml | 42 +------------------ 7 files changed, 5 insertions(+), 65 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index d3bbaa17..ed50807a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -90,6 +90,7 @@ DrawerType2 { model: 1 clip: true + reuseItems: true header: ColumnLayout { width: listView.width diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index aa8103e9..ae4fc841 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -176,7 +176,7 @@ Button { ButtonTextType { id: buttonText - color: textColor + color: root.textColor text: root.text visible: root.text === "" ? false : true diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 787890cb..60751e21 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -225,11 +225,6 @@ Item { id: backButton backButtonImage: root.headerBackButtonImage backButtonFunction: function() { menu.closeTriggered() } - onActiveFocusChanged: { - if(backButton.enabled && backButton.activeFocus) { - root.listView.positionViewAtBeginning() - } - } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index f5ac8ae3..b359b98c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -142,13 +142,6 @@ PageType { drawer.expandedHeight = implicitHeight } - Connections { - objectName: "drawerConnections" - - target: drawer - enabled: !GC.isMobile() - } - ColumnLayout { id: collapsed objectName: "collapsedColumnLayout" @@ -298,16 +291,6 @@ PageType { } } - Connections { - target: drawer - enabled: !GC.isMobile() - function onIsCollapsedChanged() { - if (!drawer.isCollapsed) { - focusItem1.forceActiveFocus() - } - } - } - ColumnLayout { id: serversMenuHeader objectName: "serversMenuHeader" diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index 859d155e..d31f63e3 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -240,7 +240,6 @@ PageType { id: underloadPacketMagicHeaderTextField Layout.fillWidth: true Layout.topMargin: 16 - parentFlickable: fl enabled: false diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 27df72b0..eb6000c2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -87,6 +87,8 @@ PageType { property int containerDefaultPort property int containerDefaultTransportProto + property bool isFocusable: true + delegate: Item { implicitWidth: containers.width implicitHeight: delegateContent.implicitHeight diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index c238d6c4..a95fd78e 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -182,15 +182,6 @@ PageType { shareFullAccessDrawer.openTriggered() } - actionButton.onFocusChanged: { - console.debug("MOVE THIS LOGIC TO CPP!") - if (actionButton.activeFocus) { - if (fl) { - fl.ensureVisible(moreButton) - } - } - } - DrawerType2 { id: shareFullAccessDrawer @@ -677,38 +668,7 @@ PageType { } clip: true - // 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) - // } - // } - // } - - // 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 - // } - // } - // } + reuseItems: true delegate: Item { implicitWidth: clientsListView.width From 5b9ba8c0270cc2180fa41cdb57521f0a221a8380 Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 9 Dec 2024 20:56:58 +0300 Subject: [PATCH 062/100] fix: get rid of the assign function call --- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/settingsController.cpp | 2 +- client/ui/controllers/sitesController.cpp | 2 +- client/ui/controllers/systemController.cpp | 12 ++++++------ client/ui/controllers/systemController.h | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 3200d0e8..28bbc9f6 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -78,7 +78,7 @@ ImportController::ImportController(const QSharedPointer &serversMo bool ImportController::extractConfigFromFile(const QString &fileName) { QString data; - if (!SystemController::readFile(fileName, &data)) { + if (!SystemController::readFile(fileName, data)) { emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); return false; } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index e20f4d3b..6d777bd8 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -132,7 +132,7 @@ void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::restoreAppConfig(const QString &fileName) { QByteArray data; - SystemController::readFile(fileName, &data); + SystemController::readFile(fileName, data); restoreAppConfigFromData(data); } diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index d94a9b6f..24ae035f 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -83,7 +83,7 @@ void SitesController::removeSite(int index) void SitesController::importSites(const QString &fileName, bool replaceExisting) { QByteArray jsonData; - if (!SystemController::readFile(fileName, &jsonData)) { + if (!SystemController::readFile(fileName, jsonData)) { emit errorOccurred(tr("Can't open file: %1").arg(fileName)); return; } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 3463d5f9..52ca1294 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -62,28 +62,28 @@ void SystemController::saveFile(const QString &fileName, const QString &data) #endif } -bool SystemController::readFile(const QString &fileName, QByteArray *data) +bool SystemController::readFile(const QString &fileName, QByteArray &data) { #ifdef Q_OS_ANDROID int fd = AndroidController::instance()->getFd(fileName); if (fd == -1) return false; QFile file; if(!file.open(fd, QIODevice::ReadOnly)) return false; - data->assign(file.readAll()); + data = file.readAll(); AndroidController::instance()->closeFd(); #else QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) return false; - data->assign(file.readAll()); + data = file.readAll(); #endif return true; } -bool SystemController::readFile(const QString &fileName, QString *data) +bool SystemController::readFile(const QString &fileName, QString &data) { QByteArray byteArray; - if(!readFile(fileName, &byteArray)) return false; - data->assign(byteArray); + if(!readFile(fileName, byteArray)) return false; + data = byteArray; return true; } diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index f80d2fdb..8cb3a0d1 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -12,8 +12,8 @@ public: explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); static void saveFile(const QString &fileName, const QString &data); - static bool readFile(const QString &fileName, QByteArray *data); - static bool readFile(const QString &fileName, QString *data); + static bool readFile(const QString &fileName, QByteArray &data); + static bool readFile(const QString &fileName, QString &data); public slots: QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "", From 53f41408b801547e952a5fc0a073011fb6deb01c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 13 Dec 2024 22:09:37 +0100 Subject: [PATCH 063/100] Scrollbar is on if the content is larger than a screen --- client/ui/qml/Controls2/ScrollBarType.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/ScrollBarType.qml b/client/ui/qml/Controls2/ScrollBarType.qml index d91431ab..0e5d244e 100644 --- a/client/ui/qml/Controls2/ScrollBarType.qml +++ b/client/ui/qml/Controls2/ScrollBarType.qml @@ -7,5 +7,5 @@ import "../Controls2" ScrollBar { id: root - policy: ScrollBar.AsNeeded + policy: ScrollBar.AlwaysOn } From ef2ffce47aee916c1a9f061f97854822054b0f84 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 13 Dec 2024 22:12:53 +0100 Subject: [PATCH 064/100] Fix selection in language change list --- client/ui/qml/Components/SelectLanguageDrawer.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index a48515cc..d3ae7146 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -63,6 +63,7 @@ DrawerType2 { clip: true interactive: true + property int selectedIndex: LanguageModel.currentLanguageIndex property bool isFocusable: true Keys.onTabPressed: { @@ -90,7 +91,6 @@ DrawerType2 { } model: LanguageModel - currentIndex: LanguageModel.currentLanguageIndex ButtonGroup { id: buttonGroup @@ -185,10 +185,10 @@ DrawerType2 { } ButtonGroup.group: buttonGroup - checked: listView.currentIndex === index + checked: listView.selectedIndex === index onClicked: { - listView.currentIndex = index + listView.selectedIndex = index LanguageModel.changeLanguage(languageIndex) root.closeTriggered() } From d18f50a3e078901b505860c6a60e8f2204cdb6e3 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 13 Dec 2024 22:16:00 +0100 Subject: [PATCH 065/100] Update select language list --- .../qml/Components/SelectLanguageDrawer.qml | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index d3ae7146..c0af3acd 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -60,35 +60,13 @@ DrawerType2 { anchors.right: parent.right anchors.bottom: parent.bottom - clip: true - interactive: true - - property int selectedIndex: LanguageModel.currentLanguageIndex property bool isFocusable: true + property int selectedIndex: LanguageModel.currentLanguageIndex - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } + clip: true + reuseItems: true - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } + ScrollBar.vertical: ScrollBarType {} model: LanguageModel From b33b97802ed609f9985b3dc7a8e2b61f385545c4 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 17:54:41 +0100 Subject: [PATCH 066/100] update logging settings page --- client/ui/qml/Pages2/PageSettingsLogging.qml | 171 ++++++++----------- 1 file changed, 69 insertions(+), 102 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 9d85173e..7d85e2b3 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -25,69 +25,6 @@ PageType { anchors.topMargin: 20 } - 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 @@ -98,47 +35,11 @@ PageType { 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: ScrollBarType {} - model: logTypes - spacing: 24 - snapMode: ListView.SnapOneItem - - reuseItems: true - - clip: true - header: ColumnLayout { - id: headerContent - width: listView.width - spacing: 0 - HeaderType { Layout.fillWidth: true Layout.leftMargin: 16 @@ -199,14 +100,17 @@ PageType { } } + model: logTypes + clip: true + reuseItems: true + snapMode: ListView.SnapOneItem + delegate: ColumnLayout { id: delegateContent width: listView.width - spacing: 0 - - visible: isVisible + enabled: isVisible ListItemTitleType { Layout.fillWidth: true @@ -257,4 +161,67 @@ PageType { DividerType {} } } + + property list logTypes: [ + clientLogs, + serviceLogs + ] + + QtObject { + id: clientLogs + + readonly property string title: qsTr("Client logs") + readonly property string description: qsTr("AmneziaVPN logs") + readonly property bool isVisible: true + readonly property var openLogsHandler: function() { + SettingsController.openLogsFolder() + } + readonly 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 + + readonly property string title: qsTr("Service logs") + readonly property string description: qsTr("AmneziaVPN-service logs") + readonly property bool isVisible: !GC.isMobile() + readonly property var openLogsHandler: function() { + SettingsController.openServiceLogsFolder() + } + readonly 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")) + } + } + } } From 8bc31b60a1912c46c62dc0883565c83f807c8bc1 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 19:29:35 +0100 Subject: [PATCH 067/100] fix checked item in lists --- .../Controls2/ListViewWithRadioButtonType.qml | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 8409e964..da5561ca 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -20,7 +20,7 @@ ListView { property var clickedFunction - currentIndex: 0 + property int selectedIndex: 0 width: rootWidth height: root.contentItem.height @@ -29,45 +29,21 @@ ListView { 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() - } - ButtonGroup { id: buttonGroup } function triggerCurrentItem() { - var item = root.itemAtIndex(currentIndex) - var radioButton = item.children[0] - radioButton.clicked() + var item = root.itemAtIndex(selectedIndex) + item.selectable.clicked() } delegate: ColumnLayout { id: content + property alias selectable: radioButton + implicitWidth: rootWidth - // implicitHeight: content.implicitHeight RadioButton { id: radioButton @@ -156,10 +132,10 @@ ListView { } ButtonGroup.group: buttonGroup - checked: root.currentIndex === index + checked: root.selectedIndex === index onClicked: { - root.currentIndex = index + root.selectedIndex = index root.selectedText = name if (clickedFunction && typeof clickedFunction === "function") { clickedFunction() @@ -168,7 +144,7 @@ ListView { } Component.onCompleted: { - if (root.currentIndex === index) { + if (root.selectedIndex === index) { root.selectedText = name } } From 906cf7b939d7f0a6ae8aa4b96d480d81a39eb44b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 19:32:46 +0100 Subject: [PATCH 068/100] fix split tunneling settings --- .../qml/Pages2/PageSettingsSplitTunneling.qml | 110 ++++++------------ 1 file changed, 37 insertions(+), 73 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index d74f3003..23a9bfa4 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -144,18 +144,17 @@ PageType { model: root.routeModesModel - currentIndex: getRouteModesModelIndex() clickedFunction: function() { selector.text = selectedText selector.closeTriggered() - if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { - SitesModel.routeMode = root.routeModesModel[currentIndex].type + if (SitesModel.routeMode !== root.routeModesModel[selectedIndex].type) { + SitesModel.routeMode = root.routeModesModel[selectedIndex].type } } Component.onCompleted: { - if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) { + if (root.routeModesModel[selectedIndex].type === SitesModel.routeMode) { selector.text = selectedText } else { selector.text = root.routeModesModel[0].name @@ -165,7 +164,7 @@ PageType { Connections { target: SitesModel function onRouteModeChanged() { - currentIndex = getRouteModesModelIndex() + selectedIndex = getRouteModesModelIndex() } } } @@ -173,42 +172,18 @@ PageType { } ListView { - id: sites + id: listView anchors.top: header.bottom anchors.topMargin: 16 - width: parent.width + anchors.bottom: addSiteButton.top - height: 200 // TODO: Change to correct height + width: parent.width enabled: root.pageEnabled 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: proxySitesModel sourceModel: SitesModel @@ -230,55 +205,44 @@ PageType { clip: true - delegate: Item { - implicitWidth: sites.width - implicitHeight: delegateContent.implicitHeight + reuseItems: true - // onActiveFocusChanged: { - // if (activeFocus) { - // site.rightButton.forceActiveFocus() - // } - // } + delegate: ColumnLayout { + id: delegateContent - ColumnLayout { - id: delegateContent + width: listView.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + LabelWithButtonType { + id: site + Layout.fillWidth: true - LabelWithButtonType { - id: site - Layout.fillWidth: true + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray - 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") - 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 yesButtonFunction = function() { + SitesController.removeSite(proxySitesModel.mapToSource(index)) + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() } - var noButtonFunction = function() { - if (!GC.isMobile()) { - site.rightButton.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - } - DividerType {} + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } } + + DividerType {} } } @@ -373,7 +337,7 @@ PageType { LabelWithButtonType { id: exportSitesButton - visible: !SettingsController.isOnTv() + enabled: !SettingsController.isOnTv() Layout.fillWidth: true text: qsTr("Save site list") @@ -398,7 +362,7 @@ PageType { } DividerType { - visible: !SettingsController.isOnTv() + enabled: !SettingsController.isOnTv() } } } From 378db1a9e5d93304b5e30ac13052bdd714d47e3e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 19:33:54 +0100 Subject: [PATCH 069/100] make unchangable properties readonly --- client/ui/qml/Pages2/PageSettingsAbout.qml | 32 +++++++++---------- .../Pages2/PageSettingsAppSplitTunneling.qml | 10 +++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 6145da8d..2160692b 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -32,10 +32,10 @@ PageType { 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() { + readonly property string title: qsTr("Telegram group") + readonly property string description: qsTr("To discuss features") + readonly property string imageSource: "qrc:/images/controls/telegram.svg" + readonly property var handler: function() { Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) } } @@ -43,10 +43,10 @@ PageType { 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() { + readonly property string title: qsTr("support@amnezia.org") + readonly property string description: qsTr("For reviews and bug reports") + readonly property string imageSource: "qrc:/images/controls/mail.svg" + readonly property var handler: function() { GC.copyToClipBoard(title) PageController.showNotificationMessage(qsTr("Copied")) } @@ -55,10 +55,10 @@ PageType { 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() { + readonly property string title: qsTr("GitHub") + readonly property string description: qsTr("Discover the source code") + readonly property string imageSource: "qrc:/images/controls/github.svg" + readonly property var handler: function() { Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) } } @@ -66,10 +66,10 @@ PageType { 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() { + readonly property string title: qsTr("Website") + readonly property string description: qsTr("Visit official website") + readonly property string imageSource: "qrc:/images/controls/amnezia.svg" + readonly property var handler: function() { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) } } diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index 91501224..84f90b9c 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -46,13 +46,15 @@ PageType { QtObject { id: onlyForwardApps - property string name: qsTr("Only the apps from the list should have access via VPN") - property int type: routeMode.onlyForwardApps + + readonly property string name: qsTr("Only the apps from the list should have access via VPN") + readonly property int type: routeMode.onlyForwardApps } QtObject { id: allExceptApps - property string name: qsTr("Apps from the list should not have access via VPN") - property int type: routeMode.allExceptApps + + readonly property string name: qsTr("Apps from the list should not have access via VPN") + readonly property int type: routeMode.allExceptApps } function getRouteModesModelIndex() { From 3021066180242e88b76813d4b81d9b973672f471 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 19:34:24 +0100 Subject: [PATCH 070/100] refactor SwitcherType --- client/ui/qml/Controls2/SwitcherType.qml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index 77b657e7..0651390f 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -158,23 +158,11 @@ Switch { enabled: false } - Keys.onEnterPressed: { - if (!event.isAutoRepeat) { - root.checked = !root.checked - root.checkedChanged() - } - event.accepted = true - } + Keys.onEnterPressed: event => handleSwitch(event) + Keys.onReturnPressed: event => handleSwitch(event) + Keys.onSpacePressed: event => handleSwitch(event) - Keys.onReturnPressed: { - if (!event.isAutoRepeat) { - root.checked = !root.checked - root.checkedChanged() - } - event.accepted = true - } - - Keys.onSpacePressed: { + function handleSwitch(event) { if (!event.isAutoRepeat) { root.checked = !root.checked root.checkedChanged() From 3a62f1f20beec125fb978ba957317c597717159c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 14 Dec 2024 23:44:15 +0100 Subject: [PATCH 071/100] fix hide/unhide password --- client/ui/qml/Pages2/PageSetupWizardCredentials.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index d3d29bfd..20b85550 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -104,7 +104,7 @@ PageType { rightButtonClickedOnEnter: true clickedFunc: function () { - clickedHandler + clickedHandler() } textField.onFocusChanged: { @@ -112,6 +112,15 @@ PageType { var _currentItem = listView.itemAtIndex(_currentIndex).children[0] listView.model[_currentIndex].textFieldText = _currentItem.textFieldText.replace(/^\s+|\s+$/g, '') } + + textField.onTextChanged: { + var _currentIndex = listView.currentIndex + textFieldText = textField.text + + if (_currentIndex === vars.secretDataIndex) { + buttonImageSource = textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + } + } } } From 10e91b06f4029e4e8498e5cced75c3adca66d61e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 15 Dec 2024 23:12:59 +0100 Subject: [PATCH 072/100] `PageShare` readonly properties --- client/ui/qml/Pages2/PageShare.qml | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 02a64bf6..ab74eb7f 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -117,38 +117,38 @@ PageType { QtObject { id: amneziaConnectionFormat - property string name: qsTr("For the AmneziaVPN app") - property var type: PageShare.ConfigType.AmneziaConnection + readonly property string name: qsTr("For the AmneziaVPN app") + readonly property int type: PageShare.ConfigType.AmneziaConnection } QtObject { id: openVpnConnectionFormat - property string name: qsTr("OpenVPN native format") - property var type: PageShare.ConfigType.OpenVpn + readonly property string name: qsTr("OpenVPN native format") + readonly property int type: PageShare.ConfigType.OpenVpn } QtObject { id: wireGuardConnectionFormat - property string name: qsTr("WireGuard native format") - property var type: PageShare.ConfigType.WireGuard + readonly property string name: qsTr("WireGuard native format") + readonly property int type: PageShare.ConfigType.WireGuard } QtObject { id: awgConnectionFormat - property string name: qsTr("AmneziaWG native format") - property var type: PageShare.ConfigType.Awg + readonly property string name: qsTr("AmneziaWG native format") + readonly property int type: PageShare.ConfigType.Awg } QtObject { id: shadowSocksConnectionFormat - property string name: qsTr("Shadowsocks native format") - property var type: PageShare.ConfigType.ShadowSocks + readonly property string name: qsTr("Shadowsocks native format") + readonly property int type: PageShare.ConfigType.ShadowSocks } QtObject { id: cloakConnectionFormat - property string name: qsTr("Cloak native format") - property var type: PageShare.ConfigType.Cloak + readonly property string name: qsTr("Cloak native format") + readonly property int type: PageShare.ConfigType.Cloak } QtObject { id: xrayConnectionFormat - property string name: qsTr("XRay native format") - property var type: PageShare.ConfigType.Xray + readonly property string name: qsTr("XRay native format") + readonly property int type: PageShare.ConfigType.Xray } FlickableType { @@ -343,8 +343,8 @@ PageType { clickedFunction: function() { handler() - if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { - serverSelector.currentIndex = serverSelectorListView.currentIndex + if (serverSelector.selectedIndex !== serverSelectorListView.selectedIndex) { + serverSelector.selectedIndex = serverSelectorListView.currentIndex serverSelector.severSelectorIndexChanged() } From 62be2928336d96e90a98f427419bdf5ee291d718 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 15 Dec 2024 23:14:52 +0100 Subject: [PATCH 073/100] Fix list view focus moving on `PageShare` --- client/ui/qml/Pages2/PageShare.qml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index ab74eb7f..60c698e1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -343,8 +343,8 @@ PageType { clickedFunction: function() { handler() - if (serverSelector.selectedIndex !== serverSelectorListView.selectedIndex) { - serverSelector.selectedIndex = serverSelectorListView.currentIndex + if (serverSelector.currentIndex !== serverSelectorListView.selectedIndex) { + serverSelector.currentIndex = serverSelectorListView.selectedIndex serverSelector.severSelectorIndexChanged() } @@ -353,9 +353,9 @@ PageType { Component.onCompleted: { if (ServersModel.isDefaultServerHasWriteAccess() && ServersModel.getDefaultServerData("hasInstalledContainers")) { - serverSelectorListView.currentIndex = proxyServersModel.mapFromSource(ServersModel.defaultIndex) + serverSelectorListView.selectedIndex = proxyServersModel.mapFromSource(ServersModel.defaultIndex) } else { - serverSelectorListView.currentIndex = 0 + serverSelectorListView.selectedIndex = 0 } serverSelectorListView.triggerCurrentItem() @@ -363,7 +363,7 @@ PageType { function handler() { serverSelector.text = selectedText - ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) + ServersModel.processedIndex = proxyServersModel.mapToSource(selectedIndex) } } } @@ -401,8 +401,6 @@ PageType { ] } - currentIndex: 0 - clickedFunction: function() { handler() @@ -414,7 +412,7 @@ PageType { function onSeverSelectorIndexChanged() { var defaultContainer = proxyContainersModel.mapFromSource(ServersModel.getProcessedServerData("defaultContainer")) - protocolSelectorListView.currentIndex = defaultContainer + protocolSelectorListView.selectedIndex = defaultContainer protocolSelectorListView.triggerCurrentItem() } } @@ -429,7 +427,7 @@ PageType { protocolSelector.text = selectedText - ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) + ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(selectedIndex)) fillConnectionTypeModel() @@ -444,7 +442,7 @@ PageType { function fillConnectionTypeModel() { root.connectionTypesModel = [amneziaConnectionFormat] - var index = proxyContainersModel.mapToSource(currentIndex) + var index = proxyContainersModel.mapToSource(selectedIndex) if (index === ContainerProps.containerFromString("amnezia-openvpn")) { root.connectionTypesModel.push(openVpnConnectionFormat) From 9620a24d6ae1796e9198954a4065ae70082f6ba4 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 15 Dec 2024 23:19:41 +0100 Subject: [PATCH 074/100] remove manual focus control on `PageShare` --- client/ui/qml/Pages2/PageShare.qml | 106 ----------------------------- 1 file changed, 106 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 60c698e1..049df1dd 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -256,9 +256,6 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 0 - if (!GC.isMobile()) { - clientNameTextField.textField.forceActiveFocus() - } } } @@ -543,10 +540,6 @@ PageType { actionButtonFunction: function() { root.isSearchBarVisible = true } - - Keys.onTabPressed: clientsListView.model.count > 0 ? - clientsListView.forceActiveFocus() : - lastItemTabClicked(focusItem) } RowLayout { @@ -560,35 +553,13 @@ PageType { textFieldPlaceholderText: qsTr("Search") - Connections { - target: root - function onIsSearchBarVisibleChanged() { - if (root.isSearchBarVisible) { - // searchTextField.textField.forceActiveFocus() - } else { - searchTextField.textFieldText = "" - if (!GC.isMobile()) { - // usersHeader.actionButton.forceActiveFocus() - } - } - } - } - Keys.onEscapePressed: { root.isSearchBarVisible = false } function navigateTo() { - if (GC.isMobile()) { - // focusItem.forceActiveFocus() - return; - } - if (searchTextField.textFieldText === "") { root.isSearchBarVisible = false - // usersHeader.actionButton.forceActiveFocus() - } else { - // closeSearchButton.forceActiveFocus() } } @@ -602,16 +573,6 @@ PageType { image: "qrc:/images/controls/close.svg" imageColor: AmneziaStyle.color.paleGray - Keys.onTabPressed: { - if (!GC.isMobile()) { - if (clientsListView.model.count > 0) { - // clientsListView.forceActiveFocus() - } else { - // lastItemTabClicked(focusItem) - } - } - } - function clickedFunc() { root.isSearchBarVisible = false } @@ -631,30 +592,6 @@ PageType { 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 @@ -672,8 +609,6 @@ PageType { implicitWidth: clientsListView.width implicitHeight: delegateContent.implicitHeight - // property alias focusItem: clientFocusItem.rightButton - ColumnLayout { id: delegateContent @@ -703,12 +638,6 @@ PageType { parent: root - onClosed: { - if (!GC.isMobile()) { - // focusItem.forceActiveFocus() - } - } - width: root.width height: root.height @@ -725,14 +654,6 @@ PageType { clientInfoDrawer.expandedHeight = expandedStateContent.implicitHeight + 32 } - Connections { - target: clientInfoDrawer - enabled: !GC.isMobile() - function onOpened() { - // focusItem1.forceActiveFocus() - } - } - Header2TextType { Layout.maximumWidth: parent.width Layout.bottomMargin: 24 @@ -809,12 +730,6 @@ PageType { anchors.fill: parent expandedHeight: root.height * 0.35 - onClosed: { - if (!GC.isMobile()) { - // focusItem1.forceActiveFocus() - } - } - expandedStateContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left @@ -823,14 +738,6 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 - Connections { - target: clientNameEditDrawer - enabled: !GC.isMobile() - function onOpened() { - // clientNameEditor.textField.forceActiveFocus() - } - } - TextFieldWithHeaderType { id: clientNameEditor Layout.fillWidth: true @@ -912,18 +819,5 @@ PageType { id: shareConnectionDrawer anchors.fill: parent - onClosed: { - if (!GC.isMobile()) { - // clientNameTextField.textField.forceActiveFocus() - } - } - } - - MouseArea { - anchors.fill: parent - onPressed: function(mouse) { - // forceActiveFocus() - mouse.accepted = false - } } } From a57e94e4f2cbae1b6592c1260eb83c284d13eb80 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Dec 2024 22:12:00 +0100 Subject: [PATCH 075/100] format `ListViewFocusController` --- .../controllers/listViewFocusController.cpp | 215 ++++++++---------- .../ui/controllers/listViewFocusController.h | 39 ++-- 2 files changed, 122 insertions(+), 132 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index c26167d1..f6290869 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -1,115 +1,110 @@ #include "listViewFocusController.h" -#include -#include #include -#include +#include #include +#include - -bool isVisible(QObject* item) +namespace focusControlTools { - 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"; + bool isVisible(QObject *item) + { + const auto res = item->property("visible").toBool(); return res; } - const auto children = object->children(); + bool isFocusable(QObject *item) + { + const auto res = item->property("isFocusable").toBool(); + return res; + } - for(const auto child : children) { - if (child - && isFocusable(child) - && isEnabled(child) - && isVisible(child) - ) { - res.append(child); - } else { - res.append(getItemsChain(child)); + 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; } } - 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; - } -} +} // namespace focusControlTools /////////////////////////////////////////////////////////////////////////////////////////////////// -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"} +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; + m_header = headerItemProperty.canConvert() ? headerItemProperty.value() : nullptr; QVariant footerItemProperty = m_listView->property("footerItem"); - m_footer = footerItemProperty.canConvert() ? footerItemProperty.value() : nullptr; + m_footer = footerItemProperty.canConvert() ? footerItemProperty.value() : nullptr; } ListViewFocusController::~ListViewFocusController() { - } void ListViewFocusController::viewAtCurrentIndex() const { - switch(m_currentSection) { - case Section::Default: - [[fallthrough]]; + switch (m_currentSection) { + case Section::Default: [[fallthrough]]; case Section::Header: { qDebug() << "===>> [FOCUS ON BEGINNING...]"; QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); @@ -117,9 +112,8 @@ void ListViewFocusController::viewAtCurrentIndex() const } 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) + QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index + Q_ARG(int, 2)); // PositionMode (0 = Visible) break; } case Section::Footer: { @@ -128,7 +122,6 @@ void ListViewFocusController::viewAtCurrentIndex() const break; } } - } int ListViewFocusController::size() const @@ -151,9 +144,9 @@ void ListViewFocusController::nextDelegate() { const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]"; - switch(m_currentSection) { + switch (m_currentSection) { case Section::Default: { - if(hasHeader()) { + if (hasHeader()) { m_currentSection = Section::Header; viewAtCurrentIndex(); break; @@ -194,9 +187,9 @@ void ListViewFocusController::nextDelegate() void ListViewFocusController::previousDelegate() { - switch(m_currentSection) { + switch (m_currentSection) { case Section::Default: { - if(hasFooter()) { + if (hasFooter()) { m_currentSection = Section::Footer; break; } @@ -237,22 +230,20 @@ void ListViewFocusController::decrementIndex() m_delegateIndex--; } -QQuickItem* ListViewFocusController::itemAtIndex(const int index) const +QQuickItem *ListViewFocusController::itemAtIndex(const int index) const { - QQuickItem* item{nullptr}; + QQuickItem *item { nullptr }; - QMetaObject::invokeMethod(m_listView, "itemAtIndex", - Q_RETURN_ARG(QQuickItem*, item), - Q_ARG(int, index)); + QMetaObject::invokeMethod(m_listView, "itemAtIndex", Q_RETURN_ARG(QQuickItem *, item), Q_ARG(int, index)); return item; } -QQuickItem* ListViewFocusController::currentDelegate() const +QQuickItem *ListViewFocusController::currentDelegate() const { - QQuickItem* result{nullptr}; + QQuickItem *result { nullptr }; - switch(m_currentSection) { + switch (m_currentSection) { case Section::Default: { qWarning() << "No elements..."; break; @@ -273,7 +264,7 @@ QQuickItem* ListViewFocusController::currentDelegate() const return result; } -QQuickItem* ListViewFocusController::focusedItem() const +QQuickItem *ListViewFocusController::focusedItem() const { return m_focusedItem; } @@ -285,7 +276,7 @@ void ListViewFocusController::focusNextItem() return; } - m_focusChain = getItemsChain(currentDelegate()); + m_focusChain = focusControlTools::getItemsChain(currentDelegate()); if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -294,7 +285,7 @@ void ListViewFocusController::focusNextItem() return; } m_focusedItemIndex++; - m_focusedItem = qobject_cast(m_focusChain.at(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); } @@ -307,7 +298,7 @@ void ListViewFocusController::focusPreviousItem() if (m_focusChain.empty()) { qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = getItemsChain(currentDelegate()); + m_focusChain = focusControlTools::getItemsChain(currentDelegate()); } if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -319,7 +310,7 @@ void ListViewFocusController::focusPreviousItem() m_focusedItemIndex = m_focusChain.size(); } m_focusedItemIndex--; - m_focusedItem = qobject_cast(m_focusChain.at(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); } @@ -343,12 +334,12 @@ bool ListViewFocusController::isLastFocusItemInDelegate() const bool ListViewFocusController::hasHeader() const { - return m_header && !getItemsChain(m_header).isEmpty(); + return m_header && !focusControlTools::getItemsChain(m_header).isEmpty(); } bool ListViewFocusController::hasFooter() const { - return m_footer && !getItemsChain(m_footer).isEmpty(); + return m_footer && !focusControlTools::getItemsChain(m_footer).isEmpty(); } bool ListViewFocusController::isFirstFocusItemInListView() const @@ -366,9 +357,7 @@ bool ListViewFocusController::isFirstFocusItemInListView() const case Section::Default: { return true; } - default: - qWarning() << "Wrong section"; - return true; + default: qWarning() << "Wrong section"; return true; } } @@ -387,9 +376,7 @@ bool ListViewFocusController::isLastFocusItemInListView() const case Section::Footer: { return isLastFocusItemInDelegate(); } - default: - qWarning() << "Wrong section"; - return true; + default: qWarning() << "Wrong section"; return true; } } diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index df14b6be..86015022 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -1,19 +1,22 @@ #ifndef LISTVIEWFOCUSCONTROLLER_H #define LISTVIEWFOCUSCONTROLLER_H +#include #include -#include -#include #include +#include +#include +namespace focusControlTools +{ + bool isEnabled(QObject *item); + bool isFocusable(QObject *item); + bool isMore(QObject *item1, QObject *item2); + bool isLess(QObject *item1, QObject *item2); + QList getSubChain(QObject *object); -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); + void printItems(const QList &items, QObject *current_item); +} /*! * \brief The ListViewFocusController class manages the focus of elements in ListView @@ -27,7 +30,7 @@ class ListViewFocusController : public QObject { Q_OBJECT public: - explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr); + explicit ListViewFocusController(QQuickItem *listView, QObject *parent = nullptr); ~ListViewFocusController(); void nextDelegate(); @@ -54,19 +57,19 @@ private: int currentIndex() const; void setDelegateIndex(int index); void viewAtCurrentIndex() const; - QQuickItem* itemAtIndex(const int index) const; - QQuickItem* currentDelegate() const; - QQuickItem* focusedItem() 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; + QQuickItem *m_listView; + QList m_focusChain; Section m_currentSection; - QQuickItem* m_header; - QQuickItem* m_footer; - QQuickItem* m_focusedItem; // Pointer to focused item on Delegate + QQuickItem *m_header; + QQuickItem *m_footer; + QQuickItem *m_focusedItem; // Pointer to focused item on Delegate qsizetype m_focusedItemIndex; qsizetype m_delegateIndex; bool m_isReturnNeeded; From 11d20974f0382ea0f84e9827e6beccdd60caee16 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Dec 2024 22:12:15 +0100 Subject: [PATCH 076/100] format `FocusController` --- client/ui/controllers/focusController.cpp | 100 ++++++++++------------ client/ui/controllers/focusController.h | 25 +++--- 2 files changed, 56 insertions(+), 69 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index f206620d..a3a10806 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -1,19 +1,16 @@ #include "focusController.h" -#include "listViewFocusController.h" - -#include #include +#include - -bool isListView(QObject* item) +bool isListView(QObject *item) { return item->inherits("QQuickListView"); } -bool isOnTheScene(QObject* object) +bool isOnTheScene(QObject *object) { - QQuickItem* item = qobject_cast(object); + QQuickItem *item = qobject_cast(object); if (!item) { qWarning() << "Couldn't recognize object as item"; return false; @@ -26,7 +23,7 @@ bool isOnTheScene(QObject* object) QRectF itemRect = item->mapRectToScene(item->childrenRect()); - QQuickWindow* window = item->window(); + QQuickWindow *window = item->window(); if (!window) { qWarning() << "Couldn't get the window on the Scene check"; return false; @@ -39,13 +36,14 @@ bool isOnTheScene(QObject* object) } 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; + // qDebug() << (res ? "===>> item is inside the Scene" : "===>> ITEM IS OUTSIDE THE SCENE") << " itemRect: " << + // itemRect << "; windowRect: " << windowRect; return res; } -QList getSubChain(QObject* object) +QList getSubChain(QObject *object) { - QList res; + QList res; if (!object) { qDebug() << "The object is NULL"; return res; @@ -53,12 +51,8 @@ QList getSubChain(QObject* object) const auto children = object->children(); - for(const auto child : children) { - if (child - && isFocusable(child) - && isOnTheScene(child) - && isEnabled(child) - ) { + for (const auto child : children) { + if (child && focusControlTools::isFocusable(child) && isOnTheScene(child) && focusControlTools::isEnabled(child)) { res.append(child); } else { res.append(getSubChain(child)); @@ -67,29 +61,28 @@ QList getSubChain(QObject* object) 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} +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(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); - }); + QObject::connect(this, &FocusController::focusedItemChanged, this, + [this]() { m_focusedItem->forceActiveFocus(Qt::TabFocusReason); }); } - void FocusController::nextKeyTabItem() { nextItem(Direction::Forward); @@ -120,7 +113,7 @@ void FocusController::nextKeyRightItem() nextItem(Direction::Forward); } -void FocusController::setFocusItem(QQuickItem* item) +void FocusController::setFocusItem(QQuickItem *item) { if (m_focusedItem != item) { m_focusedItem = item; @@ -137,7 +130,7 @@ void FocusController::setFocusOnDefaultItem() setFocusItem(m_defaultFocusItem.get()); } -void FocusController::pushRootObject(QObject* object) +void FocusController::pushRootObject(QObject *object) { qDebug() << "===>> Calling < pushRootObject >..."; m_rootObjects.push(object); @@ -147,7 +140,7 @@ void FocusController::pushRootObject(QObject* object) qDebug() << "===>> ROOT OBJECTS: " << m_rootObjects; } -void FocusController::dropRootObject(QObject* object) +void FocusController::dropRootObject(QObject *object) { qDebug() << "===>> Calling < dropRootObject >..."; if (m_rootObjects.empty()) { @@ -160,7 +153,7 @@ void FocusController::dropRootObject(QObject* object) m_rootObjects.pop(); dropListView(); setFocusOnDefaultItem(); - if(m_rootObjects.size()) { + if (m_rootObjects.size()) { qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); } else { qDebug() << "===>> ROOT OBJECT is changed to DEFAULT"; @@ -182,11 +175,9 @@ void FocusController::reload(Direction direction) qDebug() << "===>> Calling < reload >..."; m_focusChain.clear(); - QObject* rootObject = (m_rootObjects.empty() - ? m_engine->rootObjects().value(0) - : m_rootObjects.top()); + QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top()); - if(!rootObject) { + if (!rootObject) { qCritical() << "No ROOT OBJECT found!"; resetRootObject(); dropListView(); @@ -197,7 +188,8 @@ void FocusController::reload(Direction direction) m_focusChain.append(getSubChain(rootObject)); - std::sort(m_focusChain.begin(), m_focusChain.end(), direction == Direction::Forward ? isLess : isMore); + std::sort(m_focusChain.begin(), m_focusChain.end(), + direction == Direction::Forward ? focusControlTools::isLess : focusControlTools::isMore); if (m_focusChain.empty()) { qWarning() << "Focus chain is empty!"; @@ -220,7 +212,7 @@ void FocusController::nextItem(Direction direction) return; } - if(m_focusChain.empty()) { + if (m_focusChain.empty()) { qWarning() << "There are no items to navigate"; setFocusOnDefaultItem(); return; @@ -239,19 +231,19 @@ void FocusController::nextItem(Direction direction) focusedItemIndex++; } - const auto focusedItem = qobject_cast(m_focusChain.at(focusedItemIndex)); + const auto focusedItem = qobject_cast(m_focusChain.at(focusedItemIndex)); - if(focusedItem == nullptr) { + if (focusedItem == nullptr) { qWarning() << "Failed to get item to focus on. Setting focus on default"; setFocusOnDefaultItem(); return; } - - if(isListView(focusedItem)) { + + if (isListView(focusedItem)) { qDebug() << "===>> Found [ListView]"; m_lvfc = new ListViewFocusController(focusedItem, this); m_focusedItem = focusedItem; - if(direction == Direction::Forward) { + if (direction == Direction::Forward) { m_lvfc->nextDelegate(); focusNextListViewItem(); } else { @@ -263,7 +255,7 @@ void FocusController::nextItem(Direction direction) setFocusItem(focusedItem); - printItems(m_focusChain, focusedItem); + focusControlTools::printItems(m_focusChain, focusedItem); /////////////////////////////////////////////////////////// @@ -271,7 +263,7 @@ void FocusController::nextItem(Direction direction) qDebug() << "===>> CURRENT ACTIVE ITEM: " << w->activeFocusItem(); qDebug() << "===>> CURRENT FOCUS OBJECT: " << w->focusObject(); - if(m_rootObjects.empty()) { + if (m_rootObjects.empty()) { qDebug() << "===>> ROOT OBJECT IS DEFAULT"; } else { qDebug() << "===>> ROOT OBJECT: " << m_rootObjects.top(); @@ -317,7 +309,7 @@ void FocusController::dropListView() { qDebug() << "===>> Calling < dropListView >..."; - if(m_lvfc) { + if (m_lvfc) { delete m_lvfc; m_lvfc = nullptr; } diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/focusController.h index 54e0a05c..dfcac57a 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/focusController.h @@ -1,14 +1,9 @@ #ifndef FOCUSCONTROLLER_H #define FOCUSCONTROLLER_H -#include -#include -#include +#include "ui/controllers/listViewFocusController.h" - -class QQuickItem; -class QQmlApplicationEngine; -class ListViewFocusController; +#include /*! * \brief The FocusController class makes focus control more straightforward @@ -20,7 +15,7 @@ class FocusController : public QObject { Q_OBJECT public: - explicit FocusController(QQmlApplicationEngine* engine, QObject *parent = nullptr); + explicit FocusController(QQmlApplicationEngine *engine, QObject *parent = nullptr); ~FocusController() override = default; Q_INVOKABLE void nextKeyTabItem(); @@ -29,10 +24,10 @@ public: Q_INVOKABLE void nextKeyDownItem(); Q_INVOKABLE void nextKeyLeftItem(); Q_INVOKABLE void nextKeyRightItem(); - Q_INVOKABLE void setFocusItem(QQuickItem* item); + 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 pushRootObject(QObject *object); + Q_INVOKABLE void dropRootObject(QObject *object); Q_INVOKABLE void resetRootObject(); private: @@ -48,12 +43,12 @@ private: 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; + 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 + ListViewFocusController *m_lvfc; // ListView focus manager signals: void focusedItemChanged(); From 2266702a0b2fa327e7b371470cb68934f13ddc1e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 18 Dec 2024 23:45:43 +0100 Subject: [PATCH 077/100] add `focusControl` with utility functions for focus control --- client/CMakeLists.txt | 2 + client/utils/focusControl.cpp | 131 ++++++++++++++++++++++++++++++++++ client/utils/focusControl.h | 30 ++++++++ 3 files changed, 163 insertions(+) create mode 100644 client/utils/focusControl.cpp create mode 100644 client/utils/focusControl.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 05f9f17c..beebc3ee 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -146,6 +146,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h + ${CMAKE_CURRENT_LIST_DIR}/utils/focusControl.h ) # Mozilla headres @@ -197,6 +198,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils/focusControl.cpp ) # Mozilla sources diff --git a/client/utils/focusControl.cpp b/client/utils/focusControl.cpp new file mode 100644 index 00000000..a3107d55 --- /dev/null +++ b/client/utils/focusControl.cpp @@ -0,0 +1,131 @@ +#include "focusControl.h" +#include "utils/focusControl.h" + +#include +#include +#include + +namespace focusControl +{ + 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 isEnabled(QObject *obj) + { + const auto item = qobject_cast(obj); + return item && item->isEnabled(); + } + + 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; + } + + 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()) { + 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)); + return res; + } + + bool isMore(QObject *item1, QObject *item2) + { + return !isLess(item1, item2); + } + + 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()); + } + + 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; + } + + 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; + } + } +} // namespace focusControl \ No newline at end of file diff --git a/client/utils/focusControl.h b/client/utils/focusControl.h new file mode 100644 index 00000000..96ab3a83 --- /dev/null +++ b/client/utils/focusControl.h @@ -0,0 +1,30 @@ +#ifndef FOCUSCONTROL_H +#define FOCUSCONTROL_H + +#include +#include + +namespace focusControl +{ + bool isEnabled(QObject *item); + bool isVisible(QObject *item); + bool isFocusable(QObject *item); + bool isListView(QObject *item); + bool isOnTheScene(QObject *object); + bool isMore(QObject *item1, QObject *item2); + bool isLess(QObject *item1, QObject *item2); + + /*! + * \brief Make focus chain of elements which are on the scene + */ + QList getSubChain(QObject *object); + + /*! + * \brief Make focus chain of elements which could be not on the scene + */ + QList getItemsChain(QObject *object); + + void printItems(const QList &items, QObject *current_item); +} + +#endif // FOCUSCONTROL_H \ No newline at end of file From 7670d9dace63efc3e86a03e8e579c0edc77e3e54 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 18 Dec 2024 23:57:08 +0100 Subject: [PATCH 078/100] refactor `listViewFocusController` acoording to `focusControl` --- .../controllers/listViewFocusController.cpp | 84 ++----------------- .../ui/controllers/listViewFocusController.h | 11 --- 2 files changed, 5 insertions(+), 90 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index f6290869..565f3e28 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -1,81 +1,7 @@ #include "listViewFocusController.h" +#include "utils/focusControl.h" -#include -#include #include -#include - -namespace focusControlTools -{ - 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; - } - } -} // namespace focusControlTools - -/////////////////////////////////////////////////////////////////////////////////////////////////// ListViewFocusController::ListViewFocusController(QQuickItem *listView, QObject *parent) : QObject { parent }, @@ -276,7 +202,7 @@ void ListViewFocusController::focusNextItem() return; } - m_focusChain = focusControlTools::getItemsChain(currentDelegate()); + m_focusChain = focusControl::getItemsChain(currentDelegate()); if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -298,7 +224,7 @@ void ListViewFocusController::focusPreviousItem() if (m_focusChain.empty()) { qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = focusControlTools::getItemsChain(currentDelegate()); + m_focusChain = focusControl::getItemsChain(currentDelegate()); } if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -334,12 +260,12 @@ bool ListViewFocusController::isLastFocusItemInDelegate() const bool ListViewFocusController::hasHeader() const { - return m_header && !focusControlTools::getItemsChain(m_header).isEmpty(); + return m_header && !focusControl::getItemsChain(m_header).isEmpty(); } bool ListViewFocusController::hasFooter() const { - return m_footer && !focusControlTools::getItemsChain(m_footer).isEmpty(); + return m_footer && !focusControl::getItemsChain(m_footer).isEmpty(); } bool ListViewFocusController::isFirstFocusItemInListView() const diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index 86015022..a2550dd3 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -7,17 +7,6 @@ #include #include -namespace focusControlTools -{ - 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 From 1029142b4791603868d93bdd6ae10dc0642085f4 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 18 Dec 2024 23:59:14 +0100 Subject: [PATCH 079/100] refactor `focusConroller` according to `focusControl` --- client/ui/controllers/focusController.cpp | 69 ++--------------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index a3a10806..f43a8693 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -1,66 +1,9 @@ #include "focusController.h" +#include "utils/focusControl.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 && focusControlTools::isFocusable(child) && isOnTheScene(child) && focusControlTools::isEnabled(child)) { - res.append(child); - } else { - res.append(getSubChain(child)); - } - } - return res; -} - FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent) : QObject { parent }, m_engine { engine }, @@ -186,10 +129,10 @@ void FocusController::reload(Direction direction) qDebug() << "===>> ROOT OBJECTS: " << rootObject; - m_focusChain.append(getSubChain(rootObject)); + m_focusChain.append(focusControl::getSubChain(rootObject)); std::sort(m_focusChain.begin(), m_focusChain.end(), - direction == Direction::Forward ? focusControlTools::isLess : focusControlTools::isMore); + direction == Direction::Forward ? focusControl::isLess : focusControl::isMore); if (m_focusChain.empty()) { qWarning() << "Focus chain is empty!"; @@ -205,7 +148,7 @@ void FocusController::nextItem(Direction direction) reload(direction); - if (m_lvfc && isListView(m_focusedItem)) { + if (m_lvfc && focusControl::isListView(m_focusedItem)) { direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); qDebug() << "===>> Handling the [ ListView ]..."; @@ -239,7 +182,7 @@ void FocusController::nextItem(Direction direction) return; } - if (isListView(focusedItem)) { + if (focusControl::isListView(focusedItem)) { qDebug() << "===>> Found [ListView]"; m_lvfc = new ListViewFocusController(focusedItem, this); m_focusedItem = focusedItem; @@ -255,7 +198,7 @@ void FocusController::nextItem(Direction direction) setFocusItem(focusedItem); - focusControlTools::printItems(m_focusChain, focusedItem); + focusControl::printItems(m_focusChain, focusedItem); /////////////////////////////////////////////////////////// From 14d67503a8508e75a763aad937ddd6684ab00a6c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 18 Dec 2024 23:59:53 +0100 Subject: [PATCH 080/100] add `printSectionName` method to `listViewController` --- client/ui/controllers/listViewFocusController.cpp | 10 ++++++++-- client/ui/controllers/listViewFocusController.h | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 565f3e28..28d23d4d 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -68,8 +68,8 @@ void ListViewFocusController::setDelegateIndex(int index) void ListViewFocusController::nextDelegate() { - const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; - qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]"; + printSectionName(); + switch (m_currentSection) { case Section::Default: { if (hasHeader()) { @@ -268,6 +268,12 @@ bool ListViewFocusController::hasFooter() const return m_footer && !focusControl::getItemsChain(m_footer).isEmpty(); } +void ListViewFocusController::printSectionName() const +{ + const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; + qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]"; +} + bool ListViewFocusController::isFirstFocusItemInListView() const { switch (m_currentSection) { diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index a2550dd3..b77edfd6 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -53,6 +53,8 @@ private: bool hasHeader() const; bool hasFooter() const; + void printSectionName() const; + QQuickItem *m_listView; QList m_focusChain; Section m_currentSection; From ae31bafeb937a3c6d7e05ebdac6b0a06c52b55b0 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Thu, 19 Dec 2024 00:22:59 +0100 Subject: [PATCH 081/100] remove arrow from `Close application` item --- client/ui/qml/Pages2/PageSettings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 604d3074..c44e52ae 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -143,8 +143,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 clickedFunction: function() { PageController.closeApplication() From fa64c8d68d32c57e23f1d4fdf7724fd9c0333c31 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 02:05:28 +0100 Subject: [PATCH 082/100] fix focus movement in `ServersListView` --- client/ui/qml/Components/ServersListView.qml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 1711a458..8eccb324 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -18,6 +18,8 @@ import "../Config" ListView { id: root + property int selectedIndex: ServersModel.defaultIndex + anchors.top: serversMenuHeader.bottom anchors.right: parent.right anchors.left: parent.left @@ -25,7 +27,6 @@ ListView { anchors.topMargin: 16 model: ServersModel - currentIndex: ServersModel.defaultIndex ScrollBar.vertical: ScrollBarType { id: scrollBar @@ -38,11 +39,12 @@ ListView { Connections { target: ServersModel function onDefaultServerIndexChanged(serverIndex) { - root.currentIndex = serverIndex + root.selectedIndex = serverIndex } } clip: true + reuseItems: true delegate: Item { id: menuContentDelegate @@ -68,16 +70,17 @@ ListView { objectName: "serverRadioButtonRowLayout" Layout.fillWidth: true + VerticalRadioButton { id: serverRadioButton objectName: "serverRadioButton" Layout.fillWidth: true - focus: true + text: name descriptionText: serverDescription - checked: index === root.currentIndex + checked: index === root.selectedIndex checkable: !ConnectionController.isConnected ButtonGroup.group: serversRadioButtonGroup @@ -88,17 +91,11 @@ ListView { return } - root.currentIndex = index + root.selectedIndex = index ServersModel.defaultIndex = index } - MouseArea { - anchors.fill: serverRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - Keys.onEnterPressed: serverRadioButton.clicked() Keys.onReturnPressed: serverRadioButton.clicked() } From 441a2f6df6058e1f9d8a6cd4f7e1da0cf636b725 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 02:15:48 +0100 Subject: [PATCH 083/100] `Restore from backup` is visible only on start screen --- client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 2f17de84..4598c9c2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -252,7 +252,7 @@ PageType { 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 bool isVisible: PageController.isStartPageVisible() property var handler: function() { var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) From b29b5962d234e5f9fcb452b85e35a4fe712bd5f7 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 02:17:50 +0100 Subject: [PATCH 084/100] `I have nothing` is visible only on start screen --- client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 4598c9c2..f5ac64b1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -304,7 +304,7 @@ PageType { 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 bool isVisible: PageController.isStartPageVisible() property var handler: function() { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) } From 53aa14a2ee19008d0002584502df3e229b2904f9 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 02:26:05 +0100 Subject: [PATCH 085/100] fix back button on `SelectLanguageDrawer` --- client/ui/qml/Components/SelectLanguageDrawer.qml | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index c0af3acd..2c026848 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -32,9 +32,6 @@ DrawerType2 { id: backButton Layout.fillWidth: true - Layout.topMargin: 16 - Layout.rightMargin: 16 - Layout.leftMargin: 16 backButtonImage: "qrc:/images/controls/arrow-left.svg" backButtonFunction: function() { root.closeTriggered() } From c973b8868b6899ba23f459fb798614ef124c40e1 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 15:30:20 +0100 Subject: [PATCH 086/100] rename `focusControl` to `qmlUtils` --- client/ui/controllers/focusController.cpp | 4 ++-- client/ui/controllers/listViewFocusController.cpp | 2 +- client/utils/{focusControl.cpp => qmlUtils.cpp} | 3 +-- client/utils/{focusControl.h => qmlUtils.h} | 0 4 files changed, 4 insertions(+), 5 deletions(-) rename client/utils/{focusControl.cpp => qmlUtils.cpp} (97%) rename client/utils/{focusControl.h => qmlUtils.h} (100%) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index f43a8693..dc5b95c0 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -1,5 +1,5 @@ #include "focusController.h" -#include "utils/focusControl.h" +#include "utils/qmlUtils.h" #include #include @@ -60,11 +60,11 @@ 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 << "!"; } + emit focusedItemChanged(); } void FocusController::setFocusOnDefaultItem() diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 28d23d4d..574b3c9c 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -1,5 +1,5 @@ #include "listViewFocusController.h" -#include "utils/focusControl.h" +#include "utils/qmlUtils.h" #include diff --git a/client/utils/focusControl.cpp b/client/utils/qmlUtils.cpp similarity index 97% rename from client/utils/focusControl.cpp rename to client/utils/qmlUtils.cpp index a3107d55..f205f615 100644 --- a/client/utils/focusControl.cpp +++ b/client/utils/qmlUtils.cpp @@ -1,5 +1,4 @@ -#include "focusControl.h" -#include "utils/focusControl.h" +#include "qmlUtils.h" #include #include diff --git a/client/utils/focusControl.h b/client/utils/qmlUtils.h similarity index 100% rename from client/utils/focusControl.h rename to client/utils/qmlUtils.h From 0cdd2ff8260413630da8c4f062c6b89abd197d2f Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 20 Dec 2024 15:35:40 +0100 Subject: [PATCH 087/100] fix `CMakeLists.txt` --- client/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index beebc3ee..3ef92385 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -146,7 +146,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h - ${CMAKE_CURRENT_LIST_DIR}/utils/focusControl.h + ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h ) # Mozilla headres @@ -198,7 +198,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp - ${CMAKE_CURRENT_LIST_DIR}/utils/focusControl.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp ) # Mozilla sources From 8767f487b88610048b46299dc499a73b35f3f96e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 22 Dec 2024 16:28:13 +0100 Subject: [PATCH 088/100] fix `ScrollBarType` --- client/ui/qml/Controls2/ScrollBarType.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/ScrollBarType.qml b/client/ui/qml/Controls2/ScrollBarType.qml index 0e5d244e..26e8edb6 100644 --- a/client/ui/qml/Controls2/ScrollBarType.qml +++ b/client/ui/qml/Controls2/ScrollBarType.qml @@ -7,5 +7,5 @@ import "../Controls2" ScrollBar { id: root - policy: ScrollBar.AlwaysOn + policy: parent.height >= parent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn } From 13e680f6e1736d4d1180713a290b187b30925ada Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 22 Dec 2024 16:38:07 +0100 Subject: [PATCH 089/100] fix `PageSetupWizardApiServicesList` --- .../ui/qml/Pages2/PageSetupWizardApiServicesList.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index 5e00eebd..c3e3edbc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -51,16 +51,20 @@ PageType { spacing: 0 property bool isFocusable: true - selectedIndex: 1 + clip: true + reuseItems: true + model: ApiServicesModel - ScrollBar.vertical: ScrollBar {} + ScrollBar.vertical: ScrollBarType {} delegate: Item { implicitWidth: servicesListView.width implicitHeight: delegateContent.implicitHeight + enabled: isServiceAvailable + ColumnLayout { id: delegateContent @@ -80,8 +84,6 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" - enabled: isServiceAvailable - onClicked: { if (isServiceAvailable) { ApiServicesModel.setServiceIndex(index) From 867ad8ee3c65ac5160244a81c41b536c4173a8a7 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 00:33:05 +0100 Subject: [PATCH 090/100] fix focus movement on dynamic delegates in listView --- client/ui/controllers/focusController.cpp | 7 ++++--- client/ui/controllers/listViewFocusController.cpp | 9 +++++++-- client/ui/controllers/listViewFocusController.h | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index dc5b95c0..c8ec7fbc 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -216,14 +216,14 @@ void FocusController::nextItem(Direction direction) void FocusController::focusNextListViewItem() { qDebug() << "===>> Calling < focusNextListViewItem >..."; - + m_lvfc->reloadFocusChain(); 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"; + qDebug() << "===>> End of delegate's elements was reached. Going to the next delegate"; m_lvfc->resetFocusChain(); m_lvfc->nextDelegate(); } @@ -234,13 +234,14 @@ void FocusController::focusNextListViewItem() void FocusController::focusPreviousListViewItem() { qDebug() << "===>> Calling < focusPreviousListViewItem >..."; - + m_lvfc->reloadFocusChain(); 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()) { + qDebug() << "===>> End of delegate's elements was reached. Going to the previous delegate"; m_lvfc->resetFocusChain(); m_lvfc->previousDelegate(); } diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 574b3c9c..1af6d5b4 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -202,7 +202,7 @@ void ListViewFocusController::focusNextItem() return; } - m_focusChain = focusControl::getItemsChain(currentDelegate()); + reloadFocusChain(); if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -224,7 +224,7 @@ void ListViewFocusController::focusPreviousItem() if (m_focusChain.empty()) { qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - m_focusChain = focusControl::getItemsChain(currentDelegate()); + reloadFocusChain(); } if (m_focusChain.empty()) { qWarning() << "No elements found in the delegate. Going to next delegate..."; @@ -248,6 +248,11 @@ void ListViewFocusController::resetFocusChain() m_focusedItemIndex = -1; } +void ListViewFocusController::reloadFocusChain() +{ + m_focusChain = focusControl::getItemsChain(currentDelegate()); +} + bool ListViewFocusController::isFirstFocusItemInDelegate() const { return m_focusedItem && (m_focusedItem == m_focusChain.first()); diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index b77edfd6..a0d7d788 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -28,6 +28,7 @@ public: void focusNextItem(); void focusPreviousItem(); void resetFocusChain(); + void reloadFocusChain(); bool isFirstFocusItemInListView() const; bool isFirstFocusItemInDelegate() const; bool isLastFocusItemInListView() const; From 2c1f3eaf3a611556dcdf1fa6aa7fca621f937092 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 20:26:22 +0100 Subject: [PATCH 091/100] refactor `PageSetupWizardProtocols` --- .../qml/Pages2/PageSetupWizardProtocols.qml | 139 +++++------------- 1 file changed, 40 insertions(+), 99 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 15c1170f..6b6b6038 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -34,125 +34,66 @@ PageType { } } - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListView { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + property bool isFocusable: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottomMargin: 20 + ScrollBar.vertical: ScrollBarType {} - Item { - width: parent.width - height: header.implicitHeight + header: ColumnLayout { + width: listView.width - HeaderType { - id: header + HeaderType { + id: header - anchors.fill: parent + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 + headerText: qsTr("VPN protocol") + descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") + } + } - width: parent.width + model: proxyContainersModel + clip: true + spacing: 0 + reuseItems: true + snapMode: ListView.SnapToItem - headerText: qsTr("VPN protocol") - descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + + text: name + descriptionText: description + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } - ListView { - id: containers - width: parent.width - height: containers.contentItem.height - // currentIndex: -1 - clip: true - interactive: false - model: proxyContainersModel - - function ensureCurrentItemVisible() { - if (currentIndex >= 0) { - if (currentItem.y < fl.contentY) { - fl.contentY = currentItem.y - } else if (currentItem.y + currentItem.height + header.height > fl.contentY + fl.height) { - fl.contentY = currentItem.y + currentItem.height + header.height - fl.height + 40 // 40 is a bottom margin - } - } - } - - activeFocusOnTab: true - Keys.onTabPressed: { - if (currentIndex < this.count - 1) { - this.incrementCurrentIndex() - } else { - this.currentIndex = 0 - focusItem.forceActiveFocus() - } - - ensureCurrentItemVisible() - } - - onVisibleChanged: { - if (visible) { - currentIndex = 0 - } - } - - delegate: Item { - implicitWidth: containers.width - implicitHeight: delegateContent.implicitHeight - - onActiveFocusChanged: { - if (activeFocus) { - container.rightButton.forceActiveFocus() - } - } - - ColumnLayout { - id: delegateContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelWithButtonType { - id: container - Layout.fillWidth: true - - text: name - descriptionText: description - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) - } - } - - DividerType {} - } - } - } + DividerType {} } } } From 0f98fc955fc00a5529b28caf9cffffcb8f9f3911 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 21:19:56 +0100 Subject: [PATCH 092/100] remove comments and clean up --- .../qml/Components/HomeContainersListView.qml | 77 +------------------ client/ui/qml/Components/ServersListView.qml | 6 +- .../Components/SettingsContainersListView.qml | 8 +- .../qml/Components/ShareConnectionDrawer.qml | 24 ------ client/ui/qml/Controls2/BasicButtonType.qml | 1 - client/ui/qml/Controls2/DrawerType2.qml | 3 - .../Controls2/ListViewWithRadioButtonType.qml | 1 + .../ui/qml/Controls2/VerticalRadioButton.qml | 1 - client/ui/qml/Pages2/PageProtocolRaw.qml | 7 -- .../Pages2/PageProtocolWireGuardSettings.qml | 7 -- .../qml/Pages2/PageProtocolXraySettings.qml | 7 -- .../qml/Pages2/PageSettingsApiServerInfo.qml | 7 -- .../ui/qml/Pages2/PageSettingsServersList.qml | 12 +-- client/ui/qml/Pages2/PageShareFullAccess.qml | 2 - 14 files changed, 9 insertions(+), 154 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 3dc31300..337918f1 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -18,86 +18,15 @@ ListView { property var selectedText width: rootWidth - height: contentItem.height // TODO: It should be fixed size, not content item height + height: contentItem.height clip: true - // interactive: false - - // property FlickableType parentFlickable - // property var lastItemTabClicked - - // property int currentFocusIndex: 0 - - // snapMode: ListView.SnapToItem + snapMode: ListView.SnapToItem ScrollBar.vertical: ScrollBarType {} 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() - } - - // 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 } @@ -136,7 +65,7 @@ ListView { } if (checked) { - containersDropDown.closeTriggered() // TODO: containersDropDown is outside this file + containersDropDown.closeTriggered() ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) } else { ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 8eccb324..dc5c5a33 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -28,11 +28,7 @@ ListView { model: ServersModel - ScrollBar.vertical: ScrollBarType { - id: scrollBar - objectName: "scrollBar" - policy: root.height >= root.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } + ScrollBar.vertical: ScrollBarType {} property bool isFocusable: true diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index b3357b9c..9e672130 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -20,16 +20,10 @@ ListView { height: root.contentItem.height clip: true - interactive: false + reuseItems: true property bool isFocusable: false - onVisibleChanged: { - if (visible) { - this.currentIndex = 0 - } - } - delegate: Item { implicitWidth: root.width implicitHeight: delegateContent.implicitHeight diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index ed50807a..f98944f0 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -61,30 +61,6 @@ DrawerType2 { 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: ScrollBarType {} model: 1 diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 25ff1dab..b60e96cf 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -64,7 +64,6 @@ Button { implicitHeight: 56 hoverEnabled: true - focusPolicy: Qt.TabFocus onFocusChanged: { if (root.activeFocus) { diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml index 04a7635c..4c9bd010 100644 --- a/client/ui/qml/Controls2/DrawerType2.qml +++ b/client/ui/qml/Controls2/DrawerType2.qml @@ -12,9 +12,6 @@ Item { readonly property string drawerExpandedStateName: "expanded" readonly property string drawerCollapsedStateName: "collapsed" - // readonly property bool isExpanded: isExpandedStateActive() - // readonly property bool isCollapsed: isCollapsedStateActive() - readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive && (dragArea.drag.active === true)) readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false) diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index da5561ca..eafc1f5a 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -26,6 +26,7 @@ ListView { height: root.contentItem.height clip: true + reuseItems: true property bool isFocusable: true diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index dcf0f1d9..bee8ef7b 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -55,7 +55,6 @@ RadioButton { } hoverEnabled: true - // focusPolicy: Qt.TabFocus indicator: Rectangle { id: background diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 1a530780..03b4e297 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -67,13 +67,6 @@ PageType { activeFocusOnTab: true focus: true - onActiveFocusChanged: { - if (focus) { - listView.currentIndex = 0 - listView.currentItem.focusItem.forceActiveFocus() - } - } - delegate: Item { implicitWidth: parent.width implicitHeight: delegateContent.implicitHeight diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 257bc675..8aa0b185 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -56,13 +56,6 @@ PageType { model: WireGuardConfigModel - // activeFocusOnTab: true - // onActiveFocusChanged: { - // if (activeFocus) { - // listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() - // } - // } - delegate: Item { id: delegateItem diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 6d53fdd3..6d2ad3d1 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -57,13 +57,6 @@ PageType { model: XrayConfigModel - // activeFocusOnTab: true - // onActiveFocusChanged: { - // if (activeFocus) { - // listview.itemAtIndex(0)?.focusItemId.forceActiveFocus() - // } - // } - delegate: Item { property alias focusItemId: textFieldWithHeaderType.textField diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index db17196a..39207486 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -104,9 +104,6 @@ PageType { descriptionOnTop: true -// parentFlickable: fl -// KeyNavigation.tab: passwordLabel.eyeButton - rightImageSource: "qrc:/images/controls/copy.svg" rightImageColor: AmneziaStyle.color.paleGray @@ -134,8 +131,6 @@ PageType { text: qsTr("Reload API config") -// Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { var headerText = qsTr("Reload API config?") var yesButtonText = qsTr("Continue") @@ -174,8 +169,6 @@ PageType { text: qsTr("Remove from application") -// Keys.onTabPressed: lastItemTabClicked(focusItem) - clickedFunc: function() { var headerText = qsTr("Remove from application?") var yesButtonText = qsTr("Continue") diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 1852f9c3..17337a48 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -50,20 +50,14 @@ PageType { anchors.left: parent.left anchors.right: parent.right - height: 500 // servers.contentItem.height // TODO: calculate height + height: 500 - property bool isFocusable: true + property bool isFocusable: true model: ServersModel clip: true - interactive: false - - onVisibleChanged: { - if (visible) { - currentIndex = 0 - } - } + reuseItems: true delegate: Item { implicitWidth: servers.width diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 8451835c..af409d72 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -18,8 +18,6 @@ import "../Config" PageType { id: root - // defaultActiveFocusItem: focusItem - BackButtonType { id: backButton From 26ba5200228237a1f2c7e31eb1f37f14ef1c37a5 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 22:01:41 +0100 Subject: [PATCH 093/100] fix `ListViewWithLabelsType` --- client/ui/qml/Controls2/ListViewWithLabelsType.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Controls2/ListViewWithLabelsType.qml b/client/ui/qml/Controls2/ListViewWithLabelsType.qml index 5b614c43..536bc25a 100644 --- a/client/ui/qml/Controls2/ListViewWithLabelsType.qml +++ b/client/ui/qml/Controls2/ListViewWithLabelsType.qml @@ -17,7 +17,7 @@ ListView { property bool dividerVisible: false - currentIndex: 0 + property int selectedIndex: 0 width: rootWidth height: menuContent.contentItem.height @@ -45,7 +45,7 @@ ListView { rightImageSource: imageSource clickedFunction: function() { - menuContent.currentIndex = index + menuContent.selectedIndex = index menuContent.selectedText = name if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") { menuContent.clickedFunction() @@ -62,7 +62,7 @@ ListView { } Component.onCompleted: { - if (menuContent.currentIndex === index) { + if (menuContent.selectedIndex === index) { menuContent.selectedText = name } } From dccfcde6f6c6f6970ba3cb04ad96084bb4b549bd Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 22:04:00 +0100 Subject: [PATCH 094/100] fix `PageProtocolCloakSettings` --- client/ui/qml/Pages2/PageProtocolCloakSettings.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index fc43bd8e..e90325ba 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -48,11 +48,13 @@ PageType { ListView { id: listview + property int selectedIndex: 0 + width: parent.width height: listview.contentItem.height clip: true - interactive: false + reuseItems: true model: CloakConfigModel @@ -156,7 +158,7 @@ PageType { for (var i = 0; i < cipherListView.model.count; i++) { if (cipherListView.model.get(i).name === cipherDropDown.text) { - currentIndex = i + selectedIndex = i } } } From b0fdbe6c4f5d8469f3e4a731fc30c193f78181ec Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 29 Dec 2024 22:11:35 +0100 Subject: [PATCH 095/100] fix `PageSettingsAppSplitTunneling` --- client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index 84f90b9c..a78ae446 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -125,18 +125,18 @@ PageType { model: root.routeModesModel - currentIndex: getRouteModesModelIndex() + selectedIndex: getRouteModesModelIndex() clickedFunction: function() { selector.text = selectedText selector.closeTriggered() - if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[currentIndex].type) { - AppSplitTunnelingModel.routeMode = root.routeModesModel[currentIndex].type + if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[selectedIndex].type) { + AppSplitTunnelingModel.routeMode = root.routeModesModel[selectedIndex].type } } Component.onCompleted: { - if (root.routeModesModel[currentIndex].type === AppSplitTunnelingModel.routeMode) { + if (root.routeModesModel[selectedIndex].type === AppSplitTunnelingModel.routeMode) { selector.text = selectedText } else { selector.text = root.routeModesModel[0].name @@ -146,7 +146,7 @@ PageType { Connections { target: AppSplitTunnelingModel function onRouteModeChanged() { - currentIndex = getRouteModesModelIndex() + selectedIndex = getRouteModesModelIndex() } } } From 32ef1eae8489cf607c44e17cc91597cadeed7ab8 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 30 Dec 2024 00:19:11 +0100 Subject: [PATCH 096/100] fix `PageDevMenu` --- client/ui/qml/Pages2/PageDevMenu.qml | 43 ++++++++++++++++------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index 7f3ff1f0..d93e5a38 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -16,32 +16,28 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListView { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + 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 + ScrollBar.vertical: ScrollBarType {} + + header: ColumnLayout { + width: listView.width HeaderType { id: header @@ -52,6 +48,14 @@ PageType { headerText: "Dev menu" } + } + + model: 1 + clip: true + spacing: 16 + + delegate: ColumnLayout { + width: listView.width TextFieldWithHeaderType { id: passwordTextField @@ -60,7 +64,6 @@ PageType { Layout.topMargin: 16 Layout.rightMargin: 16 Layout.leftMargin: 16 - parentFlickable: fl headerText: qsTr("Gateway endpoint") textFieldText: SettingsController.gatewayEndpoint @@ -78,14 +81,18 @@ PageType { } } } + } + + footer: ColumnLayout { + width: listView.width SwitcherType { id: switcher Layout.fillWidth: true + Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 - Layout.topMargin: 16 text: qsTr("Dev gateway environment") checked: SettingsController.isDevGatewayEnv From caf29fb982b1fb6090ad617e2c0d9b076a5981be Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 30 Dec 2024 18:03:26 +0100 Subject: [PATCH 097/100] remove debug output from `FocusController` --- client/ui/controllers/focusController.cpp | 50 ----------------------- 1 file changed, 50 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index c8ec7fbc..32f3ce95 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -18,7 +18,6 @@ FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent) QQuickItem *newDefaultFocusItem = object->findChild("defaultFocusItem"); if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { m_defaultFocusItem.reset(newDefaultFocusItem); - qDebug() << "===>> NEW DEFAULT FOCUS ITEM: " << m_defaultFocusItem; } }); @@ -60,35 +59,25 @@ void FocusController::setFocusItem(QQuickItem *item) { if (m_focusedItem != item) { m_focusedItem = item; - qDebug() << "===>> FocusItem is changed to " << item << "!"; - } else { - qDebug() << "===>> FocusItem is is the same: " << item << "!"; } emit focusedItemChanged(); } 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; } @@ -96,11 +85,6 @@ void FocusController::dropRootObject(QObject *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; } @@ -108,14 +92,11 @@ void FocusController::dropRootObject(QObject *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()); @@ -127,8 +108,6 @@ void FocusController::reload(Direction direction) return; } - qDebug() << "===>> ROOT OBJECTS: " << rootObject; - m_focusChain.append(focusControl::getSubChain(rootObject)); std::sort(m_focusChain.begin(), m_focusChain.end(), @@ -144,13 +123,10 @@ void FocusController::reload(Direction direction) void FocusController::nextItem(Direction direction) { - qDebug() << "===>> Calling < nextItem >..."; - reload(direction); if (m_lvfc && focusControl::isListView(m_focusedItem)) { direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); - qDebug() << "===>> Handling the [ ListView ]..."; return; } @@ -164,13 +140,10 @@ void FocusController::nextItem(Direction direction) 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++; } @@ -183,7 +156,6 @@ void FocusController::nextItem(Direction direction) } if (focusControl::isListView(focusedItem)) { - qDebug() << "===>> Found [ListView]"; m_lvfc = new ListViewFocusController(focusedItem, this); m_focusedItem = focusedItem; if (direction == Direction::Forward) { @@ -197,33 +169,16 @@ void FocusController::nextItem(Direction direction) } setFocusItem(focusedItem); - - focusControl::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 >..."; m_lvfc->reloadFocusChain(); 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's elements was reached. Going to the next delegate"; m_lvfc->resetFocusChain(); m_lvfc->nextDelegate(); } @@ -233,15 +188,12 @@ void FocusController::focusNextListViewItem() void FocusController::focusPreviousListViewItem() { - qDebug() << "===>> Calling < focusPreviousListViewItem >..."; m_lvfc->reloadFocusChain(); 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()) { - qDebug() << "===>> End of delegate's elements was reached. Going to the previous delegate"; m_lvfc->resetFocusChain(); m_lvfc->previousDelegate(); } @@ -251,8 +203,6 @@ void FocusController::focusPreviousListViewItem() void FocusController::dropListView() { - qDebug() << "===>> Calling < dropListView >..."; - if (m_lvfc) { delete m_lvfc; m_lvfc = nullptr; From b25d911328961d0b75a1dad2c67abe8cf96c8e60 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 30 Dec 2024 18:04:05 +0100 Subject: [PATCH 098/100] remove debug output from `ListViewFocusController` --- .../ui/controllers/listViewFocusController.cpp | 16 +--------------- client/ui/controllers/listViewFocusController.h | 2 -- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 1af6d5b4..86a635dc 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -32,18 +32,15 @@ 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; } @@ -68,8 +65,6 @@ void ListViewFocusController::setDelegateIndex(int index) void ListViewFocusController::nextDelegate() { - printSectionName(); - switch (m_currentSection) { case Section::Default: { if (hasHeader()) { @@ -198,7 +193,6 @@ QQuickItem *ListViewFocusController::focusedItem() const void ListViewFocusController::focusNextItem() { if (m_isReturnNeeded) { - qDebug() << "===>> [ RETURN IS NEEDED... ]"; return; } @@ -212,7 +206,6 @@ void ListViewFocusController::focusNextItem() } 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); } @@ -223,7 +216,7 @@ void ListViewFocusController::focusPreviousItem() } if (m_focusChain.empty()) { - qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; + qInfo() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; reloadFocusChain(); } if (m_focusChain.empty()) { @@ -237,7 +230,6 @@ void ListViewFocusController::focusPreviousItem() } 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); } @@ -273,12 +265,6 @@ bool ListViewFocusController::hasFooter() const return m_footer && !focusControl::getItemsChain(m_footer).isEmpty(); } -void ListViewFocusController::printSectionName() const -{ - const auto sectionName = m_currentSectionString[static_cast(m_currentSection)]; - qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]"; -} - bool ListViewFocusController::isFirstFocusItemInListView() const { switch (m_currentSection) { diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/listViewFocusController.h index a0d7d788..f6405716 100644 --- a/client/ui/controllers/listViewFocusController.h +++ b/client/ui/controllers/listViewFocusController.h @@ -54,8 +54,6 @@ private: bool hasHeader() const; bool hasFooter() const; - void printSectionName() const; - QQuickItem *m_listView; QList m_focusChain; Section m_currentSection; From 5e492e83927d80a0d1665cf074d538eda56c025c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 30 Dec 2024 18:05:31 +0100 Subject: [PATCH 099/100] remove debug output from `focusControl` --- client/utils/qmlUtils.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/utils/qmlUtils.cpp b/client/utils/qmlUtils.cpp index f205f615..3bf8c956 100644 --- a/client/utils/qmlUtils.cpp +++ b/client/utils/qmlUtils.cpp @@ -82,7 +82,6 @@ namespace focusControl { QList res; if (!object) { - qDebug() << "The object is NULL"; return res; } @@ -102,7 +101,6 @@ namespace focusControl { QList res; if (!object) { - qDebug() << "The object is NULL"; return res; } From 25a24bf38c23a81c4d8cd8e68962e6ffaa4efb6a Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 30 Dec 2024 18:06:55 +0100 Subject: [PATCH 100/100] `focusControl` => `FocusControl` --- client/ui/controllers/focusController.cpp | 8 ++++---- client/ui/controllers/listViewFocusController.cpp | 6 +++--- client/utils/qmlUtils.cpp | 4 ++-- client/utils/qmlUtils.h | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/focusController.cpp index 32f3ce95..d32f7555 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/focusController.cpp @@ -108,10 +108,10 @@ void FocusController::reload(Direction direction) return; } - m_focusChain.append(focusControl::getSubChain(rootObject)); + m_focusChain.append(FocusControl::getSubChain(rootObject)); std::sort(m_focusChain.begin(), m_focusChain.end(), - direction == Direction::Forward ? focusControl::isLess : focusControl::isMore); + direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore); if (m_focusChain.empty()) { qWarning() << "Focus chain is empty!"; @@ -125,7 +125,7 @@ void FocusController::nextItem(Direction direction) { reload(direction); - if (m_lvfc && focusControl::isListView(m_focusedItem)) { + if (m_lvfc && FocusControl::isListView(m_focusedItem)) { direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); return; @@ -155,7 +155,7 @@ void FocusController::nextItem(Direction direction) return; } - if (focusControl::isListView(focusedItem)) { + if (FocusControl::isListView(focusedItem)) { m_lvfc = new ListViewFocusController(focusedItem, this); m_focusedItem = focusedItem; if (direction == Direction::Forward) { diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 86a635dc..9fa232ca 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -242,7 +242,7 @@ void ListViewFocusController::resetFocusChain() void ListViewFocusController::reloadFocusChain() { - m_focusChain = focusControl::getItemsChain(currentDelegate()); + m_focusChain = FocusControl::getItemsChain(currentDelegate()); } bool ListViewFocusController::isFirstFocusItemInDelegate() const @@ -257,12 +257,12 @@ bool ListViewFocusController::isLastFocusItemInDelegate() const bool ListViewFocusController::hasHeader() const { - return m_header && !focusControl::getItemsChain(m_header).isEmpty(); + return m_header && !FocusControl::getItemsChain(m_header).isEmpty(); } bool ListViewFocusController::hasFooter() const { - return m_footer && !focusControl::getItemsChain(m_footer).isEmpty(); + return m_footer && !FocusControl::getItemsChain(m_footer).isEmpty(); } bool ListViewFocusController::isFirstFocusItemInListView() const diff --git a/client/utils/qmlUtils.cpp b/client/utils/qmlUtils.cpp index 3bf8c956..b9557b14 100644 --- a/client/utils/qmlUtils.cpp +++ b/client/utils/qmlUtils.cpp @@ -4,7 +4,7 @@ #include #include -namespace focusControl +namespace FocusControl { QPointF getItemCenterPointOnScene(QQuickItem *item) { @@ -125,4 +125,4 @@ namespace focusControl qDebug() << prefix << " Item: " << i << " with coords: " << coords; } } -} // namespace focusControl \ No newline at end of file +} // namespace FocusControl \ No newline at end of file diff --git a/client/utils/qmlUtils.h b/client/utils/qmlUtils.h index 96ab3a83..535377c4 100644 --- a/client/utils/qmlUtils.h +++ b/client/utils/qmlUtils.h @@ -4,7 +4,7 @@ #include #include -namespace focusControl +namespace FocusControl { bool isEnabled(QObject *item); bool isVisible(QObject *item); @@ -25,6 +25,6 @@ namespace focusControl QList getItemsChain(QObject *object); void printItems(const QList &items, QObject *current_item); -} +} // namespace FocusControl -#endif // FOCUSCONTROL_H \ No newline at end of file +#endif // FOCUSCONTROL_H