Generic Search Page

Pull the generic aspects from Room search and join room pages into it's own component. This is done in anticipation of using the new generic search page for a user search functionality.

- `SearchPage` is now used for the generic version with the old one being renamed `RoomSearchPage`
- `JoinRoomPage` is renamed to `ExploreRoomsPage` inline with everywhere else in NeoChat

There is also some cleanup of the code for both search pages in here.
This commit is contained in:
James Graham
2024-01-19 17:59:45 +00:00
parent 80f3bd64b6
commit f6a5cc7c25
17 changed files with 459 additions and 393 deletions

View File

@@ -103,7 +103,7 @@ FormCard.FormCardPage {
visible: !chosenRoomDelegate.visible
text: i18nc("@action:button", "Pick room")
onClicked: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
@@ -182,7 +182,7 @@ FormCard.FormCardPage {
}
onClicked: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;

View File

@@ -21,7 +21,7 @@ RowLayout {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
if (isJoined) {
RoomManager.enterRoom(root.connection.room(roomId))

View File

@@ -52,7 +52,7 @@ ColumnLayout {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
if (isJoined) {
RoomManager.enterRoom(root.connection.room(roomId));

View File

@@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
/**
* @brief Component for finding rooms for the public list.
*
* This component is based on a SearchPage, adding the functionality to select or
* enter a server in the header, as well as the ability to manually type a room in
* if the public room search cannot find it.
*
* @sa SearchPage
*/
SearchPage {
id: root
/**
* @brief The connection for the current local user.
*/
required property NeoChatConnection connection
/**
* @brief Whether results should only includes spaces.
*/
property bool showOnlySpaces: false
/**
* @brief Signal emitted when a room is selected.
*
* The signal contains all the room's info so that it can be acted
* upon as required, e.g. joining or entering the room or adding the room as
* the child of a space.
*/
signal roomSelected(string roomId,
string displayName,
url avatarUrl,
string alias,
string topic,
int memberCount,
bool isJoined)
title: i18nc("@action:title", "Explore Rooms")
Component.onCompleted: focusSearch()
headerTrailing: ServerComboBox {
id: serverComboBox
connection: root.connection
}
model: PublicRoomListModel {
id: publicRoomListModel
connection: root.connection
server: serverComboBox.server
showOnlySpaces: root.showOnlySpaces
}
modelDelegate: ExplorerDelegate {
onRoomSelected: (roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
}
}
listHeaderDelegate: Delegates.RoundedItemDelegate {
onClicked: _private.openManualRoomDialog()
text: i18n("Enter a room address")
icon.name: "compass"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
}
listFooterDelegate: QQC2.ProgressBar {
width: ListView.view.width
leftInset: Kirigami.Units.largeSpacing
rightInset: Kirigami.Units.largeSpacing
visible: root.count !== 0 && publicRoomListModel.searching
indeterminate: true
}
searchFieldPlaceholder: i18n("Find a room...")
noResultPlaceholderMessage: i18nc("@info:label", "No public rooms found")
Component {
id: manualRoomDialog
ManualRoomDialog {}
}
QtObject {
id: _private
function openManualRoomDialog() {
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
});
dialog.open();
}
}
}

View File

@@ -61,7 +61,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
onTriggered: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
if (isJoined) {
RoomManager.enterRoom(root.connection.room(roomId))

View File

@@ -1,285 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
Kirigami.ScrollablePage {
id: root
required property NeoChatConnection connection
property bool showOnlySpaces: false
property alias keyword: identifierField.text
property string server
/**
* @brief Signal emitted when a room is selected.
*
* The signal contains all the room's info so that it can be acted
* upon as required, e.g. joinng or entering the room or adding the room as
* the child of a space.
*/
signal roomSelected(string roomId,
string displayName,
url avatarUrl,
string alias,
string topic,
int memberCount,
bool isJoined)
title: i18n("Explore Rooms")
Component.onCompleted: identifierField.forceActiveFocus()
header: QQC2.Control {
padding: Kirigami.Units.largeSpacing
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
}
contentItem: RowLayout {
Kirigami.SearchField {
id: identifierField
Layout.fillWidth: true
placeholderText: i18n("Find a room...")
}
QQC2.ComboBox {
id: serverField
// TODO: in KF6 we should be able to switch to using implicitContentWidthPolicy
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
Component.onCompleted: currentIndex = 0
textRole: "url"
valueRole: "url"
model: ServerListModel {
id: serverListModel
connection: root.connection
}
delegate: Delegates.RoundedItemDelegate {
id: serverItem
required property int index
required property string url
required property bool isAddServerDelegate
required property bool isHomeServer
required property bool isDeletable
text: isAddServerDelegate ? i18n("Add New Server") : url
highlighted: false
topInset: index === 0 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
bottomInset: index === ListView.view.count - 1 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
onClicked: if (isAddServerDelegate) {
addServerSheet.open()
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Delegates.SubtitleContentItem {
itemDelegate: serverItem
subtitle: serverItem.isHomeServer ? i18n("Home Server") : ""
Layout.fillWidth: true
}
QQC2.ToolButton {
visible: serverItem.isAddServerDelegate || serverItem.isDeletable
icon.name: serverItem.isAddServerDelegate ? "list-add" : "dialog-close"
text: i18nc("@action:button", "Add new server")
Accessible.name: text
display: QQC2.AbstractButton.IconOnly
onClicked: {
if (serverField.currentIndex === serverItem.index && serverItem.isDeletable) {
serverField.currentIndex = 0;
server = serverField.currentValue;
serverField.popup.close();
}
if (serverItem.isAddServerDelegate) {
addServerSheet.open();
serverItem.clicked();
} else {
serverListModel.removeServerAtIndex(serverItem.index);
}
}
}
}
}
onActivated: {
if (currentIndex !== count - 1) {
server = currentValue
}
}
Kirigami.OverlaySheet {
id: addServerSheet
parent: applicationWindow().overlay
title: i18nc("@title:window", "Add server")
onOpened: if (!serverUrlField.isValidServer && !addServerSheet.opened) {
serverField.currentIndex = 0
server = serverField.currentValue
} else if (addServerSheet.opened) {
serverUrlField.forceActiveFocus()
}
contentItem: Kirigami.FormLayout {
QQC2.Label {
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
text: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? i18n("Valid server entered") : i18n("This server cannot be resolved or has already been added")) : i18n("The entered text is not a valid url")) : i18n("Enter server url e.g. kde.org")
color: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.textColor
}
QQC2.TextField {
id: serverUrlField
property bool isValidServer: false
Kirigami.FormData.label: i18n("Server URL")
onTextChanged: {
if(acceptableInput) {
serverListModel.checkServer(text)
}
}
validator: RegularExpressionValidator {
regularExpression: /^[a-zA-Z0-9-]{1,61}\.([a-zA-Z]{2,}|[a-zA-Z0-9-]{2,}\.[a-zA-Z]{2,3})$/
}
Connections {
target: serverListModel
function onServerCheckComplete(url, valid) {
if (url == serverUrlField.text && valid) {
serverUrlField.isValidServer = true
}
}
}
}
QQC2.Button {
id: okButton
text: i18nc("@action:button", "Ok")
enabled: serverUrlField.acceptableInput && serverUrlField.isValidServer
onClicked: {
serverListModel.addServer(serverUrlField.text)
serverField.currentIndex = serverField.indexOfValue(serverUrlField.text)
server = serverField.currentValue
serverUrlField.text = ""
addServerSheet.close();
}
}
}
}
}
}
Kirigami.Separator {
z: 999
anchors {
left: parent.left
right: parent.right
top: parent.bottom
}
}
}
ListView {
id: publicRoomsListView
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
bottomMargin: Math.round(Kirigami.Units.smallSpacing / 2)
model: PublicRoomListModel {
id: publicRoomListModel
connection: root.connection
server: root.server
keyword: root.keyword
showOnlySpaces: root.showOnlySpaces
}
onContentYChanged: {
if(publicRoomListModel.hasMore && contentHeight - contentY < publicRoomsListView.height + 200)
publicRoomListModel.next();
}
delegate: ExplorerDelegate {
onRoomSelected: (roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
}
}
header: Delegates.RoundedItemDelegate {
Layout.fillWidth: true
onClicked: _private.openManualRoomDialog()
text: i18n("Enter a room address")
icon.name: "compass"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
}
footer: QQC2.ProgressBar {
width: parent.width
visible: publicRoomsListView.count !== 0 && publicRoomsListView.model.loading
indeterminate: true
padding: Kirigami.Units.largeSpacing * 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
}
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
visible: publicRoomsListView.model.loading && publicRoomsListView.count === 0
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: !publicRoomsListView.model.loading && publicRoomsListView.count === 0
text: i18nc("@info:label", "No public rooms found")
}
}
Component {
id: manualRoomDialog
ManualRoomDialog {}
}
QtObject {
id: _private
function openManualRoomDialog() {
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
});
dialog.open();
}
}
}

