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 }