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 }