Feature: Make home UniFi VPN row a collapsible card
Put the Replace config option behind an expand chevron, like Mullvad's options, and frame the card while it's expanded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,9 @@ Item {
|
|||||||
property string protonActionStatus: ""
|
property string protonActionStatus: ""
|
||||||
property string homeWgActionStatus: ""
|
property string homeWgActionStatus: ""
|
||||||
property bool homeWgImporting: homeWgImportProc.running
|
property bool homeWgImporting: homeWgImportProc.running
|
||||||
|
// Whether Home's options (Replace config) are expanded — kept across
|
||||||
|
// panel opens, like mullvadOptionsOpen.
|
||||||
|
property bool homeWgOptionsOpen: false
|
||||||
|
|
||||||
// --- Mullvad advanced options (auto-connect, relay location, multihop) --
|
// --- Mullvad advanced options (auto-connect, relay location, multihop) --
|
||||||
// ProtonVPN's CLI is plain-text-only and far less predictable across
|
// ProtonVPN's CLI is plain-text-only and far less predictable across
|
||||||
@@ -1006,7 +1009,124 @@ Item {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 2
|
// Room for the cards' outlines (drawn 3px outside them)
|
||||||
|
// so two expanded ones don't touch.
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// Same collapsible card as Mullvad below — framed only
|
||||||
|
// while its options (Replace config) are expanded.
|
||||||
|
Item {
|
||||||
|
readonly property bool _framed: root.homeWgOptionsOpen && root.homeWgInstalled
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: homeWgSectionColumn.implicitHeight
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outline offset outside the content rather than drawn on
|
||||||
|
// its own edge — same as Volume.qml's DeviceRow hover —
|
||||||
|
// so the row doesn't shift when the options open.
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -3
|
||||||
|
radius: 0
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Colors.foreground
|
||||||
|
border.width: parent._framed ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipped separately so the outline above isn't.
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: homeWgSectionColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ProviderRow {
|
||||||
|
label: "Home (UniFi)"
|
||||||
|
subIcon: "home"
|
||||||
|
state: root.homeWgState
|
||||||
|
detailText: root.homeWgDetail
|
||||||
|
installed: root.homeWgInstalled
|
||||||
|
missingText: "Not set up"
|
||||||
|
expandable: root.homeWgInstalled
|
||||||
|
expanded: root.homeWgOptionsOpen
|
||||||
|
onToggle: root.toggleHomeWg()
|
||||||
|
onExpandToggle: root.homeWgOptionsOpen = !root.homeWgOptionsOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusLine { message: root.homeWgActionStatus }
|
||||||
|
|
||||||
|
// Imports (or replaces) the UniFi client .conf through a
|
||||||
|
// file chooser. Shown outright until a profile exists
|
||||||
|
// (so first-time setup isn't hidden behind the
|
||||||
|
// chevron), then only inside the expanded options.
|
||||||
|
// Hidden while connected — replacing deletes the
|
||||||
|
// profile, which would drop the tunnel.
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: importRow.implicitHeight + 8
|
||||||
|
visible: root.homeWgState === "disconnected"
|
||||||
|
&& (!root.homeWgInstalled || root.homeWgOptionsOpen)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: importRow
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 34
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
opacity: root.homeWgImporting ? 0.5 : 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.homeWgInstalled ? "sync" : "upload_file"
|
||||||
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.homeWgImporting ? "Choosing file…"
|
||||||
|
: root.homeWgInstalled ? "Replace config" : "Add config"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
font.underline: importMouse.containsMouse
|
||||||
|
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: importMouse
|
||||||
|
anchors.fill: importRow
|
||||||
|
anchors.margins: -4
|
||||||
|
enabled: !root.homeWgImporting
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.importHomeWgConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 34
|
||||||
|
width: parent.width - x
|
||||||
|
visible: root.homeWgOptionsOpen && root.homeWgInstalled
|
||||||
|
&& root.homeWgState !== "disconnected"
|
||||||
|
text: "Disconnect to replace config"
|
||||||
|
bottomPadding: 6
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mullvad's connect row and its advanced options share a
|
// Mullvad's connect row and its advanced options share a
|
||||||
// single bordered card — the border wraps the whole
|
// single bordered card — the border wraps the whole
|
||||||
@@ -1014,14 +1134,11 @@ Item {
|
|||||||
// grows to include the options/picker as they open,
|
// grows to include the options/picker as they open,
|
||||||
// rather than the options having a separate box of
|
// rather than the options having a separate box of
|
||||||
// their own below an unbordered connect row.
|
// their own below an unbordered connect row.
|
||||||
Rectangle {
|
// Only outlined while the options are expanded.
|
||||||
|
Item {
|
||||||
|
readonly property bool _framed: root.mullvadOptionsOpen && root.mullvadInstalled
|
||||||
width: parent.width
|
width: parent.width
|
||||||
radius: 0
|
implicitHeight: mullvadSectionColumn.implicitHeight
|
||||||
color: Colors.background
|
|
||||||
border.color: Colors.muted
|
|
||||||
border.width: 1
|
|
||||||
implicitHeight: mullvadSectionColumn.implicitHeight + 16
|
|
||||||
clip: true
|
|
||||||
// Without this, the card snapped to its new size
|
// Without this, the card snapped to its new size
|
||||||
// instantly while the popup around it was still
|
// instantly while the popup around it was still
|
||||||
// animating open, so the border visibly lagged
|
// animating open, so the border visibly lagged
|
||||||
@@ -1031,12 +1148,28 @@ Item {
|
|||||||
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outline offset outside the content rather than drawn on
|
||||||
|
// its own edge — same as Volume.qml's DeviceRow hover —
|
||||||
|
// so the row doesn't shift when the options open.
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -3
|
||||||
|
radius: 0
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Colors.foreground
|
||||||
|
border.width: parent._framed ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipped separately so the outline above isn't.
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mullvadSectionColumn
|
id: mullvadSectionColumn
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
@@ -1199,6 +1332,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
label: "ProtonVPN"
|
label: "ProtonVPN"
|
||||||
@@ -1210,64 +1344,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StatusLine { message: root.protonActionStatus }
|
StatusLine { message: root.protonActionStatus }
|
||||||
|
|
||||||
ProviderRow {
|
|
||||||
label: "Home (UniFi)"
|
|
||||||
subIcon: "home"
|
|
||||||
state: root.homeWgState
|
|
||||||
detailText: root.homeWgDetail
|
|
||||||
installed: root.homeWgInstalled
|
|
||||||
missingText: "Not set up"
|
|
||||||
onToggle: root.toggleHomeWg()
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusLine { message: root.homeWgActionStatus }
|
|
||||||
|
|
||||||
// Imports (or replaces) the UniFi client .conf through a
|
|
||||||
// file chooser. Hidden while connected — replacing
|
|
||||||
// deletes the profile, which would drop the tunnel.
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
implicitHeight: importRow.implicitHeight + 8
|
|
||||||
visible: root.homeWgState === "disconnected"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: importRow
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 34
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
opacity: root.homeWgImporting ? 0.5 : 1
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.homeWgInstalled ? "sync" : "upload_file"
|
|
||||||
font.family: Appearance.iconFontFamilyRounded
|
|
||||||
font.variableAxes: Appearance.iconFontAxes
|
|
||||||
font.pixelSize: 13
|
|
||||||
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.homeWgImporting ? "Choosing file…"
|
|
||||||
: root.homeWgInstalled ? "Replace config" : "Add config"
|
|
||||||
font.family: Appearance.fontFamily
|
|
||||||
font.pixelSize: Appearance.fontSizeSmall
|
|
||||||
font.underline: importMouse.containsMouse
|
|
||||||
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: importMouse
|
|
||||||
anchors.fill: importRow
|
|
||||||
anchors.margins: -4
|
|
||||||
enabled: !root.homeWgImporting
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.importHomeWgConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|||||||
Reference in New Issue
Block a user