Warning, /network/tokodon/src/content/ui/TimelinePage.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003
0004 import QtQuick
0005 import org.kde.kirigami 2 as Kirigami
0006 import org.kde.kirigamiaddons.labs.components 1 as LabComponents
0007 import org.kde.kirigamiaddons.components 1 as Components
0008 import QtQuick.Controls 2 as QQC2
0009 import QtQuick.Layouts
0010 import org.kde.tokodon
0011 import './StatusDelegate'
0012 import './StatusComposer'
0013
0014 Kirigami.ScrollablePage {
0015 id: root
0016
0017 property var dialog: null
0018 required property var model
0019 property bool expandedPost: false
0020 property alias listViewHeader: listview.header
0021 property alias showPostAction: postAction.visible
0022 property bool completedInitialLoad: false
0023 property alias listView: listview
0024 readonly property bool showReplies: showRepliesAction.checked
0025 readonly property bool showBoosts: showBoostsAction.checked
0026 property alias showFilterAction: filterAction.visible
0027
0028 title: {
0029 // Show the account name if the drawer is not open, so there's no way to tell which account you're on.
0030 if (name === "home" && !applicationWindow().globalDrawer.drawerOpen) {
0031 if (AccountManager.rowCount() > 1) {
0032 if (AccountManager.selectedAccount === null) {
0033 return i18n("Loading");
0034 }
0035 return i18n("Home (%1)", AccountManager.selectedAccount.identity.displayNameHtml);
0036 }
0037 } else {
0038 return model.displayName;
0039 }
0040 }
0041 titleDelegate: Kirigami.Heading {
0042 // identical to normal Kirigami headers
0043 Layout.fillWidth: true
0044 Layout.maximumWidth: implicitWidth + 1
0045 Layout.minimumWidth: 0
0046 maximumLineCount: 1
0047 elide: Text.ElideRight
0048
0049 text: root.title
0050
0051 textFormat: TextEdit.RichText
0052 }
0053
0054 header: LabComponents.Banner {
0055 id: message
0056 type: Kirigami.MessageType.Error
0057 width: parent.width
0058
0059 showCloseButton: true
0060
0061 actions: Kirigami.Action {
0062 text: i18n("Settings")
0063 icon.name: "settings-configure"
0064 onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.tokodon", "SettingsPage"), {}, {title: i18n("Configure")})
0065 }
0066 }
0067
0068 globalToolBarStyle: Kirigami.ApplicationHeaderStyle.ToolBar
0069
0070 onBackRequested: if (dialog) {
0071 dialog.close();
0072 dialog = null;
0073 event.accepted = true;
0074 }
0075
0076 actions: [
0077 Kirigami.Action {
0078 id: postAction
0079 icon.name: "list-add"
0080 text: i18nc("@action:button", "Post")
0081 enabled: AccountManager.hasAccounts
0082 onTriggered: Navigation.openStatusComposer()
0083 },
0084 Kirigami.Action {
0085 id: filterAction
0086 text: i18nc("@action:button", "Filters")
0087 icon.name: "view-filter"
0088
0089 Kirigami.Action {
0090 id: showBoostsAction
0091 text: i18n("Show Boosts")
0092 icon.name: "tokodon-post-boost"
0093 checkable: true
0094 checked: true
0095 }
0096 Kirigami.Action {
0097 id: showRepliesAction
0098 text: i18n("Show Replies")
0099 icon.name: "tokodon-post-reply"
0100 checkable: true
0101 checked: true
0102 }
0103 }
0104 ]
0105
0106 Connections {
0107 target: Navigation
0108 function onOpenFullScreenImage(attachments, identity, currentIndex) {
0109 if (root.isCurrentPage) {
0110 root.dialog = fullScreenImage.createObject(parent, {
0111 attachments: attachments,
0112 identity: identity,
0113 initialIndex: currentIndex,
0114 });
0115 root.dialog.open();
0116 }
0117 }
0118 }
0119
0120 Connections {
0121 target: Controller
0122 function onNetworkErrorOccurred(error) {
0123 message.text = i18nc("@info:status Network status", "Failed to contact server: %1. Please check your settings.", error)
0124 message.visible = true
0125 }
0126 }
0127
0128 Connections {
0129 target: root.model
0130 function onPostSourceReady(backend, isEdit) {
0131 pageStack.layers.push("./StatusComposer/StatusComposer.qml", {
0132 purpose: isEdit ? StatusComposer.Edit : StatusComposer.Redraft,
0133 backend: backend
0134 });
0135 }
0136
0137 function onLoadingChanged() {
0138 root.completedInitialLoad = true;
0139 }
0140 }
0141
0142 ListView {
0143 id: listview
0144 model: root.model
0145 reuseItems: false // TODO: this causes jumping on the timeline. needs more investigation before it's re-enabled
0146
0147 Component {
0148 id: fullScreenImage
0149 FullScreenImage {}
0150 }
0151
0152 delegate: StatusDelegate {
0153 id: status
0154
0155 timelineModel: root.model
0156 expandedPost: root.expandedPost
0157 showSeparator: index !== ListView.view.count - 1
0158 loading: listview.model.loading
0159 width: ListView.view.width
0160
0161 Connections {
0162 target: listview
0163
0164 function onContentYChanged() {
0165 const aMin = status.y
0166 const aMax = status.y + status.height
0167
0168 const bMin = listview.contentY
0169 const bMax = listview.contentY + listview.height
0170
0171 if (!root.isCurrentPage) {
0172 status.inViewPort = false
0173 return
0174 }
0175
0176 let topEdgeVisible
0177 let bottomEdgeVisible
0178
0179 // we are still checking two rectangles, but if one is bigger than the other
0180 // just switch which one should be checked.
0181 if (status.height > listview.height) {
0182 topEdgeVisible = bMin > aMin && bMin < aMax
0183 bottomEdgeVisible = bMax > aMin && bMax < aMax
0184 } else {
0185 topEdgeVisible = aMin > bMin && aMin < bMax
0186 bottomEdgeVisible = aMax > bMin && aMax < bMax
0187 }
0188
0189 status.inViewPort = topEdgeVisible || bottomEdgeVisible
0190 }
0191 }
0192
0193 Connections {
0194 target: root
0195
0196 function onIsCurrentPageChanged() {
0197 if (!root.isCurrentPage) {
0198 status.inViewPort = false
0199 } else {
0200 listview.contentYChanged()
0201 }
0202 }
0203 }
0204
0205 Connections {
0206 target: appwindow
0207
0208 function onIsShowingFullScreenImageChanged() {
0209 if (appwindow.isShowingFullScreenImage) {
0210 status.inViewPort = false
0211 } else {
0212 listview.contentYChanged()
0213 }
0214 }
0215 }
0216 }
0217
0218 footer: Item {
0219 width: parent.width
0220 height: Kirigami.Units.gridUnit * 4
0221
0222 Kirigami.PlaceholderMessage {
0223 anchors.fill: parent
0224 visible: root.model.atEnd ?? false
0225 text: i18nc("@info:status", "End of Timeline")
0226 }
0227
0228 QQC2.ProgressBar {
0229 anchors.centerIn: parent
0230 visible: root.model.loading
0231 indeterminate: true
0232 }
0233 }
0234
0235 Rectangle {
0236 anchors {
0237 fill: parent
0238 topMargin: listview.headerItem ? listview.headerItem.height : 0
0239 }
0240
0241 visible: listview.model.loading && !root.completedInitialLoad
0242
0243 color: Kirigami.Theme.backgroundColor
0244
0245 Kirigami.LoadingPlaceholder {
0246 anchors.centerIn: parent
0247 }
0248 }
0249
0250 Kirigami.PlaceholderMessage {
0251 anchors {
0252 horizontalCenter: listview.horizontalCenter
0253 top: listview.top
0254 topMargin: {
0255 let height = listview.height;
0256 let y = listview.headerItem ? listview.headerItem.height : 0;
0257
0258 return ((height - y) / 2) + y;
0259 }
0260 }
0261 text: i18n("No posts")
0262 visible: !listview.model.loading && listview.count === 0
0263 width: parent.width - Kirigami.Units.gridUnit * 4
0264 }
0265
0266 Components.FloatingButton {
0267 QQC2.ToolTip.text: i18nc("@info:tooltip", "Return to Top")
0268 QQC2.ToolTip.visible: hovered
0269 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
0270
0271 opacity: listview.atYBeginning ? 0 : 1
0272 visible: opacity !== 0
0273
0274 Behavior on opacity {
0275 NumberAnimation {
0276 }
0277 }
0278
0279 anchors {
0280 right: parent.right
0281 rightMargin: Kirigami.Units.largeSpacing
0282 bottom: parent.bottom
0283 bottomMargin: Kirigami.Units.largeSpacing
0284 }
0285
0286 action: Kirigami.Action
0287 {
0288 icon.name: "arrow-up"
0289 onTriggered: listview.positionViewAtBeginning()
0290 }
0291 }
0292 }
0293 }