1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-21 20:27:45 +00:00

Overhaul initial setup flow, again

This improves the flow drastically, first by porting it from MobileForm
to FormCard. Next, it fixes some of the annoying bugs such as the
profile not switching properly when adding a new profile. Selecting an
existing game path is now possible, and it's less likely you can enter
in invalid account credentials. The overall look and behavior of some
of the pages is improved.
This commit is contained in:
Joshua Goins 2023-10-08 20:01:17 -04:00
parent 3a7dc40826
commit 67dcd90058
16 changed files with 344 additions and 261 deletions

View file

@ -109,6 +109,16 @@ qt_target_qml_sources(astra
ui/Main.qml ui/Main.qml
) )
set_source_files_properties(../zone.xiv.astra.svg PROPERTIES
QT_RESOURCE_ALIAS /zone.xiv.astra.svg
)
qt_target_qml_sources(astra
PREFIX /
RESOURCES
../zone.xiv.astra.svg
)
kconfig_add_kcfg_files(astra GENERATE_MOC config.kcfgc accountconfig.kcfgc profileconfig.kcfgc) kconfig_add_kcfg_files(astra GENERATE_MOC config.kcfgc accountconfig.kcfgc profileconfig.kcfgc)
target_include_directories(astra PRIVATE include ${CMAKE_BINARY_DIR}) target_include_directories(astra PRIVATE include ${CMAKE_BINARY_DIR})
target_link_libraries(astra PRIVATE target_link_libraries(astra PRIVATE

View file

@ -37,6 +37,8 @@ public:
Q_INVOKABLE bool canDelete(Account *account) const; Q_INVOKABLE bool canDelete(Account *account) const;
Q_INVOKABLE void deleteAccount(Account *account); Q_INVOKABLE void deleteAccount(Account *account);
Q_INVOKABLE bool hasAnyAccounts() const;
private: private:
void insertAccount(Account *account); void insertAccount(Account *account);

View file

@ -40,6 +40,8 @@ public:
Q_INVOKABLE bool canDelete(Profile *account) const; Q_INVOKABLE bool canDelete(Profile *account) const;
Q_INVOKABLE bool hasAnyExistingInstallations() const;
static QString getDefaultGamePath(const QString &uuid); static QString getDefaultGamePath(const QString &uuid);
static QString getDefaultWinePrefixPath(const QString &uuid); static QString getDefaultWinePrefixPath(const QString &uuid);

View file

@ -111,3 +111,8 @@ void AccountManager::insertAccount(Account *account)
m_accounts.append(account); m_accounts.append(account);
endInsertRows(); endInsertRows();
} }
bool AccountManager::hasAnyAccounts() const
{
return !m_accounts.empty();
}

View file

