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 }