Rework the password dialog
It's now used as a general purpose "account edit dialog". It'll be used for entering usernames, passwords, or both.
This commit is contained in:
parent
bfc9e1e5d5
commit
f3a9dde52e
@ -217,8 +217,8 @@ gui/dialogs/EditNotesDialog.h
|
|||||||
gui/dialogs/EditNotesDialog.cpp
|
gui/dialogs/EditNotesDialog.cpp
|
||||||
gui/dialogs/CustomMessageBox.h
|
gui/dialogs/CustomMessageBox.h
|
||||||
gui/dialogs/CustomMessageBox.cpp
|
gui/dialogs/CustomMessageBox.cpp
|
||||||
gui/dialogs/PasswordDialog.h
|
gui/dialogs/EditAccountDialog.h
|
||||||
gui/dialogs/PasswordDialog.cpp
|
gui/dialogs/EditAccountDialog.cpp
|
||||||
gui/dialogs/AccountListDialog.h
|
gui/dialogs/AccountListDialog.h
|
||||||
gui/dialogs/AccountListDialog.cpp
|
gui/dialogs/AccountListDialog.cpp
|
||||||
gui/dialogs/AccountSelectDialog.h
|
gui/dialogs/AccountSelectDialog.h
|
||||||
@ -370,7 +370,6 @@ gui/dialogs/SettingsDialog.ui
|
|||||||
gui/dialogs/CopyInstanceDialog.ui
|
gui/dialogs/CopyInstanceDialog.ui
|
||||||
gui/dialogs/NewInstanceDialog.ui
|
gui/dialogs/NewInstanceDialog.ui
|
||||||
gui/dialogs/LoginDialog.ui
|
gui/dialogs/LoginDialog.ui
|
||||||
gui/dialogs/PasswordDialog.ui
|
|
||||||
gui/dialogs/AboutDialog.ui
|
gui/dialogs/AboutDialog.ui
|
||||||
gui/dialogs/VersionSelectDialog.ui
|
gui/dialogs/VersionSelectDialog.ui
|
||||||
gui/dialogs/LwjglSelectDialog.ui
|
gui/dialogs/LwjglSelectDialog.ui
|
||||||
@ -382,6 +381,7 @@ gui/dialogs/OneSixModEditDialog.ui
|
|||||||
gui/dialogs/EditNotesDialog.ui
|
gui/dialogs/EditNotesDialog.ui
|
||||||
gui/dialogs/AccountListDialog.ui
|
gui/dialogs/AccountListDialog.ui
|
||||||
gui/dialogs/AccountSelectDialog.ui
|
gui/dialogs/AccountSelectDialog.ui
|
||||||
|
gui/dialogs/EditAccountDialog.ui
|
||||||
|
|
||||||
# Widgets/other
|
# Widgets/other
|
||||||
gui/widgets/MCModInfoFrame.ui
|
gui/widgets/MCModInfoFrame.ui
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
#include "gui/dialogs/CopyInstanceDialog.h"
|
#include "gui/dialogs/CopyInstanceDialog.h"
|
||||||
#include "gui/dialogs/AccountListDialog.h"
|
#include "gui/dialogs/AccountListDialog.h"
|
||||||
#include "gui/dialogs/AccountSelectDialog.h"
|
#include "gui/dialogs/AccountSelectDialog.h"
|
||||||
#include "gui/dialogs/PasswordDialog.h"
|
#include "gui/dialogs/EditAccountDialog.h"
|
||||||
|
|
||||||
#include "gui/ConsoleWindow.h"
|
#include "gui/ConsoleWindow.h"
|
||||||
|
|
||||||
@ -599,7 +599,6 @@ void MainWindow::on_actionSettings_triggered()
|
|||||||
void MainWindow::on_actionManageAccounts_triggered()
|
void MainWindow::on_actionManageAccounts_triggered()
|
||||||
{
|
{
|
||||||
AccountListDialog dialog(this);
|
AccountListDialog dialog(this);
|
||||||
connect(&dialog, SIGNAL(activeAccountChanged()), SLOT(activeAccountChanged()));
|
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,7 +811,7 @@ void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account)
|
|||||||
|
|
||||||
bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg)
|
bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg)
|
||||||
{
|
{
|
||||||
PasswordDialog passDialog(errorMsg, this);
|
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
|
||||||
if (passDialog.exec() == QDialog::Accepted)
|
if (passDialog.exec() == QDialog::Accepted)
|
||||||
{
|
{
|
||||||
// To refresh the token, we just create an authenticate task with the given account and the user's password.
|
// To refresh the token, we just create an authenticate task with the given account and the user's password.
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
#include <logic/auth/AuthenticateTask.h>
|
#include <logic/auth/AuthenticateTask.h>
|
||||||
#include <logic/net/NetJob.h>
|
#include <logic/net/NetJob.h>
|
||||||
|
|
||||||
#include <gui/dialogs/LoginDialog.h>
|
#include <gui/dialogs/EditAccountDialog.h>
|
||||||
#include <gui/dialogs/ProgressDialog.h>
|
#include <gui/dialogs/ProgressDialog.h>
|
||||||
#include <gui/dialogs/AccountSelectDialog.h>
|
#include <gui/dialogs/AccountSelectDialog.h>
|
||||||
|
|
||||||
@ -40,10 +40,12 @@ AccountListDialog::AccountListDialog(QWidget *parent) :
|
|||||||
ui->listView->setModel(m_accounts.get());
|
ui->listView->setModel(m_accounts.get());
|
||||||
|
|
||||||
QItemSelectionModel* selectionModel = ui->listView->selectionModel();
|
QItemSelectionModel* selectionModel = ui->listView->selectionModel();
|
||||||
|
|
||||||
connect(selectionModel, &QItemSelectionModel::selectionChanged,
|
connect(selectionModel, &QItemSelectionModel::selectionChanged,
|
||||||
[this] (const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); });
|
[this] (const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); });
|
||||||
connect(m_accounts.get(), &MojangAccountList::listChanged,
|
|
||||||
[this] () { updateButtonStates(); });
|
connect(m_accounts.get(), SIGNAL(listChanged), SLOT(listChanged));
|
||||||
|
connect(m_accounts.get(), SIGNAL(activeAccountChanged), SLOT(listChanged));
|
||||||
|
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
}
|
}
|
||||||
@ -53,10 +55,15 @@ AccountListDialog::~AccountListDialog()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountListDialog::listChanged()
|
||||||
|
{
|
||||||
|
updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AccountListDialog::on_addAccountBtn_clicked()
|
void AccountListDialog::on_addAccountBtn_clicked()
|
||||||
{
|
{
|
||||||
doLogin("Please log in to add your account.");
|
addAccount(tr("Please enter your Mojang or Minecraft account username and password to add your account."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::on_rmAccountBtn_clicked()
|
void AccountListDialog::on_rmAccountBtn_clicked()
|
||||||
@ -66,16 +73,9 @@ void AccountListDialog::on_rmAccountBtn_clicked()
|
|||||||
{
|
{
|
||||||
QModelIndex selected = selection.first();
|
QModelIndex selected = selection.first();
|
||||||
m_accounts->removeAccount(selected);
|
m_accounts->removeAccount(selected);
|
||||||
|
|
||||||
emit activeAccountChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::on_editAccountBtn_clicked()
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountListDialog::on_setDefaultBtn_clicked()
|
void AccountListDialog::on_setDefaultBtn_clicked()
|
||||||
{
|
{
|
||||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||||
@ -84,16 +84,12 @@ void AccountListDialog::on_setDefaultBtn_clicked()
|
|||||||
QModelIndex selected = selection.first();
|
QModelIndex selected = selection.first();
|
||||||
MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
|
MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
|
||||||
m_accounts->setActiveAccount(account->username());
|
m_accounts->setActiveAccount(account->username());
|
||||||
|
|
||||||
emit activeAccountChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::on_noDefaultBtn_clicked()
|
void AccountListDialog::on_noDefaultBtn_clicked()
|
||||||
{
|
{
|
||||||
m_accounts->setActiveAccount("");
|
m_accounts->setActiveAccount("");
|
||||||
|
|
||||||
emit activeAccountChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::on_closeBtnBox_rejected()
|
void AccountListDialog::on_closeBtnBox_rejected()
|
||||||
@ -107,58 +103,47 @@ void AccountListDialog::updateButtonStates()
|
|||||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||||
|
|
||||||
ui->rmAccountBtn->setEnabled(selection.size() > 0);
|
ui->rmAccountBtn->setEnabled(selection.size() > 0);
|
||||||
ui->editAccountBtn->setEnabled(selection.size() > 0);
|
|
||||||
ui->setDefaultBtn->setEnabled(selection.size() > 0);
|
ui->setDefaultBtn->setEnabled(selection.size() > 0);
|
||||||
|
|
||||||
ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
|
ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::doLogin(const QString& errMsg)
|
void AccountListDialog::addAccount(const QString& errMsg)
|
||||||
{
|
{
|
||||||
// TODO: We can use the login dialog for this for now, but we'll have to make something better for it eventually.
|
// TODO: We can use the login dialog for this for now, but we'll have to make something better for it eventually.
|
||||||
LoginDialog loginDialog(this);
|
EditAccountDialog loginDialog(errMsg, this, EditAccountDialog::UsernameField | EditAccountDialog::PasswordField);
|
||||||
loginDialog.exec();
|
loginDialog.exec();
|
||||||
|
|
||||||
if (loginDialog.result() == QDialog::Accepted)
|
if (loginDialog.result() == QDialog::Accepted)
|
||||||
{
|
{
|
||||||
QString username(loginDialog.getUsername());
|
QString username(loginDialog.username());
|
||||||
QString password(loginDialog.getPassword());
|
QString password(loginDialog.password());
|
||||||
|
|
||||||
MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username));
|
MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username));
|
||||||
|
|
||||||
ProgressDialog* progDialog = new ProgressDialog(this);
|
ProgressDialog progDialog(this);
|
||||||
m_authTask = new AuthenticateTask(account, password, progDialog);
|
AuthenticateTask authTask(account, password, &progDialog);
|
||||||
connect(m_authTask, SIGNAL(succeeded()), SLOT(onLoginComplete()), Qt::QueuedConnection);
|
if (progDialog.exec(&authTask))
|
||||||
connect(m_authTask, SIGNAL(failed(QString)), SLOT(doLogin(QString)), Qt::QueuedConnection);
|
{
|
||||||
progDialog->exec(m_authTask);
|
// Add the authenticated account to the accounts list.
|
||||||
//delete m_authTask;
|
MojangAccountPtr account = authTask.getMojangAccount();
|
||||||
|
m_accounts->addAccount(account);
|
||||||
|
|
||||||
|
// Grab associated player skins
|
||||||
|
auto job = new NetJob("Player skins: " + account->username());
|
||||||
|
|
||||||
|
for(AccountProfile profile : account->profiles())
|
||||||
|
{
|
||||||
|
auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png");
|
||||||
|
auto action = CacheDownload::make(
|
||||||
|
QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"),
|
||||||
|
meta);
|
||||||
|
job->addNetAction(action);
|
||||||
|
meta->stale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
job->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListDialog::onLoginComplete()
|
|
||||||
{
|
|
||||||
// Add the authenticated account to the accounts list.
|
|
||||||
MojangAccountPtr account = m_authTask->getMojangAccount();
|
|
||||||
m_accounts->addAccount(account);
|
|
||||||
|
|
||||||
emit activeAccountChanged();
|
|
||||||
|
|
||||||
//ui->listView->update();
|
|
||||||
|
|
||||||
// Grab associated player skins
|
|
||||||
auto job = new NetJob("Player skins: " + account->username());
|
|
||||||
|
|
||||||
for(AccountProfile profile : account->profiles())
|
|
||||||
{
|
|
||||||
auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png");
|
|
||||||
auto action = CacheDownload::make(
|
|
||||||
QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"),
|
|
||||||
meta);
|
|
||||||
job->addNetAction(action);
|
|
||||||
meta->stale = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(job, SIGNAL(succeeded()), SIGNAL(activeAccountChanged()));
|
|
||||||
job->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -40,8 +40,6 @@ slots:
|
|||||||
|
|
||||||
void on_rmAccountBtn_clicked();
|
void on_rmAccountBtn_clicked();
|
||||||
|
|
||||||
void on_editAccountBtn_clicked();
|
|
||||||
|
|
||||||
void on_setDefaultBtn_clicked();
|
void on_setDefaultBtn_clicked();
|
||||||
|
|
||||||
void on_noDefaultBtn_clicked();
|
void on_noDefaultBtn_clicked();
|
||||||
@ -49,21 +47,17 @@ slots:
|
|||||||
// This will be sent when the "close" button is clicked.
|
// This will be sent when the "close" button is clicked.
|
||||||
void on_closeBtnBox_rejected();
|
void on_closeBtnBox_rejected();
|
||||||
|
|
||||||
|
void listChanged();
|
||||||
|
|
||||||
//! Updates the states of the dialog's buttons.
|
//! Updates the states of the dialog's buttons.
|
||||||
void updateButtonStates();
|
void updateButtonStates();
|
||||||
|
|
||||||
signals:
|
|
||||||
void activeAccountChanged();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<MojangAccountList> m_accounts;
|
std::shared_ptr<MojangAccountList> m_accounts;
|
||||||
|
|
||||||
AuthenticateTask* m_authTask;
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
slots:
|
slots:
|
||||||
void doLogin(const QString& errMsg="");
|
void addAccount(const QString& errMsg="");
|
||||||
void onLoginComplete();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::AccountListDialog *ui;
|
Ui::AccountListDialog *ui;
|
||||||
|
@ -38,13 +38,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="editAccountBtn">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Edit</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="rmAccountBtn">
|
<widget class="QPushButton" name="rmAccountBtn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -13,25 +13,33 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PasswordDialog.h"
|
#include "EditAccountDialog.h"
|
||||||
#include "ui_PasswordDialog.h"
|
#include "ui_EditAccountDialog.h"
|
||||||
|
|
||||||
PasswordDialog::PasswordDialog(const QString& errorMsg, QWidget *parent) :
|
EditAccountDialog::EditAccountDialog(const QString& text, QWidget *parent, int flags) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
ui(new Ui::PasswordDialog)
|
ui(new Ui::EditAccountDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
ui->errorLabel->setText(errorMsg);
|
ui->label->setText(text);
|
||||||
ui->errorLabel->setVisible(!errorMsg.isEmpty());
|
ui->label->setVisible(!text.isEmpty());
|
||||||
|
|
||||||
|
ui->userTextBox->setVisible(flags & UsernameField);
|
||||||
|
ui->passTextBox->setVisible(flags & PasswordField);
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordDialog::~PasswordDialog()
|
EditAccountDialog::~EditAccountDialog()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PasswordDialog::password() const
|
QString EditAccountDialog::username() const
|
||||||
|
{
|
||||||
|
return ui->userTextBox->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EditAccountDialog::password() const
|
||||||
{
|
{
|
||||||
return ui->passTextBox->text();
|
return ui->passTextBox->text();
|
||||||
}
|
}
|
@ -18,23 +18,39 @@
|
|||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class PasswordDialog;
|
class EditAccountDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordDialog : public QDialog
|
class EditAccountDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PasswordDialog(const QString& errorMsg="", QWidget *parent = 0);
|
explicit EditAccountDialog(const QString& text="", QWidget *parent = 0, int flags=UsernameField | PasswordField);
|
||||||
~PasswordDialog();
|
~EditAccountDialog();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Gets the text entered in the dialog's username field.
|
||||||
|
*/
|
||||||
|
QString username() const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Gets the text entered in the dialog's password field.
|
* Gets the text entered in the dialog's password field.
|
||||||
*/
|
*/
|
||||||
QString password() const;
|
QString password() const;
|
||||||
|
|
||||||
|
enum Flags
|
||||||
|
{
|
||||||
|
NoFlags=0,
|
||||||
|
|
||||||
|
//! Specifies that the dialog should have a username field.
|
||||||
|
UsernameField,
|
||||||
|
|
||||||
|
//! Specifies that the dialog should have a password field.
|
||||||
|
PasswordField,
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::PasswordDialog *ui;
|
Ui::EditAccountDialog *ui;
|
||||||
};
|
};
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>PasswordDialog</class>
|
<class>EditAccountDialog</class>
|
||||||
<widget class="QDialog" name="PasswordDialog">
|
<widget class="QDialog" name="EditAccountDialog">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>400</width>
|
||||||
<height>94</height>
|
<height>128</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -15,9 +15,16 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="errorLabel">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Error message here...</string>
|
<string>Message label placeholder.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="userTextBox">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Email / Username</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -26,6 +33,9 @@
|
|||||||
<property name="echoMode">
|
<property name="echoMode">
|
||||||
<enum>QLineEdit::Password</enum>
|
<enum>QLineEdit::Password</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Password</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -45,7 +55,7 @@
|
|||||||
<connection>
|
<connection>
|
||||||
<sender>buttonBox</sender>
|
<sender>buttonBox</sender>
|
||||||
<signal>accepted()</signal>
|
<signal>accepted()</signal>
|
||||||
<receiver>PasswordDialog</receiver>
|
<receiver>EditAccountDialog</receiver>
|
||||||
<slot>accept()</slot>
|
<slot>accept()</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel">
|
<hint type="sourcelabel">
|
||||||
@ -61,7 +71,7 @@
|
|||||||
<connection>
|
<connection>
|
||||||
<sender>buttonBox</sender>
|
<sender>buttonBox</sender>
|
||||||
<signal>rejected()</signal>
|
<signal>rejected()</signal>
|
||||||
<receiver>PasswordDialog</receiver>
|
<receiver>EditAccountDialog</receiver>
|
||||||
<slot>reject()</slot>
|
<slot>reject()</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel">
|
<hint type="sourcelabel">
|
Loading…
Reference in New Issue
Block a user