NOISSUE some more fixes for accounts dialog

- Dialog size is now saved and restored
- Add New Account should appear correctly on first run of MultiMC
- Added UI for accounts that don't own Minecraft
- Added UI for accounts with expired MSA tokens
- After applying a skin, the skin preview updates correctly
- The initial size of the dialog is now smaller
- Changed 'Refresh' buttons to 'Refresh Account' to clarify what they do
This commit is contained in:
Petr Mrázek 2025-01-15 02:17:06 +01:00
parent 538c943f10
commit 9e4da6399c
9 changed files with 393 additions and 69 deletions

View File

@ -773,6 +773,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("UpdateDialogGeometry", "");
m_settings->registerSetting("AccountsDialogGeometry", "");
m_settings->registerSetting("AccountsDialogSplitterState", "");
// paste.ee API key
m_settings->registerSetting("PasteEEAPIKey", "multimc");

View File

@ -245,19 +245,15 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
return false;
}
auto typeS = typeV.toString();
if(typeS == "MSA") {
type = AccountType::MSA;
} else {
if(typeS != "MSA") {
qWarning() << "Failed to parse account data: type is not recognized.";
return false;
}
if(type == AccountType::MSA) {
msaToken = tokenFromJSONV3(data, "msa");
userToken = tokenFromJSONV3(data, "utoken");
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
mojangservicesToken = tokenFromJSONV3(data, "xrp-mc");
}
msaToken = tokenFromJSONV3(data, "msa");
userToken = tokenFromJSONV3(data, "utoken");
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
mojangservicesToken = tokenFromJSONV3(data, "xrp-mc");
yggdrasilToken = tokenFromJSONV3(data, "ygg");
minecraftProfile = profileFromJSONV3(data, "profile");
@ -275,13 +271,11 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
QJsonObject AccountData::saveState() const {
QJsonObject output;
if (type == AccountType::MSA) {
output["type"] = "MSA";
tokenToJSONV3(output, msaToken, "msa");
tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main");
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
}
output["type"] = "MSA";
tokenToJSONV3(output, msaToken, "msa");
tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main");
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
tokenToJSONV3(output, yggdrasilToken, "ygg");
profileToJSONV3(output, minecraftProfile, "profile");

View File

@ -36,10 +36,6 @@ struct MinecraftProfile {
Katabasis::Validity validity = Katabasis::Validity::None;
};
enum class AccountType {
MSA
};
enum class AccountState {
Unchecked,
Offline,
@ -68,11 +64,6 @@ struct AccountData {
QString lastError() const;
AccountType type = AccountType::MSA;
bool legacy = false;
bool canMigrateToMSA = false;
bool mustMigrateToMSA = false;
Katabasis::Token msaToken;
Katabasis::Token userToken;
Katabasis::Token xboxApiToken;

View File

@ -451,6 +451,8 @@ bool AccountList::loadList()
return false;
}
m_accounts.append(Entry{false, nullptr});
QFile file(m_listFilePath);
// Try to open the file and fail if we can't.
@ -505,7 +507,6 @@ bool AccountList::loadList()
bool AccountList::loadV3(QJsonObject& root) {
beginResetModel();
m_accounts.append(Entry{false, nullptr});
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{

View File

@ -48,7 +48,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
{
MinecraftAccountPtr account(new MinecraftAccount());
account->data.type = AccountType::MSA;
return account;
}

View File

@ -116,10 +116,6 @@ public: /* queries */
bool isActive() const;
bool canMigrate() const {
return data.canMigrateToMSA;
}
bool ownsMinecraft() const {
return data.minecraftEntitlement.ownsMinecraft;
}
@ -129,15 +125,7 @@ public: /* queries */
}
QString typeString() const {
switch(data.type) {
case AccountType::MSA: {
return "msa";
}
break;
default: {
return "unknown";
}
}
return "msa";
}
QPixmap getFace() const;

View File

@ -59,10 +59,19 @@ AccountsDialog::AccountsDialog(QWidget *parent, const QString& internalId) : QDi
connect(ui->saveSkinButton, &QPushButton::clicked, this, &AccountsDialog::onSaveSkinClicked);
connect(ui->openSkinsButton, &QPushButton::clicked, this, &AccountsDialog::onOpenSkinsFolderClicked);
connect(ui->refreshButton, &QPushButton::clicked, this, &AccountsDialog::onRefreshButtonClicked);
connect(ui->signOutButton, &QPushButton::clicked, this, &AccountsDialog::onSignOutButtonClicked);
connect(ui->refreshButton_Setup, &QPushButton::clicked, this, &AccountsDialog::onRefreshButtonClicked);
connect(ui->signOutButton_Setup, &QPushButton::clicked, this, &AccountsDialog::onSignOutButtonClicked);
connect(ui->signOutButton_Demo, &QPushButton::clicked, this, &AccountsDialog::onSignOutButtonClicked);
connect(ui->signOutButton_Expired, &QPushButton::clicked, this, &AccountsDialog::onSignOutButtonClicked);
connect(ui->removeAndSignInButton, &QPushButton::clicked, this, &AccountsDialog::onRemoveAndSignInButtonClicked);
connect(ui->getMinecraftButton, &QPushButton::clicked, this, &AccountsDialog::onGetMinecraftButtonClicked);
connect(ui->refreshButton, &QPushButton::clicked, this, &AccountsDialog::onRefreshButtonClicked);
connect(ui->refreshButton_Setup, &QPushButton::clicked, this, &AccountsDialog::onRefreshButtonClicked);
connect(ui->refreshButton_Demo, &QPushButton::clicked, this, &AccountsDialog::onRefreshButtonClicked);
connect(ui->getFreshCodeButton, &QPushButton::clicked, this, &AccountsDialog::onGetFreshCodeButtonClicked);
QItemSelectionModel *selectionModel = ui->accountListView->selectionModel();
@ -128,6 +137,9 @@ AccountsDialog::AccountsDialog(QWidget *parent, const QString& internalId) : QDi
connect(ui->createProfileButton, &QCommandLinkButton::clicked, this, &AccountsDialog::onCreateProfileButtonClicked);
setNameStatus(NameStatus::NotSet, QString());
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("AccountsDialogGeometry").toByteArray()));
ui->splitter->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("AccountsDialogSplitterState").toByteArray()));
}
AccountsDialog::~AccountsDialog()
@ -135,6 +147,14 @@ AccountsDialog::~AccountsDialog()
delete ui;
}
void AccountsDialog::closeEvent(QCloseEvent* event)
{
APPLICATION->settings()->set("AccountsDialogSplitterState", ui->splitter->saveState().toBase64());
APPLICATION->settings()->set("AccountsDialogGeometry", saveGeometry().toBase64());
QDialog::closeEvent(event);
}
void AccountsDialog::onRevertChangesClicked(bool)
{
revertEdits();
@ -363,6 +383,32 @@ void AccountsDialog::updateStates()
return;
}
if(m_currentAccount->accountState() == AccountState::Expired)
{
ui->accountPageStack->setCurrentWidget(ui->expiredPage);
if(m_currentAccount->hasProfile())
{
ui->selectedAccountLabel_Expired->setText(m_currentAccount->profileName());
ui->selectedAccountIconLabel_Expired->setIcon(m_currentAccount->getFace());
}
else
{
ui->selectedAccountLabel_Expired->setText(m_currentAccount->gamerTag());
ui->selectedAccountIconLabel_Expired->setIcon(APPLICATION->getThemedIcon("noaccount"));
}
return;
}
// Demo account (no Minecraft entitlement)
if(!m_currentAccount->ownsMinecraft())
{
ui->accountPageStack->setCurrentWidget(ui->demoPage);
ui->setupProfilePage->setEnabled(accountIsReady);
ui->selectedAccountLabel_Demo->setText(m_currentAccount->gamerTag());
ui->selectedAccountIconLabel_Demo->setIcon(APPLICATION->getThemedIcon("noaccount"));
return;
}
// Profile setup page
if(!m_currentAccount->hasProfile())
{
@ -373,32 +419,31 @@ void AccountsDialog::updateStates()
return;
}
ui->fullAccountPage->setEnabled(accountIsReady);
// Full account page
ui->fullAccountPage->setEnabled(accountIsReady);
if(prevAccount != m_currentAccount)
{
revertEdits();
m_capesModel->setAccount(m_currentAccount);
ui->accountPageStack->setCurrentWidget(ui->fullAccountPage);
ui->selectedAccountLabel->setText(m_currentAccount->profileName());
ui->selectedAccountIconLabel->setIcon(m_currentAccount->getFace());
QByteArray playerSkinData = m_currentAccount->getSkin();
QImage image;
QString textureID;
Skins::readSkinFromData(playerSkinData, image, textureID);
auto maybeEntry = m_skinsModel->skinEntryByTextureID(textureID);
m_playerSkinState = SkinState{
m_currentAccount->getCurrentCape(),
m_currentAccount->getSkinModel(),
(!maybeEntry.isNull()) ? maybeEntry : Skins::SkinEntry("player", "", image, textureID, playerSkinData)
};
updateModelToMatchSkin();
updateSkinDisplay();
}
m_capesModel->setAccount(m_currentAccount);
ui->accountPageStack->setCurrentWidget(ui->fullAccountPage);
ui->selectedAccountLabel->setText(m_currentAccount->profileName());
ui->selectedAccountIconLabel->setIcon(m_currentAccount->getFace());
QByteArray playerSkinData = m_currentAccount->getSkin();
QImage image;
QString textureID;
Skins::readSkinFromData(playerSkinData, image, textureID);
auto maybeEntry = m_skinsModel->skinEntryByTextureID(textureID);
m_playerSkinState = SkinState{
m_currentAccount->getCurrentCape(),
m_currentAccount->getSkinModel(),
(!maybeEntry.isNull()) ? maybeEntry : Skins::SkinEntry("player", "", image, textureID, playerSkinData)
};
updateModelToMatchSkin();
updateSkinDisplay();
}
void AccountsDialog::updateModelToMatchSkin()
@ -639,6 +684,21 @@ void AccountsDialog::onSignOutButtonClicked(bool)
}
}
void AccountsDialog::onRemoveAndSignInButtonClicked(bool)
{
if(m_currentAccount)
{
m_accounts->removeAccount(m_currentAccount->internalId());
}
ui->accountListView->setCurrentIndex(m_accounts->index(0));
}
void AccountsDialog::onGetMinecraftButtonClicked(bool)
{
DesktopServices::openUrl(QUrl("https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"));
}
void AccountsDialog::setNameStatus(AccountsDialog::NameStatus status, QString errorString = QString())
{
nameStatus = status;

View File

@ -53,6 +53,9 @@ public:
Error
} nameStatus = NameStatus::NotSet;
protected:
void closeEvent(QCloseEvent *event) override;
// Skins stuff
private slots:
void onSkinSelectionChanged(const class QItemSelection &selected, const class QItemSelection &deselected);
@ -95,6 +98,9 @@ private slots:
// Account display page
void onRefreshButtonClicked(bool);
void onSignOutButtonClicked(bool);
void onRemoveAndSignInButtonClicked(bool);
void onGetMinecraftButtonClicked(bool);
void onAccountChanged(MinecraftAccount * account);
void onAccountActivityChanged(MinecraftAccount * account, bool active);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1085</width>
<height>1022</height>
<width>927</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
@ -63,7 +63,7 @@
<enum>QFrame::Raised</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>4</number>
</property>
<widget class="QWidget" name="fullAccountPage">
<layout class="QVBoxLayout" name="verticalLayout">
@ -135,7 +135,7 @@
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>Refresh</string>
<string>Refresh Account</string>
</property>
</widget>
</item>
@ -353,6 +353,12 @@
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="spacing">
<number>5</number>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
@ -539,7 +545,7 @@
<item>
<widget class="QPushButton" name="refreshButton_Setup">
<property name="text">
<string>Refresh</string>
<string>Refresh Account</string>
</property>
</widget>
</item>
@ -635,6 +641,283 @@ Choose your name carefully:</string>
</item>
</layout>
</widget>
<widget class="QWidget" name="expiredPage">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QWidget" name="headerBar_Expired" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="IconLabel32" name="selectedAccountIconLabel_Expired" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="selectedAccountLabel_Expired">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Account Name Here</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_Expired">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="signOutButton_Expired">
<property name="text">
<string>Sign Out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
<widget class="QFrame" name="frame_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="createProfileLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>This account's authentication tokens have expired and it needs to be signed in into again.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>createProfileNameEdit</cstring>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="removeAndSignInButton">
<property name="text">
<string>Remove and Sign In Again</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="demoPage">
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QWidget" name="headerBar_Demo" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="IconLabel32" name="selectedAccountIconLabel_Demo" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="selectedAccountLabel_Demo">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Account Name Here</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_Demo">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshButton_Demo">
<property name="text">
<string>Refresh Account</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="signOutButton_Demo">
<property name="text">
<string>Sign Out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
<widget class="QFrame" name="frame_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="getMinecraftLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>This account doesn't own Minecraft yet. Without purchasing the game, you can only play the demo.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>createProfileNameEdit</cstring>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="getMinecraftButton">
<property name="text">
<string>Get Minecraft</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0" rowspan="2">
@ -667,7 +950,6 @@ Choose your name carefully:</string>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
</item>
</layout>