Mobile explore component

Create a mobile version of explore component and place it at the bottom for single handed use. This also refactors the UserInfo component so it can be at the top on mobile as well as the bottom on dektop.

This should have no effect on desktop and should be identical.

![image](/uploads/9b3133fbde74ca27069d6b039efb1079/image.png)
This commit is contained in:
James Graham
2023-11-03 17:22:57 +00:00
parent 9cac2a8abd
commit 2065eb6684
5 changed files with 375 additions and 130 deletions

View File

@@ -146,12 +146,14 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/main.qml qml/main.qml
qml/AccountMenu.qml qml/AccountMenu.qml
qml/ExploreComponent.qml qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
qml/ContextMenu.qml qml/ContextMenu.qml
qml/CollapsedRoomDelegate.qml qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml qml/RoomDelegate.qml
qml/RoomListPage.qml qml/RoomListPage.qml
qml/SpaceListContextMenu.qml qml/SpaceListContextMenu.qml
qml/UserInfo.qml qml/UserInfo.qml
qml/UserInfoDesktop.qml
qml/LoadingPage.qml qml/LoadingPage.qml
qml/RoomPage.qml qml/RoomPage.qml
qml/RoomWindow.qml qml/RoomWindow.qml

View File

@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2023 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
ColumnLayout {
id: root
spacing: 0
/**
* @brief The connection for the current user.
*/
required property NeoChatConnection connection
/**
* @brief Emitted when the text is changed in the search field.
*/
signal textChanged(string newText)
Kirigami.Separator {
Layout.fillWidth: true
}
Kirigami.NavigationTabBar {
id: exploreTabBar
Layout.fillWidth: true
actions: [
Kirigami.Action {
id: infoAction
text: i18n("Search")
icon.name: "search"
onTriggered: {
if (explorePopup.visible && explorePopupLoader.sourceComponent == search) {
explorePopup.close();
exploreTabBar.currentIndex = -1;
} else if (explorePopup.visible && explorePopupLoader.sourceComponent != search) {
explorePopup.close();
explorePopup.open();
} else {
explorePopup.open();
}
explorePopupLoader.sourceComponent = search;
}
},
Kirigami.Action {
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")})
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
if (isJoined) {
RoomManager.enterRoom(root.connection.room(roomId));
} else {
Controller.joinRoom(roomId.length > 0 ? roomId : alias);
}
})
exploreTabBar.currentIndex = -1;
}
},
Kirigami.Action {
text: i18n("Start a Chat")
icon.name: "list-add-user"
onTriggered: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")})
exploreTabBar.currentIndex = -1;
}
},
Kirigami.Action {
text: i18n("Create New")
icon.name: "list-add"
onTriggered: {
if (explorePopup.visible && explorePopupLoader.sourceComponent == create) {
explorePopup.close();
exploreTabBar.currentIndex = -1;
} else if (explorePopup.visible && explorePopupLoader.sourceComponent != create) {
explorePopup.close();
explorePopup.open();
} else {
explorePopup.open();
}
explorePopupLoader.sourceComponent = create;
}
}
]
}
QQC2.Popup {
id: explorePopup
parent: root
y: -height + 1
width: root.width
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing
closePolicy: QQC2.Popup.CloseOnEscape
contentItem: Loader {
id: explorePopupLoader
sourceComponent: search
}
background: ColumnLayout {
spacing: 0
Kirigami.Separator {
Layout.fillWidth: true
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Kirigami.Theme.backgroundColor
}
}
Component {
id: search
Kirigami.SearchField {
onTextChanged: root.textChanged(text)
}
}
Component {
id: create
ColumnLayout {
spacing: 0
Delegates.RoundedItemDelegate {
Layout.fillWidth: true
action: Kirigami.Action {
text: i18n("Create a Room")
icon.name: "system-users-symbolic"
onTriggered: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Create a Room")})
explorePopup.close()
}
shortcut: StandardKey.New
}
}
Delegates.RoundedItemDelegate {
Layout.fillWidth: true
action: Kirigami.Action {
text: i18n("Create a Space")
icon.name: "list-add"
onTriggered: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection, isSpace: true, title: i18nc("@title", "Create a Space")}, {title: i18nc("@title", "Create a Space")})
explorePopup.close()
}
}
}
}
}
}
}

View File

