mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-04-21 21:52:03 +02:00
swarmdetailspanel: show kicked contacts if administrator
This patch avoid for non-admins to try to re-add kicked members as filtered out from the list. However kicked members are only visible for administrators. Change-Id: Ie01b7071c072d147bbc0f39e477cc24d7fd58b1a
This commit is contained in:
parent
06ab19f213
commit
3570b23d8a
16 changed files with 216 additions and 44 deletions
|
@ -215,6 +215,7 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/wizardviewstepmodel.cpp
|
||||
${APP_SRC_DIR}/avatarregistry.cpp
|
||||
${APP_SRC_DIR}/currentconversation.cpp
|
||||
${APP_SRC_DIR}/currentconversationmembers.cpp
|
||||
${APP_SRC_DIR}/currentaccount.cpp
|
||||
${APP_SRC_DIR}/videodevices.cpp
|
||||
${APP_SRC_DIR}/videoprovider.cpp
|
||||
|
@ -275,6 +276,7 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/wizardviewstepmodel.h
|
||||
${APP_SRC_DIR}/avatarregistry.h
|
||||
${APP_SRC_DIR}/currentconversation.h
|
||||
${APP_SRC_DIR}/currentconversationmembers.h
|
||||
${APP_SRC_DIR}/currentaccount.h
|
||||
${APP_SRC_DIR}/videodevices.h
|
||||
${APP_SRC_DIR}/videoprovider.h
|
||||
|
|
2
daemon
2
daemon
|
@ -1 +1 @@
|
|||
Subproject commit 47f3fd14ab0532ff94fd5317f885689928d9293b
|
||||
Subproject commit 3481da56c29e378bf3003d067c06e409d5c52c32
|
|
@ -828,6 +828,7 @@ Item {
|
|||
property string goToConversation: qsTr("Go to conversation")
|
||||
property string promoteAdministrator: qsTr("Promote to administrator")
|
||||
property string kickMember: qsTr("Kick member")
|
||||
property string reinstateMember: qsTr("Reinstate member")
|
||||
property string administrator: qsTr("Administrator")
|
||||
property string invited: qsTr("Invited")
|
||||
property string removeMember: qsTr("Remove member")
|
||||
|
|
|
@ -24,6 +24,7 @@ CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* pare
|
|||
: QObject(parent)
|
||||
, lrcInstance_(lrcInstance)
|
||||
{
|
||||
uris_ = new CurrentConversationMembers(lrcInstance, this);
|
||||
// whenever the account changes, reconnect the new conversation model
|
||||
// for updates to the conversation and call state/id
|
||||
connect(lrcInstance_,
|
||||
|
@ -59,7 +60,23 @@ CurrentConversation::updateData()
|
|||
if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
set_lastSelfMessageId(convInfo.lastSelfMessageId);
|
||||
set_uris(convInfo.participantsUris());
|
||||
QStringList uris, bannedUris;
|
||||
auto isAdmin = false;
|
||||
for (const auto& p : convInfo.participants) {
|
||||
if (p.uri == accInfo.profileInfo.uri) {
|
||||
isAdmin = p.role == member::Role::ADMIN;
|
||||
}
|
||||
if (p.role == member::Role::BANNED) {
|
||||
bannedUris.push_back(p.uri);
|
||||
} else {
|
||||
uris.push_back(p.uri);
|
||||
}
|
||||
}
|
||||
if (isAdmin) {
|
||||
for (const auto& banned : bannedUris)
|
||||
uris.push_back(banned);
|
||||
}
|
||||
uris_->setMembers(accountId, convId, uris);
|
||||
set_isSwarm(convInfo.isSwarm());
|
||||
set_isLegacy(convInfo.isLegacy());
|
||||
set_isCoreDialog(convInfo.isCoreDialog());
|
||||
|
@ -170,6 +187,12 @@ CurrentConversation::setInfo(const QString& key, const QString& value)
|
|||
accInfo.conversationModel->updateConversationInfos(convId, infos);
|
||||
}
|
||||
|
||||
CurrentConversationMembers*
|
||||
CurrentConversation::uris() const
|
||||
{
|
||||
return uris_;
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::onConversationUpdated(const QString& convId)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "lrcinstance.h"
|
||||
#include "currentconversationmembers.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
@ -32,7 +33,6 @@ class CurrentConversation final : public QObject
|
|||
QML_PROPERTY(QString, id)
|
||||
QML_PROPERTY(QString, title)
|
||||
QML_PROPERTY(QString, description)
|
||||
QML_PROPERTY(QStringList, uris)
|
||||
QML_PROPERTY(bool, isSwarm)
|
||||
QML_PROPERTY(bool, isLegacy)
|
||||
QML_PROPERTY(bool, isCoreDialog)
|
||||
|
@ -66,6 +66,7 @@ public:
|
|||
Q_INVOKABLE QString getPreference(const QString& key) const;
|
||||
Q_INVOKABLE MapStringString getPreferences() const;
|
||||
Q_INVOKABLE void setInfo(const QString& key, const QString& value);
|
||||
CurrentConversationMembers* uris() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void scrollTo(const QString& msgId);
|
||||
|
@ -87,6 +88,7 @@ Q_SIGNALS:
|
|||
|
||||
private:
|
||||
LRCInstance* lrcInstance_;
|
||||
CurrentConversationMembers* uris_;
|
||||
|
||||
void connectModel();
|
||||
};
|
||||
|
|
76
src/app/currentconversationmembers.cpp
Normal file
76
src/app/currentconversationmembers.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "currentconversationmembers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
CurrentConversationMembers::CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, lrcInstance_(lrcInstance)
|
||||
{}
|
||||
|
||||
int
|
||||
CurrentConversationMembers::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return members_.size();
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversationMembers::setMembers(const QString& accountId,
|
||||
const QString& convId,
|
||||
const QStringList& members)
|
||||
{
|
||||
beginResetModel();
|
||||
accountId_ = accountId;
|
||||
convId_ = convId;
|
||||
members_ = members;
|
||||
set_count(members.size());
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant
|
||||
CurrentConversationMembers::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
auto member = members_.at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Members::Role::MemberUri:
|
||||
return QVariant::fromValue(member);
|
||||
case Members::Role::MemberRole:
|
||||
return QVariant::fromValue(
|
||||
lrcInstance_->getAccountInfo(accountId_).conversationModel->memberRole(convId_, member));
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
CurrentConversationMembers::roleNames() const
|
||||
{
|
||||
using namespace Members;
|
||||
QHash<int, QByteArray> roles;
|
||||
#define X(role) roles[role] = #role;
|
||||
MEMBERS_ROLES
|
||||
#undef X
|
||||
return roles;
|
||||
}
|
59
src/app/currentconversationmembers.h
Normal file
59
src/app/currentconversationmembers.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "lrcinstance.h"
|
||||
#include "appsettingsmanager.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
|
||||
#define MEMBERS_ROLES \
|
||||
X(MemberUri) \
|
||||
X(MemberRole)
|
||||
|
||||
namespace Members {
|
||||
Q_NAMESPACE
|
||||
enum Role {
|
||||
DummyRole = Qt::UserRole + 1,
|
||||
#define X(role) role,
|
||||
MEMBERS_ROLES
|
||||
#undef X
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace Members
|
||||
|
||||
class CurrentConversationMembers : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_PROPERTY(int, count)
|
||||
|
||||
public:
|
||||
explicit CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
||||
void setMembers(const QString& accountId, const QString& convId, const QStringList& members);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
LRCInstance* lrcInstance_;
|
||||
QString accountId_;
|
||||
QString convId_;
|
||||
QStringList members_;
|
||||
};
|
|
@ -63,10 +63,9 @@ Rectangle {
|
|||
model: ContactAdapter.getContactSelectableModel(type)
|
||||
|
||||
Connections {
|
||||
enabled: visible
|
||||
target: CurrentConversation
|
||||
target: CurrentConversationMembers
|
||||
|
||||
function onUrisChanged(uris) {
|
||||
function onCountChanged() {
|
||||
contactPickerListView.model = ContactAdapter.getContactSelectableModel(type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,13 +157,6 @@ Rectangle {
|
|||
Connections {
|
||||
target: CurrentConversation
|
||||
|
||||
function onUrisChanged(uris) {
|
||||
if (CurrentConversation.uris.length >= 8 && addMemberPanel.visible) {
|
||||
swarmDetailsPanel.visible = false
|
||||
addMemberPanel.visible = !addMemberPanel.visible
|
||||
}
|
||||
}
|
||||
|
||||
function onNeedsHost() {
|
||||
viewCoordinator.presentDialog(
|
||||
appWindow,
|
||||
|
@ -171,6 +164,17 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CurrentConversationMembers
|
||||
|
||||
function onCountChanged() {
|
||||
if (CurrentConversationMembers.count >= 8 && addMemberPanel.visible) {
|
||||
swarmDetailsPanel.visible = false
|
||||
addMemberPanel.visible = !addMemberPanel.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAddToConversationClicked: {
|
||||
swarmDetailsPanel.visible = false
|
||||
if (addMemberPanel.visible) {
|
||||
|
|
|
@ -208,7 +208,7 @@ Rectangle {
|
|||
normalColor: JamiTheme.chatviewBgColor
|
||||
imageColor: JamiTheme.chatviewButtonColor
|
||||
|
||||
visible: CurrentConversation.uris.length < 8 && addMemberVisibility
|
||||
visible: CurrentConversationMembers.count < 8 && addMemberVisibility
|
||||
|
||||
onClicked: addToConversationClicked()
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ Popup {
|
|||
if (isCall) {
|
||||
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
|
||||
} else {
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
|
||||
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ Popup {
|
|||
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
|
||||
} else {
|
||||
var accountId = LRCInstance.currentAccountId
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
|
||||
PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded)
|
||||
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ Popup {
|
|||
if (isCall) {
|
||||
return PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
|
||||
} else {
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
|
||||
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
|
||||
return PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,7 +238,7 @@ Rectangle {
|
|||
|
||||
down: tabBar.currentIndex === 1
|
||||
labelText: {
|
||||
var membersNb = CurrentConversation.uris.length;
|
||||
var membersNb = CurrentConversationMembers.count;
|
||||
if (membersNb > 1)
|
||||
return JamiStrings.members.arg(membersNb)
|
||||
return JamiStrings.member
|
||||
|
@ -605,7 +605,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
model: CurrentConversation.uris
|
||||
model: CurrentConversationMembers
|
||||
delegate: ItemDelegate {
|
||||
id: member
|
||||
|
||||
|
@ -626,11 +626,11 @@ Rectangle {
|
|||
id: memberMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: modelData !== CurrentAccount.uri
|
||||
enabled: MemberUri !== CurrentAccount.uri
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: function (mouse) {
|
||||
var position = mapToItem(members, mouse.x, mouse.y)
|
||||
contextMenu.openMenuAt(position.x, position.y, modelData)
|
||||
contextMenu.openMenuAt(position.x, position.y, MemberUri)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,19 +640,17 @@ Rectangle {
|
|||
anchors.rightMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
width: JamiTheme.smartListAvatarSize
|
||||
height: JamiTheme.smartListAvatarSize
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
z: -index
|
||||
opacity: {
|
||||
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
|
||||
return role === Member.Role.INVITED ? 0.5 : 1
|
||||
}
|
||||
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
|
||||
|
||||
imageId: CurrentAccount.uri == modelData ? CurrentAccount.id : modelData
|
||||
showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, modelData)
|
||||
mode: CurrentAccount.uri == modelData ? Avatar.Mode.Account : Avatar.Mode.Contact
|
||||
imageId: CurrentAccount.uri == MemberUri ? CurrentAccount.id : MemberUri
|
||||
showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, MemberUri)
|
||||
mode: CurrentAccount.uri == MemberUri ? Avatar.Mode.Account : Avatar.Mode.Contact
|
||||
}
|
||||
|
||||
ElidedTextLabel {
|
||||
|
@ -662,16 +660,12 @@ Rectangle {
|
|||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
eText: UtilsAdapter.getContactBestName(CurrentAccount.id, modelData)
|
||||
eText: UtilsAdapter.getContactBestName(CurrentAccount.id, MemberUri)
|
||||
maxWidth: width
|
||||
|
||||
font.pointSize: JamiTheme.participantFontSize
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
opacity: {
|
||||
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
|
||||
return role === Member.Role.INVITED ? 0.5 : 1
|
||||
}
|
||||
|
||||
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
|
||||
font.kerning: true
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
@ -682,27 +676,25 @@ Rectangle {
|
|||
}
|
||||
|
||||
ElidedTextLabel {
|
||||
id: role
|
||||
id: roleLabel
|
||||
|
||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
|
||||
eText: {
|
||||
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
|
||||
if (role === Member.Role.ADMIN)
|
||||
if (MemberRole === Member.Role.ADMIN)
|
||||
return JamiStrings.administrator
|
||||
if (role === Member.Role.INVITED)
|
||||
if (MemberRole === Member.Role.INVITED)
|
||||
return JamiStrings.invited
|
||||
if (MemberRole === Member.Role.BANNED)
|
||||
return JamiStrings.banned
|
||||
return ""
|
||||
}
|
||||
maxWidth: JamiTheme.preferredFieldWidth
|
||||
|
||||
font.pointSize: JamiTheme.participantFontSize
|
||||
color: JamiTheme.textColorHovered
|
||||
opacity: {
|
||||
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
|
||||
return role === Member.Role.INVITED ? 0.5 : 1
|
||||
}
|
||||
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
|
||||
font.kerning: true
|
||||
|
||||
horizontalAlignment: Text.AlignRight
|
||||
|
|
|
@ -74,12 +74,17 @@ ContextMenuAutoLoader {
|
|||
},
|
||||
GeneralMenuItem {
|
||||
id: kickMember
|
||||
itemName: JamiStrings.kickMember
|
||||
property var memberRole: UtilsAdapter.getParticipantRole(CurrentAccount.id, conversationId, participantUri)
|
||||
itemName: memberRole === Member.Role.BANNED ? JamiStrings.reinstateMember : JamiStrings.kickMember
|
||||
iconSource: JamiResources.kick_member_svg
|
||||
canTrigger: role === Member.Role.ADMIN
|
||||
|
||||
onClicked: {
|
||||
MessagesAdapter.removeConversationMember(conversationId, participantUri)
|
||||
if (memberRole === Member.Role.BANNED) {
|
||||
MessagesAdapter.addConversationMember(conversationId, participantUri)
|
||||
} else {
|
||||
MessagesAdapter.removeConversationMember(conversationId, participantUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -516,6 +516,13 @@ MessagesAdapter::removeConversationMember(const QString& convUid, const QString&
|
|||
accInfo.conversationModel->removeConversationMember(convUid, memberUri);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::addConversationMember(const QString& convUid, const QString& memberUri)
|
||||
{
|
||||
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
|
||||
accInfo.conversationModel->addConversationMember(convUid, memberUri);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::removeContact(const QString& convUid, bool banContact)
|
||||
{
|
||||
|
|
|
@ -85,6 +85,7 @@ protected:
|
|||
Q_INVOKABLE void connectConversationModel();
|
||||
Q_INVOKABLE void sendConversationRequest();
|
||||
Q_INVOKABLE void removeConversation(const QString& convUid);
|
||||
Q_INVOKABLE void addConversationMember(const QString& convUid, const QString& participantUri);
|
||||
Q_INVOKABLE void removeConversationMember(const QString& convUid, const QString& participantUri);
|
||||
Q_INVOKABLE void removeContact(const QString& convUid, bool banContact = false);
|
||||
Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid);
|
||||
|
|
|
@ -141,6 +141,7 @@ registerTypes(QQmlEngine* engine,
|
|||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentCall, "CurrentCall");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation, "CurrentConversation");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation->uris(), "CurrentConversationMembers");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccount, "CurrentAccount");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, videoDevices, "VideoDevices");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccountToMigrate, "CurrentAccountToMigrate")
|
||||
|
|
Loading…
Add table
Reference in a new issue