View File

@@ -95,9 +95,8 @@ QQC2.ScrollView {
Layout.fillWidth: true
onClicked: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/SearchPage.qml", {
currentRoom: root.room,
connection: root.connection
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/RoomSearchPage.qml", {
room: root.room
}, {
title: i18nc("@action:title", "Search")
})

View File

@@ -164,7 +164,7 @@ Kirigami.Page {
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
onTriggered: {
let dialog = pageStack.layers.push("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {
let dialog = pageStack.layers.push("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {
connection: root.connection,
keyword: sortFilterRoomListModel.filterText
}, {

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import org.kde.neochat
/**
* @brief Component for finding messages in a room.
*
* This component is based on a SearchPage and allows the user to enter a search
* term into the input field and then search the room for messages with text that
* matches the input.
*
* @sa SearchPage
*/
SearchPage {
id: root
/**
* @brief The room the search is being performed in.
*/
required property NeoChatRoom room
title: i18nc("@action:title", "Search Messages")
model: SearchModel {
id: searchModel
room: root.room
}
modelDelegate: EventDelegate {
room: root.room
}
searchFieldPlaceholder: i18n("Find messages…")
noSearchPlaceholderMessage: i18n("Enter text to start searching")
noResultPlaceholderMessage: i18n("No messages found")
listVerticalLayoutDirection: ListView.BottomToTop
}

View File

@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
// 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
@@ -7,29 +7,87 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
/**
* @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
property NeoChatRoom currentRoom
required property NeoChatConnection connection
/**
* @brief Any additional controls after the search button.
*/
property alias headerTrailing: headerContent.children
title: i18nc("@action:title", "Search Messages")
/**
* @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
Kirigami.Theme.colorSet: Kirigami.Theme.Window
/**
* @brief The number of delegates currently in the view.
*/
property alias count: listView.count
SearchModel {
id: searchModel
connection: root.connection
searchText: searchField.text
room: root.currentRoom
/**
* @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 Force the search field to be focussed.
*/
function focusSearch() {
searchField.forceActiveFocus();
}
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 {
@@ -42,6 +100,7 @@ Kirigami.ScrollablePage {
}
contentItem: RowLayout {
id: headerContent
spacing: Kirigami.Units.largeSpacing
Kirigami.SearchField {
@@ -50,44 +109,48 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true
Keys.onEnterPressed: searchButton.clicked()
Keys.onReturnPressed: searchButton.clicked()
onTextChanged: {
if (model) {
model.searchText = text;
}
}
}
QQC2.Button {
id: searchButton
onClicked: searchModel.search()
icon.name: "search"
onClicked: {
if (typeof model.search === 'function') {
model.search()
}
}
}
}
}
ListView {
id: messageListView
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
section.property: "section"
Kirigami.PlaceholderMessage {
id: noSearchMessage
anchors.centerIn: parent
visible: searchField.text.length === 0 && messageListView.count === 0
text: i18n("Enter a text to start searching")
visible: searchField.text.length === 0 && listView.count === 0
}
Kirigami.PlaceholderMessage {
id: noResultMessage
anchors.centerIn: parent
visible: searchField.text.length > 0 && messageListView.count === 0 && !searchModel.searching
text: i18n("No results found")
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching
}
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
visible: searchModel.searching
}
model: searchModel
delegate: EventDelegate {
room: root.currentRoom
visible: root.model.searching
}
}
}

View File

@@ -49,7 +49,7 @@ Kirigami.Dialog {
visible: !chosenRoomDelegate.visible
text: i18nc("@action:button", "Pick room")
onClicked: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.room.connection, showOnlySpaces: true}, {title: i18nc("@title", "Choose Parent Space")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.room.connection, showOnlySpaces: true}, {title: i18nc("@title", "Choose Parent Space")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
@@ -128,7 +128,7 @@ Kirigami.Dialog {
}
onClicked: {
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.room.connection, showOnlySpaces: true}, {title: i18nc("@title", "Explore Rooms")})
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.room.connection, showOnlySpaces: true}, {title: i18nc("@title", "Explore Rooms")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;

161
src/qml/ServerComboBox.qml Normal file
View File

@@ -0,0 +1,161 @@
// 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
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
QQC2.ComboBox {
id: root
/**
* @brief The connection for the current local user.
*/
required property NeoChatConnection connection
/**
* @brief The server to get the search results from.
*/
property string server
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
Component.onCompleted: currentIndex = 0
textRole: "url"
valueRole: "url"
model: ServerListModel {
id: serverListModel
connection: root.connection
}
delegate: Delegates.RoundedItemDelegate {
id: serverItem
required property int index
required property string url
required property bool isAddServerDelegate
required property bool isHomeServer
required property bool isDeletable
text: isAddServerDelegate ? i18n("Add New Server") : url
highlighted: false
topInset: index === 0 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
bottomInset: index === ListView.view.count - 1 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
onClicked: if (isAddServerDelegate) {
addServerSheet.open()
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Delegates.SubtitleContentItem {
itemDelegate: serverItem
subtitle: serverItem.isHomeServer ? i18n("Home Server") : ""
Layout.fillWidth: true
}
QQC2.ToolButton {
visible: serverItem.isAddServerDelegate || serverItem.isDeletable
icon.name: serverItem.isAddServerDelegate ? "list-add" : "dialog-close"
text: i18nc("@action:button", "Add new server")
Accessible.name: text
display: QQC2.AbstractButton.IconOnly
onClicked: {
if (root.currentIndex === serverItem.index && serverItem.isDeletable) {
root.currentIndex = 0;
root.server = root.currentValue
root.popup.close();
}
if (serverItem.isAddServerDelegate) {
addServerSheet.open();
serverItem.clicked();
} else {
serverListModel.removeServerAtIndex(serverItem.index);
}
}
}
}
}
onActivated: {
if (currentIndex !== count - 1) {
root.server = root.currentValue
}
}
Kirigami.OverlaySheet {
id: addServerSheet
parent: applicationWindow().overlay
title: i18nc("@title:window", "Add server")
onOpened: if (!serverUrlField.isValidServer && !addServerSheet.opened) {
root.currentIndex = 0
root.server = root.currentValue
} else if (addServerSheet.opened) {
serverUrlField.forceActiveFocus()
}
onClosed: if (serverUrlField.length <= 0) {
root.currentIndex = root.indexOfValue(root.server)
}
contentItem: Kirigami.FormLayout {
QQC2.Label {
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
text: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? i18n("Valid server entered") : i18n("This server cannot be resolved or has already been added")) : i18n("The entered text is not a valid url")) : i18n("Enter server url e.g. kde.org")
color: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.textColor
}
QQC2.TextField {
id: serverUrlField
property bool isValidServer: false
Kirigami.FormData.label: i18n("Server URL")
onTextChanged: {
if(acceptableInput) {
serverListModel.checkServer(text)
}
}
validator: RegularExpressionValidator {
regularExpression: /^[a-zA-Z0-9-]{1,61}\.([a-zA-Z]{2,}|[a-zA-Z0-9-]{2,}\.[a-zA-Z]{2,3})$/
}
Connections {
target: serverListModel
function onServerCheckComplete(url, valid) {
if (url == serverUrlField.text && valid) {
serverUrlField.isValidServer = true
}
}
}
}
QQC2.Button {
id: okButton
text: i18nc("@action:button", "Ok")
enabled: serverUrlField.acceptableInput && serverUrlField.isValidServer
onClicked: {
serverListModel.addServer(serverUrlField.text)
root.currentIndex = root.indexOfValue(serverUrlField.text)
root.server = root.currentValue
serverUrlField.text = ""
addServerSheet.close();
}
}
}
}
}