@@ -84,11 +84,9 @@ Kirigami.Page {
goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread)); goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread));
} }
titleDelegate: ExploreComponent { titleDelegate: Loader {
Layout.fillWidth: true Layout.fillWidth: true
desiredWidth: root.width - Kirigami.Units.largeSpacing sourceComponent: Kirigami.Settings.isMobile ? userInfo : exploreComponent
collapsed: root.collapsed
connection: root.connection
} }
padding: 0 padding: 0
@@ -284,10 +282,9 @@ Kirigami.Page {
} }
} }
footer: UserInfo { footer: Loader {
width: parent.width width: parent.width
visible: !root.collapsed sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
connection: root.connection
} }
MouseArea { MouseArea {
@@ -333,6 +330,43 @@ Kirigami.Page {
} }
} }
Component {
id: userInfo
UserInfo {
visible: !root.collapsed
bottomEdge: false
connection: root.connection
}
}
Component {
id: userInfoDesktop
UserInfoDesktop {
visible: !root.collapsed
connection: root.connection
}
}
Component {
id: exploreComponent
ExploreComponent {
desiredWidth: root.width - Kirigami.Units.largeSpacing
collapsed: root.collapsed
connection: root.connection
}
}
Component {
id: exploreComponentMobile
ExploreComponentMobile {
connection: root.connection
onTextChanged: (newText) => {
sortFilterRoomListModel.filterText = newText
}
}
}
/* /*
* Hold the modifiable currentWidth in a private object so that only internal * Hold the modifiable currentWidth in a private object so that only internal
* members can modify it. * members can modify it.

View File

@@ -12,142 +12,20 @@ import org.kde.neochat
import org.kde.neochat.config import org.kde.neochat.config
import org.kde.neochat.accounts import org.kde.neochat.accounts
QQC2.ToolBar { RowLayout {
id: root id: root
padding: 0
required property NeoChatConnection connection required property NeoChatConnection connection
property bool bottomEdge: true
property var addAccount property var addAccount
contentItem: ColumnLayout {
id: content
spacing: 0
ListView {
id: accounts
currentIndex: Controller.activeConnectionIndex
header: Kirigami.Separator {}
footer: Delegates.RoundedItemDelegate {
id: addButton
width: parent.width
highlighted: focus || (addAccount.highlighted || addAccount.ListView.isCurrentItem) && !addAccount.pressed
Component.onCompleted: root.addAccount = this
icon {
name: "list-add"
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
}
text: i18n("Add Account")
contentItem: Delegates.SubtitleContentItem {
itemDelegate: parent
subtitle: i18n("Log in to an existing account")
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
}
onClicked: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/WelcomePage.qml", {}, {
title: i18nc("@title:window", "Login"),
});
if (switchUserButton.checked) {
switchUserButton.checked = false
}
accounts.currentIndex = Controller.activeConnectionIndex
}
Keys.onUpPressed: {
accounts.currentIndex = accounts.count - 1
accounts.forceActiveFocus()
}
Keys.onDownPressed: {
accounts.currentIndex = 0
accounts.forceActiveFocus()
}
}
visible: switchUserButton.checked
onVisibleChanged: if (visible) accounts.forceActiveFocus()
clip: true
model: AccountRegistry
keyNavigationEnabled: false
Keys.onDownPressed: {
if (accounts.currentIndex === accounts.count - 1) {
addAccount.forceActiveFocus()
accounts.currentIndex = -1
} else {
accounts.incrementCurrentIndex()
}
}
Keys.onUpPressed: {
if (accounts.currentIndex === 0) {
addAccount.forceActiveFocus()
accounts.currentIndex = -1
} else {
accounts.decrementCurrentIndex()
}
}
Keys.onReleased: if (event.key == Qt.Key_Escape) {
if (switchUserButton.checked) {
switchUserButton.checked = false
}
}
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property NeoChatConnection connection
width: parent.width
text: connection.localUser.displayName
contentItem: RowLayout {
KirigamiComponents.Avatar {
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
sourceSize {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
}
source: userDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + userDelegate.connection.localUser.avatarMediaId) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
}
Delegates.SubtitleContentItem {
itemDelegate: userDelegate
subtitle: userDelegate.connection.localUser.id
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
}
}
onClicked: {
Controller.activeConnection = userDelegate.connection
if (switchUserButton.checked) {
switchUserButton.checked = false
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.smallSpacing Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.minimumHeight: Kirigami.Units.gridUnit * 2 - 2 // HACK: -2 here is to ensure the ChatBox and the UserInfo have the same height Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 2 - 2 : -1 // HACK: -2 here is to ensure the ChatBox and the UserInfo have the same height
QQC2.AbstractButton { QQC2.AbstractButton {
id: accountButton id: accountButton
@@ -251,9 +129,149 @@ QQC2.ToolBar {
AccountMenu { AccountMenu {
id: accountMenu id: accountMenu
y: -height y: root.bottomEdge ? -height : accountButton.height
connection: root.connection connection: root.connection
} }
QQC2.Popup {
id: accountsPopup
parent: root
visible: switchUserButton.checked
onVisibleChanged: if (visible) accounts.forceActiveFocus()
x: -Kirigami.Units.smallSpacing
y: root.bottomEdge ? -height - Kirigami.Units.smallSpacing - 1 : root.height + Kirigami.Units.smallSpacing - 1
width: root.width + (root.bottomEdge ? 0 : Kirigami.Units.smallSpacing * 2)
leftPadding: 0
rightPadding: 0
bottomPadding: Kirigami.Units.smallSpacing
topPadding: Kirigami.Units.smallSpacing
closePolicy: QQC2.Popup.CloseOnEscape
contentItem: ListView {
id: accounts
implicitHeight: contentHeight
currentIndex: Controller.activeConnectionIndex
header: Kirigami.Separator {}
footer: Delegates.RoundedItemDelegate {
id: addButton
width: parent.width
highlighted: focus || (addAccount.highlighted || addAccount.ListView.isCurrentItem) && !addAccount.pressed
Component.onCompleted: root.addAccount = this
icon {
name: "list-add"
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
}
text: i18n("Add Account")
contentItem: Delegates.SubtitleContentItem {
itemDelegate: parent
subtitle: i18n("Log in to an existing account")
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
}
onClicked: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/WelcomePage.qml", {}, {
title: i18nc("@title:window", "Login"),
});
if (switchUserButton.checked) {
switchUserButton.checked = false
}
accounts.currentIndex = Controller.activeConnectionIndex
}
Keys.onUpPressed: {
accounts.currentIndex = accounts.count - 1
accounts.forceActiveFocus()
}
Keys.onDownPressed: {
accounts.currentIndex = 0
accounts.forceActiveFocus()
}
}
clip: true
model: AccountRegistry
keyNavigationEnabled: false
Keys.onDownPressed: {
if (accounts.currentIndex === accounts.count - 1) {
addAccount.forceActiveFocus()
accounts.currentIndex = -1
} else {
accounts.incrementCurrentIndex()
}
}
Keys.onUpPressed: {
if (accounts.currentIndex === 0) {
addAccount.forceActiveFocus()
accounts.currentIndex = -1
} else {
accounts.decrementCurrentIndex()
}
}
Keys.onReleased: if (event.key == Qt.Key_Escape) {
if (switchUserButton.checked) {
switchUserButton.checked = false
}
}
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property NeoChatConnection connection
width: parent.width
text: connection.localUser.displayName
contentItem: RowLayout {
KirigamiComponents.Avatar {
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
sourceSize {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
}
source: userDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + userDelegate.connection.localUser.avatarMediaId) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
}
Delegates.SubtitleContentItem {
itemDelegate: userDelegate
subtitle: userDelegate.connection.localUser.id
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
}
}
onClicked: {
Controller.activeConnection = userDelegate.connection
if (switchUserButton.checked) {
switchUserButton.checked = false
}
}
}
}
background: ColumnLayout {
spacing: 0
Kirigami.Separator {
Layout.fillWidth: true
visible: root.bottomEdge
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Kirigami.Theme.backgroundColor
}
Kirigami.Separator {
Layout.fillWidth: true
visible: !root.bottomEdge
}
} }
} }
} }

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 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.neochat
QQC2.ToolBar {
id: root
/**
* @brief The connection for the current user.
*/
required property NeoChatConnection connection
padding: 0
contentItem: ColumnLayout {
spacing: 0
Kirigami.Separator {
Layout.fillWidth: true
}
UserInfo {
bottomEdge: true
connection: root.connection
}
}
}