fix: fix self-hosted TextFields and Keyboard reset issue (#1983)

Co-authored-by: vkamn <vk@amnezia.org>
This commit is contained in:
NickVs2015
2025-11-12 10:57:53 +03:00
committed by GitHub
parent 3cc18c5807
commit b53cdcff08
10 changed files with 321 additions and 81 deletions

View File

@@ -247,6 +247,7 @@
<file>ui/qml/Components/OtpCodeDrawer.qml</file> <file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file> <file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file> <file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
<file>ui/qml/Components/SmartScroll.qml</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>

View File

@@ -446,7 +446,11 @@ bool SettingsController::isOnTv()
bool SettingsController::isEdgeToEdgeEnabled() bool SettingsController::isEdgeToEdgeEnabled()
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
return AndroidController::instance()->isEdgeToEdgeEnabled(); if (!m_edgeToEdgeCached) {
m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled();
m_edgeToEdgeCached = true;
}
return m_cachedEdgeToEdgeEnabled;
#else #else
return false; return false;
#endif #endif

View File

@@ -150,6 +150,8 @@ private:
mutable int m_cachedStatusBarHeight = -1; mutable int m_cachedStatusBarHeight = -1;
mutable int m_cachedNavigationBarHeight = -1; mutable int m_cachedNavigationBarHeight = -1;
mutable bool m_cachedEdgeToEdgeEnabled = false;
mutable bool m_edgeToEdgeCached = false;
int m_imeHeight = 0; int m_imeHeight = 0;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;

View File

@@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Controls
QtObject {
id: root
property var listView: null
property var scrollToItemTarget: null
property Connections imeConnection: Connections {
target: SettingsController
function onImeHeightChanged() {
if (root.scrollToItemTarget && SettingsController.imeHeight > 0) {
scrollTimer.restart()
}
}
}
property Timer scrollTimer: Timer {
interval: 100
repeat: false
onTriggered: {
if (root.scrollToItemTarget && root.listView) {
if (SettingsController.imeHeight > 0) {
var item = root.scrollToItemTarget
var itemY = item.mapToItem(root.listView.contentItem, 0, 0).y
var itemHeight = item.height
var keyboardHeight = SettingsController.imeHeight + SettingsController.safeAreaBottomMargin
var visibleHeight = root.listView.height - keyboardHeight
var desiredTopOffset = visibleHeight * 0.25
var targetContentY = itemY - desiredTopOffset
if (targetContentY < 0) {
targetContentY = 0
}
var maxContentY = root.listView.contentHeight - root.listView.height
if (targetContentY > maxContentY) {
targetContentY = maxContentY
}
root.listView.contentY = targetContentY
root.scrollToItemTarget = null
}
}
}
}
function scrollToItem(item) {
scrollToItemTarget = item
scrollTimer.restart()
}
}

View File

@@ -19,6 +19,9 @@ Item {
property string buttonText property string buttonText
property string buttonImageSource property string buttonImageSource
property string buttonImageColor: AmneziaStyle.color.midnightBlack
property string buttonBackgroundColor: AmneziaStyle.color.paleGray
property string buttonHoveredColor: AmneziaStyle.color.lightGray
property var clickedFunc property var clickedFunc
property alias textField: textField property alias textField: textField
@@ -48,7 +51,7 @@ Item {
Keys.onUpPressed: { Keys.onUpPressed: {
FocusController.nextKeyUpItem() FocusController.nextKeyUpItem()
} }
Keys.onDownPressed: { Keys.onDownPressed: {
FocusController.nextKeyDownItem() FocusController.nextKeyDownItem()
} }
@@ -67,7 +70,7 @@ Item {
border.width: 1 border.width: 1
Behavior on border.color { Behavior on border.color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 100 }
} }
RowLayout { RowLayout {
@@ -194,6 +197,14 @@ Item {
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
text: root.buttonText text: root.buttonText
leftImageSource: root.buttonImageSource leftImageSource: root.buttonImageSource
leftImageColor: root.buttonImageColor
defaultColor: root.buttonBackgroundColor
hoveredColor: root.buttonHoveredColor
pressedColor: root.buttonHoveredColor
disabledColor: AmneziaStyle.color.transparent
borderWidth: 0
anchors.top: content.top anchors.top: content.top
anchors.bottom: content.bottom anchors.bottom: content.bottom
@@ -201,7 +212,7 @@ Item {
height: content.implicitHeight height: content.implicitHeight
width: content.implicitHeight width: content.implicitHeight
squareLeftSide: true squareLeftSide: false
clickedFunc: function() { clickedFunc: function() {
if (root.clickedFunc && typeof root.clickedFunc === "function") { if (root.clickedFunc && typeof root.clickedFunc === "function") {

View File

@@ -31,6 +31,11 @@ PageType {
} }
} }
SmartScroll {
id: smartScroll
listView: listView
}
ListViewType { ListViewType {
id: listView id: listView
@@ -80,6 +85,13 @@ PageType {
clientMtu = textField.text clientMtu = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(mtuTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -97,6 +109,12 @@ PageType {
clientJunkPacketCount = textField.text clientJunkPacketCount = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -113,6 +131,12 @@ PageType {
clientJunkPacketMinSize = textField.text clientJunkPacketMinSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -129,6 +153,12 @@ PageType {
clientJunkPacketMaxSize = textField.text clientJunkPacketMaxSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -147,6 +177,12 @@ PageType {
clientSpecialJunk1 = textField.text clientSpecialJunk1 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk1TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -165,6 +201,12 @@ PageType {
clientSpecialJunk2 = textField.text clientSpecialJunk2 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk2TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -183,6 +225,12 @@ PageType {
clientSpecialJunk3 = textField.text clientSpecialJunk3 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk3TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -201,6 +249,12 @@ PageType {
clientSpecialJunk4 = textField.text clientSpecialJunk4 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk4TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -219,6 +273,12 @@ PageType {
clientSpecialJunk5 = textField.text clientSpecialJunk5 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk5TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -237,6 +297,12 @@ PageType {
clientControlledJunk1 = textField.text clientControlledJunk1 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk1TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -255,6 +321,12 @@ PageType {
clientControlledJunk2 = textField.text clientControlledJunk2 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk2TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -273,6 +345,12 @@ PageType {
clientControlledJunk3 = textField.text clientControlledJunk3 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk3TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -290,6 +368,12 @@ PageType {
clientSpecialHandshakeTimeout = textField.text clientSpecialHandshakeTimeout = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(iTimeTextField)
}
}
} }
Header2TextType { Header2TextType {

View File

@@ -34,6 +34,11 @@ PageType {
} }
} }
SmartScroll {
id: smartScroll
listView: listView
}
ListViewType { ListViewType {
id: listView id: listView
@@ -81,6 +86,12 @@ PageType {
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(vpnAddressSubnetTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -104,6 +115,12 @@ PageType {
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(portTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -121,6 +138,12 @@ PageType {
serverJunkPacketCount = textField.text serverJunkPacketCount = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -137,6 +160,12 @@ PageType {
serverJunkPacketMinSize = textField.text serverJunkPacketMinSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -153,6 +182,12 @@ PageType {
serverJunkPacketMaxSize = textField.text serverJunkPacketMaxSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -169,6 +204,12 @@ PageType {
serverInitPacketJunkSize = textField.text serverInitPacketJunkSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketJunkSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -185,6 +226,12 @@ PageType {
serverResponsePacketJunkSize = textField.text serverResponsePacketJunkSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketJunkSizeTextField)
}
}
} }
// AwgTextField { // AwgTextField {
@@ -233,6 +280,12 @@ PageType {
serverInitPacketMagicHeader = textField.text serverInitPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -249,6 +302,12 @@ PageType {
serverResponsePacketMagicHeader = textField.text serverResponsePacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -265,6 +324,12 @@ PageType {
serverUnderloadPacketMagicHeader = textField.text serverUnderloadPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(underloadPacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -281,6 +346,12 @@ PageType {
serverTransportPacketMagicHeader = textField.text serverTransportPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(transportPacketMagicHeaderTextField)
}
}
} }
BasicButtonType { BasicButtonType {

View File

@@ -165,9 +165,11 @@ PageType {
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn } ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
anchors.top: header.bottom anchors.top: header.bottom
anchors.bottom: addAppButton.top anchors.bottom: parent.bottom
anchors.bottomMargin: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
clip: true
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxyAppSplitTunnelingModel id: proxyAppSplitTunnelingModel
@@ -215,50 +217,54 @@ PageType {
} }
Rectangle { Rectangle {
anchors.fill: addAppButton
anchors.bottomMargin: -24 - SettingsController.safeAreaBottomMargin
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
}
RowLayout {
id: addAppButton
enabled: root.pageEnabled
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 24 anchors.bottom: parent.bottom
anchors.rightMargin: 16
anchors.leftMargin: 16 height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin
anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
RowLayout {
id: addAppButton
TextFieldWithHeaderType { enabled: root.pageEnabled
id: searchField
Layout.fillWidth: true anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
textField.placeholderText: qsTr("application name") TextFieldWithHeaderType {
buttonImageSource: "qrc:/images/controls/plus.svg" id: searchField
rightButtonClickedOnEnter: true Layout.fillWidth: true
clickedFunc: function() { textField.placeholderText: qsTr("application name")
searchField.focus = false buttonImageSource: "qrc:/images/controls/plus.svg"
PageController.showBusyIndicator(true)
if (Qt.platform.os === "windows") { rightButtonClickedOnEnter: true
var fileName = SystemController.getFileName(qsTr("Open executable file"),
qsTr("Executable files (*.*)")) clickedFunc: function() {
if (fileName !== "") { searchField.focus = false
AppSplitTunnelingController.addApp(fileName) PageController.showBusyIndicator(true)
if (Qt.platform.os === "windows") {
var fileName = SystemController.getFileName(qsTr("Open executable file"),
qsTr("Executable files (*.*)"))
if (fileName !== "") {
AppSplitTunnelingController.addApp(fileName)
}
} else if (Qt.platform.os === "android"){
installedAppDrawer.openTriggered()
} }
} else if (Qt.platform.os === "android"){
installedAppDrawer.openTriggered()
}
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
}
} }
} }
} }

View File

@@ -168,11 +168,13 @@ PageType {
anchors.top: header.bottom anchors.top: header.bottom
anchors.topMargin: 16 anchors.topMargin: 16
anchors.bottom: addSiteButton.top anchors.bottom: parent.bottom
anchors.bottomMargin: addSiteButton.implicitHeight + 48 + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
width: parent.width width: parent.width
enabled: root.pageEnabled enabled: root.pageEnabled
clip: true
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxySitesModel id: proxySitesModel
@@ -231,56 +233,60 @@ PageType {
} }
Rectangle { Rectangle {
anchors.fill: addSiteButton
anchors.bottomMargin: -24
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
}
RowLayout {
id: addSiteButton
enabled: root.pageEnabled
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 24 anchors.bottom: parent.bottom
anchors.rightMargin: 16
anchors.leftMargin: 16 height: addSiteButton.implicitHeight + 48
anchors.bottomMargin: 24
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
RowLayout {
id: addSiteButton
TextFieldWithHeaderType { enabled: root.pageEnabled
id: searchField
Layout.fillWidth: true anchors.bottom: parent.bottom
rightButtonClickedOnEnter: true anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
textField.placeholderText: qsTr("website or IP") TextFieldWithHeaderType {
buttonImageSource: "qrc:/images/controls/plus.svg" id: searchField
clickedFunc: function() { Layout.fillWidth: true
PageController.showBusyIndicator(true) rightButtonClickedOnEnter: true
SitesController.addSite(textField.text)
textField.text = ""
PageController.showBusyIndicator(false)
}
}
ImageButtonType { textField.placeholderText: qsTr("website or IP")
id: addSiteButtonImage buttonImageSource: "qrc:/images/controls/plus.svg"
implicitWidth: 56
implicitHeight: 56
image: "qrc:/images/controls/more-vertical.svg" clickedFunc: function() {
imageColor: AmneziaStyle.color.paleGray PageController.showBusyIndicator(true)
SitesController.addSite(textField.text)
onClicked: function () { textField.text = ""
moreActionsDrawer.openTriggered() PageController.showBusyIndicator(false)
}
} }
Keys.onReturnPressed: addSiteButtonImage.clicked() ImageButtonType {
Keys.onEnterPressed: addSiteButtonImage.clicked() id: addSiteButtonImage
implicitWidth: 56
implicitHeight: 56
image: "qrc:/images/controls/more-vertical.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: function () {
moreActionsDrawer.openTriggered()
}
Keys.onReturnPressed: addSiteButtonImage.clicked()
Keys.onEnterPressed: addSiteButtonImage.clicked()
}
} }
} }

View File

@@ -183,7 +183,7 @@ Window {
id: privateKeyPassphraseDrawer id: privateKeyPassphraseDrawer
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.35 + SettingsController.safeAreaBottomMargin expandedHeight: root.height * 0.35 + SettingsController.safeAreaBottomMargin + SettingsController.imeHeight
expandedStateContent: ColumnLayout { expandedStateContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top