Start restructuring how the account/room pane work
This commit is contained in:
parent
da6a54f0bf
commit
72c96b3ba5
5
TODO.md
5
TODO.md
|
@ -1,7 +1,8 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- fix alt+shift+j/k when going to the end of a list is taller than window
|
- message delegate too tall
|
||||||
- fix focusRoomAtIndex
|
|
||||||
|
- fix escape keybinds (filter rooms, message selection)
|
||||||
- if last room event is a membership change, it won't be visible in timeline
|
- if last room event is a membership change, it won't be visible in timeline
|
||||||
- use uiState instead of open_room
|
- use uiState instead of open_room
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,5 @@ import QtQuick 2.12
|
||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Loader.Ready
|
// visible: status === Loader.Ready
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,17 @@ SwipeView {
|
||||||
saveEnabled = true
|
saveEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: if (saveEnabled) window.saveState(this)
|
onCurrentIndexChanged: {
|
||||||
|
if (saveEnabled) window.saveState(this)
|
||||||
|
|
||||||
|
if (currentIndex < previousIndex) lastMove = HSwipeView.Move.ToPrevious
|
||||||
|
if (currentIndex > previousIndex) lastMove = HSwipeView.Move.ToNext
|
||||||
|
|
||||||
|
previousIndex = currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum Move { ToPrevious, ToNext }
|
||||||
|
|
||||||
property string saveName: ""
|
property string saveName: ""
|
||||||
property var saveId: "ALL"
|
property var saveId: "ALL"
|
||||||
|
@ -22,7 +31,9 @@ SwipeView {
|
||||||
// Prevent onCurrentIndexChanged from running before Component.onCompleted
|
// Prevent onCurrentIndexChanged from running before Component.onCompleted
|
||||||
property bool saveEnabled: false
|
property bool saveEnabled: false
|
||||||
|
|
||||||
|
property int previousIndex: 0
|
||||||
property int defaultIndex: 0
|
property int defaultIndex: 0
|
||||||
|
property int lastMove: HSwipeView.Move.ToNext
|
||||||
property bool changed: currentIndex !== defaultIndex
|
property bool changed: currentIndex !== defaultIndex
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,43 +6,30 @@ import Clipboard 0.1
|
||||||
import "../Base"
|
import "../Base"
|
||||||
import "../Base/HTile"
|
import "../Base/HTile"
|
||||||
|
|
||||||
HTileDelegate {
|
HTile {
|
||||||
id: account
|
id: account
|
||||||
rightPadding: 0
|
implicitHeight: theme.baseElementsHeight
|
||||||
backgroundColor: theme.mainPane.listView.account.background
|
backgroundColor: theme.mainPane.listView.account.background
|
||||||
opacity: collapsed && ! mainPane.filter ?
|
padded: false
|
||||||
theme.mainPane.listView.account.collapsedOpacity : 1
|
|
||||||
|
|
||||||
contentItem: ContentRow {
|
contentItem: ContentRow {
|
||||||
tile: account
|
tile: account
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
HUserAvatar {
|
HUserAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
userId: model.id
|
userId: model.id
|
||||||
displayName: model.display_name
|
displayName: model.display_name
|
||||||
mxc: model.avatar_url
|
mxc: model.avatar_url
|
||||||
compact: account.compact
|
radius: 0
|
||||||
|
|
||||||
radius:
|
|
||||||
mainPane.small ?
|
|
||||||
theme.mainPane.listView.account.collapsedAvatarRadius :
|
|
||||||
theme.mainPane.listView.account.avatarRadius
|
|
||||||
|
|
||||||
Behavior on radius { HNumberAnimation {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleLabel {
|
TitleLabel {
|
||||||
text: model.display_name || model.id
|
text: model.display_name || model.id
|
||||||
font.pixelSize: theme.fontSize.big
|
|
||||||
color:
|
color:
|
||||||
hovered ?
|
hovered ?
|
||||||
utils.nameColor(model.display_name || model.id.substring(1)) :
|
utils.nameColor(model.display_name || model.id.substring(1)) :
|
||||||
theme.mainPane.listView.account.name
|
theme.mainPane.listView.account.name
|
||||||
|
|
||||||
Layout.leftMargin: theme.spacing
|
|
||||||
Layout.rightMargin: theme.spacing
|
|
||||||
|
|
||||||
Behavior on color { HColorAnimation {} }
|
Behavior on color { HColorAnimation {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,51 +43,15 @@ HTileDelegate {
|
||||||
"AddChat/AddChat", {userId: model.id},
|
"AddChat/AddChat", {userId: model.id},
|
||||||
)
|
)
|
||||||
|
|
||||||
leftPadding: theme.spacing / 2
|
|
||||||
rightPadding: leftPadding
|
|
||||||
|
|
||||||
opacity: expand.loading ? 0 : 1
|
|
||||||
visible: opacity > 0 && Layout.maximumWidth > 0
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.maximumWidth:
|
Layout.maximumWidth:
|
||||||
account.width >= 100 * theme.uiScale ? implicitWidth : 0
|
account.width >= 100 * theme.uiScale ? implicitWidth : 0
|
||||||
|
|
||||||
Behavior on Layout.maximumWidth { HNumberAnimation {} }
|
HShortcut {
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
enabled: isCurrent
|
||||||
|
sequences: window.settings.keys.addNewChat
|
||||||
|
onActivated: addChat.clicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
HButton {
|
|
||||||
id: expand
|
|
||||||
loading:
|
|
||||||
! model.first_sync_done || model.profile_updated < new Date(1)
|
|
||||||
iconItem.small: true
|
|
||||||
icon.name: "expand"
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
toolTip.text: collapsed ? qsTr("Expand") : qsTr("Collapse")
|
|
||||||
onClicked: account.toggleCollapse()
|
|
||||||
|
|
||||||
leftPadding: theme.spacing / 2
|
|
||||||
rightPadding: theme.spacing
|
|
||||||
|
|
||||||
opacity: ! loading && mainPane.filter ? 0 : 1
|
|
||||||
visible: opacity > 0 && Layout.maximumWidth > 0
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.maximumWidth:
|
|
||||||
account.width >= 120 * theme.uiScale ? implicitWidth : 0
|
|
||||||
|
|
||||||
|
|
||||||
iconItem.transform: Rotation {
|
|
||||||
origin.x: expand.iconItem.width / 2
|
|
||||||
origin.y: expand.iconItem.height / 2
|
|
||||||
angle: expand.loading ? 0 : collapsed ? 180 : 90
|
|
||||||
|
|
||||||
Behavior on angle { HNumberAnimation {} }
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on Layout.maximumWidth { HNumberAnimation {} }
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,33 +72,16 @@ HTileDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivated: {
|
onLeftClicked: {
|
||||||
pageLoader.showPage(
|
pageLoader.showPage(
|
||||||
"AccountSettings/AccountSettings", { "userId": model.id }
|
"AccountSettings/AccountSettings", { "userId": model.id }
|
||||||
)
|
)
|
||||||
mainPaneList.detachedCurrentIndex = false
|
|
||||||
mainPaneList.centerToHighlight = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
readonly property alias addChat: addChat
|
property bool isCurrent: false
|
||||||
|
|
||||||
readonly property bool collapsed:
|
|
||||||
(window.uiState.collapseAccounts[model.id] || false) &&
|
|
||||||
! mainPane.filter
|
|
||||||
|
|
||||||
|
|
||||||
function setCollapse(collapse) {
|
|
||||||
window.uiState.collapseAccounts[model.id] = collapse
|
|
||||||
window.uiStateChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCollapse() {
|
|
||||||
setCollapse(! collapsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
|
||||||
Behavior on leftPadding { HNumberAnimation {} }
|
Behavior on leftPadding { HNumberAnimation {} }
|
||||||
Behavior on topPadding { HNumberAnimation {} }
|
Behavior on topPadding { HNumberAnimation {} }
|
||||||
|
|
||||||
|
@ -165,4 +99,11 @@ HTileDelegate {
|
||||||
value: theme.spacing
|
value: theme.spacing
|
||||||
when: mainPane.small
|
when: mainPane.small
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
enabled: isCurrent
|
||||||
|
sequences: window.settings.keys.accountSettings
|
||||||
|
onActivated: leftClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
73
src/gui/MainPane/AccountBar.qml
Normal file
73
src/gui/MainPane/AccountBar.qml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import ".."
|
||||||
|
import "../Base"
|
||||||
|
import "../Base/HTile"
|
||||||
|
|
||||||
|
HColumnLayout {
|
||||||
|
property RoomPane roomContainer
|
||||||
|
|
||||||
|
|
||||||
|
HButton {
|
||||||
|
id: settingsButton
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
icon.name: "settings"
|
||||||
|
toolTip.text: qsTr("Open config folder")
|
||||||
|
|
||||||
|
onClicked: py.callCoro("get_config_dir", [], Qt.openUrlExternally)
|
||||||
|
|
||||||
|
Layout.preferredHeight: theme.baseElementsHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
HListView {
|
||||||
|
id: accountList
|
||||||
|
model: ModelStore.get("accounts")
|
||||||
|
currentIndex: roomContainer.currentIndex
|
||||||
|
|
||||||
|
delegate: HTileDelegate {
|
||||||
|
id: tile
|
||||||
|
width: accountList.width
|
||||||
|
backgroundColor: theme.mainPane.listView.account.background
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: leftPadding
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
implicitHeight: avatar.height
|
||||||
|
|
||||||
|
HUserAvatar {
|
||||||
|
id: avatar
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
userId: model.id
|
||||||
|
displayName: model.display_name
|
||||||
|
mxc: model.avatar_url
|
||||||
|
// compact: account.compact
|
||||||
|
|
||||||
|
radius: theme.mainPane.listView.account.avatarRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLeftClicked: roomContainer.currentIndex = model.index
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
HButton {
|
||||||
|
id: addAccountButton
|
||||||
|
icon.name: "add-account"
|
||||||
|
toolTip.text: qsTr("Add another account")
|
||||||
|
backgroundColor: theme.mainPane.bottomBar.settingsButtonBackground
|
||||||
|
onClicked: pageLoader.showPage("AddAccount/AddAccount")
|
||||||
|
|
||||||
|
Layout.preferredHeight: theme.baseElementsHeight
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
sequences: window.settings.keys.addNewAccount
|
||||||
|
onActivated: addAccountButton.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,117 +0,0 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
import ".."
|
|
||||||
import "../Base"
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: accountRooms
|
|
||||||
// visible: account.opacity > 0
|
|
||||||
|
|
||||||
|
|
||||||
readonly property string userId: model.id
|
|
||||||
readonly property bool firstSyncDone: model.first_sync_done
|
|
||||||
readonly property HListView view: ListView.view
|
|
||||||
readonly property int listIndex: index
|
|
||||||
readonly property bool noFilterResults:
|
|
||||||
mainPane.filter && roomList.model.count === 0
|
|
||||||
|
|
||||||
readonly property alias account: account
|
|
||||||
readonly property alias collapsed: account.collapsed
|
|
||||||
readonly property alias roomList: roomList
|
|
||||||
|
|
||||||
|
|
||||||
Account {
|
|
||||||
id: account
|
|
||||||
width: parent.width
|
|
||||||
view: accountRooms.view
|
|
||||||
|
|
||||||
opacity: collapsed || noFilterResults ?
|
|
||||||
theme.mainPane.listView.account.collapsedOpacity : 1
|
|
||||||
}
|
|
||||||
|
|
||||||
HListView {
|
|
||||||
id: roomList
|
|
||||||
width: parent.width
|
|
||||||
height: contentHeight
|
|
||||||
interactive: false
|
|
||||||
|
|
||||||
// https://github.com/oKcerG/SortFilterProxyModel/issues/75
|
|
||||||
model:
|
|
||||||
mainPane.filter ? proxy :
|
|
||||||
account.collapsed ? null :
|
|
||||||
proxy.sourceModel
|
|
||||||
|
|
||||||
delegate: HLoader {
|
|
||||||
asynchronous: false
|
|
||||||
active: index === 0 || (
|
|
||||||
roomList.firstDelegateHeight !== 0 &&
|
|
||||||
index >= roomList.firstIndexInView &&
|
|
||||||
index <= roomList.lastIndexInView
|
|
||||||
)
|
|
||||||
|
|
||||||
width: roomList.width
|
|
||||||
height: roomList.firstDelegateHeight
|
|
||||||
|
|
||||||
readonly property var sourceModel: model
|
|
||||||
|
|
||||||
sourceComponent: Room {
|
|
||||||
width: roomList.width
|
|
||||||
userId: accountRooms.userId
|
|
||||||
view: roomList
|
|
||||||
model: sourceModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight: null // managed by the AccountRoomsList
|
|
||||||
|
|
||||||
|
|
||||||
// Delete 0 must *always* be loaded, and all delegates must have the
|
|
||||||
// same height
|
|
||||||
property int firstDelegateHeight:
|
|
||||||
contentItem.visibleChildren[0] ?
|
|
||||||
contentItem.visibleChildren[0].implicitHeight :
|
|
||||||
0
|
|
||||||
|
|
||||||
readonly property int firstIndexInView:
|
|
||||||
(mainPaneList.contentY - account.height - accountRooms.spacing) /
|
|
||||||
firstDelegateHeight -
|
|
||||||
accountRooms.y / firstDelegateHeight
|
|
||||||
|
|
||||||
readonly property int lastIndexInView:
|
|
||||||
firstIndexInView + mainPaneList.height / firstDelegateHeight
|
|
||||||
|
|
||||||
readonly property bool hasActiveRoom:
|
|
||||||
window.uiState.page === "Pages/Chat/Chat.qml" &&
|
|
||||||
window.uiState.pageProperties.userId === userId
|
|
||||||
|
|
||||||
readonly property var activeRoomIndex:
|
|
||||||
hasActiveRoom ?
|
|
||||||
model.findIndex(window.uiState.pageProperties.roomId) : null
|
|
||||||
|
|
||||||
readonly property HSortFilterProxyModel proxy: HSortFilterProxyModel {
|
|
||||||
sourceModel: ModelStore.get(accountRooms.userId, "rooms")
|
|
||||||
|
|
||||||
filters: ExpressionFilter {
|
|
||||||
expression: utils.filterMatches(
|
|
||||||
mainPane.filter, model.display_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Binding on currentIndex {
|
|
||||||
value:
|
|
||||||
roomList.hasActiveRoom ?
|
|
||||||
(
|
|
||||||
roomList.activeRoomIndex === null ?
|
|
||||||
-1 : roomList.activeRoomIndex
|
|
||||||
) : -1
|
|
||||||
|
|
||||||
when: ! view.detachedCurrentIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height { HNumberAnimation {} }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,293 +0,0 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import ".."
|
|
||||||
import "../Base"
|
|
||||||
|
|
||||||
HListView {
|
|
||||||
id: mainPaneList
|
|
||||||
model: ModelStore.get("accounts")
|
|
||||||
spacing: mainPane.small ? theme.spacing : 0
|
|
||||||
|
|
||||||
delegate: AccountRoomsDelegate {
|
|
||||||
width: mainPaneList.width
|
|
||||||
height: childrenRect.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must handle the highlight's position and size manually because
|
|
||||||
// of our nested lists
|
|
||||||
highlightFollowsCurrentItem: false
|
|
||||||
highlightRangeMode: ListView.NoHighlightRange
|
|
||||||
|
|
||||||
highlight: Rectangle {
|
|
||||||
id: highlightRectangle
|
|
||||||
y:
|
|
||||||
! currentItem ?
|
|
||||||
0 :
|
|
||||||
|
|
||||||
selectedRoom ?
|
|
||||||
currentItem.y + currentItem.account.height +
|
|
||||||
currentItem.roomList.currentItem.y :
|
|
||||||
|
|
||||||
currentItem.y
|
|
||||||
|
|
||||||
width: mainPaneList.width
|
|
||||||
height:
|
|
||||||
! currentItem ?
|
|
||||||
0 :
|
|
||||||
|
|
||||||
selectedRoom ?
|
|
||||||
currentItem.roomList.currentItem.height :
|
|
||||||
currentItem.account.height
|
|
||||||
|
|
||||||
color:
|
|
||||||
mainPane.small ?
|
|
||||||
theme.controls.listView.smallPaneHighlight :
|
|
||||||
theme.controls.listView.highlight
|
|
||||||
|
|
||||||
Behavior on y { HNumberAnimation { id: yAnimation } }
|
|
||||||
Behavior on height { HNumberAnimation {} }
|
|
||||||
Behavior on color { HColorAnimation {} }
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
target: mainPaneList
|
|
||||||
property: "contentY"
|
|
||||||
value: highlightRectangle.y + highlightRectangle.height / 2 -
|
|
||||||
mainPaneList.height / 2
|
|
||||||
delayed: true
|
|
||||||
when: centerToHighlight && yAnimation.running
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: mainPaneList
|
|
||||||
enabled: centerToHighlight && yAnimation.running
|
|
||||||
onContentYChanged: mainPaneList.returnToBounds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMovingChanged: if (moving) centerToHighlight = false
|
|
||||||
|
|
||||||
|
|
||||||
property bool detachedCurrentIndex: false
|
|
||||||
property bool centerToHighlight: true
|
|
||||||
|
|
||||||
readonly property HLoader selectedRoom:
|
|
||||||
currentItem ? currentItem.roomList.currentItem : null
|
|
||||||
|
|
||||||
readonly property bool hasActiveAccount:
|
|
||||||
window.uiState.page === "Pages/Chat/Chat.qml" ||
|
|
||||||
window.uiState.page === "Pages/AddChat/AddChat.qml" ||
|
|
||||||
window.uiState.page === "Pages/AccountSettings/AccountSettings.qml"
|
|
||||||
|
|
||||||
readonly property var activeAccountIndex:
|
|
||||||
hasActiveAccount ?
|
|
||||||
model.findIndex(window.uiState.pageProperties.userId) : null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function previous() {
|
|
||||||
centerToHighlight = true
|
|
||||||
detachedCurrentIndex = true
|
|
||||||
|
|
||||||
if (! mainPane.filter) {
|
|
||||||
_previous()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let reachedStart = false
|
|
||||||
do {
|
|
||||||
if (currentIndex === count - 1 && reachedStart) break
|
|
||||||
_previous()
|
|
||||||
if (currentIndex === 0) reachedStart = true
|
|
||||||
} while (! currentItem.roomList.currentItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
function _previous() {
|
|
||||||
const currentAccount = currentItem
|
|
||||||
|
|
||||||
// Nothing is selected
|
|
||||||
if (! currentAccount) {
|
|
||||||
decrementCurrentIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomList = currentAccount.roomList
|
|
||||||
|
|
||||||
// An account is selected
|
|
||||||
if (! roomList.currentItem) {
|
|
||||||
decrementCurrentIndex()
|
|
||||||
// Select the last room of the previous account that's now selected
|
|
||||||
currentItem.roomList.decrementCurrentIndex()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A room is selected
|
|
||||||
const selectedIsFirst = roomList.currentIndex === 0
|
|
||||||
const noRooms = roomList.count === 0
|
|
||||||
|
|
||||||
if (currentAccount.collapsed || selectedIsFirst || noRooms) {
|
|
||||||
// Have the account itself be selected
|
|
||||||
roomList.currentIndex = -1 // XXX
|
|
||||||
} else {
|
|
||||||
roomList.decrementCurrentIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function next() {
|
|
||||||
centerToHighlight = true
|
|
||||||
detachedCurrentIndex = true
|
|
||||||
|
|
||||||
if (! mainPane.filter) {
|
|
||||||
_next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let reachedEnd = false
|
|
||||||
do {
|
|
||||||
if (currentIndex === 0 && reachedEnd) break
|
|
||||||
_next()
|
|
||||||
if (currentIndex === count - 1) reachedEnd = true
|
|
||||||
} while (! currentItem.roomList.currentItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
function _next() {
|
|
||||||
const currentAccount = currentItem
|
|
||||||
|
|
||||||
// Nothing is selected
|
|
||||||
if (! currentAccount) {
|
|
||||||
incrementCurrentIndex()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomList = currentAccount.roomList
|
|
||||||
|
|
||||||
// An account is selected
|
|
||||||
if (! roomList.currentItem) {
|
|
||||||
if (currentAccount.collapsed || roomList.count === 0) {
|
|
||||||
incrementCurrentIndex()
|
|
||||||
} else {
|
|
||||||
roomList.incrementCurrentIndex()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A room is selected
|
|
||||||
const selectedIsLast = roomList.currentIndex >= roomList.count - 1
|
|
||||||
const noRooms = roomList.count === 0
|
|
||||||
|
|
||||||
if (currentAccount.collapsed || selectedIsLast || noRooms) {
|
|
||||||
roomList.currentIndex = -1 // XXX
|
|
||||||
mainPaneList.incrementCurrentIndex()
|
|
||||||
} else {
|
|
||||||
roomList.incrementCurrentIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToRoom(index) {
|
|
||||||
if (! currentItem) next()
|
|
||||||
if (! currentItem) return
|
|
||||||
|
|
||||||
const room = currentItem.roomList.contentItem.children[index]
|
|
||||||
print(index, room, room.item)
|
|
||||||
if (room && room.item && room.item.activated) room.item.activated()
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestActivate() {
|
|
||||||
activateLimiter.restart()
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate() {
|
|
||||||
if (! currentItem) next()
|
|
||||||
if (! currentItem) return
|
|
||||||
|
|
||||||
selectedRoom ?
|
|
||||||
currentItem.roomList.currentItem.item.activated() :
|
|
||||||
currentItem.account.activated()
|
|
||||||
|
|
||||||
detachedCurrentIndex = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function accountSettings() {
|
|
||||||
if (! currentItem) next()
|
|
||||||
if (! currentItem) return
|
|
||||||
currentItem.account.activated()
|
|
||||||
|
|
||||||
detachedCurrentIndex = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNewChat() {
|
|
||||||
if (! currentItem) next()
|
|
||||||
if (! currentItem) return
|
|
||||||
currentItem.account.addChat.clicked()
|
|
||||||
|
|
||||||
detachedCurrentIndex = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCollapseAccount(collapse) {
|
|
||||||
if (! currentItem) return
|
|
||||||
currentItem.account.setCollapse(collapse)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCollapseAccount() {
|
|
||||||
if (mainPane.filter) return
|
|
||||||
if (! currentItem) next()
|
|
||||||
|
|
||||||
currentItem.account.toggleCollapse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Binding on currentIndex {
|
|
||||||
value:
|
|
||||||
hasActiveAccount ?
|
|
||||||
(activeAccountIndex === null ? -1 : activeAccountIndex) : -1
|
|
||||||
|
|
||||||
when: ! detachedCurrentIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.addNewChat
|
|
||||||
onActivated: addNewChat()
|
|
||||||
}
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.accountSettings
|
|
||||||
onActivated: accountSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.toggleCollapseAccount
|
|
||||||
onActivated: toggleCollapseAccount()
|
|
||||||
}
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.goToPreviousRoom
|
|
||||||
onActivated: { previous(); requestActivate() }
|
|
||||||
}
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.goToNextRoom
|
|
||||||
onActivated: { next(); requestActivate() }
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Object.keys(window.settings.keys.focusRoomAtIndex)
|
|
||||||
|
|
||||||
Item {
|
|
||||||
HShortcut {
|
|
||||||
sequence: window.settings.keys.focusRoomAtIndex[modelData]
|
|
||||||
onActivated: goToRoom(parseInt(modelData - 1, 10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: activateLimiter
|
|
||||||
interval: 200
|
|
||||||
onTriggered: activate()
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -100
|
|
||||||
color: theme.mainPane.listView.background
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import "../Base"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
// Hide filter field overflowing for a sec on size changes
|
|
||||||
clip: true
|
|
||||||
color: theme.mainPane.bottomBar.background
|
|
||||||
|
|
||||||
|
|
||||||
property AccountRoomsList mainPaneList
|
|
||||||
readonly property alias addAccountButton: addAccountButton
|
|
||||||
readonly property alias filterField: filterField
|
|
||||||
property alias roomFilter: filterField.text
|
|
||||||
|
|
||||||
|
|
||||||
HRowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
HButton {
|
|
||||||
id: addAccountButton
|
|
||||||
icon.name: "add-account"
|
|
||||||
toolTip.text: qsTr("Add another account")
|
|
||||||
backgroundColor: theme.mainPane.bottomBar.settingsButtonBackground
|
|
||||||
onClicked: pageLoader.showPage("AddAccount/AddAccount")
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.addNewAccount
|
|
||||||
onActivated: addAccountButton.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HTextField {
|
|
||||||
id: filterField
|
|
||||||
saveName: "roomFilterField"
|
|
||||||
|
|
||||||
placeholderText: qsTr("Filter rooms")
|
|
||||||
backgroundColor: theme.mainPane.bottomBar.filterFieldBackground
|
|
||||||
bordered: false
|
|
||||||
opacity: width >= 16 * theme.uiScale ? 1 : 0
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
Keys.onUpPressed: mainPaneList.previous(false) // do not activate
|
|
||||||
Keys.onDownPressed: mainPaneList.next(false)
|
|
||||||
|
|
||||||
Keys.onEnterPressed: Keys.onReturnPressed(event)
|
|
||||||
Keys.onReturnPressed: {
|
|
||||||
if (event.modifiers & Qt.ShiftModifier) {
|
|
||||||
mainPaneList.toggleCollapseAccount()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.settings.clearRoomFilterOnEnter) {
|
|
||||||
mainPaneList.setCollapseAccount(false)
|
|
||||||
text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
mainPaneList.requestActivate()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEscapePressed: {
|
|
||||||
if (window.settings.clearRoomFilterOnEscape) text = ""
|
|
||||||
mainUI.pageLoader.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
|
||||||
|
|
||||||
HShortcut {
|
|
||||||
sequences: window.settings.keys.clearRoomFilter
|
|
||||||
onActivated: filterField.text = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,37 +8,13 @@ HDrawer {
|
||||||
id: mainPane
|
id: mainPane
|
||||||
saveName: "mainPane"
|
saveName: "mainPane"
|
||||||
color: theme.mainPane.background
|
color: theme.mainPane.background
|
||||||
minimumSize: bottomBar.addAccountButton.width
|
// minimumSize: bottomBar.addAccountButton.width
|
||||||
|
|
||||||
onHasFocusChanged:
|
// property alias filter: bottomBar.roomFilter
|
||||||
if (! hasFocus) mainPaneList.detachedCurrentIndex = false
|
|
||||||
|
|
||||||
|
|
||||||
property alias filter: bottomBar.roomFilter
|
|
||||||
|
|
||||||
readonly property bool small:
|
readonly property bool small:
|
||||||
width < theme.controls.avatar.size + theme.spacing * 2
|
width < theme.controls.avatar.size + theme.spacing * 2
|
||||||
|
|
||||||
readonly property bool hasFocus: bottomBar.filterField.activeFocus
|
|
||||||
readonly property alias mainPaneList: mainPaneList
|
|
||||||
readonly property alias topBar: topBar
|
|
||||||
readonly property alias bottomBar: bottomBar
|
|
||||||
|
|
||||||
|
|
||||||
function toggleFocus() {
|
|
||||||
if (mainPane.activeFocus) {
|
|
||||||
pageLoader.takeFocus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mainPane.open()
|
|
||||||
bottomBar.filterField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function addccount() {
|
|
||||||
bottomBar.addAccountButton.clicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
|
||||||
|
@ -47,38 +23,22 @@ HDrawer {
|
||||||
when: ! mainUI.accountsPresent
|
when: ! mainUI.accountsPresent
|
||||||
}
|
}
|
||||||
|
|
||||||
HShortcut {
|
HRowLayout {
|
||||||
active: mainUI.accountsPresent
|
|
||||||
sequences: window.settings.keys.toggleFocusMainPane
|
|
||||||
onActivated: toggleFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
HColumnLayout {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
TopBar {
|
AccountBar {
|
||||||
id: topBar
|
id: accountBar
|
||||||
|
roomContainer: roomPane
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: false
|
||||||
Layout.fillHeight: false
|
|
||||||
Layout.preferredHeight: theme.baseElementsHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountRoomsList {
|
RoomPane {
|
||||||
id: mainPaneList
|
id: roomPane
|
||||||
clip: true
|
currentIndex: 0
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomBar {
|
|
||||||
id: bottomBar
|
|
||||||
mainPaneList: mainPaneList
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: false
|
|
||||||
Layout.preferredHeight: theme.baseElementsHeight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,6 @@ HTileDelegate {
|
||||||
// topPadding: theme.spacing / (model.index === 0 ? 1 : 1.5)
|
// topPadding: theme.spacing / (model.index === 0 ? 1 : 1.5)
|
||||||
// bottomPadding:
|
// bottomPadding:
|
||||||
// theme.spacing / (model.index === view.count - 1 ? 1 : 1.5)
|
// theme.spacing / (model.index === view.count - 1 ? 1 : 1.5)
|
||||||
leftPadding: theme.spacing * 2
|
|
||||||
rightPadding: theme.spacing
|
|
||||||
|
|
||||||
contentItem: ContentRow {
|
contentItem: ContentRow {
|
||||||
tile: room
|
tile: room
|
||||||
|
@ -207,15 +205,8 @@ HTileDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivated: {
|
|
||||||
pageLoader.showRoom(userId, model.id)
|
|
||||||
mainPaneList.detachedCurrentIndex = false
|
|
||||||
mainPaneList.centerToHighlight = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
property string userId
|
property string userId
|
||||||
property QtObject model
|
|
||||||
|
|
||||||
readonly property bool joined: ! invited && ! parted
|
readonly property bool joined: ! invited && ! parted
|
||||||
readonly property bool invited: model.inviter_id && ! parted
|
readonly property bool invited: model.inviter_id && ! parted
|
||||||
|
|
74
src/gui/MainPane/RoomList.qml
Normal file
74
src/gui/MainPane/RoomList.qml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import ".."
|
||||||
|
import "../Base"
|
||||||
|
|
||||||
|
HListView {
|
||||||
|
id: roomList
|
||||||
|
model: ModelStore.get(accountModel.id, "rooms")
|
||||||
|
|
||||||
|
delegate: Room {
|
||||||
|
width: roomList.width
|
||||||
|
userId: accountModel.id
|
||||||
|
onActivated: showRoom(model.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
property var accountModel
|
||||||
|
property var roomPane
|
||||||
|
property bool isCurrent: false
|
||||||
|
|
||||||
|
|
||||||
|
function showRoom(index=currentIndex) {
|
||||||
|
pageLoader.showRoom(accountModel.id, model.get(index).id)
|
||||||
|
currentIndex = index
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: showRoomLimiter
|
||||||
|
interval: 200
|
||||||
|
onTriggered: showRoom()
|
||||||
|
}
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
enabled: isCurrent
|
||||||
|
sequences: window.settings.keys.goToPreviousRoom
|
||||||
|
onActivated: { decrementCurrentIndex(); showRoomLimiter.restart() }
|
||||||
|
}
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
enabled: isCurrent
|
||||||
|
sequences: window.settings.keys.goToNextRoom
|
||||||
|
onActivated: { incrementCurrentIndex(); showRoomLimiter.restart() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Object.keys(window.settings.keys.focusRoomAtIndex)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
HShortcut {
|
||||||
|
enabled: isCurrent
|
||||||
|
sequence: window.settings.keys.focusRoomAtIndex[modelData]
|
||||||
|
onActivated: showRoom(parseInt(modelData - 1, 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
onWheel: {
|
||||||
|
const goingDown = wheel.angleDelta.y < 0
|
||||||
|
|
||||||
|
if (! goingDown && roomList.atYBeginning)
|
||||||
|
roomPane.decrementCurrentIndex()
|
||||||
|
else if (goingDown && roomList.atYEnd)
|
||||||
|
roomPane.incrementCurrentIndex()
|
||||||
|
else
|
||||||
|
wheel.accepted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/gui/MainPane/RoomPane.qml
Normal file
100
src/gui/MainPane/RoomPane.qml
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import ".."
|
||||||
|
import "../Base"
|
||||||
|
|
||||||
|
HSwipeView {
|
||||||
|
id: swipeView
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ModelStore.get("accounts")
|
||||||
|
|
||||||
|
HLoader {
|
||||||
|
id: loader
|
||||||
|
active:
|
||||||
|
HSwipeView.isCurrentItem ||
|
||||||
|
HSwipeView.isNextItem ||
|
||||||
|
HSwipeView.isPreviousItem
|
||||||
|
|
||||||
|
readonly property bool isCurrent: HSwipeView.isCurrentItem
|
||||||
|
|
||||||
|
sourceComponent: HColumnLayout {
|
||||||
|
id: column
|
||||||
|
|
||||||
|
readonly property QtObject accountModel: model
|
||||||
|
readonly property alias roomList: roomList
|
||||||
|
|
||||||
|
Account {
|
||||||
|
id: account
|
||||||
|
isCurrent: loader.isCurrent
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomList {
|
||||||
|
id: roomList
|
||||||
|
clip: true
|
||||||
|
accountModel: column.accountModel
|
||||||
|
roomPane: swipeView
|
||||||
|
isCurrent: loader.isCurrent
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
HTextField {
|
||||||
|
id: filterField
|
||||||
|
saveName: "roomFilterField"
|
||||||
|
|
||||||
|
placeholderText: qsTr("Filter rooms")
|
||||||
|
backgroundColor:
|
||||||
|
theme.mainPane.bottomBar.filterFieldBackground
|
||||||
|
bordered: false
|
||||||
|
opacity: width >= 16 * theme.uiScale ? 1 : 0
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: theme.baseElementsHeight
|
||||||
|
|
||||||
|
Keys.onUpPressed: roomList.decrementCurrentIndex()
|
||||||
|
Keys.onDownPressed: roomList.incrementCurrentIndex()
|
||||||
|
|
||||||
|
Keys.onEnterPressed: Keys.onReturnPressed(event)
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
if (window.settings.clearRoomFilterOnEnter) text = ""
|
||||||
|
roomList.showRoom()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
if (window.settings.clearRoomFilterOnEscape) text = ""
|
||||||
|
mainUI.pageLoader.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
enabled: loader.isCurrent
|
||||||
|
sequences: window.settings.keys.clearRoomFilter
|
||||||
|
onActivated: filterField.text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
HShortcut {
|
||||||
|
enabled: loader.isCurrent
|
||||||
|
sequences: window.settings.keys.toggleFocusMainPane
|
||||||
|
onActivated: {
|
||||||
|
if (filterField.activeFocus) {
|
||||||
|
pageLoader.takeFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPane.open()
|
||||||
|
filterField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import "../Base"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
clip: true
|
|
||||||
color: theme.mainPane.topBar.background
|
|
||||||
|
|
||||||
HRowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
HButton {
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
icon.name: "settings"
|
|
||||||
toolTip.text: qsTr("Open config folder")
|
|
||||||
|
|
||||||
onClicked: py.callCoro("get_config_dir", [], Qt.openUrlExternally)
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
HButton {
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
|
|
||||||
text: qsTr("%1 %2")
|
|
||||||
.arg(Qt.application.displayName).arg(Qt.application.version)
|
|
||||||
label.color: theme.mainPane.topBar.nameVersionLabel
|
|
||||||
toolTip.text: qsTr("Open project repository")
|
|
||||||
|
|
||||||
onClicked:
|
|
||||||
Qt.openUrlExternally("https://github.com/mirukana/mirage")
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
HButton {
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
icon.name: "reload-config-files"
|
|
||||||
toolTip.text: qsTr("Reload config files")
|
|
||||||
|
|
||||||
onClicked: mainUI.reloadSettings()
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
HButton {
|
|
||||||
visible: Layout.preferredWidth > 0
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
icon.name: "go-back-to-chat-from-main-pane"
|
|
||||||
toolTip.text: qsTr("Go back to room")
|
|
||||||
|
|
||||||
onClicked: mainPane.toggleFocus()
|
|
||||||
|
|
||||||
Layout.preferredWidth: mainPane.collapse ? implicitWidth : 0
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth { HNumberAnimation {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user