add UI PageProtocolXrayConfigsSettings, PageProtocolXrayFlowSettings, PageProtocolXraySecuritySettings

This commit is contained in:
dranik
2026-03-24 11:34:03 +03:00
parent bbd07f6bad
commit dc58e1832c
7 changed files with 702 additions and 22 deletions

View File

@@ -82,9 +82,12 @@ namespace PageLoader
PageDevMenu,
PageProtocolXrayConfigsSettings,
PageProtocolXrayTransportSettings,
PageProtocolXrayXmuxSettings,
PageProtocolXrayXPaddingSettings,
PageProtocolXrayFlowSettings,
PageProtocolXraySecuritySettings,
};
Q_ENUM_NS(PageEnum)

View File

@@ -0,0 +1,217 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
// Temporary model — will be replaced by real XrayConfigsModel
ListModel {
id: configsModel
ListElement {
configName: "XHTTP TLS Reality"; configDate: "24.02.2026 11:12"
}
ListElement {
configName: "RAW (TCP) TLS Reality"; configDate: "24.02.2026 11:14"
}
ListElement {
configName: "RAW (TCP) TLS Reality"; configDate: "24.02.2026 11:14"
}
ListElement {
configName: "RAW (TCP) TLS Reality"; configDate: "24.02.2026 11:15"
}
}
// Currently selected config for the drawer
property string selectedConfigName: ""
property int selectedConfigIndex: -1
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
}
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
header: ColumnLayout {
width: listView.width
spacing: 0
// ── Header ────────────────────────────────────────────────
Header2TextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 0
Layout.bottomMargin: 24
text: qsTr("XRay Configurations")
wrapMode: Text.WordWrap
}
// ── Create config from current settings ───────────────────
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Create configuration based on current settings")
textMaximumLineCount: 2
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () {
// XrayConfigModel.createConfigFromCurrent()
}
}
DividerType {
}
// ── Export ────────────────────────────────────────────────
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Export settings")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () {
// XrayConfigModel.exportSettings()
}
}
DividerType {
}
// ── Import ────────────────────────────────────────────────
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Import settings")
descriptionText: qsTr("In JSON format")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () {
// XrayConfigModel.importSettings()
}
}
DividerType {
}
// ── Configurations section label ──────────────────────────
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 24
Layout.bottomMargin: 8
text: qsTr("Configurations")
color: AmneziaStyle.color.mutedGray
}
}
model: configsModel
delegate: ColumnLayout {
width: listView.width
spacing: 0
LabelWithButtonType {
Layout.fillWidth: true
text: configName
descriptionText: configDate
rightImageSource: "qrc:/images/controls/more-vertical.svg"
clickedFunction: function () {
root.selectedConfigName = configName
root.selectedConfigIndex = index
configActionsDrawer.openTriggered()
}
}
DividerType {
}
}
}
// ── Per-config actions drawer ─────────────────────────────────────
DrawerType2 {
id: configActionsDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.4
expandedStateContent: ColumnLayout {
id: drawerContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
onImplicitHeightChanged: {
configActionsDrawer.expandedHeight = drawerContent.implicitHeight + 32
}
BackButtonType {
Layout.fillWidth: true
Layout.topMargin: 16
backButtonFunction: function () {
configActionsDrawer.closeTriggered()
}
}
Header2TextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 16
text: root.selectedConfigName
wrapMode: Text.WordWrap
}
// ── Apply config ──────────────────────────────────────────
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Apply configuration")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () {
configActionsDrawer.closeTriggered()
// XrayConfigModel.applyConfig(root.selectedConfigIndex)
}
}
DividerType {
}
// ── Delete config ─────────────────────────────────────────
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Delete configuration")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function () {
configActionsDrawer.closeTriggered()
// XrayConfigModel.deleteConfig(root.selectedConfigIndex)
}
}
DividerType {
}
Item {
Layout.preferredHeight: 16
}
}
}
}

View File

