Improve navigation cpp (#1061)

* add focusController class

* add more key handlers

* add focus navigation to qml

* fixed language selector

* add reverse focus change to FocusController

* add default focus item

* update transitions

* update pages

* add ListViewFocusController

* fix ListView navigation

* update CardType for using with focus navigation

* remove useless key navigation

* remove useless slots, logs, Drawer open and close

* fix reverse focus move on listView

* fix drawer radio buttons selection

* fix drawer layout and focus move

* fix PageSetupWizardProtocolSettings focus move

* fix back navigation on default focus item

* fix crashes after ListView navigation

* fix protocol settings focus move

* fix focus on users on page share

* clean up page share

* fix server rename

* fix page share default server selection

* refactor about page for correct focus move

* fix focus move on list views with header and-or footer

* minor fixes

* fix server list back button handler

* fix spawn signals on switch

* fix share details drawer

* fix drawer open close usage

* refactor listViewFocusController

* refactor focusController to make the logic more
straightforward

* fix focus on notification

* update config page for scrolling with tab

* fix crash on return with esc key

* fix focus navigation in dynamic delegate of list view

* fix focus move on qr code on share page

* refactor page logging settings for focus navigation

* update popup

* Bump version

* Add mandatory requirement for android.software.leanback.

* Fix importing files on TVs

* fix: add separate method for reading files to fix file reading on Android TV

* fix(android): add CHANGE_NETWORK_STATE permission for all Android versions

* Fix connection check for AWG/WG

* chore: minor fixes (#1235)

* fix: add a workaround to open files on Android TV due to lack of SAF

* fix: change the banner format for TV

* refactor: make TvFilePicker activity more sustainable

* fix: add the touch emulation method for Android TV

* fix: null uri processing

* fix: add the touch emulation method for Android TV

* fix: hide UI elements that use file saving

* chore: bump version code

* add `ScrollBarType`

* update initial config page

* refactor credentials setup page to handle the focus navigation

* add `setDelegateIndex` method to `listViewFocusController`

* fix focus behavior on new page/popup

* make minor fixes and clean up

* fix: get rid of the assign function call

* Scrollbar is on if the content is larger than a screen

* Fix selection in language change list

* Update select language list

* update logging settings page

* fix checked item in lists

* fix split tunneling settings

* make unchangable properties readonly

* refactor SwitcherType

* fix hide/unhide password

* `PageShare` readonly properties

* Fix list view focus moving on `PageShare`

* remove manual focus control on `PageShare`

* format `ListViewFocusController`

* format `FocusController`

* add `focusControl` with utility functions for
focus control

* refactor `listViewFocusController` acoording to `focusControl`

* refactor `focusConroller` according to `focusControl`

* add `printSectionName` method to `listViewController`

* remove arrow from `Close application` item

* fix focus movement in `ServersListView`

* `Restore from backup` is visible only on start screen

* `I have nothing` is visible only on start screen

* fix back button on `SelectLanguageDrawer`

* rename `focusControl` to `qmlUtils`

* fix `CMakeLists.txt`

* fix `ScrollBarType`

* fix `PageSetupWizardApiServicesList`

* fix focus movement on dynamic delegates in listView

* refactor `PageSetupWizardProtocols`

* remove comments and clean up

* fix `ListViewWithLabelsType`

* fix `PageProtocolCloakSettings`

* fix `PageSettingsAppSplitTunneling`

* fix `PageDevMenu`

* remove debug output from `FocusController`

* remove debug output from `ListViewFocusController`

* remove debug output from `focusControl`

* `focusControl` => `FocusControl`

---------

Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Nethius <nethiuswork@gmail.com>
This commit is contained in:
Cyril Anisimov 2024-12-31 04:16:52 +01:00 committed by GitHub
parent 212e9b3a91
commit 6acaab0ffa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
109 changed files with 4036 additions and 3700 deletions

View file

@ -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
@ -116,53 +115,37 @@ PageType {
Keys.onEnterPressed: splitTunnelingButton.clicked()
Keys.onReturnPressed: splitTunnelingButton.clicked()
KeyNavigation.tab: drawer
onClicked: {
homeSplitTunnelingDrawer.open()
homeSplitTunnelingDrawer.openTriggered()
}
HomeSplitTunnelingDrawer {
id: homeSplitTunnelingDrawer
parent: root
objectName: "homeSplitTunnelingDrawer"
onClosed: {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
parent: root
}
}
}
}
DrawerType2 {
id: drawer
objectName: "drawerProtocol"
anchors.fill: parent
onClosed: {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
collapsedStateContent: Item {
objectName: "ProtocolDrawerCollapsedContent"
collapsedContent: Item {
implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
Component.onCompleted: {
drawer.expandedHeight = implicitHeight
}
Connections {
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
@ -181,6 +164,8 @@ PageType {
}
RowLayout {
objectName: "rowLayout"
Layout.topMargin: 14
Layout.leftMargin: 24
Layout.rightMargin: 24
@ -189,9 +174,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 {
@ -199,8 +186,8 @@ PageType {
}
}
function onExited() {
if (drawer.isCollapsed) {
function onCursorExited() {
if (drawer.isCollapsedStateActive) {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 1
} else {
@ -209,7 +196,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 {
@ -220,6 +207,8 @@ PageType {
Header1TextType {
id: collapsedButtonHeader
objectName: "collapsedButtonHeader"
Layout.maximumWidth: drawer.width - 48 - 18 - 12
maximumLineCount: 2
@ -228,8 +217,6 @@ PageType {
text: ServersModel.defaultServerName
horizontalAlignment: Qt.AlignHCenter
KeyNavigation.tab: tabBar
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
@ -237,10 +224,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"
@ -255,18 +243,17 @@ PageType {
Keys.onEnterPressed: collapsedButtonChevron.clicked()
Keys.onReturnPressed: collapsedButtonChevron.clicked()
Keys.onTabPressed: lastItemTabClicked()
onClicked: {
if (drawer.isCollapsed) {
drawer.open()
if (drawer.isCollapsedStateActive()) {
drawer.openTriggered()
}
}
}
}
RowLayout {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
@ -305,18 +292,9 @@ PageType {
}
}
Connections {
target: drawer
enabled: !GC.isMobile()
function onIsCollapsedChanged() {
if (!drawer.isCollapsed) {
focusItem1.forceActiveFocus()
}
}
}
ColumnLayout {
id: serversMenuHeader
objectName: "serversMenuHeader"
anchors.top: collapsed.bottom
anchors.right: parent.right
@ -328,13 +306,9 @@ PageType {
visible: !ServersModel.isDefaultServerFromApi
Item {
id: focusItem1
KeyNavigation.tab: containersDropDown
}
DropDownType {
id: containersDropDown
objectName: "containersDropDown"
rootButtonImageColor: AmneziaStyle.color.midnightBlack
rootButtonBackgroundColor: AmneziaStyle.color.paleGray
@ -345,28 +319,29 @@ PageType {
rootButtonTextTopMargin: 8
rootButtonTextBottomMargin: 8
enabled: drawer.isOpened
text: ServersModel.defaultServerDefaultContainerName
textColor: AmneziaStyle.color.midnightBlack
headerText: qsTr("VPN protocol")
headerBackButtonImage: "qrc:/images/controls/arrow-left.svg"
rootButtonClickedFunction: function() {
containersDropDown.open()
containersDropDown.openTriggered()
}
drawerParent: root
KeyNavigation.tab: serversMenuContent
listView: HomeContainersListView {
id: containersListView
objectName: "containersListView"
rootWidth: root.width
onVisibleChanged: {
if (containersDropDown.visible && !GC.isMobile()) {
focusItem1.forceActiveFocus()
}
}
height: 500 // TODO: make calculated
Connections {
objectName: "rowLayoutConnections"
target: ServersModel
function onDefaultServerIndexChanged() {
@ -408,167 +383,21 @@ PageType {
ButtonGroup {
id: serversRadioButtonGroup
objectName: "serversRadioButtonGroup"
}
ListView {
ServersListView {
id: serversMenuContent
objectName: "serversMenuContent"
anchors.top: serversMenuHeader.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: 16
model: ServersModel
currentIndex: ServersModel.defaultIndex
ScrollBar.vertical: ScrollBar {
id: scrollBar
policy: serversMenuContent.height >= serversMenuContent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}
activeFocusOnTab: true
focus: true
property int focusItemIndex: 0
onActiveFocusChanged: {
if (activeFocus) {
serversMenuContent.focusItemIndex = 0
serversMenuContent.itemAtIndex(focusItemIndex).forceActiveFocus()
}
}
onFocusItemIndexChanged: {
const focusedElement = serversMenuContent.itemAtIndex(focusItemIndex)
if (focusedElement) {
if (focusedElement.y + focusedElement.height > serversMenuContent.height) {
serversMenuContent.contentY = focusedElement.y + focusedElement.height - serversMenuContent.height
} else {
serversMenuContent.contentY = 0
}
}
}
Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase()
isFocusable: false
Connections {
target: drawer
enabled: !GC.isMobile()
function onIsCollapsedChanged() {
if (drawer.isCollapsed) {
const item = serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex)
if (item) { item.serverRadioButtonProperty.focus = false }
}
}
}
Connections {
target: ServersModel
function onDefaultServerIndexChanged(serverIndex) {
serversMenuContent.currentIndex = serverIndex
}
}
clip: true
delegate: Item {
id: menuContentDelegate
property variant delegateData: model
property VerticalRadioButton serverRadioButtonProperty: serverRadioButton
implicitWidth: serversMenuContent.width
implicitHeight: serverRadioButtonContent.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
serverRadioButton.forceActiveFocus()
}
}
ColumnLayout {
id: serverRadioButtonContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 0
RowLayout {
Layout.fillWidth: true
VerticalRadioButton {
id: serverRadioButton
Layout.fillWidth: true
text: name
descriptionText: serverDescription
checked: index === serversMenuContent.currentIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
serversMenuContent.currentIndex = index
ServersModel.defaultIndex = index
}
MouseArea {
anchors.fill: serverRadioButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onTabPressed: serverInfoButton.forceActiveFocus()
Keys.onEnterPressed: serverRadioButton.clicked()
Keys.onReturnPressed: serverRadioButton.clicked()
}
ImageButtonType {
id: serverInfoButton
image: "qrc:/images/controls/settings.svg"
imageColor: AmneziaStyle.color.paleGray
implicitWidth: 56
implicitHeight: 56
z: 1
Keys.onTabPressed: {
if (serversMenuContent.focusItemIndex < serversMenuContent.count - 1) {
serversMenuContent.focusItemIndex++
serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex).forceActiveFocus()
} else {
focusItem1.forceActiveFocus()
serversMenuContent.contentY = 0
}
}
Keys.onEnterPressed: serverInfoButton.clicked()
Keys.onReturnPressed: serverInfoButton.clicked()
onClicked: function() {
ServersModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsServerInfo)
drawer.close()
}
}
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 0
}
// this item shouldn't be focused when drawer is closed
function onIsOpenedChanged() {
serversMenuContent.isFocusable = drawer.isOpened
}
}
}