@ -794,7 +794,7 @@ void LauncherCore::setCurrentProfile(Profile *profile)
{ {
Q_ASSERT(profile != nullptr); Q_ASSERT(profile != nullptr);
const int newIndex = m_profileManager->getProfileIndex(profile->name()); const int newIndex = m_profileManager->getProfileIndex(profile->uuid());
if (newIndex != m_currentProfileIndex) { if (newIndex != m_currentProfileIndex) {
m_currentProfileIndex = newIndex; m_currentProfileIndex = newIndex;
Q_EMIT currentProfileChanged(); Q_EMIT currentProfileChanged();

View file

@ -510,13 +510,13 @@ QString Profile::bootVersion() const
QString Profile::baseGameVersion() const QString Profile::baseGameVersion() const
{ {
Q_ASSERT(m_repositories.repositories_count > 1); Q_ASSERT(m_repositories.repositories_count >= 1);
return m_repositories.repositories[0].version; return m_repositories.repositories[0].version;
} }
int Profile::numInstalledExpansions() const int Profile::numInstalledExpansions() const
{ {
Q_ASSERT(m_repositories.repositories_count > 1); Q_ASSERT(m_repositories.repositories_count >= 1);
return m_repositories.repositories_count - 1; return m_repositories.repositories_count - 1;
} }

View file

@ -22,7 +22,7 @@ Profile *ProfileManager::getProfile(const int index)
int ProfileManager::getProfileIndex(const QString &name) int ProfileManager::getProfileIndex(const QString &name)
{ {
for (int i = 0; i < m_profiles.size(); i++) { for (int i = 0; i < m_profiles.size(); i++) {
if (m_profiles[i]->name() == name) if (m_profiles[i]->uuid() == name)
return i; return i;
} }
@ -147,3 +147,14 @@ bool ProfileManager::canDelete(Profile *account) const
Q_UNUSED(account) Q_UNUSED(account)
return m_profiles.size() != 1; return m_profiles.size() != 1;
} }
bool ProfileManager::hasAnyExistingInstallations() const
{
for (auto &profile : m_profiles) {
if (profile->isGameInstalled()) {
return true;
}
}
return false;
}

View file

@ -4,9 +4,9 @@
import QtCore import QtCore
import QtQuick.Dialogs import QtQuick.Dialogs
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
MobileForm.FormButtonDelegate { FormCard.FormButtonDelegate {
id: control id: control
property string file property string file

View file

@ -88,7 +88,6 @@ FormCard.FormCardPage {
} }
} }
FormCard.FormCard { FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -5,75 +5,91 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { FormCard.FormCardPage {
id: page id: page
property var profile property var profile
title: i18n("Account Setup") title: i18n("Account Setup")
ColumnLayout { FormCard.FormCard {
width: parent.width Layout.topMargin: Kirigami.Units.largeSpacing
MobileForm.FormCard { Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader { FormCard.FormTextDelegate {
title: i18n("Accounts") id: helpTextDelegate
}
MobileForm.FormTextDelegate { text: i18n("Select an account to use for '%1'", LauncherCore.currentProfile.name)
text: i18n("Select an account below to use for profile '%1'.", LauncherCore.currentProfile.name) }
} }
Repeater { FormCard.FormHeader {
model: LauncherCore.accountManager title: i18n("Existing Accounts")
visible: LauncherCore.accountManager.hasAnyAccounts()
}
MobileForm.FormButtonDelegate { FormCard.FormCard {
required property var account visible: LauncherCore.accountManager.hasAnyAccounts()
text: account.name Layout.fillWidth: true
onClicked: { FormCard.FormTextDelegate {
page.profile.account = account id: existingHelpDelegate
applicationWindow().checkSetup()
} text: i18n("You can select an existing account.")
}
}
}
} }
MobileForm.FormCard { FormCard.FormDelegateSeparator {
Layout.topMargin: Kirigami.Units.largeSpacing above: existingHelpDelegate
Layout.fillWidth: true }
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormButtonDelegate { Repeater {
text: i18n("Add Square Enix Account") model: LauncherCore.accountManager
icon.name: "list-add-symbolic"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"), {
profile: page.profile
})
}
MobileForm.FormDelegateSeparator { FormCard.FormButtonDelegate {
} required property var account
MobileForm.FormButtonDelegate { text: account.name
text: i18n("Add Sapphire Account")
icon.name: "list-add-symbolic" onClicked: {
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"), { page.profile.account = account
profile: page.profile applicationWindow().checkSetup()
})
} }
} }
} }
} }
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: addSquareEnixButton
text: i18n("Add Square Enix Account")
icon.name: "list-add-symbolic"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"), {
profile: page.profile
})
}
FormCard.FormDelegateSeparator {
above: addSquareEnixButton
below: addSapphireButton
}
FormCard.FormButtonDelegate {
id: addSapphireButton
text: i18n("Add Sapphire Account")
icon.name: "list-add-symbolic"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"), {
profile: page.profile
})
}
}
} }

View file