@@ -0,0 +1,109 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
// Temporary local state — will be replaced by model role
property int selectedFlow: 1 // 0=Empty, 1=xtls-rprx-vision, 2=xtls-rprx-vision-udp443
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
}
FlickableType {
id: flickable
anchors.top: backButton.bottom
anchors.bottom: saveButton.top
anchors.left: parent.left
anchors.right: parent.right
contentHeight: mainColumn.implicitHeight
ColumnLayout {
id: mainColumn
width: flickable.width
spacing: 0
Header2TextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 0
Layout.bottomMargin: 24
text: qsTr("Flow")
}
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Empty")
checked: root.selectedFlow === 0
onClicked: root.selectedFlow = 0
}
DividerType {
}
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("xtls-rprx-vision")
checked: root.selectedFlow === 1
onClicked: root.selectedFlow = 1
}
DividerType {
}
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("xtls-rprx-vision-udp443")
checked: root.selectedFlow === 2
onClicked: root.selectedFlow = 2
}
DividerType {
}
Item {
Layout.preferredHeight: 16
}
}
}
BasicButtonType {
id: saveButton
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
anchors.leftMargin: 16
anchors.rightMargin: 16
text: qsTr("Save")
onClicked: {
forceActiveFocus()
// XrayConfigModel.setFlow(...)
}
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
}
}

View File

@@ -0,0 +1,316 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
// Temporary local state — will be replaced by model roles
property int selectedSecurity: 2 // 0=None, 1=TLS, 2=Reality
// Shared TLS + Reality fields
property string fingerprint: "Mozilla/5.0"
property string serverName: "cdn.example.com"
// TLS-only fields
property string alpn: "HTTP/2"
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
}
FlickableType {
id: flickable
anchors.top: backButton.bottom
anchors.bottom: saveButton.top
anchors.left: parent.left
anchors.right: parent.right
contentHeight: mainColumn.implicitHeight
ColumnLayout {
id: mainColumn
width: flickable.width
spacing: 0
Header2TextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 0
Layout.bottomMargin: 24
text: qsTr("Security")
}
// ── Radio: None ───────────────────────────────────────────
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("None")
checked: root.selectedSecurity === 0
onClicked: root.selectedSecurity = 0
}
DividerType {
}
// ── Radio: TLS ────────────────────────────────────────────
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("TLS")
checked: root.selectedSecurity === 1
onClicked: root.selectedSecurity = 1
}
DividerType {
}
// ── Radio: Reality ────────────────────────────────────────
VerticalRadioButton {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Reality")
checked: root.selectedSecurity === 2
onClicked: root.selectedSecurity = 2
}
DividerType {
}
// ══════════════════════════════════════════════════════════
// TLS fields (ALPN + Fingerprint + SNI)
// ══════════════════════════════════════════════════════════
ColumnLayout {
visible: root.selectedSecurity === 1
Layout.fillWidth: true
spacing: 0
DropDownType {
id: tlsAlpnDropDown
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: root.alpn
descriptionText: qsTr("ALPN")
headerText: qsTr("ALPN")
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: ListModel {
ListElement {
name: "HTTP/2"
}
ListElement {
name: "HTTP/1.1"
}
ListElement {
name: "HTTP/2,HTTP/1.1"
}
}
clickedFunction: function () {
root.alpn = selectedText
tlsAlpnDropDown.text = selectedText
tlsAlpnDropDown.closeTriggered()
}
Component.onCompleted: {
for (var i = 0; i < model.count; i++) {
if (model.get(i).name === root.alpn) {
selectedIndex = i;
break
}
}
}
}
}
DropDownType {
id: tlsFingerprintDropDown
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
text: root.fingerprint
descriptionText: qsTr("Fingerprint")
headerText: qsTr("Fingerprint")
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: ListModel {
ListElement {
name: "Mozilla/5.0"
}
ListElement {
name: "Chrome"
}
ListElement {
name: "Firefox"
}
ListElement {
name: "Safari"
}
ListElement {
name: "iOS"
}
ListElement {
name: "Android"
}
ListElement {
name: "Edge"
}
ListElement {
name: "360"
}
ListElement {
name: "QQ"
}
ListElement {
name: "Random"
}
}
clickedFunction: function () {
root.fingerprint = selectedText
tlsFingerprintDropDown.text = selectedText
tlsFingerprintDropDown.closeTriggered()
}
Component.onCompleted: {
for (var i = 0; i < model.count; i++) {
if (model.get(i).name === root.fingerprint) {
selectedIndex = i;
break
}
}
}
}
}
TextFieldWithHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
textField.text: root.serverName
textField.onEditingFinished: root.serverName = textField.text
}
}
// ══════════════════════════════════════════════════════════
// Reality fields (Fingerprint + SNI)
// ══════════════════════════════════════════════════════════
ColumnLayout {
visible: root.selectedSecurity === 2
Layout.fillWidth: true
spacing: 0
DropDownType {
id: realityFingerprintDropDown
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: root.fingerprint
descriptionText: qsTr("Fingerprint")
headerText: qsTr("Fingerprint")
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: ListModel {
ListElement {
name: "Mozilla/5.0"
}
ListElement {
name: "Chrome"
}
ListElement {
name: "Firefox"
}
ListElement {
name: "Safari"
}
ListElement {
name: "iOS"
}
ListElement {
name: "Android"
}
ListElement {
name: "Edge"
}
ListElement {
name: "360"
}
ListElement {
name: "QQ"
}
ListElement {
name: "Random"
}
}
clickedFunction: function () {
root.fingerprint = selectedText
realityFingerprintDropDown.text = selectedText
realityFingerprintDropDown.closeTriggered()
}
Component.onCompleted: {
for (var i = 0; i < model.count; i++) {
if (model.get(i).name === root.fingerprint) {
selectedIndex = i;
break
}
}
}
}
}
TextFieldWithHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
textField.text: root.serverName
textField.onEditingFinished: root.serverName = textField.text
}
}
Item {
Layout.preferredHeight: 16
}
}
}
BasicButtonType {
id: saveButton
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
anchors.leftMargin: 16
anchors.rightMargin: 16
text: qsTr("Save")
onClicked: {
forceActiveFocus()
// XrayConfigModel.setSecurity(...)
}
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
}
}

