refactor: move item list -> new component

This commit is contained in:
2 * r + 2 * t 2026-06-07 00:32:10 +10:00
parent b874dfc983
commit ab87ba51da
2 changed files with 230 additions and 203 deletions

View file

@ -0,0 +1,123 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.services
StyledRect {
id: root
property bool showList
property string placeholderIcon
property string placeholderText
property int extraHeight
property alias model: list.model
property alias delegate: list.delegate
readonly property alias list: list
Layout.fillWidth: true
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Tokens.padding.extraLarge * 2) + extraHeight
radius: Tokens.rounding.extraSmall
color: Colours.tPalette.m3surfaceContainer
clip: true
Behavior on implicitHeight {
Anim {}
}
Loader {
id: placeholder
anchors.centerIn: parent
active: opacity > 0
asynchronous: true
opacity: root.showList && list.count > 0 ? 0 : 1
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: root.placeholderIcon
color: Colours.palette.m3outline
font: Tokens.font.icon.large
animate: true
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: root.placeholderText
color: Colours.palette.m3outline
font: Tokens.font.body.large
animate: true
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
ListView {
id: list
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 0
interactive: false
opacity: root.showList ? 1 : 0
add: Transition {
Anim {
property: "opacity"
from: 0
to: 1
type: Anim.DefaultEffects
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
displaced: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}

View file

@ -59,226 +59,130 @@ PageBase {
verticalPadding: Tokens.padding.large
}
StyledRect {
Layout.fillWidth: true
implicitHeight: (Nmcli.wifiEnabled && networkList.count > 0 ? networkList.contentHeight : noNetworksPlaceholder.implicitHeight + Tokens.padding.extraLarge * 2) + (Nmcli.scanning ? Tokens.rounding.extraSmall : 0) // Inline so it isn't affected by anim
radius: Tokens.rounding.extraSmall
color: Colours.tPalette.m3surfaceContainer
clip: true
ItemList {
id: networkList
Behavior on implicitHeight {
Anim {}
showList: Nmcli.wifiEnabled
placeholderIcon: Nmcli.wifiEnabled ? "wifi_find" : "signal_wifi_off"
placeholderText: Nmcli.wifiEnabled ? qsTr("No networks found") : qsTr("Wi-Fi disabled")
extraHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0 // Inline so it isn't affected by anim
list.anchors.top: scanningIndicator.bottom
model: ScriptModel {
values: [...Nmcli.networks].sort((a, b) => {
if (a.active)
return -1;
if (b.active)
return 1;
return b.strength - a.strength;
})
}
Loader {
id: noNetworksPlaceholder
delegate: StateLayer {
id: network
anchors.centerIn: parent
active: opacity > 0
asynchronous: true
opacity: Nmcli.wifiEnabled && networkList.count > 0 ? 0 : 1
required property Nmcli.AccessPoint modelData
property bool currentSelected
property real textOpacity: disabled ? 0.5 : 1
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.extraSmall
disabled: currentSelected || Nmcli.connectingSsid() === modelData.ssid
anchors.left: networkList.list.contentItem.left
anchors.right: networkList.list.contentItem.right
implicitHeight: networkLayout.implicitHeight + networkLayout.anchors.margins * 2
radius: Tokens.rounding.extraSmall
anchors.fill: undefined
onClicked: {
if (!modelData.active) {
NetworkConnection.handleConnect(modelData);
currentSelected = true;
root.networkSelected(modelData);
}
}
Behavior on textOpacity {
Anim {
type: Anim.DefaultEffects
}
}
Connections {
function onActiveChanged(): void {
if (network.modelData.active)
network.currentSelected = false;
}
target: network.modelData
}
Connections {
function onNetworkSelected(ap: Nmcli.AccessPoint): void {
if (ap !== network.modelData)
network.currentSelected = false;
}
target: root
}
RowLayout {
id: networkLayout
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: Tokens.padding.largeIncreased
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: Nmcli.wifiEnabled ? "wifi_find" : "signal_wifi_off"
color: Colours.palette.m3outline
font: Tokens.font.icon.large
animate: true
text: Icons.getNetworkIcon(network.modelData.strength)
color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Nmcli.wifiEnabled ? qsTr("No networks found") : qsTr("Wi-Fi disabled")
color: Colours.palette.m3outline
font: Tokens.font.body.large
animate: true
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
opacity: network.textOpacity
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
ListView {
id: networkList
anchors.left: parent.left
anchors.right: parent.right
anchors.top: scanningIndicator.bottom
anchors.bottom: parent.bottom
spacing: 0
interactive: false
model: ScriptModel {
values: [...Nmcli.networks].sort((a, b) => {
if (a.active)
return -1;
if (b.active)
return 1;
return b.strength - a.strength;
})
}
add: Transition {
Anim {
property: "opacity"
from: 0
to: 1
type: Anim.DefaultEffects
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
displaced: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
delegate: StateLayer {
id: network
required property Nmcli.AccessPoint modelData
property bool currentSelected
property real textOpacity: disabled ? 0.5 : 1
disabled: currentSelected || Nmcli.connectingSsid() === modelData.ssid
anchors.left: networkList.contentItem.left
anchors.right: networkList.contentItem.right
implicitHeight: networkLayout.implicitHeight + networkLayout.anchors.margins * 2
radius: Tokens.rounding.extraSmall
anchors.fill: undefined
onClicked: {
if (!modelData.active) {
NetworkConnection.handleConnect(modelData);
currentSelected = true;
root.networkSelected(modelData);
}
}
Behavior on textOpacity {
Anim {
type: Anim.DefaultEffects
}
}
Connections {
function onActiveChanged(): void {
if (network.modelData.active)
network.currentSelected = false;
}
target: network.modelData
}
Connections {
function onNetworkSelected(ap: Nmcli.AccessPoint): void {
if (ap !== network.modelData)
network.currentSelected = false;
}
target: root
}
RowLayout {
id: networkLayout
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: Tokens.padding.largeIncreased
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
MaterialIcon {
text: Icons.getNetworkIcon(network.modelData.strength)
color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
ColumnLayout {
StyledText {
Layout.fillWidth: true
spacing: 0
opacity: network.textOpacity
StyledText {
Layout.fillWidth: true
text: network.modelData.ssid
font: Tokens.font.body.small
elide: Text.ElideRight
}
StyledText {
Layout.fillWidth: true
text: qsTr("Security: %1").arg(network.modelData.security)
color: Colours.tPalette.m3outline
font: Tokens.font.label.small
elide: Text.ElideRight
}
text: network.modelData.ssid
font: Tokens.font.body.small
elide: Text.ElideRight
}
AnimLoader {
sourceComp: Nmcli.connectingSsid() === network.modelData.ssid ? loadingComp : iconComp
Component {
id: iconComp
MaterialIcon {
text: network.modelData.active ? "settings" : "lock"
color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
}
Component {
id: loadingComp
LoadingIndicator {
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
}
}
StyledText {
Layout.fillWidth: true
text: qsTr("Security: %1").arg(network.modelData.security)
color: Colours.tPalette.m3outline
font: Tokens.font.label.small
elide: Text.ElideRight
}
}
}
opacity: Nmcli.wifiEnabled ? 1 : 0
AnimLoader {
sourceComp: Nmcli.connectingSsid() === network.modelData.ssid ? loadingComp : iconComp
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
Component {
id: iconComp
MaterialIcon {
text: network.modelData.active ? "settings" : "lock"
color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant
font: Tokens.font.icon.medium
opacity: network.textOpacity
}
}
Component {
id: loadingComp
LoadingIndicator {
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
}
}
}
}
}