Move remaining code to app module
There's still some stuff that could potentially go elsewhere but I think it's enough for now.
This commit is contained in:
211
src/app/qml/SearchPage.qml
Normal file
211
src/app/qml/SearchPage.qml
Normal file
@@ -0,0 +1,211 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* @brief Component for a generic search page.
|
||||
*
|
||||
* This component provides a header with the search field and a ListView to visualise
|
||||
* search results from the given model.
|
||||
*/
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief Any additional controls after the search button.
|
||||
*/
|
||||
property alias headerTrailing: headerContent.children
|
||||
|
||||
/**
|
||||
* @brief The model that provides the search results.
|
||||
*
|
||||
* The model needs to provide the following properties:
|
||||
* - searchText
|
||||
* - searching
|
||||
* Where searchText is the text from the searchField and is used to match results
|
||||
* and searching is true while the model is finding results.
|
||||
*
|
||||
* The model must also provide a search() function to start the search if
|
||||
* it doesn't do so when the searchText is changed.
|
||||
*/
|
||||
property alias model: listView.model
|
||||
|
||||
/**
|
||||
* @brief The number of delegates currently in the view.
|
||||
*/
|
||||
property alias count: listView.count
|
||||
|
||||
/**
|
||||
* @brief The delegate to use to visualize the model data.
|
||||
*/
|
||||
property alias modelDelegate: listView.delegate
|
||||
|
||||
/**
|
||||
* @brief The delegate to appear as the header of the list.
|
||||
*/
|
||||
property alias listHeaderDelegate: listView.header
|
||||
|
||||
/**
|
||||
* @brief The delegate to appear as the footer of the list.
|
||||
*/
|
||||
property alias listFooterDelegate: listView.footer
|
||||
|
||||
/**
|
||||
* @brief The placeholder text in the search field.
|
||||
*/
|
||||
property alias searchFieldPlaceholder: searchField.placeholderText
|
||||
|
||||
/**
|
||||
* @brief The text to show when no search term has been entered.
|
||||
*/
|
||||
property alias noSearchPlaceholderMessage: noSearchMessage.text
|
||||
|
||||
/**
|
||||
* @brief The text to show when no results have been found.
|
||||
*/
|
||||
property alias noResultPlaceholderMessage: noResultMessage.text
|
||||
|
||||
/**
|
||||
* @brief The verticalLayoutDirection property of the internal ListView.
|
||||
*/
|
||||
property alias listVerticalLayoutDirection: listView.verticalLayoutDirection
|
||||
|
||||
/**
|
||||
* @brief Set the visibility of the search button.
|
||||
*/
|
||||
property bool showSearchButton: true
|
||||
|
||||
/**
|
||||
* @brief Message to be shown in a custom placeholder.
|
||||
* The custom placeholder will be shown if the text is not empty
|
||||
*/
|
||||
property alias customPlaceholderText: customPlaceholder.text
|
||||
|
||||
/**
|
||||
* @brief icon for the custom placeholder
|
||||
*/
|
||||
property string customPlaceholderIcon: ""
|
||||
|
||||
/**
|
||||
* @brief Force the search field to be focussed.
|
||||
*/
|
||||
function focusSearch() {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Force the search to be updated if the model has a valid search function.
|
||||
*/
|
||||
function updateSearch() {
|
||||
searchTimer.restart();
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
background: Rectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.Theme.inherit: false
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: headerContent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
focus: true
|
||||
Layout.fillWidth: true
|
||||
Keys.onEnterPressed: searchButton.clicked()
|
||||
Keys.onReturnPressed: searchButton.clicked()
|
||||
onTextChanged: {
|
||||
searchTimer.restart();
|
||||
if (model) {
|
||||
model.searchText = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: searchButton
|
||||
icon.name: "search"
|
||||
display: QQC2.Button.IconOnly
|
||||
visible: root.showSearchButton
|
||||
text: i18nc("@action:button", "Search")
|
||||
|
||||
onClicked: {
|
||||
if (typeof model.search === 'function') {
|
||||
model.search();
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
Timer {
|
||||
id: searchTimer
|
||||
interval: 500
|
||||
running: true
|
||||
onTriggered: if (typeof model.search === 'function') {
|
||||
model.search();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
|
||||
section.property: "section"
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noSearchMessage
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noResultMessage
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: customPlaceholder
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && text.length > 0
|
||||
icon.name: root.customPlaceholderIcon
|
||||
}
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
|
||||
}
|
||||
|
||||
Keys.onUpPressed: {
|
||||
if (listView.currentIndex > 0) {
|
||||
listView.decrementCurrentIndex();
|
||||
} else {
|
||||
listView.currentIndex = -1; // This is so the list view doesn't appear to have two selected items
|
||||
listView.headerItem.forceActiveFocus(Qt.TabFocusReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user