View File

@@ -69,6 +69,9 @@ PageType {
implicitHeight: 40
image: "qrc:/images/controls/more-vertical.svg"
imageColor: AmneziaStyle.color.mutedGray
onClicked: function () {
PageController.goToPage(PageEnum.PageProtocolXrayConfigsSettings)
}
}
}

View File

@@ -35,6 +35,12 @@ PageType {
// Traffic Shaping
property string uplinkChunkSize: "0"
property string scMaxBufferedPosts: ""
property string scMaxEachPostBytesMin: "1"
property string scMaxEachPostBytesMax: "100"
property string scMinPostsIntervalMsMin: "100"
property string scMinPostsIntervalMsMax: "800"
property string scStreamUpServerSecsMin: "1"
property string scStreamUpServerSecsMax: "100"
// mKCP fields
property string mkcpTti: ""
property string mkcpUplinkCapacity: ""
@@ -603,41 +609,64 @@ PageType {
textField.onEditingFinished: root.scMaxBufferedPosts = textField.text
}
// scMaxEachPostBytes → nav row
LabelWithButtonType {
// scMaxEachPostBytes — min/max range
CaptionTextType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 8
text: qsTr("scMaxEachPostBytes")
descriptionText: qsTr("1—100")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () { /* navigate */
}
color: AmneziaStyle.color.mutedGray
}
DividerType {
MinMaxRowType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
minValue: root.scMaxEachPostBytesMin
maxValue: root.scMaxEachPostBytesMax
onMinChanged: root.scMaxEachPostBytesMin = val
onMaxChanged: root.scMaxEachPostBytesMax = val
}
// scMinPostsIntervalMs → nav row
LabelWithButtonType {
// scMinPostsIntervalMs — min/max range
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 8
text: qsTr("scMinPostsIntervalMs")
descriptionText: qsTr("100—600ms")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () { /* navigate */
}
color: AmneziaStyle.color.mutedGray
}
DividerType {
MinMaxRowType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
minValue: root.scMinPostsIntervalMsMin
maxValue: root.scMinPostsIntervalMsMax
onMinChanged: root.scMinPostsIntervalMsMin = val
onMaxChanged: root.scMinPostsIntervalMsMax = val
}
// scStreamUpServerSecs → nav row
LabelWithButtonType {
// scStreamUpServerSecs — min/max range
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 8
text: qsTr("scStreamUpServerSecs")
descriptionText: qsTr("1—100")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function () { /* navigate */
}
color: AmneziaStyle.color.mutedGray
}
DividerType {
MinMaxRowType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
minValue: root.scStreamUpServerSecsMin
maxValue: root.scStreamUpServerSecsMax
onMinChanged: root.scStreamUpServerSecsMin = val
onMaxChanged: root.scStreamUpServerSecsMax = val
}
// ── Padding and multiplexing ──────────────────────────

View File

@@ -78,6 +78,9 @@
<file>Pages2/PageProtocolWireGuardSettings.qml</file>
<file>Pages2/PageProtocolXraySettings.qml</file>
<file>Pages2/PageProtocolXrayConfigsSettings.qml</file>
<file>Pages2/PageProtocolXrayFlowSettings.qml</file>
<file>Pages2/PageProtocolXraySecuritySettings.qml</file>
<file>Pages2/PageProtocolXrayTransportSettings.qml</file>
<file>Pages2/PageProtocolXrayXmuxSettings.qml</file>
<file>Pages2/PageProtocolXrayXPaddingSettings.qml</file>