diff --git a/1stparty/atproto b/1stparty/atproto index 85c85350..eb20ff64 160000 --- a/1stparty/atproto +++ b/1stparty/atproto @@ -1 +1 @@ -Subproject commit 85c85350d13fb4468c12045e086ad91733b4bcb3 +Subproject commit eb20ff64a2d8e3061c652e1e247bf9b0fe3c41a6 diff --git a/app/main.cpp b/app/main.cpp index e93570ae..e29cd2b6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) app.setOrganizationName(QStringLiteral("relog")); app.setOrganizationDomain(QStringLiteral("hagoromo.relog.tech")); app.setApplicationName(QStringLiteral("Hagoromo")); - app.setApplicationVersion(QStringLiteral("0.37.0")); + app.setApplicationVersion(QStringLiteral("0.38.0")); #ifndef HAGOROMO_RELEASE_BUILD app.setApplicationVersion(app.applicationVersion() + "d"); #endif diff --git a/app/qml/dialogs/AccountDialog.qml b/app/qml/dialogs/AccountDialog.qml index 8888c251..2940c0ed 100644 --- a/app/qml/dialogs/AccountDialog.qml +++ b/app/qml/dialogs/AccountDialog.qml @@ -123,6 +123,14 @@ Dialog { fontPointSize: AdjustedValues.f8 visible: model.isMain } + TagLabel { + source: "" + text: " Please login" + color: Material.color(Material.Red) + fontPointSize: AdjustedValues.f8 + visible: !model.authorized + } + Item { Layout.fillWidth: true Layout.preferredHeight: 1 diff --git a/app/qml/dialogs/MessageDialog.qml b/app/qml/dialogs/MessageDialog.qml index fd571679..d48dbcd1 100644 --- a/app/qml/dialogs/MessageDialog.qml +++ b/app/qml/dialogs/MessageDialog.qml @@ -17,7 +17,7 @@ Dialog { property int parentWidth: parent.width property alias text: messageTextArea.text - property string status: "normal" + property string status: "normal" // normal, error property bool useCancel: false function show(status, title, message){ diff --git a/app/qml/main.qml b/app/qml/main.qml index e83bfebf..9daede1b 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -161,8 +161,27 @@ ApplicationWindow { AccountDialog { id: accountDialog + property bool showLogainAgainMessage: false accountModel: accountListModel - onClosed: accountListModel.syncColumn() + onOpened: { + if(showLogainAgainMessage){ + messageDialog.show("error", qsTr("Authentication error"), + qsTr("Some accounts require you to log in again.")) + } + showLogainAgainMessage = false + } + onClosed: { + if(columnManageModel.rowCount() === 0){ + if(accountListModel.allAccountsReady){ + // すべてのアカウント情報の認証が終わったのでカラムを復元 + console.log("start loading columns") + columnManageModel.load() + listsListModel.load() + } + }else{ + accountListModel.syncColumn() + } + } onErrorOccured: (account_uuid, code, message) => appWindow.errorHandler(account_uuid, code, message) onRequestAddMutedWords: (account_uuid) => { if(addMutedWordDialog.account.set(accountListModel, account_uuid)){ @@ -450,18 +469,31 @@ ApplicationWindow { id: accountListModel onUpdatedSession: (row, uuid) => { console.log("onUpdatedSession:" + row + ", " + uuid) - if(columnManageModel.rowCount() === 0 && allAccountsReady()){ - // すべてのアカウント情報の認証が終わったのでカラムを復元 - console.log("start loading columns") - columnManageModel.load() - listsListModel.load() - } } onUpdatedAccount: (row, uuid) => { console.log("onUpdatedAccount:" + row + ", " + uuid) // カラムを更新しにいく repeater.updateAccount(uuid) } + onFinished: { + console.log("onFinished:" + allAccountsReady + ", count=" + columnManageModel.rowCount()) + if(rowCount() === 0){ + accountDialog.open() + }else if(columnManageModel.rowCount() === 0){ + if(allAccountsReady){ + // すべてのアカウント情報の認証が終わったのでカラムを復元 + console.log("start loading columns") + columnManageModel.load() + listsListModel.load() + }else{ + // 失敗しているアカウントがあるのでダイアログを出す + messageDialog.close() + accountDialog.showLogainAgainMessage = true + accountDialog.open() + } + } + } + onErrorOccured: (code, message) => appWindow.errorHandler("", code, message) function syncColumn(){ @@ -717,6 +749,7 @@ ApplicationWindow { SideBar { anchors.fill: parent anchors.margins: 1 + ready: accountListModel.allAccountsReady post.onClicked: postDialog.open() search.onClicked: searchDialog.open() addColumn.onClicked: addColumnDialog.open() diff --git a/app/qml/parts/ExternalLinkCard.qml b/app/qml/parts/ExternalLinkCard.qml index d5b4c4ef..e8466cad 100644 --- a/app/qml/parts/ExternalLinkCard.qml +++ b/app/qml/parts/ExternalLinkCard.qml @@ -32,7 +32,7 @@ ClickableFrame { ImageWithIndicator { id: thumbImage Layout.preferredWidth: externalLinkFrame.width - Layout.preferredHeight: status !== Image.Null ? externalLinkFrame.width * 0.5 : 5 + Layout.preferredHeight: thumbImage.source !== "" ? externalLinkFrame.width * 0.5 : 5 fillMode: Image.PreserveAspectCrop } Label { diff --git a/app/qml/parts/ImagePreview.qml b/app/qml/parts/ImagePreview.qml index 45ed9467..40287c50 100644 --- a/app/qml/parts/ImagePreview.qml +++ b/app/qml/parts/ImagePreview.qml @@ -6,88 +6,109 @@ import tech.relog.hagoromo.singleton 1.0 import "../controls" -GridLayout { +Frame { id: imagePreviewLayout visible: embedImages.length > 0 - columnSpacing: 6 - rowSpacing: 6 - columns: 2 + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight + topInset: 0 + leftInset: 0 + rightInset: 0 + bottomInset: 0 + topPadding: 0 + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + background: Rectangle { + color: "transparent" + } // 0:compact, 1:normal, 2:when one is whole, 3:all whole property int layoutType: 1 property int layoutWidth: 100 property var embedImages: [] property var embedAlts: [] + property var embedImageRatios: [] property int cellWidthAdjust: 3 - property int cellWidth: imagePreviewLayout.layoutWidth / columns - cellWidthAdjust + property int cellWidth: imagePreviewLayout.layoutWidth / contentRootLayout.columns - cellWidthAdjust signal requestViewImages(int index) states: [ State { when: layoutType === 0 - PropertyChanges { target: imagePreviewLayout; columns: 4 } + PropertyChanges { target: contentRootLayout; columns: 4 } PropertyChanges { target: imagePreviewLayout; cellWidthAdjust: 5 } }, State { when: layoutType === 1 || layoutType === 2 - PropertyChanges { target: imagePreviewLayout; columns: 2 } + PropertyChanges { target: contentRootLayout; columns: 2 } PropertyChanges { target: imagePreviewLayout; cellWidthAdjust: 3 } }, State { when: layoutType === 3 - PropertyChanges { target: imagePreviewLayout; columns: 1 } + PropertyChanges { target: contentRootLayout; columns: 1 } PropertyChanges { target: imagePreviewLayout; cellWidthAdjust: 0 } } ] - Repeater { - id: repeater - model: imagePreviewLayout.embedImages - delegate: ImageWithIndicator { - id: image - property bool isWide: false - property bool isTall: false - Layout.preferredWidth: isWide ? imagePreviewLayout.layoutWidth : imagePreviewLayout.cellWidth - Layout.preferredHeight: isTall ? (imagePreviewLayout.layoutWidth * sourceSize.height / sourceSize.width) : imagePreviewLayout.cellWidth - Layout.columnSpan: isWide ? 2 : 1 - fillMode: Image.PreserveAspectCrop - source: modelData - states: [ - State { - when: imagePreviewLayout.layoutType === 0 - PropertyChanges { target: image; isWide: false } - PropertyChanges { target: image; isTall: false } - }, - State { - when: imagePreviewLayout.layoutType === 1 - PropertyChanges { target: image; isWide: (repeater.count % 2 === 1 && model.index === (repeater.count - 1)) } - PropertyChanges { target: image; isTall: false } - }, - State { - when: imagePreviewLayout.layoutType === 2 - PropertyChanges { target: image; isWide: (repeater.count % 2 === 1 && model.index === (repeater.count - 1)) } - PropertyChanges { target: image; isTall: (repeater.count === 1) } - }, - State { - when: imagePreviewLayout.layoutType === 3 - PropertyChanges { target: image; isWide: false } - PropertyChanges { target: image; isTall: true } + GridLayout { + id: contentRootLayout + columnSpacing: 6 + rowSpacing: 6 + columns: 2 + Repeater { + id: repeater + model: imagePreviewLayout.embedImages + delegate: ImageWithIndicator { + id: image + property bool isWide: false + property bool isTall: false + Layout.preferredWidth: isWide ? imagePreviewLayout.layoutWidth : imagePreviewLayout.cellWidth + Layout.preferredHeight: isTall ? (imagePreviewLayout.layoutWidth * ( + model.index < embedImageRatios.length ? parseFloat(embedImageRatios[model.index]) : (sourceSize.height / sourceSize.width) + ) + ) : imagePreviewLayout.cellWidth + Layout.columnSpan: isWide ? 2 : 1 + fillMode: Image.PreserveAspectCrop + source: modelData + states: [ + State { + when: imagePreviewLayout.layoutType === 0 + PropertyChanges { target: image; isWide: false } + PropertyChanges { target: image; isTall: false } + }, + State { + when: imagePreviewLayout.layoutType === 1 + PropertyChanges { target: image; isWide: (repeater.count % 2 === 1 && model.index === (repeater.count - 1)) } + PropertyChanges { target: image; isTall: false } + }, + State { + when: imagePreviewLayout.layoutType === 2 + PropertyChanges { target: image; isWide: (repeater.count % 2 === 1 && model.index === (repeater.count - 1)) } + PropertyChanges { target: image; isTall: (repeater.count === 1) } + }, + State { + when: imagePreviewLayout.layoutType === 3 + PropertyChanges { target: image; isWide: false } + PropertyChanges { target: image; isTall: true } + } + ] + TagLabel { + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 3 + visible: model.index < embedAlts.length ? embedAlts[model.index].length > 0 : false + source: "" + fontPointSize: AdjustedValues.f8 + text: "Alt" + } + MouseArea { + anchors.fill: parent + onClicked: imagePreviewLayout.requestViewImages(model.index) } - ] - TagLabel { - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.margins: 3 - visible: model.index < embedAlts.length ? embedAlts[model.index].length > 0 : false - source: "" - fontPointSize: AdjustedValues.f8 - text: "Alt" - } - MouseArea { - anchors.fill: parent - onClicked: imagePreviewLayout.requestViewImages(model.index) } } } diff --git a/app/qml/parts/PostDelegate.qml b/app/qml/parts/PostDelegate.qml index 05714ed3..bbb79032 100644 --- a/app/qml/parts/PostDelegate.qml +++ b/app/qml/parts/PostDelegate.qml @@ -265,8 +265,7 @@ ClickableFrame { Layout.preferredWidth: parent.width Layout.topMargin: 5 visible: postFrame.hasQuote && - quoteFilterFrame.showContent && - contentMediaFilterFrame.showContent + quoteFilterFrame.showContent basisWidth: bodyLayout.basisWidth onOpenLink: (url) => postFrame.openLink(url) onDisplayLink: (url) => postFrame.displayLink(url) diff --git a/app/qml/parts/SideBar.qml b/app/qml/parts/SideBar.qml index 8199dc85..93d0296c 100644 --- a/app/qml/parts/SideBar.qml +++ b/app/qml/parts/SideBar.qml @@ -11,6 +11,7 @@ ColumnLayout { anchors.margins: 1 spacing: 0 + property bool ready: false property alias post: post property alias search: search property alias addColumn: addColumn @@ -23,7 +24,7 @@ ColumnLayout { width: parent.width Layout.preferredWidth: parent.width Layout.preferredHeight: AdjustedValues.b36 - enabled: accountListModel.count > 0 + enabled: parent.ready display: AbstractButton.IconOnly iconSource: "../images/edit.png" // iconText: qsTr("New Post") @@ -35,7 +36,7 @@ ColumnLayout { width: parent.width Layout.preferredWidth: parent.width Layout.preferredHeight: AdjustedValues.b36 - enabled: accountListModel.count > 0 + enabled: parent.ready display: AbstractButton.IconOnly iconSource: "../images/search.png" onWidthChanged: console.log("search:" + width) @@ -51,7 +52,7 @@ ColumnLayout { width: parent.width Layout.preferredWidth: parent.width Layout.preferredHeight: AdjustedValues.b36 - enabled: accountListModel.count > 0 + enabled: parent.ready display: AbstractButton.IconOnly iconSource: "../images/column.png" // iconText: qsTr("Add column") @@ -63,7 +64,7 @@ ColumnLayout { Layout.preferredWidth: parent.width Layout.preferredHeight: AdjustedValues.b36 visible: false - enabled: accountListModel.count > 0 + enabled: parent.ready display: AbstractButton.IconOnly iconSource: "../images/hand.png" } diff --git a/app/qml/parts/VideoFrame.qml b/app/qml/parts/VideoFrame.qml index a4bbfb5d..9d5a2c00 100644 --- a/app/qml/parts/VideoFrame.qml +++ b/app/qml/parts/VideoFrame.qml @@ -33,7 +33,7 @@ ClickableFrame { id: thumbImage width: videoFrame.width height: { - if(status !== Image.Null) { + if(thumbImage.source !== "") { if(sourceSize.width > sourceSize.height){ return (width / sourceSize.width) * sourceSize.height }else{ diff --git a/app/qml/view/NotificationListView.qml b/app/qml/view/NotificationListView.qml index 92bd3013..5d4b29e9 100644 --- a/app/qml/view/NotificationListView.qml +++ b/app/qml/view/NotificationListView.qml @@ -116,6 +116,7 @@ ScrollView { postImagePreview.layoutType: notificationListView.imageLayoutType postImagePreview.embedImages: model.embedImages postImagePreview.embedAlts: model.embedImagesAlt + postImagePreview.embedImageRatios: model.embedImagesRatio postImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.embedImagesFull, model.embedImagesAlt) blockedQuoteFrame.visible: model.quoteRecordBlocked blockedQuoteFrameLabel.text: model.quoteRecordBlockedStatus diff --git a/app/qml/view/PostThreadView.qml b/app/qml/view/PostThreadView.qml index 32761be6..3153db6d 100644 --- a/app/qml/view/PostThreadView.qml +++ b/app/qml/view/PostThreadView.qml @@ -144,6 +144,7 @@ ColumnLayout { postImagePreview.layoutType: postThreadView.imageLayoutType postImagePreview.embedImages: model.embedImages postImagePreview.embedAlts: model.embedImagesAlt + postImagePreview.embedImageRatios: model.embedImagesRatio postImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.embedImagesFull, model.embedImagesAlt) quoteFilterFrame.visible: model.quoteFilterMatched && !model.quoteRecordBlocked diff --git a/app/qml/view/TimelineView.qml b/app/qml/view/TimelineView.qml index 098fe2cc..a31bbcaa 100644 --- a/app/qml/view/TimelineView.qml +++ b/app/qml/view/TimelineView.qml @@ -141,6 +141,7 @@ ScrollView { postImagePreview.layoutType: timelineView.imageLayoutType postImagePreview.embedImages: model.embedImages postImagePreview.embedAlts: model.embedImagesAlt + postImagePreview.embedImageRatios: model.embedImagesRatio postImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.embedImagesFull, model.embedImagesAlt) quoteFilterFrame.visible: model.quoteFilterMatched && !model.quoteRecordBlocked diff --git a/app/qtquick/account/accountlistmodel.cpp b/app/qtquick/account/accountlistmodel.cpp index e55d7cf7..ce2d06cd 100644 --- a/app/qtquick/account/accountlistmodel.cpp +++ b/app/qtquick/account/accountlistmodel.cpp @@ -24,7 +24,8 @@ using AtProtocolInterface::ComAtprotoServerCreateSessionEx; using AtProtocolInterface::ComAtprotoServerRefreshSessionEx; using AtProtocolInterface::DirectoryPlc; -AccountListModel::AccountListModel(QObject *parent) : QAbstractListModel { parent } +AccountListModel::AccountListModel(QObject *parent) + : QAbstractListModel { parent }, m_allAccountsReady(false) { connect(&m_timer, &QTimer::timeout, [=]() { for (int row = 0; row < m_accountList.count(); row++) { @@ -93,6 +94,8 @@ QVariant AccountListModel::item(int row, AccountListModelRoles role) const else if (role == StatusRole) return static_cast(m_accountList.at(row).status); + else if (role == AuthorizedRole) + return m_accountList.at(row).status == AccountStatus::Authorized; return QVariant(); } @@ -189,6 +192,7 @@ void AccountListModel::updateAccount(const QString &service, const QString &iden emit countChanged(); } + checkAllAccountsReady(); save(); } @@ -202,6 +206,7 @@ void AccountListModel::removeAccount(int row) endRemoveRows(); emit countChanged(); + checkAllAccountsReady(); save(); } @@ -257,16 +262,16 @@ void AccountListModel::setMainAccount(int row) save(); } -bool AccountListModel::allAccountsReady() const +bool AccountListModel::checkAllAccountsReady() { - bool ready = true; + int ready_count = 0; for (const AccountData &item : qAsConst(m_accountList)) { - if (item.status == AccountStatus::Unknown) { - ready = false; - break; + if (item.status == AccountStatus::Authorized) { + ready_count++; } } - return ready; + setAllAccountsReady(!m_accountList.isEmpty() && m_accountList.count() == ready_count); + return allAccountsReady(); } void AccountListModel::refreshAccountSession(const QString &uuid) @@ -352,6 +357,7 @@ void AccountListModel::load() item.password = m_encryption.decrypt( doc.array().at(i).toObject().value("password").toString()); item.refreshJwt = m_encryption.decrypt(temp_refresh); + item.handle = item.identifier; for (const auto &value : doc.array().at(i).toObject().value("post_languages").toArray()) { item.post_languages.append(value.toString()); @@ -393,6 +399,11 @@ void AccountListModel::load() m_accountList[0].is_main = true; } } + + checkAllAccountsReady(); + if (m_accountList.isEmpty()) { + emit finished(); + } } QVariant AccountListModel::account(int row) const @@ -425,6 +436,7 @@ QHash AccountListModel::roleNames() const roles[ThreadGateOptionsRole] = "threadGateOptions"; roles[PostGateQuoteEnabledRole] = "postGateQuoteEnabled"; roles[StatusRole] = "status"; + roles[AuthorizedRole] = "authorized"; return roles; } @@ -459,6 +471,10 @@ void AccountListModel::createSession(int row) emit errorOccured(session->errorCode(), session->errorMessage()); } emit dataChanged(index(row), index(row)); + checkAllAccountsReady(); + if (allAccountTried()) { + emit finished(); + } session->deleteLater(); }); session->setAccount(m_accountList.at(row)); @@ -488,16 +504,21 @@ void AccountListModel::refreshSession(int row, bool initial) // 詳細を取得 getProfile(row); } else { - m_accountList[row].status = AccountStatus::Unauthorized; if (initial) { // 初期化時のみ(つまりloadから呼ばれたときだけは失敗したらcreateSessionで再スタート) qDebug() << "Initial refresh session fail."; + m_accountList[row].status = AccountStatus::Unknown; createSession(row); } else { + m_accountList[row].status = AccountStatus::Unauthorized; emit errorOccured(session->errorCode(), session->errorMessage()); } } emit dataChanged(index(row), index(row)); + checkAllAccountsReady(); + if (allAccountTried()) { + emit finished(); + } session->deleteLater(); }); session->setAccount(m_accountList.at(row)); @@ -534,7 +555,9 @@ void AccountListModel::getProfile(int row) emit updatedAccount(row, m_accountList[row].uuid); emit dataChanged(index(row), index(row)); - getRawProfile(row); + qDebug() << "Update pinned post" << detail.pinnedPost.uri; + PinnedPostCache::getInstance()->update(m_accountList.at(row).did, + detail.pinnedPost.uri); } else { emit errorOccured(profile->errorCode(), profile->errorMessage()); } @@ -544,29 +567,6 @@ void AccountListModel::getProfile(int row) }); } -void AccountListModel::getRawProfile(int row) -{ - if (row < 0 || row >= m_accountList.count()) - return; - - ComAtprotoRepoGetRecordEx *record = new ComAtprotoRepoGetRecordEx(this); - connect(record, &ComAtprotoRepoGetRecordEx::finished, this, [=](bool success) { - if (success) { - AtProtocolType::AppBskyActorProfile::Main profile = - AtProtocolType::LexiconsTypeUnknown::fromQVariant< - AtProtocolType::AppBskyActorProfile::Main>(record->value()); - qDebug() << "Update pinned post" << profile.pinnedPost; - PinnedPostCache::getInstance()->update(m_accountList.at(row).did, profile.pinnedPost); - - } else { - emit errorOccured(record->errorCode(), record->errorMessage()); - } - record->deleteLater(); - }); - record->setAccount(m_accountList.at(row)); - record->profile(m_accountList.at(row).did); -} - void AccountListModel::getServiceEndpoint(const QString &did, const QString &service, std::function callback) { @@ -591,7 +591,31 @@ void AccountListModel::getServiceEndpoint(const QString &did, const QString &ser plc->directory(did); } +bool AccountListModel::allAccountTried() const +{ + int count = 0; + for (const AccountData &item : qAsConst(m_accountList)) { + if (item.status != AccountStatus::Unknown) { + count++; + } + } + return (m_accountList.count() == count); +} + int AccountListModel::count() const { return m_accountList.count(); } + +bool AccountListModel::allAccountsReady() const +{ + return m_allAccountsReady; +} + +void AccountListModel::setAllAccountsReady(bool newAllAccountsReady) +{ + if (m_allAccountsReady == newAllAccountsReady) + return; + m_allAccountsReady = newAllAccountsReady; + emit allAccountsReadyChanged(); +} diff --git a/app/qtquick/account/accountlistmodel.h b/app/qtquick/account/accountlistmodel.h index 1aec64d7..37c650ca 100644 --- a/app/qtquick/account/accountlistmodel.h +++ b/app/qtquick/account/accountlistmodel.h @@ -13,6 +13,8 @@ class AccountListModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged CONSTANT) + Q_PROPERTY(bool allAccountsReady READ allAccountsReady WRITE setAllAccountsReady NOTIFY + allAccountsReadyChanged FINAL) public: explicit AccountListModel(QObject *parent = nullptr); @@ -38,6 +40,7 @@ class AccountListModel : public QAbstractListModel ThreadGateOptionsRole, PostGateQuoteEnabledRole, StatusRole, + AuthorizedRole, }; Q_ENUM(AccountListModelRoles) @@ -58,7 +61,7 @@ class AccountListModel : public QAbstractListModel Q_INVOKABLE int indexAt(const QString &uuid); Q_INVOKABLE int getMainAccountIndex() const; Q_INVOKABLE void setMainAccount(int row); - Q_INVOKABLE bool allAccountsReady() const; + Q_INVOKABLE bool checkAllAccountsReady(); Q_INVOKABLE void refreshAccountSession(const QString &uuid); Q_INVOKABLE void refreshAccountProfile(const QString &uuid); @@ -68,12 +71,17 @@ class AccountListModel : public QAbstractListModel Q_INVOKABLE QVariant account(int row) const; int count() const; + bool allAccountsReady() const; + void setAllAccountsReady(bool newAllAccountsReady); signals: void errorOccured(const QString &code, const QString &message); void updatedSession(int row, const QString &uuid); void updatedAccount(int row, const QString &uuid); void countChanged(); + void finished(); + + void allAccountsReadyChanged(); protected: QHash roleNames() const; @@ -83,15 +91,16 @@ class AccountListModel : public QAbstractListModel QVariant m_accountTemp; QTimer m_timer; Encryption m_encryption; + bool m_allAccountsReady; QString appDataFolder() const; void createSession(int row); void refreshSession(int row, bool initial = false); void getProfile(int row); - void getRawProfile(int row); void getServiceEndpoint(const QString &did, const QString &service, std::function callback); + bool allAccountTried() const; }; #endif // ACCOUNTLISTMODEL_H diff --git a/app/qtquick/notification/notificationlistmodel.cpp b/app/qtquick/notification/notificationlistmodel.cpp index b0866e0d..4d331b11 100644 --- a/app/qtquick/notification/notificationlistmodel.cpp +++ b/app/qtquick/notification/notificationlistmodel.cpp @@ -186,6 +186,13 @@ QVariant NotificationListModel::item(int row, NotificationListModelRoles role) c AtProtocolType::LexiconsTypeUnknown::CopyImageType::Alt); else return QStringList(); + } else if (role == EmbedImagesRatioRole) { + if (m_postHash.contains(current.cid)) + return AtProtocolType::LexiconsTypeUnknown::copyImagesFromPostView( + m_postHash[current.cid], + AtProtocolType::LexiconsTypeUnknown::CopyImageType::Ratio); + else + return QStringList(); //---------------------------------------- } else if (role == ReplyCountRole) { @@ -928,6 +935,7 @@ QHash NotificationListModel::roleNames() const roles[EmbedImagesRole] = "embedImages"; roles[EmbedImagesFullRole] = "embedImagesFull"; roles[EmbedImagesAltRole] = "embedImagesAlt"; + roles[EmbedImagesRatioRole] = "embedImagesRatio"; roles[IsRepostedRole] = "isReposted"; roles[IsLikedRole] = "isLiked"; diff --git a/app/qtquick/notification/notificationlistmodel.h b/app/qtquick/notification/notificationlistmodel.h index 0283d80c..2603a978 100644 --- a/app/qtquick/notification/notificationlistmodel.h +++ b/app/qtquick/notification/notificationlistmodel.h @@ -53,6 +53,7 @@ class NotificationListModel : public AtpAbstractListModel EmbedImagesRole, EmbedImagesFullRole, EmbedImagesAltRole, + EmbedImagesRatioRole, IsRepostedRole, IsLikedRole, diff --git a/app/qtquick/operation/recordoperator.cpp b/app/qtquick/operation/recordoperator.cpp index 3e3c6f3b..26a197be 100644 --- a/app/qtquick/operation/recordoperator.cpp +++ b/app/qtquick/operation/recordoperator.cpp @@ -791,7 +791,7 @@ void RecordOperator::updateProfile(const QString &avatar_url, const QString &ban old_profile->profile(m_account.did); } -void RecordOperator::updatePostPinning(const QString &post_uri) +void RecordOperator::updatePostPinning(const QString &post_uri, const QString &post_cid) { if (running()) return; @@ -816,9 +816,12 @@ void RecordOperator::updatePostPinning(const QString &post_uri) setRunning(false); new_profile->deleteLater(); }); + ComAtprotoRepoStrongRef::Main pinned_post; + pinned_post.uri = post_uri; + pinned_post.cid = post_cid; new_profile->setAccount(m_account); new_profile->profile(old_record.avatar, old_record.banner, old_record.description, - old_record.displayName, post_uri, old_cid); + old_record.displayName, pinned_post, old_cid); } else { setProgressMessage(QString()); emit errorOccured(old_profile->errorCode(), old_profile->errorMessage()); diff --git a/app/qtquick/operation/recordoperator.h b/app/qtquick/operation/recordoperator.h index 90c07033..c07e63f5 100644 --- a/app/qtquick/operation/recordoperator.h +++ b/app/qtquick/operation/recordoperator.h @@ -71,7 +71,7 @@ class RecordOperator : public QObject Q_INVOKABLE void updateProfile(const QString &avatar_url, const QString &banner_url, const QString &description, const QString &display_name); - Q_INVOKABLE void updatePostPinning(const QString &post_uri); + Q_INVOKABLE void updatePostPinning(const QString &post_uri, const QString &post_cid); Q_INVOKABLE void updateList(const QString &uri, const QString &avatar_url, const QString &description, const QString &name); Q_INVOKABLE void updateThreadGate(const QString &uri, const QString &threadgate_uri, diff --git a/app/qtquick/profile/userprofile.cpp b/app/qtquick/profile/userprofile.cpp index bd91b96b..78d707cf 100644 --- a/app/qtquick/profile/userprofile.cpp +++ b/app/qtquick/profile/userprofile.cpp @@ -110,6 +110,7 @@ void UserProfile::getProfile(const QString &did) } setBelongingLists( ListItemsCache::getInstance()->getListNames(m_account.did, detail.did)); + setPinnedPost(detail.pinnedPost.uri); if (detail.associated.chat.allowIncoming == "none") { setAssociatedChatAllow(false); @@ -507,39 +508,25 @@ void UserProfile::getRawProfile() return; } - getRawInformation( - did(), - [=](const QString &service_endpoint, const QString ®istration_date, - const QList &handle_history) { - ComAtprotoRepoGetRecordEx *record = new ComAtprotoRepoGetRecordEx(this); - connect(record, &ComAtprotoRepoGetRecordEx::finished, this, [=](bool success) { - if (success) { - AtProtocolType::AppBskyActorProfile::Main profile = - AtProtocolType::LexiconsTypeUnknown::fromQVariant< - AtProtocolType::AppBskyActorProfile::Main>(record->value()); - setPinnedPost(profile.pinnedPost); - } - setRunning(false); - record->deleteLater(); - }); - record->setAccount(m_account); - if (!service_endpoint.isEmpty()) { - record->setService(service_endpoint); - setServiceEndpoint(service_endpoint); - } else { - // プロフィールを参照されるユーザーのサービスが参照する側と同じとは限らない(bsky.socialだったとしても) - setServiceEndpoint(QString()); - } - setRegistrationDate(registration_date); - QStringList history; - for (const auto &item : handle_history) { - history.append(item.date); - history.append(item.handle); - history.append(item.endpoint); - } - setHandleHistory(history); - record->profile(did()); - }); + getRawInformation(did(), + [=](const QString &service_endpoint, const QString ®istration_date, + const QList &handle_history) { + if (!service_endpoint.isEmpty()) { + setServiceEndpoint(service_endpoint); + } else { + // プロフィールを参照されるユーザーのサービスが参照する側と同じとは限らない(bsky.socialだったとしても) + setServiceEndpoint(QString()); + } + setRegistrationDate(registration_date); + QStringList history; + for (const auto &item : handle_history) { + history.append(item.date); + history.append(item.handle); + history.append(item.endpoint); + } + setHandleHistory(history); + setRunning(false); + }); } QString UserProfile::labelsTitle(const QString &label, const bool for_image, diff --git a/app/qtquick/timeline/authorfeedlistmodel.cpp b/app/qtquick/timeline/authorfeedlistmodel.cpp index d8c55951..fbcd8244 100644 --- a/app/qtquick/timeline/authorfeedlistmodel.cpp +++ b/app/qtquick/timeline/authorfeedlistmodel.cpp @@ -42,7 +42,7 @@ bool AuthorFeedListModel::getLatest() } timeline->setAccount(account()); timeline->setLabelers(labelerDids()); - timeline->getAuthorFeed(authorDid(), -1, QString(), filter_type); + timeline->getAuthorFeed(authorDid(), -1, QString(), filter_type, false); }); return true; } @@ -78,7 +78,7 @@ bool AuthorFeedListModel::getNext() } timeline->setAccount(account()); timeline->setLabelers(labelerDids()); - timeline->getAuthorFeed(authorDid(), -1, m_cursor, filter_type); + timeline->getAuthorFeed(authorDid(), -1, m_cursor, filter_type, false); }); return true; } diff --git a/app/qtquick/timeline/timelinelistmodel.cpp b/app/qtquick/timeline/timelinelistmodel.cpp index e2020a13..f29d4b13 100644 --- a/app/qtquick/timeline/timelinelistmodel.cpp +++ b/app/qtquick/timeline/timelinelistmodel.cpp @@ -181,6 +181,9 @@ QVariant TimelineListModel::item(int row, TimelineListModelRoles role) const else if (role == EmbedImagesAltRole) return LexiconsTypeUnknown::copyImagesFromPostView(current.post, LexiconsTypeUnknown::CopyImageType::Alt); + else if (role == EmbedImagesRatioRole) + return LexiconsTypeUnknown::copyImagesFromPostView( + current.post, LexiconsTypeUnknown::CopyImageType::Ratio); else if (role == IsRepostedRole) return current.post.viewer.repost.contains(account().did); @@ -189,6 +192,7 @@ QVariant TimelineListModel::item(int row, TimelineListModelRoles role) const else if (role == PinnedRole) return isPinnedPost(current.post.cid) && row == 0; else if (role == PinnedByMeRole) + // return current.post.viewer.pinned; return PinnedPostCache::getInstance()->pinned(account().did, current.post.uri); else if (role == ThreadMutedRole) return current.post.viewer.threadMuted; @@ -382,6 +386,7 @@ void TimelineListModel::update(int row, TimelineListModelRoles role, const QVari QVector() << role << IsLikedRole << LikeCountRole); } else if (role == PinnedByMeRole) { qDebug() << "update Pinned by me:" << value.toString(); + current.post.viewer.pinned = value.toBool(); emit dataChanged(index(row), index(row), QVector() << role << PinnedRole); } else if (role == ThreadMutedRole) { bool muted = value.toBool(); @@ -607,8 +612,10 @@ bool TimelineListModel::pin(int row) const AppBskyFeedDefs::FeedViewPost ¤t = m_viewPostHash.value(m_cidList.at(row)); QString pin_uri; + QString pin_cid; if (!item(row, PinnedByMeRole).toBool()) { pin_uri = current.post.uri; + pin_cid = current.post.cid; } if (runningPostPinning(row)) @@ -635,7 +642,7 @@ bool TimelineListModel::pin(int row) }); ope->setAccount(account().service, account().did, account().handle, account().email, account().accessJwt, account().refreshJwt); - ope->updatePostPinning(pin_uri); + ope->updatePostPinning(pin_uri, pin_cid); return true; } @@ -764,6 +771,7 @@ QHash TimelineListModel::roleNames() const roles[EmbedImagesRole] = "embedImages"; roles[EmbedImagesFullRole] = "embedImagesFull"; roles[EmbedImagesAltRole] = "embedImagesAlt"; + roles[EmbedImagesRatioRole] = "embedImagesRatio"; roles[IsRepostedRole] = "isReposted"; roles[IsLikedRole] = "isLiked"; diff --git a/app/qtquick/timeline/timelinelistmodel.h b/app/qtquick/timeline/timelinelistmodel.h index 1e09497f..78186bc6 100644 --- a/app/qtquick/timeline/timelinelistmodel.h +++ b/app/qtquick/timeline/timelinelistmodel.h @@ -60,6 +60,7 @@ class TimelineListModel : public AtpAbstractListModel EmbedImagesRole, EmbedImagesFullRole, EmbedImagesAltRole, + EmbedImagesRatioRole, IsRepostedRole, IsLikedRole, diff --git a/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.cpp b/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.cpp index 1627c761..d0732b42 100644 --- a/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.cpp +++ b/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.cpp @@ -10,7 +10,8 @@ AppBskyFeedGetAuthorFeed::AppBskyFeedGetAuthorFeed(QObject *parent) } void AppBskyFeedGetAuthorFeed::getAuthorFeed(const QString &actor, const int limit, - const QString &cursor, const QString &filter) + const QString &cursor, const QString &filter, + const bool includePins) { QUrlQuery url_query; if (!actor.isEmpty()) { @@ -25,6 +26,9 @@ void AppBskyFeedGetAuthorFeed::getAuthorFeed(const QString &actor, const int lim if (!filter.isEmpty()) { url_query.addQueryItem(QStringLiteral("filter"), filter); } + if (includePins) { + url_query.addQueryItem(QStringLiteral("includePins"), "true"); + } get(QStringLiteral("xrpc/app.bsky.feed.getAuthorFeed"), url_query); } diff --git a/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.h b/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.h index ef0e895b..353d43f5 100644 --- a/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.h +++ b/lib/atprotocol/app/bsky/feed/appbskyfeedgetauthorfeed.h @@ -11,7 +11,7 @@ class AppBskyFeedGetAuthorFeed : public AppBskyFeedGetTimeline explicit AppBskyFeedGetAuthorFeed(QObject *parent = nullptr); void getAuthorFeed(const QString &actor, const int limit, const QString &cursor, - const QString &filter); + const QString &filter, const bool includePins); }; } diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 581fed4f..08918ae5 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -179,6 +179,16 @@ struct Relationship }; } +// com.atproto.repo.strongRef +namespace ComAtprotoRepoStrongRef { +struct Main +{ + QString uri; // at-uri + QString cid; // cid +}; +// A URI with a content-hash fingerprint. +} + // app.bsky.actor.defs namespace AppBskyActorDefs { struct ProfileAssociatedChat @@ -250,6 +260,7 @@ struct ProfileViewDetailed QString createdAt; // datetime ViewerState viewer; QList labels; + ComAtprotoRepoStrongRef::Main pinnedPost; }; struct AdultContentPref { @@ -370,16 +381,6 @@ struct Preferences }; } -// com.atproto.repo.strongRef -namespace ComAtprotoRepoStrongRef { -struct Main -{ - QString uri; // at-uri - QString cid; // cid -}; -// A URI with a content-hash fingerprint. -} - // app.bsky.actor.profile namespace AppBskyActorProfile { enum class MainLabelsType : int { @@ -399,8 +400,8 @@ struct Main // application, on the overall account. // union end : labels ComAtprotoRepoStrongRef::Main joinedViaStarterPack; + ComAtprotoRepoStrongRef::Main pinnedPost; QString createdAt; // datetime - QString pinnedPost; // at-uri , (Unofficial field) }; } @@ -573,6 +574,7 @@ namespace AppBskyFeedDefs { enum class SkeletonFeedPostReasonType : int { none, reason_SkeletonReasonRepost, + reason_SkeletonReasonPin, }; enum class ThreadViewPostParentType : int { none, @@ -589,6 +591,7 @@ enum class ThreadViewPostRepliesType : int { enum class FeedViewPostReasonType : int { none, reason_ReasonRepost, + reason_ReasonPin, }; enum class ReplyRefRootType : int { none, @@ -642,6 +645,7 @@ struct ViewerState bool threadMuted = false; bool replyDisabled = false; bool embeddingDisabled = false; + bool pinned = false; }; struct ThreadgateView { @@ -705,6 +709,9 @@ struct ReasonRepost AppBskyActorDefs::ProfileViewBasic by; QString indexedAt; // datetime }; +struct ReasonPin +{ +}; struct FeedViewPost { PostView post; @@ -712,6 +719,7 @@ struct FeedViewPost // union start : reason FeedViewPostReasonType reason_type = FeedViewPostReasonType::none; ReasonRepost reason_ReasonRepost; + ReasonPin reason_ReasonPin; // union end : reason QString feedContext; // Context provided by feed generator that may be passed back alongside // interactions. @@ -736,12 +744,16 @@ struct SkeletonReasonRepost { QString repost; // at-uri }; +struct SkeletonReasonPin +{ +}; struct SkeletonFeedPost { QString post; // at-uri // union start : reason SkeletonFeedPostReasonType reason_type = SkeletonFeedPostReasonType::none; SkeletonReasonRepost reason_SkeletonReasonRepost; + SkeletonReasonPin reason_SkeletonReasonPin; // union end : reason QString feedContext; // Context that will be passed through to client and may be passed to feed // generator back alongside interactions. diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 77c45fa1..43ca6f22 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -112,6 +112,11 @@ void copyProfileViewDetailed(const QJsonObject &src, AppBskyActorDefs::ProfileVi ComAtprotoLabelDefs::copyLabel(s.toObject(), child); dest.labels.append(child); } + if (src.value("pinnedPost").isString()) { + dest.pinnedPost.uri = src.value("pinnedPost").toString(); + } else { + ComAtprotoRepoStrongRef::copyMain(src.value("pinnedPost").toObject(), dest.pinnedPost); + } } } void copyAdultContentPref(const QJsonObject &src, AppBskyActorDefs::AdultContentPref &dest) @@ -560,8 +565,8 @@ void copyMain(const QJsonObject &src, AppBskyActorProfile::Main &dest) } ComAtprotoRepoStrongRef::copyMain(src.value("joinedViaStarterPack").toObject(), dest.joinedViaStarterPack); + ComAtprotoRepoStrongRef::copyMain(src.value("pinnedPost").toObject(), dest.pinnedPost); dest.createdAt = src.value("createdAt").toString(); - dest.pinnedPost = src.value("pinnedPost").toString(); } } } @@ -929,6 +934,7 @@ void copyViewerState(const QJsonObject &src, AppBskyFeedDefs::ViewerState &dest) dest.threadMuted = src.value("threadMuted").toBool(); dest.replyDisabled = src.value("replyDisabled").toBool(); dest.embeddingDisabled = src.value("embeddingDisabled").toBool(); + dest.pinned = src.value("pinned").toBool(); } } void copyThreadgateView(const QJsonObject &src, AppBskyFeedDefs::ThreadgateView &dest) @@ -1053,6 +1059,10 @@ void copyReasonRepost(const QJsonObject &src, AppBskyFeedDefs::ReasonRepost &des dest.indexedAt = src.value("indexedAt").toString(); } } +void copyReasonPin(const QJsonObject &src, AppBskyFeedDefs::ReasonPin &dest) +{ + if (!src.isEmpty()) { } +} void copyFeedViewPost(const QJsonObject &src, AppBskyFeedDefs::FeedViewPost &dest) { if (!src.isEmpty()) { @@ -1064,6 +1074,10 @@ void copyFeedViewPost(const QJsonObject &src, AppBskyFeedDefs::FeedViewPost &des AppBskyFeedDefs::copyReasonRepost(src.value("reason").toObject(), dest.reason_ReasonRepost); } + if (reason_type == QStringLiteral("app.bsky.feed.defs#reasonPin")) { + dest.reason_type = AppBskyFeedDefs::FeedViewPostReasonType::reason_ReasonPin; + AppBskyFeedDefs::copyReasonPin(src.value("reason").toObject(), dest.reason_ReasonPin); + } dest.feedContext = src.value("feedContext").toString(); } } @@ -1126,6 +1140,10 @@ void copySkeletonReasonRepost(const QJsonObject &src, AppBskyFeedDefs::SkeletonR dest.repost = src.value("repost").toString(); } } +void copySkeletonReasonPin(const QJsonObject &src, AppBskyFeedDefs::SkeletonReasonPin &dest) +{ + if (!src.isEmpty()) { } +} void copySkeletonFeedPost(const QJsonObject &src, AppBskyFeedDefs::SkeletonFeedPost &dest) { if (!src.isEmpty()) { @@ -1137,6 +1155,12 @@ void copySkeletonFeedPost(const QJsonObject &src, AppBskyFeedDefs::SkeletonFeedP AppBskyFeedDefs::copySkeletonReasonRepost(src.value("reason").toObject(), dest.reason_SkeletonReasonRepost); } + if (reason_type == QStringLiteral("app.bsky.feed.defs#skeletonReasonPin")) { + dest.reason_type = + AppBskyFeedDefs::SkeletonFeedPostReasonType::reason_SkeletonReasonPin; + AppBskyFeedDefs::copySkeletonReasonPin(src.value("reason").toObject(), + dest.reason_SkeletonReasonPin); + } dest.feedContext = src.value("feedContext").toString(); } } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 1d82a7c6..5ba028dd 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -125,9 +125,11 @@ void copyNotFoundPost(const QJsonObject &src, AppBskyFeedDefs::NotFoundPost &des void copyBlockedPost(const QJsonObject &src, AppBskyFeedDefs::BlockedPost &dest); void copyReplyRef(const QJsonObject &src, AppBskyFeedDefs::ReplyRef &dest); void copyReasonRepost(const QJsonObject &src, AppBskyFeedDefs::ReasonRepost &dest); +void copyReasonPin(const QJsonObject &src, AppBskyFeedDefs::ReasonPin &dest); void copyFeedViewPost(const QJsonObject &src, AppBskyFeedDefs::FeedViewPost &dest); void copyThreadViewPost(const QJsonObject &src, AppBskyFeedDefs::ThreadViewPost &dest); void copySkeletonReasonRepost(const QJsonObject &src, AppBskyFeedDefs::SkeletonReasonRepost &dest); +void copySkeletonReasonPin(const QJsonObject &src, AppBskyFeedDefs::SkeletonReasonPin &dest); void copySkeletonFeedPost(const QJsonObject &src, AppBskyFeedDefs::SkeletonFeedPost &dest); void copyInteraction(const QJsonObject &src, AppBskyFeedDefs::Interaction &dest); } diff --git a/lib/atprotocol/lexicons_func_unknown.cpp b/lib/atprotocol/lexicons_func_unknown.cpp index 4df9a86f..0e290c1f 100644 --- a/lib/atprotocol/lexicons_func_unknown.cpp +++ b/lib/atprotocol/lexicons_func_unknown.cpp @@ -107,6 +107,14 @@ QStringList copyImagesFromPostView(const AppBskyFeedDefs::PostView &post, const images.append(image.fullsize); else if (type == CopyImageType::Alt) images.append(image.alt); + else if (type == CopyImageType::Ratio) { + if (image.aspectRatio.width == 0) { + images.append("1"); + } else { + images.append(QString::number(static_cast(image.aspectRatio.height) + / static_cast(image.aspectRatio.width))); + } + } } return images; } else if (post.embed_type @@ -123,6 +131,14 @@ QStringList copyImagesFromPostView(const AppBskyFeedDefs::PostView &post, const images.append(image.fullsize); else if (type == CopyImageType::Alt) images.append(image.alt); + else if (type == CopyImageType::Ratio) { + if (image.aspectRatio.width == 0) { + images.append("1"); + } else { + images.append(QString::number(static_cast(image.aspectRatio.height) + / static_cast(image.aspectRatio.width))); + } + } } return images; } else { diff --git a/lib/atprotocol/lexicons_func_unknown.h b/lib/atprotocol/lexicons_func_unknown.h index 4f0ef2dc..ddc73a57 100644 --- a/lib/atprotocol/lexicons_func_unknown.h +++ b/lib/atprotocol/lexicons_func_unknown.h @@ -53,6 +53,7 @@ enum class CopyImageType : int { Thumb, FullSize, Alt, + Ratio, }; QStringList copyImagesFromPostView(const AppBskyFeedDefs::PostView &post, const CopyImageType type); diff --git a/lib/extension/com/atproto/repo/comatprotorepoputrecordex.cpp b/lib/extension/com/atproto/repo/comatprotorepoputrecordex.cpp index a989b789..d42669eb 100644 --- a/lib/extension/com/atproto/repo/comatprotorepoputrecordex.cpp +++ b/lib/extension/com/atproto/repo/comatprotorepoputrecordex.cpp @@ -8,10 +8,10 @@ ComAtprotoRepoPutRecordEx::ComAtprotoRepoPutRecordEx(QObject *parent) { } -void ComAtprotoRepoPutRecordEx::profile(const AtProtocolType::Blob &avatar, - const AtProtocolType::Blob &banner, - const QString &description, const QString &display_name, - const QString &pinned_post_uri, const QString &cid) +void ComAtprotoRepoPutRecordEx::profile( + const AtProtocolType::Blob &avatar, const AtProtocolType::Blob &banner, + const QString &description, const QString &display_name, + const AtProtocolType::ComAtprotoRepoStrongRef::Main &pinned_post, const QString &cid) { QString type = QStringLiteral("app.bsky.actor.profile"); QJsonObject json_record; @@ -32,8 +32,11 @@ void ComAtprotoRepoPutRecordEx::profile(const AtProtocolType::Blob &avatar, setJsonBlob(banner, json_banner); json_record.insert("banner", json_banner); } - if (!pinned_post_uri.isEmpty()) { - json_record.insert("pinnedPost", pinned_post_uri); + if (!pinned_post.uri.isEmpty()) { + QJsonObject json_subject; + json_subject.insert("cid", pinned_post.cid); + json_subject.insert("uri", pinned_post.uri); + json_record.insert("pinnedPost", json_subject); } putRecord(this->did(), type, QStringLiteral("self"), true, json_record, cid, QString()); diff --git a/lib/extension/com/atproto/repo/comatprotorepoputrecordex.h b/lib/extension/com/atproto/repo/comatprotorepoputrecordex.h index 7b8ffc33..f1cc6f3d 100644 --- a/lib/extension/com/atproto/repo/comatprotorepoputrecordex.h +++ b/lib/extension/com/atproto/repo/comatprotorepoputrecordex.h @@ -12,7 +12,8 @@ class ComAtprotoRepoPutRecordEx : public ComAtprotoRepoPutRecord void profile(const AtProtocolType::Blob &avatar, const AtProtocolType::Blob &banner, const QString &description, const QString &display_name, - const QString &pinned_post_uri, const QString &cid); + const AtProtocolType::ComAtprotoRepoStrongRef::Main &pinned_post, + const QString &cid); void list(const AtProtocolType::Blob &avatar, const QString &purpose, const QString &description, const QString &name, const QString &rkey); void postGate(const QString &uri, diff --git a/scripts/lexicons/app.bsky.actor.profile.json b/scripts/lexicons/app.bsky.actor.profile.json deleted file mode 100644 index 79d5ce3e..00000000 --- a/scripts/lexicons/app.bsky.actor.profile.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "defs": { - "main": { - "record": { - "properties": { - "pinnedPost": { - "type": "string", - "description": "(Unofficial field)", - "format": "at-uri" - } - } - } - } - } -} diff --git a/tests/atprotocol_test/tst_atprotocol_test.cpp b/tests/atprotocol_test/tst_atprotocol_test.cpp index 3236127c..154e400f 100644 --- a/tests/atprotocol_test/tst_atprotocol_test.cpp +++ b/tests/atprotocol_test/tst_atprotocol_test.cpp @@ -1809,6 +1809,7 @@ void atprotocol_test::test_ComAtprotoRepoGetRecord_profile() void atprotocol_test::test_ComAtprotoRepoPutRecord_profile() { AtProtocolInterface::ComAtprotoRepoPutRecordEx record; + AtProtocolType::ComAtprotoRepoStrongRef::Main pinned_post; AtProtocolType::Blob avatar; AtProtocolType::Blob banner; @@ -1821,7 +1822,7 @@ void atprotocol_test::test_ComAtprotoRepoPutRecord_profile() avatar.size = 52880; { QSignalSpy spy(&record, SIGNAL(finished(bool))); - record.profile(avatar, banner, "description", "display name", QString(), + record.profile(avatar, banner, "description", "display name", pinned_post, "bafyreie3ckzfk5xadlunbotovrffkhsfb2hdnr7bujofy2bb5ro45elcmy"); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); @@ -1840,7 +1841,7 @@ void atprotocol_test::test_ComAtprotoRepoPutRecord_profile() banner.size = 51567; { QSignalSpy spy(&record, SIGNAL(finished(bool))); - record.profile(avatar, banner, "epub\nLeME", "IoriAYANE", QString(), + record.profile(avatar, banner, "epub\nLeME", "IoriAYANE", pinned_post, "bafyreif4chy7iugq3blmvqt6sgqeo72pxkkr4v4fnzjqii2yriijh545ei"); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); @@ -2089,7 +2090,7 @@ void atprotocol_test::test_AppBskyFeedGetAuthorFeed() { QSignalSpy spy(&api, SIGNAL(finished(bool))); - api.getAuthorFeed("", 0, QString(), QString()); + api.getAuthorFeed("", 0, QString(), QString(), false); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); diff --git a/tests/deps.pri b/tests/deps.pri index 5181738d..9ecc7532 100644 --- a/tests/deps.pri +++ b/tests/deps.pri @@ -12,3 +12,6 @@ else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../l else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/release/lib.lib else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/debug/lib.lib else:unix: PRE_TARGETDEPS += $$OUT_PWD/../../lib/liblib.a + +# openssl.priなどで追加した依存ファイルのコピーに必要 +win32:QMAKE_POST_LINK += nmake -f $(MAKEFILE) install diff --git a/tests/hagoromo_test2/data/profile/1/com.atproto.repo.putRecord b/tests/hagoromo_test2/data/profile/1/com.atproto.repo.putRecord index 01e70c9b..2c0c2bb8 100644 --- a/tests/hagoromo_test2/data/profile/1/com.atproto.repo.putRecord +++ b/tests/hagoromo_test2/data/profile/1/com.atproto.repo.putRecord @@ -22,7 +22,10 @@ }, "description": "description", "displayName": "display_name", - "pinnedPost": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n" + "pinnedPost": { + "uri": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "cid": "cid_hoge_before" + } }, "swapRecord": "bafyreif4chy7iugq3blmvqt6sgqeo72pxkkr4v4fnzjqii2yriijh545ei", "validate": true diff --git a/tests/hagoromo_test2/data/profile/3.1/com.atproto.repo.putRecord b/tests/hagoromo_test2/data/profile/3.1/com.atproto.repo.putRecord index cdbb5182..8bf392ba 100644 --- a/tests/hagoromo_test2/data/profile/3.1/com.atproto.repo.putRecord +++ b/tests/hagoromo_test2/data/profile/3.1/com.atproto.repo.putRecord @@ -22,7 +22,10 @@ }, "description": "epub\nLeME", "displayName": "IoriAYANE", - "pinnedPost": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n" + "pinnedPost": { + "uri": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "cid": "cid_after" + } }, "swapRecord": "bafyreif4chy7iugq3blmvqt6sgqeo72pxkkr4v4fnzjqii2yriijh545ei", "validate": true diff --git a/tests/hagoromo_test2/response/profile/1/xrpc/com.atproto.repo.getRecord b/tests/hagoromo_test2/response/profile/1/xrpc/com.atproto.repo.getRecord index 4d78a45c..e7ec9719 100644 --- a/tests/hagoromo_test2/response/profile/1/xrpc/com.atproto.repo.getRecord +++ b/tests/hagoromo_test2/response/profile/1/xrpc/com.atproto.repo.getRecord @@ -19,7 +19,10 @@ "mimeType": "image/jpeg", "size": 51567 }, - "pinnedPost": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "pinnedPost": { + "uri": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "cid": "cid_hoge_before" + }, "description": "epub\nLeME", "displayName": "IoriAYANE" } diff --git a/tests/hagoromo_test2/response/profile/3.1/xrpc/com.atproto.repo.getRecord b/tests/hagoromo_test2/response/profile/3.1/xrpc/com.atproto.repo.getRecord index 4d78a45c..d95453b2 100644 --- a/tests/hagoromo_test2/response/profile/3.1/xrpc/com.atproto.repo.getRecord +++ b/tests/hagoromo_test2/response/profile/3.1/xrpc/com.atproto.repo.getRecord @@ -19,7 +19,10 @@ "mimeType": "image/jpeg", "size": 51567 }, - "pinnedPost": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "pinnedPost": { + "uri": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "cid": "cid_before" + }, "description": "epub\nLeME", "displayName": "IoriAYANE" } diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index d89ef4cf..2d777f80 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -153,7 +153,8 @@ void hagoromo_test::test_RecordOperator_profile() { QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); ope.updatePostPinning( - "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n"); + "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k5ml2mujje2n", + "cid_after"); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); @@ -166,7 +167,7 @@ void hagoromo_test::test_RecordOperator_profile() QString(), "dummy", QString()); { QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); - ope.updatePostPinning(QString()); + ope.updatePostPinning(QString(), QString()); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index 20784039..1d7248d0 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -8,6 +8,14 @@ description: This is a multi-column Bluesky client. ## 2024 +### v0.38.0 - 2024/9/28 + +- Update + - Make the implementation of pinned posts officially compliant + - Change the flow when there are unauthenticated accounts +- Fix + - Fix the display of posts quoted from labeled posts + ### v0.37.0 - 2024/9/22 - Add diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index 7dd5a5bd..f1448318 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -8,6 +8,14 @@ description: マルチカラム対応Blueskyクライアント ## 2024 +### v0.38.0 - 2024/9/28 + +- 更新 + - 固定ポストを公式準拠に変更 + - 認証できないアカウントがあるときのフローを変更 +- 修正 + - ラベリングされたポストから引用されたポストの表示を修正 + ### v0.37.0 - 2024/9/22 - 追加 diff --git a/web/layouts/shortcodes/download_link.html b/web/layouts/shortcodes/download_link.html index 2fa5aefb..490e5959 100644 --- a/web/layouts/shortcodes/download_link.html +++ b/web/layouts/shortcodes/download_link.html @@ -1,9 +1,9 @@