nosignal-shell/modules/utilities/cards/RecordingList.qml
2026-04-11 21:41:31 +10:00

241 lines
7.1 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Models
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
import Caelestia.Config
import qs.utils
ColumnLayout {
id: root
required property var props
required property DrawerVisibilities visibilities
spacing: 0
WrapperMouseArea {
Layout.fillWidth: true
cursorShape: Qt.PointingHandCursor
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
RowLayout {
spacing: Tokens.spacing.smaller
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
text: "list"
font.pointSize: Tokens.font.size.large
}
StyledText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: qsTr("Recordings")
font.pointSize: Tokens.font.size.normal
}
IconButton {
icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
type: IconButton.Text
label.animate: true
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
}
}
}
StyledListView {
id: list
model: FileSystemModel {
path: Paths.recsdir
nameFilters: ["recording_*.mp4"]
sortReverse: true
}
Layout.fillWidth: true
Layout.rightMargin: -Tokens.spacing.small
implicitHeight: (Tokens.font.size.larger + Tokens.padding.small) * (root.props.recordingListExpanded ? 10 : 3)
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: list
}
delegate: RowLayout {
id: recording
required property FileSystemEntry modelData
property string baseName
anchors.left: list.contentItem.left
anchors.right: list.contentItem.right
anchors.rightMargin: Tokens.spacing.small
spacing: Tokens.spacing.small / 2
Component.onCompleted: baseName = modelData.baseName
StyledText {
Layout.fillWidth: true
Layout.rightMargin: Tokens.spacing.small / 2
text: {
const time = recording.baseName;
const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/);
if (!matches)
return time;
const date = new Date(...matches.slice(1));
date.setMonth(date.getMonth() - 1); // Woe (months start from 0)
return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale()));
}
color: Colours.palette.m3onSurfaceVariant
elide: Text.ElideRight
}
IconButton {
icon: "play_arrow"
type: IconButton.Text
onClicked: {
root.visibilities.utilities = false;
root.visibilities.sidebar = false;
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
}
}
IconButton {
icon: "folder"
type: IconButton.Text
onClicked: {
root.visibilities.utilities = false;
root.visibilities.sidebar = false;
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
}
}
IconButton {
icon: "delete_forever"
type: IconButton.Text
label.color: Colours.palette.m3error
stateLayer.color: Colours.palette.m3error
onClicked: root.props.recordingConfirmDelete = recording.modelData.path
}
}
add: Transition {
Anim {
property: "opacity"
from: 0
to: 1
}
Anim {
property: "scale"
from: 0.5
to: 1
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
Anim {
property: "scale"
to: 0.5
}
}
displaced: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
Anim {
property: "y"
}
}
Loader {
asynchronous: true
anchors.centerIn: parent
opacity: list.count === 0 ? 1 : 0
active: opacity > 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "scan_delete"
color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.extraLarge
opacity: root.props.recordingListExpanded ? 1 : 0
scale: root.props.recordingListExpanded ? 1 : 0
Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on Layout.preferredHeight {
Anim {}
}
}
RowLayout {
spacing: Tokens.spacing.smaller
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "scan_delete"
color: Colours.palette.m3outline
opacity: !root.props.recordingListExpanded ? 1 : 0
scale: !root.props.recordingListExpanded ? 1 : 0
Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on Layout.preferredWidth {
Anim {}
}
}
StyledText {
text: qsTr("No recordings found")
color: Colours.palette.m3outline
}
}
}
Behavior on opacity {
Anim {}
}
}
Behavior on implicitHeight {
Anim {
duration: Tokens.anim.durations.expressiveDefaultSpatial
easing: Tokens.anim.expressiveDefaultSpatial
}
}
}
}