@ -6,60 +6,65 @@ import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { FormCard.FormCardPage {
id: page id: page
property var profile property var profile
title: i18n("Add Sapphire Account") title: i18n("Add Sapphire Account")
ColumnLayout { readonly property bool isValid: usernameField.text.length !== 0 && lobbyUrlField.text.length !== 0
width: parent.width
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormTextDelegate { FormCard.FormCard {
description: i18n("Passwords will be entered on the login page. The username will be associated with this profile but can be changed later.") Layout.topMargin: Kirigami.Units.largeSpacing
} Layout.fillWidth: true
MobileForm.FormDelegateSeparator { FormCard.FormTextDelegate {
} id: helpTextDelegate
description: i18n("The password will be entered on the login page. A username will be associated with this account but can always be changed later.")
}
MobileForm.FormTextFieldDelegate { FormCard.FormDelegateSeparator {
id: lobbyUrlField above: helpTextDelegate
label: i18n("Lobby URL") below: usernameField
} }
MobileForm.FormDelegateSeparator { FormCard.FormTextFieldDelegate {
} id: usernameField
label: i18n("Username")
}
MobileForm.FormTextFieldDelegate { FormCard.FormDelegateSeparator {
id: usernameField above: usernameField
label: i18n("Username") below: lobbyUrlField
} }
MobileForm.FormDelegateSeparator { FormCard.FormTextFieldDelegate {
} id: lobbyUrlField
label: i18n("Lobby URL")
}
MobileForm.FormButtonDelegate { FormCard.FormDelegateSeparator {
text: i18n("Add Account") above: lobbyUrlField
icon.name: "list-add-symbolic" below: buttonDelegate
onClicked: { }
let account = LauncherCore.accountManager.createSapphireAccount(lobbyUrlField.text, usernameField.text)
if (page.profile) { FormCard.FormButtonDelegate {
page.profile.account = account id: buttonDelegate
applicationWindow().checkSetup() text: i18n("Add Account")
} else { icon.name: "list-add-symbolic"
page.Window.window.pageStack.layers.pop() enabled: page.isValid
} onClicked: {
} let account = LauncherCore.accountManager.createSapphireAccount(lobbyUrlField.text, usernameField.text)
if (page.profile) {
page.profile.account = account
applicationWindow().checkSetup()
} else {
page.Window.window.pageStack.layers.pop()
} }
} }
} }

View file

@ -6,72 +6,79 @@ import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { FormCard.FormCardPage {
id: page id: page
property var profile property var profile
title: i18n("Add Square Enix Account") title: i18n("Add Square Enix Account")
ColumnLayout { readonly property bool isValid: usernameField.text.length !== 0
width: parent.width
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormTextDelegate { FormCard.FormCard {
description: i18n("Passwords will be entered on the login page. The username will be associated with this profile but can be changed later.") Layout.topMargin: Kirigami.Units.largeSpacing
} Layout.fillWidth: true
MobileForm.FormDelegateSeparator { FormCard.FormTextDelegate {
} id: helpTextDelegate
description: i18n("The password will be entered on the login page. A username will be associated with this account but can always be changed later.")
}
MobileForm.FormTextFieldDelegate { FormCard.FormDelegateSeparator {
id: usernameField above: helpTextDelegate
label: i18n("Username") below: usernameField
} }
MobileForm.FormDelegateSeparator { FormCard.FormTextFieldDelegate {
} id: usernameField
label: i18n("Username")
}
MobileForm.FormComboBoxDelegate { FormCard.FormDelegateSeparator {
id: licenseField above: usernameField
text: i18n("License") below: licenseField
description: i18n("If the account holds multiple licenses, choose the preferred one.") }
model: ["Windows", "Steam", "macOS"]
currentIndex: 0
}
MobileForm.FormDelegateSeparator { FormCard.FormComboBoxDelegate {
} id: licenseField
text: i18n("License")
description: i18n("If the account holds multiple licenses, choose the preferred one.")
model: ["Windows", "Steam", "macOS"]
currentIndex: 0
}
MobileForm.FormCheckDelegate { FormCard.FormDelegateSeparator {
id: freeTrialField above: licenseField
text: i18n("Free Trial") below: freeTrialField
description: i18n("Check if the account is currently on free trial.") }
}
MobileForm.FormDelegateSeparator { FormCard.FormCheckDelegate {
} id: freeTrialField
text: i18n("Free Trial")
description: i18n("Check if the account is currently on free trial.")
}
MobileForm.FormButtonDelegate { FormCard.FormDelegateSeparator {
text: i18n("Add Account") above: freeTrialField
icon.name: "list-add-symbolic" below: buttonDelegate
onClicked: { }
let account = LauncherCore.accountManager.createSquareEnixAccount(usernameField.text, licenseField.currentIndex, freeTrialField.checkState === Qt.Checked)
if (page.profile) { FormCard.FormButtonDelegate {
page.profile.account = account id: buttonDelegate
applicationWindow().checkSetup() text: i18n("Add Account")
} else { icon.name: "list-add-symbolic"
page.Window.window.pageStack.layers.pop() enabled: page.isValid
} onClicked: {
} let account = LauncherCore.accountManager.createSquareEnixAccount(usernameField.text, licenseField.currentIndex, freeTrialField.checkState === Qt.Checked)
if (page.profile) {
page.profile.account = account
applicationWindow().checkSetup()
} else {
page.Window.window.pageStack.layers.pop()
} }
} }
} }

View file

@ -5,45 +5,41 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { FormCard.FormCardPage {
id: page id: page
property var profile property var profile
title: i18n("Download Game") title: i18n("Download Game")
ColumnLayout { FormCard.FormCard {
width: parent.width Layout.topMargin: Kirigami.Units.largeSpacing
MobileForm.FormCard { Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader { FormCard.FormTextDelegate {
title: i18n("Download Game") id: helpTextDelegate
}
MobileForm.FormTextDelegate { text: i18n("Press the button below to download and setup the game.")
text: i18n("Press the button below to download and setup the game.") description: i18n("This only installs the base files required for the initial update. Astra can only download patches with a legitimate Square Enix account.")
description: i18n("This is for the base files required for start-up, only when logged in will Astra begin downloading the full game.") }
}
MobileForm.FormDelegateSeparator { FormCard.FormDelegateSeparator {
} above: helpTextDelegate
below: buttonDelegate
}
MobileForm.FormButtonDelegate { FormCard.FormButtonDelegate {
text: i18n("Begin installation") id: buttonDelegate
icon.name: "cloud-download"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), { text: i18n("Begin installation")
gameInstaller: LauncherCore.createInstaller(page.profile) icon.name: "cloud-download"
}) onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), {
} gameInstaller: LauncherCore.createInstaller(page.profile)
} })
} }
} }
} }

