Refactor the last.fm, magnatune, spotify and di.fm settings pages - moving the login state display into a separate widget.

This commit is contained in:
David Sansome 2011-08-27 22:01:28 +01:00
parent 143bbb4019
commit 13fc24f6c6
31 changed files with 605 additions and 383 deletions

View File

@ -1,2 +1,2 @@
# Increment this whenever the user needs to download a new blob
set(SPOTIFY_BLOB_VERSION 2)
set(SPOTIFY_BLOB_VERSION 3)

View File

@ -320,8 +320,7 @@
<file>schema/schema-33.sql</file>
<file>spotify-core-logo-128x128.png</file>
<file>icons/22x22/dialog-warning.png</file>
<file>icons/22x22/task-complete.png</file>
<file>icons/22x22/task-reject.png</file>
<file>icons/22x22/dialog-ok-apply.png</file>
<file>schema/schema-34.sql</file>
<file>pythonlibs/uic/driver.py</file>
<file>pythonlibs/uic/exceptions.py</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -108,12 +108,25 @@ void SpotifyClient::Init(quint16 port) {
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
const bool success = error == SP_ERROR_OK;
protobuf::LoginResponse_Error error_code = protobuf::LoginResponse_Error_Other;
if (!success) {
qLog(Warning) << "Failed to login" << sp_error_message(error);
}
me->SendLoginCompleted(success, sp_error_message(error));
switch (error) {
case SP_ERROR_BAD_USERNAME_OR_PASSWORD:
error_code = protobuf::LoginResponse_Error_BadUsernameOrPassword;
break;
case SP_ERROR_USER_BANNED:
error_code = protobuf::LoginResponse_Error_UserBanned;
break;
case SP_ERROR_USER_NEEDS_PREMIUM :
error_code = protobuf::LoginResponse_Error_UserNeedsPremium;
break;
}
me->SendLoginCompleted(success, sp_error_message(error), error_code);
if (success) {
sp_playlistcontainer_add_callbacks(
@ -208,7 +221,7 @@ void SpotifyClient::Login(const QString& username, const QString& password) {
sp_error error = sp_session_create(&spotify_config_, &session_);
if (error != SP_ERROR_OK) {
qLog(Warning) << "Failed to create session" << sp_error_message(error);
SendLoginCompleted(false, sp_error_message(error));
SendLoginCompleted(false, sp_error_message(error), protobuf::LoginResponse_Error_Other);
return;
}
@ -217,13 +230,18 @@ void SpotifyClient::Login(const QString& username, const QString& password) {
sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData());
}
void SpotifyClient::SendLoginCompleted(bool success, const QString& error) {
void SpotifyClient::SendLoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code) {
protobuf::SpotifyMessage message;
protobuf::LoginResponse* response = message.mutable_login_response();
response->set_success(success);
response->set_error(DataCommaSizeFromQString(error));
if (!success) {
response->set_error_code(error_code);
}
handler_->SendMessage(message);
}

View File

@ -52,7 +52,8 @@ private slots:
void MediaSocketDisconnected();
private:
void SendLoginCompleted(bool success, const QString& error);
void SendLoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void SendPlaybackError(const QString& error);
// Spotify session callbacks.

View File

@ -30,8 +30,16 @@ message LoginRequest {
}
message LoginResponse {
enum Error {
BadUsernameOrPassword = 1;
UserBanned = 2;
UserNeedsPremium = 3;
Other = 4;
}
required bool success = 1;
required string error = 2;
optional Error error_code = 3 [default = Other];
}
message Playlists {

View File

@ -275,6 +275,7 @@ set(SOURCES
widgets/groupediconview.cpp
widgets/lineedit.cpp
widgets/linetextedit.cpp
widgets/loginstatewidget.cpp
widgets/multiloadingindicator.cpp
widgets/nowplayingwidget.cpp
widgets/osd.cpp
@ -482,6 +483,7 @@ set(HEADERS
widgets/groupediconview.h
widgets/lineedit.h
widgets/linetextedit.h
widgets/loginstatewidget.h
widgets/multiloadingindicator.h
widgets/nowplayingwidget.h
widgets/osd.h
@ -567,6 +569,7 @@ set(UI
widgets/equalizerslider.ui
widgets/errordialog.ui
widgets/fileview.ui
widgets/loginstatewidget.ui
widgets/osdpretty.ui
widgets/trackslider.ui

View File

@ -28,6 +28,12 @@ DigitallyImportedSettingsPage::DigitallyImportedSettingsPage(SettingsDialog* dia
{
ui_->setupUi(this);
setWindowIcon(QIcon(":/providers/digitallyimported-32.png"));
ui_->login_state->SetAccountTypeText(tr(
"You can listen for free without an account, but Premium members can "
"listen to higher quality streams without advertisements."));
ui_->login_state->SetAccountTypeVisible(true);
ui_->login_state->HideLoggedInState();
}
DigitallyImportedSettingsPage::~DigitallyImportedSettingsPage() {

View File

@ -14,6 +14,9 @@
<string>Digitally Imported</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox_10">
<property name="sizePolicy">
@ -51,16 +54,6 @@
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_24">
<property name="text">
<string>You can &lt;b&gt;listen for free&lt;/b&gt; without an account, but Premium members can listen to &lt;b&gt;higher quality&lt;/b&gt; streams without advertisements.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="upgrade_link">
<property name="text">
<string>&lt;a href=&quot;http://www.di.fm/premium/&quot;&gt;Upgrade to Premium now&lt;/a&gt;</string>
@ -157,6 +150,14 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -329,6 +329,8 @@ void LastFMService::UpdateSubscriberStatusFinished() {
Q_ASSERT(reply);
reply->deleteLater();
bool is_subscriber = false;
try {
const lastfm::XmlQuery lfm = lastfm::ws::parse(reply);
#ifdef Q_OS_WIN32
@ -338,26 +340,20 @@ void LastFMService::UpdateSubscriberStatusFinished() {
connection_problems_ = false;
QString subscriber = lfm["user"]["subscriber"].text();
const bool is_subscriber = (subscriber.toInt() == 1);
is_subscriber = (subscriber.toInt() == 1);
QSettings settings;
settings.beginGroup(kSettingsGroup);
settings.setValue("Subscriber", is_subscriber);
qLog(Info) << lastfm::ws::Username << "Subscriber status:" << is_subscriber;
emit UpdatedSubscriberStatus(is_subscriber);
} catch (lastfm::ws::ParseError e) {
qLog(Error) << "Last.fm parse error: " << e.enumValue();
if (e.enumValue() == lastfm::ws::MalformedResponse) {
// The connection to the server is unavailable
connection_problems_ = true;
emit UpdatedSubscriberStatus(false);
} else {
// Errors not related to connection
connection_problems_ = false;
}
connection_problems_ = e.enumValue() == lastfm::ws::MalformedResponse;
} catch (std::runtime_error& e) {
qLog(Error) << e.what();
}
emit UpdatedSubscriberStatus(is_subscriber);
}
QUrl LastFMService::FixupUrl(const QUrl& url) {

View File

@ -27,32 +27,27 @@
#include <QMovie>
#include <QSettings>
// Use Qt specific icons, since freedesktop doesn't seem to have suitable icons.
const char* kSubscribedIcon = "task-complete";
const char* kNotSubscribedIcon = "dialog-warning";
const char* kWaitingIcon = ":spinner.gif";
LastFMSettingsPage::LastFMSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog),
service_(static_cast<LastFMService*>(InternetModel::ServiceByName("Last.fm"))),
ui_(new Ui_LastFMSettingsPage),
loading_icon_(new QMovie(kWaitingIcon, QByteArray(), this)),
waiting_for_auth_(false)
{
ui_->setupUi(this);
ui_->busy->hide();
// Icons
setWindowIcon(QIcon(":/last.fm/as.png"));
ui_->sign_out->setIcon(IconLoader::Load("list-remove"));
ui_->warn_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(16));
ui_->warn_icon->setMinimumSize(16, 16);
connect(service_, SIGNAL(AuthenticationComplete(bool)), SLOT(AuthenticationComplete(bool)));
connect(service_, SIGNAL(UpdatedSubscriberStatus(bool)), SLOT(UpdatedSubscriberStatus(bool)));
connect(ui_->sign_out, SIGNAL(clicked()), SLOT(SignOut()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->groupBox);
ui_->username->setMinimumWidth(QFontMetrics(QFont()).width("WWWWWWWWWWWW"));
resize(sizeHint());
}
@ -62,9 +57,9 @@ LastFMSettingsPage::~LastFMSettingsPage() {
}
void LastFMSettingsPage::Login() {
ui_->busy->show();
waiting_for_auth_ = true;
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
service_->Authenticate(ui_->username->text(), ui_->password->text());
}
@ -72,7 +67,6 @@ void LastFMSettingsPage::AuthenticationComplete(bool success) {
if (!waiting_for_auth_)
return; // Wasn't us that was waiting for auth
ui_->busy->hide();
waiting_for_auth_ = false;
if (success) {
@ -93,8 +87,6 @@ void LastFMSettingsPage::Load() {
ui_->love_ban_->setChecked(service_->AreButtonsVisible());
ui_->scrobble_button->setChecked(service_->IsScrobbleButtonVisible());
ui_->icon->setMovie(loading_icon_);
loading_icon_->start();
if (service_->IsAuthenticated()) {
service_->UpdateSubscriberStatus();
}
@ -103,21 +95,15 @@ void LastFMSettingsPage::Load() {
}
void LastFMSettingsPage::UpdatedSubscriberStatus(bool is_subscriber) {
const char* icon_path = is_subscriber ? kSubscribedIcon : kNotSubscribedIcon;
ui_->icon->setPixmap(IconLoader::Load(icon_path).pixmap(16));
loading_icon_->stop();
ui_->login_state->SetAccountTypeVisible(!is_subscriber);
if (is_subscriber) {
ui_->subscriber_warning->hide();
ui_->warn_icon->hide();
} else {
ui_->warn_icon->show();
if (!is_subscriber) {
if (service_->HasConnectionProblems()) {
ui_->subscriber_warning->setText(
ui_->login_state->SetAccountTypeText(
tr("Clementine couldn't fetch your subscription status since there are problems "
"with your connection. Played tracks will be cached and sent later to Last.fm."));
} else {
ui_->subscriber_warning->setText(
ui_->login_state->SetAccountTypeText(
tr("You will not be able to play Last.fm radio stations "
"as you are not a Last.fm subscriber."));
}
@ -135,7 +121,7 @@ void LastFMSettingsPage::Save() {
service_->ReloadSettings();
}
void LastFMSettingsPage::SignOut() {
void LastFMSettingsPage::Logout() {
ui_->username->clear();
ui_->password->clear();
RefreshControls(false);
@ -144,19 +130,15 @@ void LastFMSettingsPage::SignOut() {
}
void LastFMSettingsPage::RefreshControls(bool authenticated) {
ui_->groupBox->setVisible(!authenticated);
ui_->sign_out->setVisible(authenticated);
if (authenticated) {
ui_->status->setText(QString(tr("You're logged in as <b>%1</b>")).arg(lastfm::ws::Username));
} else {
ui_->icon->setPixmap(IconLoader::Load("task-reject").pixmap(16));
ui_->status->setText(tr("Please fill in the blanks to login into Last.fm"));
ui_->login_state->SetLoggedIn(authenticated ? LoginStateWidget::LoggedIn
: LoginStateWidget::LoggedOut,
lastfm::ws::Username);
ui_->login_state->SetAccountTypeVisible(!authenticated);
ui_->subscriber_warning->setText(
if (!authenticated) {
ui_->login_state->SetAccountTypeText(
tr("You can scrobble tracks for free, but only "
"<span style=\" font-weight:600;\">paid subscribers</span> "
"can stream Last.fm radio from Clementine."));
ui_->subscriber_warning->show();
ui_->warn_icon->show();
}
}

View File

@ -36,7 +36,7 @@ public:
private slots:
void Login();
void AuthenticationComplete(bool success);
void SignOut();
void Logout();
void UpdatedSubscriberStatus(bool is_subscriber);
private:

View File

@ -15,64 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sign_out">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Sign out</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
@ -170,121 +113,24 @@
</property>
</spacer>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="warn_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="subscriber_warning">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>username</tabstop>
<tabstop>password</tabstop>
<tabstop>login</tabstop>
<tabstop>sign_out</tabstop>
<tabstop>scrobble</tabstop>
<tabstop>love_ban_</tabstop>
<tabstop>scrobble_button</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>username</sender>
<signal>returnPressed()</signal>
<receiver>login</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>332</x>
<y>104</y>
</hint>
<hint type="destinationlabel">
<x>706</x>
<y>103</y>
</hint>
</hints>
</connection>
<connection>
<sender>password</sender>
<signal>returnPressed()</signal>
<receiver>login</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>719</x>
<y>137</y>
</hint>
<hint type="destinationlabel">
<x>725</x>
<y>108</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

View File

@ -30,23 +30,28 @@
MagnatuneSettingsPage::MagnatuneSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog),
credentials_changed_(false),
network_(new NetworkAccessManager(this)),
ui_(new Ui_MagnatuneSettingsPage)
ui_(new Ui_MagnatuneSettingsPage),
logged_in_(false)
{
ui_->setupUi(this);
ui_->busy->hide();
setWindowIcon(QIcon(":/providers/magnatune.png"));
connect(ui_->membership, SIGNAL(currentIndexChanged(int)), SLOT(MembershipChanged(int)));
connect(network_, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
SLOT(AuthenticationRequired(QNetworkReply*, QAuthenticator*)));
connect(ui_->username, SIGNAL(textEdited(const QString&)),
SLOT(CredentialsChanged()));
connect(ui_->password, SIGNAL(textEdited(const QString&)),
SLOT(CredentialsChanged()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->login_container);
ui_->login_state->SetAccountTypeText(tr(
"You can listen to Magnatune songs for free without an account. "
"Purchasing a membership removes the messages at the end of each track."));
}
MagnatuneSettingsPage::~MagnatuneSettingsPage() {
@ -56,11 +61,14 @@ MagnatuneSettingsPage::~MagnatuneSettingsPage() {
const char* kMagnatuneDownloadValidateUrl = "http://download.magnatune.com/";
const char* kMagnatuneStreamingValidateUrl = "http://streaming.magnatune.com/";
void MagnatuneSettingsPage::Login() {
if (!credentials_changed_)
return;
ui_->busy->show();
void MagnatuneSettingsPage::UpdateLoginState() {
ui_->login_state->SetLoggedIn(logged_in_ ? LoginStateWidget::LoggedIn
: LoginStateWidget::LoggedOut,
ui_->username->text());
ui_->login_state->SetAccountTypeVisible(!logged_in_);
}
void MagnatuneSettingsPage::Login() {
MagnatuneService::MembershipType type =
MagnatuneService::MembershipType(ui_->membership->currentIndex());
@ -81,27 +89,33 @@ void MagnatuneSettingsPage::Login() {
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
QNetworkReply* reply = network_->head(req);
connect(reply, SIGNAL(finished()), SLOT(LoginFinished()));
}
void MagnatuneSettingsPage::Logout() {
logged_in_ = false;
UpdateLoginState();
}
void MagnatuneSettingsPage::LoginFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Q_ASSERT(reply);
reply->deleteLater();
ui_->busy->hide();
const bool success =
logged_in_ =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200;
credentials_changed_ = !success;
if (!success) {
if (!logged_in_) {
QMessageBox::warning(
this, tr("Authentication failed"), tr("Your Magnatune credentials were incorrect"));
} else {
Save();
}
ui_->login->setEnabled(!success);
UpdateLoginState();
}
void MagnatuneSettingsPage::AuthenticationRequired(
@ -119,8 +133,11 @@ void MagnatuneSettingsPage::Load() {
ui_->username->setText(s.value("username").toString());
ui_->password->setText(s.value("password").toString());
ui_->format->setCurrentIndex(s.value("format", MagnatuneService::Format_Ogg).toInt());
logged_in_ = s.value("logged_in",
!ui_->username->text().isEmpty() &&
!ui_->password->text().isEmpty()).toBool();
credentials_changed_ = false;
UpdateLoginState();
}
void MagnatuneSettingsPage::Save() {
@ -131,6 +148,7 @@ void MagnatuneSettingsPage::Save() {
s.setValue("username", ui_->username->text());
s.setValue("password", ui_->password->text());
s.setValue("format", ui_->format->currentIndex());
s.setValue("logged_in", logged_in_);
InternetModel::Service<MagnatuneService>()->ReloadSettings();
}
@ -141,7 +159,3 @@ void MagnatuneSettingsPage::MembershipChanged(int value) {
ui_->login_container->setEnabled(enabled);
ui_->preferences_group->setEnabled(enabled);
}
void MagnatuneSettingsPage::CredentialsChanged() {
credentials_changed_ = true;
}

View File

@ -37,15 +37,19 @@ public:
private slots:
void Login();
void Logout();
void MembershipChanged(int value);
void LoginFinished();
void AuthenticationRequired(QNetworkReply* reply, QAuthenticator* auth);
void CredentialsChanged();
private:
bool credentials_changed_;
void UpdateLoginState();
private:
NetworkAccessManager* network_;
Ui_MagnatuneSettingsPage* ui_;
bool logged_in_;
};
#endif // MAGNATUNESETTINGSPAGE_H

View File

@ -14,12 +14,18 @@
<string>Magnatune</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Account details</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -47,16 +53,6 @@
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>You can listen to Magnatune songs for free without an account. Purchasing a membership removes the messages at the end of each track.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QWidget" name="login_container" native="true">
<property name="enabled">
<bool>false</bool>
@ -166,42 +162,14 @@
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>

View File

@ -98,7 +98,8 @@ void SpotifyServer::HandleMessage(const protobuf::SpotifyMessage& message) {
queued_messages_.clear();
}
emit LoginCompleted(response.success(), QStringFromStdString(response.error()));
emit LoginCompleted(response.success(), QStringFromStdString(response.error()),
response.error_code());
} else if (message.has_playlists_updated()) {
emit PlaylistsUpdated(message.playlists_updated());
} else if (message.has_load_playlist_response()) {

View File

@ -50,7 +50,8 @@ public:
int server_port() const;
signals:
void LoginCompleted(bool success, const QString& error);
void LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void PlaylistsUpdated(const protobuf::Playlists& playlists);
void StarredLoaded(const protobuf::LoadPlaylistResponse& response);

View File

@ -46,7 +46,8 @@ SpotifyService::SpotifyService(InternetModel* parent)
login_task_id_(0),
pending_search_playlist_(NULL),
context_menu_(NULL),
search_delay_(new QTimer(this)) {
search_delay_(new QTimer(this)),
login_state_(LoginState_OtherError) {
// Build the search path for the binary blob.
// Look for one distributed alongside clementine first, then check in the
// user's home directory for any that have been downloaded.
@ -124,24 +125,44 @@ QModelIndex SpotifyService::GetCurrentIndex() {
}
void SpotifyService::Login(const QString& username, const QString& password) {
delete server_;
delete blob_process_;
server_ = NULL;
blob_process_ = NULL;
Logout();
EnsureServerCreated(username, password);
}
void SpotifyService::LoginCompleted(bool success, const QString& error) {
void SpotifyService::LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code) {
if (login_task_id_) {
model()->task_manager()->SetTaskFinished(login_task_id_);
login_task_id_ = 0;
}
login_state_ = LoginState_LoggedIn;
if (!success) {
QMessageBox::warning(NULL, tr("Spotify login error"), error, QMessageBox::Close);
switch (error_code) {
case protobuf::LoginResponse_Error_BadUsernameOrPassword:
login_state_ = LoginState_BadCredentials;
break;
case protobuf::LoginResponse_Error_UserBanned:
login_state_ = LoginState_Banned;
break;
case protobuf::LoginResponse_Error_UserNeedsPremium:
login_state_ = LoginState_NoPremium;
break;
default:
login_state_ = LoginState_OtherError;
}
}
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("login_state", login_state_);
emit LoginFinished(success);
}
@ -155,6 +176,13 @@ void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
}
}
void SpotifyService::ReloadSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
login_state_ = LoginState(s.value("login_state", LoginState_OtherError).toInt());
}
void SpotifyService::EnsureServerCreated(const QString& username,
const QString& password) {
if (server_ && blob_process_) {
@ -164,7 +192,8 @@ void SpotifyService::EnsureServerCreated(const QString& username,
delete server_;
server_ = new SpotifyServer(this);
connect(server_, SIGNAL(LoginCompleted(bool,QString)), SLOT(LoginCompleted(bool,QString)));
connect(server_, SIGNAL(LoginCompleted(bool,QString,protobuf::LoginResponse_Error)),
SLOT(LoginCompleted(bool,QString,protobuf::LoginResponse_Error)));
connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)),
SLOT(PlaylistsUpdated(protobuf::Playlists)));
connect(server_, SIGNAL(InboxLoaded(protobuf::LoadPlaylistResponse)),
@ -593,3 +622,12 @@ void SpotifyService::SyncPlaylistProgress(
void SpotifyService::ShowConfig() {
emit OpenSettingsAtPage(SettingsDialog::Page_Spotify);
}
void SpotifyService::Logout() {
delete server_;
delete blob_process_;
server_ = NULL;
blob_process_ = NULL;
login_state_ = LoginState_OtherError;
}

View File

@ -35,17 +35,29 @@ public:
Role_UserPlaylistIndex = InternetModel::RoleCount,
};
// Values are persisted - don't change.
enum LoginState {
LoginState_LoggedIn = 1,
LoginState_Banned = 2,
LoginState_BadCredentials = 3,
LoginState_NoPremium = 4,
LoginState_OtherError = 5
};
static const char* kServiceName;
static const char* kSettingsGroup;
static const char* kBlobDownloadUrl;
static const int kSearchDelayMsec;
void ReloadSettings();
QStandardItem* CreateRootItem();
void LazyPopulate(QStandardItem* parent);
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
void ItemDoubleClicked(QStandardItem* item);
PlaylistItem::Options playlistitem_options() const;
void Logout();
void Login(const QString& username, const QString& password);
void Search(const QString& text, Playlist* playlist, bool now = false);
Q_INVOKABLE void LoadImage(const QUrl& url);
@ -55,6 +67,9 @@ public:
bool IsBlobInstalled() const;
void InstallBlob();
// Persisted in the settings and updated on each Login().
LoginState login_state() const { return login_state_; }
static void SongFromProtobuf(const protobuf::Track& track, Song* song);
signals:
@ -77,7 +92,8 @@ private:
private slots:
void BlobProcessError(QProcess::ProcessError error);
void LoginCompleted(bool success, const QString& error);
void LoginCompleted(bool success, const QString& error,
protobuf::LoginResponse_Error error_code);
void PlaylistsUpdated(const protobuf::Playlists& response);
void InboxLoaded(const protobuf::LoadPlaylistResponse& response);
void StarredLoaded(const protobuf::LoadPlaylistResponse& response);
@ -122,6 +138,8 @@ private:
int inbox_sync_id_;
int starred_sync_id_;
QMap<int, int> playlist_sync_ids_;
LoginState login_state_;
};
#endif

View File

@ -37,8 +37,6 @@ SpotifySettingsPage::SpotifySettingsPage(SettingsDialog* dialog)
validated_(false)
{
ui_->setupUi(this);
ui_->busy->hide();
ui_->warn_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(16));
setWindowIcon(QIcon(":/icons/svg/spotify.svg"));
@ -48,10 +46,16 @@ SpotifySettingsPage::SpotifySettingsPage(SettingsDialog* dialog)
connect(ui_->download_blob, SIGNAL(clicked()), SLOT(DownloadBlob()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(service_, SIGNAL(LoginFinished(bool)), SLOT(LoginFinished(bool)));
connect(service_, SIGNAL(BlobStateChanged()), SLOT(BlobStateChanged()));
ui_->login_state->AddCredentialField(ui_->username);
ui_->login_state->AddCredentialField(ui_->password);
ui_->login_state->AddCredentialGroup(ui_->account_group);
BlobStateChanged();
}
@ -82,15 +86,12 @@ void SpotifySettingsPage::Login() {
}
if (ui_->username->text() == original_username_ &&
ui_->password->text() == original_password_) {
ui_->password->text() == original_password_ &&
service_->login_state() == SpotifyService::LoginState_LoggedIn) {
return;
}
if (!validated_) {
return;
}
ui_->busy->show();
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
service_->Login(ui_->username->text(), ui_->password->text());
}
@ -104,6 +105,8 @@ void SpotifySettingsPage::Load() {
ui_->username->setText(original_username_);
ui_->password->setText(original_password_);
validated_ = false;
UpdateLoginState();
}
void SpotifySettingsPage::Save() {
@ -112,15 +115,41 @@ void SpotifySettingsPage::Save() {
s.setValue("username", ui_->username->text());
s.setValue("password", ui_->password->text());
InternetModel::Service<SpotifyService>()->ReloadSettings();
}
void SpotifySettingsPage::LoginFinished(bool success) {
validated_ = success;
ui_->busy->hide();
ui_->login->setEnabled(!success);
// Save the settings
Save();
UpdateLoginState();
}
void SpotifySettingsPage::UpdateLoginState() {
const bool logged_in =
service_->login_state() == SpotifyService::LoginState_LoggedIn;
ui_->login_state->SetLoggedIn(logged_in ? LoginStateWidget::LoggedIn
: LoginStateWidget::LoggedOut,
ui_->username->text());
ui_->login_state->SetAccountTypeVisible(!logged_in);
switch (service_->login_state()) {
case SpotifyService::LoginState_NoPremium:
ui_->login_state->SetAccountTypeText(tr("You do not have a Spotify Premium account."));
break;
case SpotifyService::LoginState_Banned:
case SpotifyService::LoginState_BadCredentials:
ui_->login_state->SetAccountTypeText(tr("Your username or password was incorrect."));
break;
default:
ui_->login_state->SetAccountTypeText(tr("A Spotify Premium account is required."));
break;
}
}
void SpotifySettingsPage::Logout() {
service_->Logout();
UpdateLoginState();
}

View File

@ -41,6 +41,10 @@ public slots:
private slots:
void Login();
void LoginFinished(bool success);
void Logout();
private:
void UpdateLoginState();
private:
NetworkAccessManager* network_;

View File

@ -14,50 +14,15 @@
<string>Spotify</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item>
<widget class="QGroupBox" name="account_group">
<property name="title">
<string>Account details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="warn_icon">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>A Spotify Premium account is required.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="login_container" native="true">
<property name="enabled">
@ -206,42 +171,14 @@
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>

View File

@ -30,11 +30,14 @@ class SettingsPage : public QWidget {
public:
SettingsPage(SettingsDialog* dialog);
// Return false to grey out the page's item in the list.
virtual bool IsEnabled() const { return true; }
// Load is called when the dialog is shown, Save when the user clicks OK.
virtual void Load() = 0;
virtual void Save() = 0;
// The dialog that this page belongs to.
SettingsDialog* dialog() const { return dialog_; }
signals:

View File

@ -39,6 +39,7 @@ void BusyIndicator::Init(const QString& text) {
icon->setMinimumSize(16, 16);
label_->setWordWrap(true);
label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
@ -63,7 +64,10 @@ void BusyIndicator::hideEvent(QHideEvent*) {
void BusyIndicator::set_text(const QString& text) {
label_->setText(text);
if (text.isEmpty())
label_->hide();
label_->setVisible(!text.isEmpty());
}
QString BusyIndicator::text() const {
return label_->text();
}

View File

@ -24,6 +24,7 @@ class QMovie;
class BusyIndicator : public QWidget {
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE set_text)
public:
explicit BusyIndicator(const QString& text, QWidget* parent = 0);

View File

@ -0,0 +1,104 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "loginstatewidget.h"
#include "ui_loginstatewidget.h"
#include "ui/iconloader.h"
#include <QKeyEvent>
LoginStateWidget::LoginStateWidget(QWidget* parent)
: QWidget(parent),
ui_(new Ui_LoginStateWidget)
{
ui_->setupUi(this);
ui_->signed_in->hide();
ui_->account_type->hide();
ui_->busy->hide();
ui_->sign_out->setIcon(IconLoader::Load("list-remove"));
QFont bold_font(font());
bold_font.setBold(true);
ui_->signed_out_label->setFont(bold_font);
connect(ui_->sign_out, SIGNAL(clicked()), SLOT(Logout()));
}
LoginStateWidget::~LoginStateWidget() {
delete ui_;
}
void LoginStateWidget::Logout() {
SetLoggedIn(LoggedOut);
emit LogoutClicked();
}
void LoginStateWidget::SetAccountTypeText(const QString& text) {
ui_->account_type_label->setText(text);
}
void LoginStateWidget::SetAccountTypeVisible(bool visible) {
ui_->account_type->setVisible(visible);
}
void LoginStateWidget::SetLoggedIn(State state, const QString& account_name) {
ui_->signed_in->setVisible(state == LoggedIn);
ui_->signed_out->setVisible(state != LoggedIn);
ui_->busy->setVisible(state == LoginInProgress);
if (account_name.isEmpty())
ui_->signed_in_label->setText("<b>" + tr("You are signed in.") + "</b>");
else
ui_->signed_in_label->setText(tr("You are signed in as %1.").arg("<b>" + account_name + "</b>"));
foreach (QWidget* widget, credential_groups_) {
widget->setVisible(state != LoggedIn);
widget->setEnabled(state != LoginInProgress);
}
}
void LoginStateWidget::HideLoggedInState() {
ui_->signed_in->hide();
ui_->signed_out->hide();
}
void LoginStateWidget::AddCredentialField(QWidget* widget) {
widget->installEventFilter(this);
credential_fields_ << widget;
}
void LoginStateWidget::AddCredentialGroup(QWidget* widget) {
credential_groups_ << widget;
}
bool LoginStateWidget::eventFilter(QObject* object, QEvent* event) {
if (!credential_fields_.contains(object))
return QWidget::eventFilter(object, event);
if (event->type() == QEvent::KeyPress) {
QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
if (key_event->key() == Qt::Key_Enter ||
key_event->key() == Qt::Key_Return) {
emit LoginClicked();
return true;
}
}
return QWidget::eventFilter(object, event);
}

View File

@ -0,0 +1,74 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LOGINSTATEWIDGET_H
#define LOGINSTATEWIDGET_H
#include <QWidget>
class Ui_LoginStateWidget;
class LoginStateWidget : public QWidget {
Q_OBJECT
public:
LoginStateWidget(QWidget* parent = 0);
~LoginStateWidget();
enum State {
LoggedIn,
LoginInProgress,
LoggedOut
};
// Installs an event handler on the field so that pressing enter will emit
// LoginClicked() instead of doing the default action (closing the dialog).
void AddCredentialField(QWidget* widget);
// This widget (usually a QGroupBox) will be hidden when SetLoggedIn(true)
// is called.
void AddCredentialGroup(QWidget* widget);
// QObject
bool eventFilter(QObject* object, QEvent* event);
public slots:
// Changes the "You are logged in/out" label, shows/hides any QGroupBoxes
// added with AddCredentialGroup.
void SetLoggedIn(State state, const QString& account_name = QString::null);
// Hides the "You are logged in/out" label completely.
void HideLoggedInState();
void SetAccountTypeText(const QString& text);
void SetAccountTypeVisible(bool visible);
signals:
void LogoutClicked();
void LoginClicked();
private slots:
void Logout();
private:
Ui_LoginStateWidget* ui_;
QList<QObject*> credential_fields_;
QList<QWidget*> credential_groups_;
};
#endif // LOGINSTATEWIDGET_H

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoginStateWidget</class>
<widget class="QWidget" name="LoginStateWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>526</width>
<height>187</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="signed_out" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="signed_out_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>You are not signed in.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="signed_in" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/22x22/dialog-ok-apply.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="signed_in_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sign_out">
<property name="text">
<string>Sign out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="account_type" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/22x22/dialog-warning.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="account_type_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="busy" native="true">
<property name="text" stdset="0">
<string>Signing in...</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections/>
</ui>