Warning, /network/tokodon/src/content/ui/AccountInfo.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.components 1 as Components 0007 import org.kde.kirigamiaddons.formcard 1 as FormCard 0008 import Qt5Compat.GraphicalEffects 0009 import QtQuick.Controls 2 as QQC2 0010 import QtQml.Models 0011 import QtQuick.Layouts 0012 import QtQuick.Dialogs 0013 import org.kde.tokodon 0014 0015 TimelinePage { 0016 id: accountInfo 0017 0018 required property string accountId 0019 0020 property var postsBar 0021 0022 readonly property var currentIndex: postsBar ? postsBar.currentIndex : 0 0023 readonly property bool onPostsTab: accountInfo.currentIndex === 0 0024 readonly property bool onRepliesTab: accountInfo.currentIndex === 1 0025 readonly property bool onMediaTab: accountInfo.currentIndex === 2 0026 0027 readonly property bool canExcludeBoosts: accountInfo.onPostsTab || accountInfo.onRepliesTab 0028 property alias excludeBoosts: model.excludeBoosts 0029 0030 readonly property bool largeScreen: width > Kirigami.Units.gridUnit * 25 0031 0032 showFilterAction: false 0033 0034 model: AccountModel { 0035 id: model 0036 0037 accountId: accountInfo.accountId 0038 0039 currentTab: accountInfo.currentIndex 0040 } 0041 0042 listViewHeader: QQC2.Pane { 0043 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0044 Kirigami.Theme.inherit: false 0045 0046 width: parent.width 0047 0048 leftPadding: 0 0049 rightPadding: 0 0050 bottomPadding: 0 0051 topPadding: 0 0052 0053 contentItem: Loader { 0054 active: model.identity 0055 sourceComponent: ColumnLayout { 0056 spacing: 0 0057 0058 QQC2.Control { 0059 Layout.fillWidth: true 0060 0061 background: Item { 0062 Item { 0063 anchors.fill: parent 0064 0065 Rectangle { 0066 anchors.fill: parent 0067 color: avatar.color 0068 opacity: 0.2 0069 0070 } 0071 Kirigami.Icon { 0072 visible: source 0073 scale: 1.8 0074 anchors.fill: parent 0075 0076 source: model.identity.backgroundUrl 0077 0078 implicitWidth: 512 0079 implicitHeight: 512 0080 } 0081 0082 layer.enabled: true 0083 layer.effect: HueSaturation { 0084 cached: true 0085 0086 saturation: 1.9 0087 0088 layer { 0089 enabled: true 0090 effect: FastBlur { 0091 cached: true 0092 radius: 100 0093 } 0094 } 0095 } 0096 } 0097 0098 Rectangle { 0099 anchors.fill: parent 0100 gradient: Gradient { 0101 GradientStop { position: -1.0; color: "transparent" } 0102 GradientStop { position: 1.0; color: Kirigami.Theme.backgroundColor } 0103 } 0104 } 0105 } 0106 0107 QQC2.Control { 0108 visible: accountInfo.model.identity.relationship && accountInfo.model.identity.relationship.followedBy 0109 0110 anchors { 0111 right: parent.right 0112 rightMargin: Kirigami.Units.smallSpacing 0113 top: parent.top 0114 topMargin: Kirigami.Units.smallSpacing 0115 } 0116 0117 contentItem: QQC2.Label { 0118 text: i18n("Follows you") 0119 color: '#fafafa' 0120 } 0121 0122 background: Rectangle { 0123 radius: 3 0124 color: '#090b0d' 0125 } 0126 } 0127 0128 contentItem: RowLayout { 0129 RowLayout { 0130 Layout.maximumWidth: Kirigami.Units.gridUnit * 30 0131 Layout.fillWidth: true 0132 Layout.alignment: Qt.AlignHCenter 0133 0134 Kirigami.ShadowedRectangle { 0135 Layout.margins: accountInfo.largeScreen ? Kirigami.Units.gridUnit * 2 : Kirigami.Units.largeSpacing 0136 Layout.preferredWidth: accountInfo.largeScreen ? Kirigami.Units.gridUnit * 5 : Kirigami.Units.gridUnit * 3 0137 Layout.preferredHeight: accountInfo.largeScreen ? Kirigami.Units.gridUnit * 5 : Kirigami.Units.gridUnit * 3 0138 0139 color: Kirigami.Theme.backgroundColor 0140 radius: width 0141 0142 shadow { 0143 size: 15 0144 xOffset: 5 0145 yOffset: 5 0146 color: Qt.rgba(0, 0, 0, 0.2) 0147 } 0148 0149 Components.Avatar { 0150 id: avatar 0151 0152 height: parent.height 0153 width: height 0154 0155 name: model.identity.displayName 0156 source: model.identity.avatarUrl 0157 imageMode: Components.Avatar.ImageMode.AdaptiveImageOrInitals 0158 } 0159 } 0160 0161 ColumnLayout { 0162 spacing: Kirigami.Units.smallSpacing 0163 0164 Layout.leftMargin: Kirigami.Units.largeSpacing 0165 Layout.rightMargin: Kirigami.Units.largeSpacing 0166 Layout.fillWidth: true 0167 0168 QQC2.Label { 0169 Layout.fillWidth: true 0170 text: model.identity.displayNameHtml 0171 font.bold: true 0172 font.pixelSize: 24 0173 maximumLineCount: 2 0174 wrapMode: Text.Wrap 0175 elide: Text.ElideRight 0176 } 0177 0178 QQC2.TextArea { 0179 text: "@" + model.identity.account 0180 textFormat: TextEdit.PlainText 0181 wrapMode: TextEdit.Wrap 0182 readOnly: true 0183 background: null 0184 font.pixelSize: 18 0185 0186 leftPadding: 0 0187 rightPadding: 0 0188 topPadding: 0 0189 0190 Layout.fillWidth: true 0191 } 0192 0193 Kirigami.ActionToolBar { 0194 id: toolbar 0195 0196 flat: false 0197 alignment: Qt.AlignLeft 0198 0199 actions: [ 0200 Kirigami.Action { 0201 icon.name: { 0202 if (model.identity.relationship && model.identity.relationship.following) { 0203 return "list-remove-user"; 0204 } 0205 return "list-add-user"; 0206 } 0207 0208 text: { 0209 if (model.identity.relationship && model.identity.relationship.requested) { 0210 return i18n("Follow Requested"); 0211 } 0212 if (model.identity.relationship && model.identity.relationship.following) { 0213 return i18n("Unfollow"); 0214 } 0215 return i18n("Follow"); 0216 } 0217 onTriggered: { 0218 if (model.identity.relationship.requested 0219 || model.identity.relationship.following) { 0220 model.account.unfollowAccount(model.identity); 0221 } else { 0222 model.account.followAccount(model.identity); 0223 } 0224 } 0225 visible: !model.isSelf 0226 }, 0227 Kirigami.Action { 0228 displayHint: Kirigami.DisplayHint.IconOnly 0229 icon.name: { 0230 if (model.identity.relationship && model.identity.relationship.notifying) { 0231 return "notifications"; 0232 } else { 0233 return "notifications-disabled"; 0234 } 0235 } 0236 0237 visible: model.identity.relationship && model.identity.relationship.following && !model.isSelf 0238 tooltip: { 0239 if (model.identity.relationship && model.identity.relationship.notifying) { 0240 return i18n("Stop notifying me when %1 posts.", '@' + model.identity.account); 0241 } else { 0242 return i18n("Notify me when %1 posts.", '@' + model.identity.account); 0243 } 0244 } 0245 onTriggered: { 0246 if (model.identity.relationship && model.identity.relationship.notifying) { 0247 model.account.followAccount(model.identity, model.identity.relationship.showingReblogs, false); 0248 } else { 0249 model.account.followAccount(model.identity, model.identity.relationship.showingReblogs, true); 0250 } 0251 } 0252 }, 0253 Kirigami.Action { 0254 icon.name: "view-hidden" 0255 displayHint: Kirigami.DisplayHint.IconOnly 0256 visible: model.identity.relationship && model.identity.relationship.following && !model.isSelf 0257 text: { 0258 if (model.identity.relationship && model.identity.relationship.showingReblogs) { 0259 return i18n("Hide Boosts from %1", '@' + model.identity.account); 0260 } else { 0261 return i18n("Show Boosts from %1", '@' + model.identity.account); 0262 } 0263 } 0264 onTriggered: { 0265 if (model.identity.relationship && model.identity.relationship.showingReblogs) { 0266 model.account.followAccount(model.identity, false, model.identity.relationship.notifying); 0267 } else { 0268 model.account.followAccount(model.identity, true, model.identity.relationship.notifying); 0269 } 0270 } 0271 }, 0272 Kirigami.Action { 0273 icon.name: "favorite" 0274 visible: model.identity.relationship && !model.isSelf 0275 text: { 0276 if (model.identity.relationship && model.identity.relationship.endorsed) { 0277 return i18n("Stop Featuring on Profile"); 0278 } else { 0279 return i18n("Feature on Profile"); 0280 } 0281 } 0282 onTriggered: { 0283 if (model.identity.relationship && model.identity.relationship.endorsed) { 0284 model.account.unpin(model.identity); 0285 } else { 0286 model.account.pin(model.identity); 0287 } 0288 } 0289 }, 0290 Kirigami.Action { 0291 icon.name: "dialog-cancel" 0292 visible: model.identity.relationship && !model.isSelf 0293 text: { 0294 if (model.identity.relationship && model.identity.relationship.muting) { 0295 return i18n("Unmute"); 0296 } else { 0297 return i18n("Mute"); 0298 } 0299 } 0300 onTriggered: { 0301 if (model.identity.relationship && model.identity.relationship.muting) { 0302 model.account.unmuteAccount(model.identity); 0303 } else { 0304 model.account.muteAccount(model.identity); 0305 } 0306 } 0307 }, 0308 Kirigami.Action { 0309 icon.name: "im-ban-kick-user" 0310 visible: model.identity.relationship && !model.isSelf 0311 text: { 0312 if (model.identity.relationship && model.identity.relationship.blocking) { 0313 return i18n("Unblock"); 0314 } else { 0315 return i18n("Block"); 0316 } 0317 } 0318 onTriggered: { 0319 if (model.identity.relationship && model.identity.relationship.blocking) { 0320 model.account.unblock(model.identity); 0321 } else { 0322 model.account.block(model.identity); 0323 } 0324 } 0325 }, 0326 Kirigami.Action { 0327 icon.name: "dialog-warning-symbolic" 0328 visible: !model.isSelf 0329 text: i18nc("@action:inmenu Report this post", "Report…"); 0330 onTriggered: Navigation.reportUser(model.identity) 0331 }, 0332 Kirigami.Action { 0333 icon.name: "user-group-properties" 0334 visible: model.isSelf 0335 text: i18n("Edit Profile") 0336 onTriggered: pageStack.push(Qt.createComponent("org.kde.tokodon", "ProfileEditor"), { 0337 account: model.account 0338 }, { 0339 title: i18n("Account editor") 0340 }) 0341 }, 0342 Kirigami.Action { 0343 icon.name: "settings-configure" 0344 visible: model.isSelf 0345 text: i18n("Settings") 0346 onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.tokodon", "SettingsPage"), {}, { title: i18n("Configure") }) 0347 }, 0348 Kirigami.Action { 0349 icon.name: "list-add-user" 0350 visible: model.isSelf 0351 text: i18n("Follow Requests") 0352 onTriggered: pageStack.push(socialGraphComponent, { name: "request" }); 0353 }, 0354 Kirigami.Action { 0355 icon.name: "microphone-sensitivity-muted" 0356 visible: model.isSelf 0357 text: i18n("Muted Users") 0358 onTriggered: pageStack.push(socialGraphComponent, { name: "mutes" }); 0359 }, 0360 Kirigami.Action { 0361 icon.name: "cards-block" 0362 visible: model.isSelf 0363 text: i18n("Blocked Users") 0364 onTriggered: pageStack.push(socialGraphComponent, { name: "blocks" }); 0365 }, 0366 Kirigami.Action { 0367 icon.name: "favorite" 0368 visible: model.isSelf 0369 text: i18n("Featured Users") 0370 onTriggered: pageStack.push(socialGraphComponent, { name: "featured" }); 0371 }, 0372 Kirigami.Action { 0373 icon.name: "edit-copy" 0374 text: i18n("Copy Link to This Profile") 0375 onTriggered: { 0376 Clipboard.saveText(model.identity.url) 0377 applicationWindow().showPassiveNotification(i18n("Post link copied.")); 0378 } 0379 }, 0380 ShareAction { 0381 id: shareAction 0382 0383 inputData: { 0384 'urls': [model.identity.url.toString()], 0385 'title': "Profile", 0386 } 0387 } 0388 ] 0389 } 0390 } 0391 } 0392 } 0393 } 0394 0395 FormCard.FormCard { 0396 id: usernameCard 0397 0398 Layout.topMargin: bioCard.cardWidthRestricted ? 0 : Kirigami.Units.largeSpacing 0399 0400 visible: accountInfo.model.identity.fields.length > 0 0401 0402 Repeater { 0403 model: accountInfo.model.identity.fields 0404 ColumnLayout { 0405 Layout.fillWidth: true 0406 spacing: 0 0407 FormCard.FormDelegateSeparator { visible: index !== 0 } 0408 0409 FormCard.AbstractFormDelegate { 0410 topPadding: 0 0411 bottomPadding: 0 0412 hoverEnabled: false 0413 0414 background: Rectangle { 0415 color: modelData.verified_at !== null ? Kirigami.Theme.positiveBackgroundColor : "transparent" 0416 } 0417 0418 contentItem: RowLayout { 0419 spacing: 0 0420 0421 QQC2.Label { 0422 text: modelData.name 0423 wrapMode: Text.Wrap 0424 0425 topPadding: Kirigami.Units.smallSpacing 0426 bottomPadding: Kirigami.Units.smallSpacing 0427 0428 Layout.minimumWidth: Kirigami.Units.gridUnit * 7 0429 Layout.maximumWidth: Kirigami.Units.gridUnit * 7 0430 } 0431 0432 QQC2.TextArea { 0433 Layout.fillWidth: true 0434 readOnly: true 0435 background: null 0436 wrapMode: TextEdit.Wrap 0437 textFormat: TextEdit.RichText 0438 text: modelData.value 0439 onLinkActivated: Qt.openUrlExternally(link) 0440 MouseArea { 0441 anchors.fill: parent 0442 acceptedButtons: Qt.NoButton // don't eat clicks on the Text 0443 cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor 0444 } 0445 } 0446 } 0447 } 0448 } 0449 } 0450 } 0451 0452 FormCard.FormCard { 0453 visible: accountInfo.model.identity.relationship 0454 0455 Layout.topMargin: Kirigami.Units.largeSpacing 0456 0457 FormCard.AbstractFormDelegate { 0458 background: null 0459 0460 contentItem: ColumnLayout { 0461 spacing: 0 0462 RowLayout { 0463 Layout.fillWidth: true 0464 0465 Kirigami.Heading { 0466 Layout.fillWidth: true 0467 text: i18n("Note:") 0468 level: 5 0469 } 0470 0471 QQC2.Control { 0472 id: savedNotification 0473 visible: false 0474 contentItem: QQC2.Label { 0475 text: i18n("Saved") 0476 color: Kirigami.Theme.positiveTextColor 0477 } 0478 0479 background: Rectangle { 0480 radius: 3 0481 color: Kirigami.Theme.positiveBackgroundColor 0482 } 0483 } 0484 } 0485 0486 QQC2.TextArea { 0487 id: noteField 0488 Layout.fillWidth: true 0489 background: null 0490 placeholderText: i18n("Click to add a note") 0491 textFormat: TextEdit.PlainText 0492 wrapMode: TextEdit.Wrap 0493 leftPadding: 0 0494 rightPadding: 0 0495 text: accountInfo.model.identity.relationship ? accountInfo.model.identity.relationship.note : '' 0496 property string lastSavedText: '' 0497 onActiveFocusChanged: { 0498 lastSavedText = text; 0499 if (activeFocus) { 0500 autoSaveTimer.start() 0501 } else { 0502 accountInfo.model.account.addNote(accountInfo.model.identity, text); 0503 savedNotification.visible = true; 0504 savedNotificationTimer.restart(); 0505 lastSavedText = text; 0506 autoSaveTimer.stop() 0507 } 0508 } 0509 Timer { 0510 id: autoSaveTimer 0511 running: false 0512 repeat: true 0513 interval: 5000 0514 onTriggered: if (noteField.lastSavedText !== noteField.text) { 0515 accountInfo.model.account.addNote(accountInfo.model.identity, noteField.text); 0516 savedNotification.visible = true; 0517 noteField.lastSavedText = noteField.text; 0518 savedNotificationTimer.restart(); 0519 } 0520 } 0521 0522 Timer { 0523 id: savedNotificationTimer 0524 running: false 0525 interval: 5000 0526 onTriggered: savedNotification.visible = false 0527 } 0528 } 0529 } 0530 } 0531 } 0532 0533 FormCard.FormCard { 0534 id: bioCard 0535 0536 visible: accountInfo.model.identity.bio.length > 0 0537 0538 Layout.topMargin: Kirigami.Units.largeSpacing 0539 0540 FormCard.AbstractFormDelegate { 0541 background: null 0542 contentItem: QQC2.TextArea { 0543 text: accountInfo.model.identity.bio 0544 textFormat: TextEdit.RichText 0545 readOnly: true 0546 Layout.fillWidth: true 0547 Layout.leftMargin: Kirigami.Units.largeSpacing 0548 Layout.rightMargin: Kirigami.Units.largeSpacing 0549 Layout.topMargin: Kirigami.Units.smallSpacing 0550 Layout.bottomMargin: Kirigami.Units.smallSpacing 0551 leftPadding: 0 0552 rightPadding: 0 0553 bottomPadding: 0 0554 topPadding: 0 0555 background: null 0556 wrapMode: TextEdit.Wrap 0557 onLinkActivated: (link) => applicationWindow().navigateLink(link, true) 0558 onHoveredLinkChanged: if (hoveredLink.length > 0) { 0559 applicationWindow().hoverLinkIndicator.text = hoveredLink; 0560 } else { 0561 applicationWindow().hoverLinkIndicator.text = ""; 0562 } 0563 0564 MouseArea { 0565 anchors.fill: parent 0566 acceptedButtons: Qt.NoButton // don't eat clicks on the Text 0567 cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor 0568 } 0569 } 0570 } 0571 } 0572 0573 Item { 0574 Layout.fillWidth: true 0575 Layout.topMargin: Kirigami.Units.largeSpacing 0576 Layout.bottomMargin: Kirigami.Units.largeSpacing 0577 0578 implicitHeight: chips.implicitHeight 0579 implicitWidth: chips.implicitWidth + chips.anchors.leftMargin + chips.anchors.rightMargin 0580 0581 RowLayout { 0582 id: chips 0583 0584 anchors { 0585 left: parent.left 0586 right: parent.right 0587 top: parent.top 0588 leftMargin: usernameCard.cardWidthRestricted ? Math.round((usernameCard.width - usernameCard.maximumWidth) / 2) : Kirigami.Units.largeSpacing 0589 rightMargin: usernameCard.cardWidthRestricted ? Math.round((usernameCard.width - usernameCard.maximumWidth) / 2) : Kirigami.Units.largeSpacing 0590 } 0591 0592 Kirigami.Chip { 0593 closable: false 0594 enabled: false 0595 0596 text: i18ncp("@label User's number of statuses", "<b>%1</b> post", "<b>%1</b> posts", model.identity.statusesCount) 0597 } 0598 0599 Kirigami.Chip { 0600 closable: false 0601 checkable: false 0602 0603 onClicked: { 0604 pageStack.push(socialGraphComponent, { 0605 name: "followers", 0606 accountId: accountId, 0607 }); 0608 } 0609 text: i18ncp("@label User's number of followers", "<b>%1</b> follower", "<b>%1</b> followers", model.identity.followersCount) 0610 } 0611 0612 Kirigami.Chip { 0613 closable: false 0614 checkable: false 0615 0616 onClicked: { 0617 pageStack.push(socialGraphComponent, { 0618 name: "following", 0619 accountId: accountId, 0620 }); 0621 } 0622 text: i18ncp("@label User's number of followed accounts", "<b>%1</b> follows", "<b>%1</b> following", model.identity.followingCount) 0623 } 0624 0625 Item { 0626 Layout.fillWidth: true 0627 } 0628 } 0629 } 0630 0631 QQC2.TabBar { 0632 id: bar 0633 0634 Kirigami.Theme.inherit: false 0635 Kirigami.Theme.colorSet: Kirigami.Theme.View 0636 0637 // Hack to disable the qqc2-desktop-style scrolling behavior. 0638 // This bar is on a scrollable page, you will eventually run into this tab bar which is annoying. 0639 background: null 0640 0641 enabled: !accountInfo.model.loading 0642 0643 Component.onCompleted: accountInfo.postsBar = bar 0644 0645 Layout.alignment: Qt.AlignHCenter 0646 Layout.fillWidth: true 0647 0648 QQC2.TabButton { 0649 text: i18nc("@item:inmenu Profile Post Filter", "Posts") 0650 implicitWidth: bar.width / 3 0651 } 0652 QQC2.TabButton { 0653 text: i18nc("@item:inmenu Profile Post Filter", "Posts && Replies") 0654 implicitWidth: bar.width / 3 0655 } 0656 QQC2.TabButton { 0657 text: i18nc("@item:inmenu Profile Post Filter", "Media") 0658 implicitWidth: bar.width / 3 0659 } 0660 } 0661 Rectangle { 0662 Layout.fillWidth: true 0663 0664 Kirigami.Theme.inherit: false 0665 Kirigami.Theme.colorSet: Kirigami.Theme.View 0666 0667 enabled: !accountInfo.model.loading 0668 0669 implicitHeight: extraLayout.implicitHeight + Kirigami.Units.largeSpacing * 2 0670 0671 color: Kirigami.Theme.backgroundColor 0672 0673 RowLayout { 0674 id: extraLayout 0675 0676 anchors { 0677 fill: parent 0678 0679 topMargin: Kirigami.Units.largeSpacing 0680 leftMargin: Kirigami.Units.largeSpacing 0681 rightMargin: Kirigami.Units.largeSpacing 0682 bottomMargin: Kirigami.Units.largeSpacing 0683 } 0684 0685 QQC2.Switch { 0686 text: i18nc("@option:check", "Hide boosts") 0687 0688 checked: accountInfo.excludeBoosts 0689 enabled: accountInfo.canExcludeBoosts && !accountInfo.model.loading 0690 0691 onToggled: accountInfo.excludeBoosts = checked 0692 } 0693 0694 QQC2.ScrollView { 0695 Layout.fillWidth: true 0696 Layout.fillHeight: true 0697 0698 RowLayout { 0699 spacing: Kirigami.Units.mediumSpacing 0700 0701 QQC2.ButtonGroup { 0702 id: tagGroup 0703 } 0704 0705 Kirigami.Chip { 0706 text: i18nc("@action:button Show all of a profile's posts", "All") 0707 closable: false 0708 checked: true 0709 0710 onClicked: accountInfo.model.tagged = "" 0711 0712 QQC2.ButtonGroup.group: tagGroup 0713 } 0714 0715 Repeater { 0716 model: FeaturedTagsModel { 0717 accountId: accountInfo.accountId 0718 } 0719 0720 delegate: Kirigami.Chip 0721 { 0722 required property string name 0723 0724 text: '#' + name 0725 closable: false 0726 0727 onClicked: accountInfo.model.tagged = name 0728 0729 QQC2.ButtonGroup.group: tagGroup 0730 } 0731 } 0732 } 0733 } 0734 } 0735 } 0736 0737 Kirigami.Separator { 0738 Layout.fillWidth: true 0739 } 0740 } 0741 } 0742 } 0743 }