View file

@ -3,31 +3,52 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { import "../Components"
title: i18n("Find Game Installation")
ColumnLayout { FormCard.FormCardPage {
width: parent.width id: page
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader { property var profile
title: i18n("Find an existing installation")
}
MobileForm.FormTextDelegate { title: i18n("Find Existing Installation")
text: i18n("Please select the path to your existing installation.")
} FormCard.FormCard {
} Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormTextDelegate {
id: helpTextDelegate
text: i18n("Please select the path to your existing installation.")
}
FormCard.FormDelegateSeparator {
above: helpTextDelegate
below: selectDelegate
}
FormCard.FormButtonDelegate {
id: selectDelegate
text: i18n("Select Existing Path")
icon.name: "document-open-folder"
onClicked: dialog.open()
}
}
data: FolderDialog {
id: dialog
onAccepted: {
page.profile.gamePath = decodeURIComponent(selectedFolder.toString().replace("file://", ""));
applicationWindow().checkSetup();
} }
} }
} }

View file

@ -5,7 +5,6 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm
import zone.xiv.astra import zone.xiv.astra

View file

@ -5,97 +5,107 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.mobileform as MobileForm import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra import zone.xiv.astra
Kirigami.Page { FormCard.FormCardPage {
id: page id: page
property var profile property var profile
readonly property bool isInitialSetup: !LauncherCore.profileManager.hasAnyExistingInstallations()
title: i18n("Game Setup") title: isInitialSetup ? i18n("Initial Setup") : i18n("Profile Setup")
ColumnLayout { Image {
width: parent.width source: "qrc:/zone.xiv.astra.svg"
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader { fillMode: Image.PreserveAspectFit
title: i18n("Welcome to Astra")
}
MobileForm.FormTextDelegate { Layout.fillWidth: true
text: i18n("The profile '%1' must be set up before first.", LauncherCore.currentProfile.name) Layout.fillHeight: true
} Layout.margins: Kirigami.Units.largeSpacing * 3
}
MobileForm.FormTextDelegate { FormCard.FormCard {
text: i18n("A copy of the game must be located or installed.") Layout.fillWidth: true
description: i18n("A valid game account will be required at the end of installation.")
}
}
}
MobileForm.FormCard { FormCard.FormTextDelegate {
Layout.topMargin: Kirigami.Units.largeSpacing text: {
Layout.fillWidth: true if (isInitialSetup) {
contentItem: ColumnLayout { return i18n("You must have a legitimate installation of the FFXIV to continue.");
spacing: 0 } else {
return i18n("You must select a legitimate installation of FFXIV for '%1'", page.profile.name);
MobileForm.FormCardHeader {
title: i18n("Existing Installations")
}
MobileForm.FormTextDelegate {
text: i18n("Select an existing installation from another profile below.")
}
Repeater {
model: LauncherCore.profileManager
MobileForm.FormButtonDelegate {
required property var profile
text: profile.name
description: profile.gamePath
onClicked: {
LauncherCore.currentProfile.gamePath = profile.gamePath;
applicationWindow().checkSetup();
}
}
}
}
}
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormButtonDelegate {
text: i18n("Find Existing Installation")
icon.name: "edit-find"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ExistingSetup"), {
profile: page.profile
})
}
MobileForm.FormDelegateSeparator {
}
MobileForm.FormButtonDelegate {
text: i18n("Download Game")
icon.name: "cloud-download"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "DownloadSetup"), {
profile: page.profile
})
} }
} }
} }
} }
FormCard.FormHeader {
title: i18n("Existing Installations")
visible: LauncherCore.profileManager.hasAnyExistingInstallations()
}
FormCard.FormCard {
visible: LauncherCore.profileManager.hasAnyExistingInstallations()
Layout.fillWidth: true
FormCard.FormTextDelegate {
id: existingHelpDelegate
text: i18n("You can select an existing installation from another profile.")
}
FormCard.FormDelegateSeparator {
above: existingHelpDelegate
}
Repeater {
model: LauncherCore.profileManager
FormCard.FormButtonDelegate {
required property var profile
text: profile.name
description: profile.gamePath
visible: profile.isGameInstalled
onClicked: {
LauncherCore.currentProfile.gamePath = profile.gamePath;
applicationWindow().checkSetup();
}
}
}
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: findExistingDelegate
text: i18n("Find Existing Installation")
icon.name: "edit-find"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ExistingSetup"), {
profile: page.profile
})
}
FormCard.FormDelegateSeparator {
above: findExistingDelegate
below: downloadDelegate
}
FormCard.FormButtonDelegate {
id: downloadDelegate
text: i18n("Download Game")
icon.name: "cloud-download"
onClicked: pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "DownloadSetup"), {
profile: page.profile
})
}
}
} }