RR User Interface Improvements, including Premium notices (#992)

* RR UX Improvements, including Premium notices

* Fixing Error handling bug in LoginDialog
This commit is contained in:
Robert Scullin
2020-11-02 03:40:30 -05:00
committed by GitHub
parent 88f124af9d
commit 67702096d7
6 changed files with 363 additions and 42 deletions

View File

@@ -49,6 +49,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.ConnectException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
/**
* Login dialog for radioreference.com with support for testing connection to the service.
@@ -66,6 +69,7 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
private Button mTestConnectionButton;
private IconNode mTestPassIcon;
private IconNode mTestFailIcon;
private IconNode mTextExpiredIcon;
/**
* Constructs an instance.
@@ -166,6 +170,10 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
getTestFailIcon().setVisible(false);
GridPane.setConstraints(getTestFailIcon(), 2, 4);
mGridPane.getChildren().add(getTestFailIcon());
getTestExpiredIcon().setVisible(false);
GridPane.setConstraints(getTestExpiredIcon(), 2, 4);
mGridPane.getChildren().add(getTestExpiredIcon());
}
return mGridPane;
@@ -275,6 +283,7 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
getTestConnectionButton().setDisable(true);
getTestPassIcon().setVisible(false);
getTestFailIcon().setVisible(false);
getTestExpiredIcon().setVisible(false);
String userName = getUserNameText().getText();
String password = getPasswordField().isVisible() ? getPasswordField().getText() : getPasswordText().getText();
@@ -307,13 +316,56 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
public void run()
{
boolean success = false;
RadioReference.LoginStatus loginStatus;
try
{
success = RadioReference.testConnection(userName, password);
loginStatus = RadioReference.testConnectionWithExp(userName, password);
if(loginStatus == RadioReference.LoginStatus.VALID_PREMIUM)
{
getTestPassIcon().setVisible(true);
getTestFailIcon().setVisible(false);
getTestExpiredIcon().setVisible(false);
}
else if (loginStatus == RadioReference.LoginStatus.EXPIRED_PREMIUM)
{
getTestPassIcon().setVisible(false);
getTestFailIcon().setVisible(false);
getTestExpiredIcon().setVisible(true);
Alert alert = new Alert(Alert.AlertType.WARNING, "You do not have a valid Radio Reference Premium Subscription (it may have expired).", ButtonType.OK);
alert.setHeaderText("Expired Premium Account");
alert.setTitle("Radio Reference Account issue");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
}
else if (loginStatus == RadioReference.LoginStatus.INVALID_LOGIN)
{
getTestPassIcon().setVisible(false);
getTestFailIcon().setVisible(true);
getTestExpiredIcon().setVisible(false);
Alert alert = new Alert(Alert.AlertType.ERROR, "Please verify username and password", ButtonType.OK);
alert.setHeaderText("Login Failed");
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Login failed. Invalid username or password. Can't login to radioreference.com");
}
else
{
// Only way to get here is via an exception
}
}
catch(RadioReferenceException rre)
{
// Set an error state
getTestPassIcon().setVisible(false);
getTestFailIcon().setVisible(true);
getTestExpiredIcon().setVisible(false);
if(rre.getCause() instanceof ConnectException)
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please check network or radio reference availability", ButtonType.OK);
@@ -327,24 +379,12 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
{
Fault fault = rre.getFault();
if(fault.getFaultCode() != null && fault.getFaultCode().contentEquals("AUTH"))
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please verify username and password", ButtonType.OK);
alert.setHeaderText("Login Failed");
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Login failed. Invalid username or password. Can't login to radioreference.com");
}
else
{
Alert alert = new Alert(Alert.AlertType.ERROR, fault.getFaultString(), ButtonType.OK);
alert.setHeaderText(fault.getFaultCode());
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Test failed. Fault [" + fault.toString() + "] Can't login to radioreference.com");
}
Alert alert = new Alert(Alert.AlertType.ERROR, fault.getFaultString(), ButtonType.OK);
alert.setHeaderText(fault.getFaultCode());
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Test failed. Fault [" + fault.toString() + "] Can't login to radioreference.com");
}
else
{
@@ -357,8 +397,7 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
}
}
getTestPassIcon().setVisible(success);
getTestFailIcon().setVisible(!success);
getTestConnectionButton().setDisable(false);
}
});
@@ -392,4 +431,16 @@ public class LoginDialog extends Dialog<AuthorizationInformation>
return mTestFailIcon;
}
private IconNode getTestExpiredIcon()
{
if(mTextExpiredIcon == null)
{
mTextExpiredIcon = new IconNode(FontAwesome.EXCLAMATION_TRIANGLE);
mTextExpiredIcon.setIconSize(32);
mTextExpiredIcon.setFill(Color.ORANGERED);
}
return mTextExpiredIcon;
}
}

View File

@@ -35,6 +35,7 @@ import io.github.dsheirer.rrapi.type.StateInfo;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@@ -43,12 +44,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.util.StringConverter;
import jiconfont.icons.font_awesome.FontAwesome;
@@ -67,10 +63,12 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
private PlaylistManager mPlaylistManager;
private VBox mTopBox;
private HBox mCredentialsBox;
private Label mRRStatusText;
private TextField mUserNameText;
private TextField mAccountExpiresText;
private IconNode mTestPassIcon;
private IconNode mTestFailIcon;
private IconNode mTestExpiredIcon;
private Button mLoginButton;
private GridPane mComboBoxPane;
private ComboBox<Country> mCountryComboBox;
@@ -99,13 +97,13 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
login();
if(mRadioReference.availableProperty().get())
if(mRadioReference.premiumAccountProperty().get())
{
refreshCountries();
}
//Refresh the countries combo box once we're logged on, if not already
mRadioReference.availableProperty().addListener((observable, oldValue, newValue) -> {
mRadioReference.premiumAccountProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
refreshCountries();
@@ -173,6 +171,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
mTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
mTabPane.getTabs().addAll(getCountySystemTab(), getStateSystemTab(), getCountyAgencyTab(),
getStateAgencyTab(), getNationalAgencyTab());
mTabPane.disableProperty().bind(mRadioReference.premiumAccountProperty().not());
}
return mTabPane;
@@ -395,6 +394,8 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
GridPane.setConstraints(getCountyComboBox(), 2, 1);
GridPane.setHgrow(getCountyComboBox(), Priority.ALWAYS);
mComboBoxPane.getChildren().add(getCountyComboBox());
mComboBoxPane.disableProperty().bind(mRadioReference.premiumAccountProperty().not());
}
return mComboBoxPane;
@@ -407,18 +408,43 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
mCredentialsBox = new HBox();
mCredentialsBox.setAlignment(Pos.CENTER_RIGHT);
mCredentialsBox.setSpacing(5.0);
Region leftFiller = new Region();
HBox.setHgrow(leftFiller, Priority.ALWAYS);
mCredentialsBox.getChildren().add(leftFiller);
mCredentialsBox.getChildren().add(getRRPremiumReqText());
Region rightFiller = new Region();
HBox.setHgrow(rightFiller, Priority.ALWAYS);
mCredentialsBox.getChildren().add(rightFiller);
mCredentialsBox.getChildren().add(new Label("User Name:"));
mCredentialsBox.getChildren().add(getUserNameText());
mCredentialsBox.getChildren().add(new Label("Expires:"));
mCredentialsBox.getChildren().add(getAccountExpiresText());
mCredentialsBox.getChildren().add(getTestFailIcon());
mCredentialsBox.getChildren().add(getTestPassIcon());
mCredentialsBox.getChildren().add(getTestExpiredIcon());
mCredentialsBox.getChildren().add(getLoginButton());
}
return mCredentialsBox;
}
private Label getRRPremiumReqText()
{
if(mRRStatusText == null)
{
mRRStatusText = new Label();
mRRStatusText.setText("A premium Radio Reference subscription is required to import systems.");
mRRStatusText.visibleProperty().bind(
Bindings.and(
mRadioReference.availableProperty(),
mRadioReference.premiumAccountProperty().not()));
mRRStatusText.setTextFill(Color.ORANGERED);
mRRStatusText.setStyle("-fx-font-weight: bold;"); // I guess
}
return mRRStatusText;
}
private TextField getUserNameText()
{
if(mUserNameText == null)
@@ -449,7 +475,12 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
{
mTestPassIcon = new IconNode(FontAwesome.CHECK);
mTestPassIcon.setFill(Color.GREEN);
mTestPassIcon.visibleProperty().bind(mRadioReference.availableProperty());
mTestPassIcon.setVisible(false);
// Need to have a valid account, _and_ have premium
mTestPassIcon.visibleProperty().bind(
Bindings.and(
mRadioReference.availableProperty(),
mRadioReference.premiumAccountProperty()));
}
return mTestPassIcon;
@@ -461,12 +492,29 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
{
mTestFailIcon = new IconNode(FontAwesome.TIMES);
mTestFailIcon.setFill(Color.RED);
mTestFailIcon.setVisible(false);
mTestFailIcon.visibleProperty().bind(mRadioReference.availableProperty().not());
}
return mTestFailIcon;
}
private IconNode getTestExpiredIcon()
{
if(mTestExpiredIcon == null)
{
mTestExpiredIcon = new IconNode(FontAwesome.EXCLAMATION_TRIANGLE);
mTestExpiredIcon.setFill(Color.ORANGERED);
// Need to be signed in, but _not_ have a valid premium subscription
mTestExpiredIcon.visibleProperty().bind(
Bindings.and(
mRadioReference.availableProperty(),
mRadioReference.premiumAccountProperty().not()));
}
return mTestExpiredIcon;
}
private Button getLoginButton()
{
if(mLoginButton == null)

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.gui.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.type.CountyInfo;
import io.github.dsheirer.rrapi.type.Flavor;
import io.github.dsheirer.rrapi.type.Site;
@@ -229,6 +230,18 @@ public class SystemEditor extends VBox
private String getType(System system)
{
if(mRadioReferenceDecoder == null)
{
try
{
initRadioReferenceDecoder();
}
catch (Throwable t)
{
mLog.error("Error retrieving system information", t);
}
}
if(mRadioReferenceDecoder != null)
{
Type type = mRadioReferenceDecoder.getType(system);
@@ -268,12 +281,7 @@ public class SystemEditor extends VBox
{
if(mRadioReferenceDecoder == null)
{
Map<Integer,Type> typeMap = mRadioReference.getService().getTypesMap();
Map<Integer,Flavor> flavorMap = mRadioReference.getService().getFlavorsMap();
Map<Integer,Voice> voiceMap = mRadioReference.getService().getVoicesMap();
Map<Integer,Tag> tagMap = mRadioReference.getService().getTagsMap();
mRadioReferenceDecoder = new RadioReferenceDecoder(mUserPreferences, typeMap, flavorMap,
voiceMap, tagMap);
initRadioReferenceDecoder();
}
//Query and load the system view editor first
@@ -320,6 +328,20 @@ public class SystemEditor extends VBox
}
}
/**
* Initializes the Radio Reference Decoder
* @throws RadioReferenceException
*/
private void initRadioReferenceDecoder() throws RadioReferenceException
{
Map<Integer, Type> typeMap = mRadioReference.getService().getTypesMap();
Map<Integer, Flavor> flavorMap = mRadioReference.getService().getFlavorsMap();
Map<Integer, Voice> voiceMap = mRadioReference.getService().getVoicesMap();
Map<Integer, Tag> tagMap = mRadioReference.getService().getTagsMap();
mRadioReferenceDecoder = new RadioReferenceDecoder(mUserPreferences, typeMap, flavorMap,
voiceMap, tagMap);
}
public class SystemListCell extends ListCell<System>
{
private HBox mHBox;

View File

@@ -22,18 +22,22 @@
package io.github.dsheirer.service.radioreference;
import com.google.common.annotations.VisibleForTesting;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.RadioReferenceService;
import io.github.dsheirer.rrapi.response.Fault;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.rrapi.type.UserInfo;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Scanner;
/**
@@ -51,7 +55,19 @@ public class RadioReference
private StringProperty mPassword = new SimpleStringProperty();
private StringProperty mAccountExpiresProperty = new SimpleStringProperty();
private BooleanProperty mAvailable = new SimpleBooleanProperty();
private BooleanProperty mPremiumAccount = new SimpleBooleanProperty();
private LoginStatus mLoginStatus = LoginStatus.UNKNOWN;
/**
* Enum to determine "validity" of a RR Login
*/
public enum LoginStatus
{
UNKNOWN,
INVALID_LOGIN,
EXPIRED_PREMIUM,
VALID_PREMIUM
}
/**
* Constructs an instance of the radio reference service
@@ -70,6 +86,22 @@ public class RadioReference
return mAvailable;
}
/**
* Externally monitored and controlled status indicator for a Premium RR Account
*/
public BooleanProperty premiumAccountProperty()
{
return mPremiumAccount;
}
/**
* Current login state / validity
*/
public LoginStatus getLoginStatus()
{
return mLoginStatus;
}
/**
* User name for radio reference account
*/
@@ -140,7 +172,94 @@ public class RadioReference
}
/**
* Login with credentials
* Tests the connection to radio reference service using the provided credentials.
* @param userName for radio reference account
* @param password for radio reference account
* @return The state and "validity" of the account
* @throws RadioReferenceException if there are any issues with the connection
*/
public static LoginStatus testConnectionWithExp(String userName, String password) throws RadioReferenceException
{
AuthorizationInformation credentials = new AuthorizationInformation(SDRTRUNK_APP_KEY, userName, password);
try
{
RadioReferenceService service = new RadioReferenceService(credentials);
UserInfo ui = service.getUserInfo();
return CheckExpDate(ui.getExpirationDate());
}
catch (RadioReferenceException rre)
{
if (rre.hasFault())
{
Fault fault = rre.getFault();
if (fault.getFaultCode() != null && fault.getFaultCode().contentEquals("AUTH"))
{
return LoginStatus.INVALID_LOGIN;
}
else
{
throw rre;
}
}
else
{
throw rre;
}
}
}
/**
* Determines the account status (Premium) based on the given expiration date
* @param RRExpirationString The Premium Expiration string returned from RR
* @return Account Login Status
*/
@VisibleForTesting
protected static LoginStatus CheckExpDate(String RRExpirationString)
{
try
{
SimpleDateFormat formatter = new SimpleDateFormat("MM-dd-yyyy", Locale.ENGLISH);
Date RRExpDate = formatter.parse(RRExpirationString);
// We want to err on the side of the user getting an error with an invalid subscription,
// rather than the user being prevented from using their valid, but shortly expiring, subscription.
// Get the current Date and subtract two days (due to no >=, only > ),
// as I am not sure how timezones work for RR and the end user.
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DAY_OF_MONTH, -2);
if(RRExpDate.after(c.getTime()))
{
return LoginStatus.VALID_PREMIUM;
}
else
{
return LoginStatus.EXPIRED_PREMIUM;
}
}
catch (ParseException e)
{
// This doesn't have to be a date; it can also be a string
// If we get anything we can't parse, assume it's a string and valid
// Accounts that have never had a valid subscription return a year of 1969
// See https://wiki.radioreference.com/index.php/RadioReference.com_Web_Service3.1#Versions :
// Version 5 - Issued a fix for the getUserData method which returned a subscription expiration
// date in 1969 for admins and feed providers. Now returns "Never - Feed Provider" and "Never - Admin"
// in string format.
return LoginStatus.VALID_PREMIUM;
}
}
/**
* Login with credentials, and update properties based on the login status
*/
private void login()
{
@@ -148,12 +267,31 @@ public class RadioReference
{
UserInfo userInfo = getService().getUserInfo();
accountExpiresProperty().setValue(userInfo.getExpirationDate());
availableProperty().set(true);
mLoginStatus = CheckExpDate(userInfo.getExpirationDate());
if(mLoginStatus == LoginStatus.VALID_PREMIUM)
{
availableProperty().set(true);
premiumAccountProperty().set(true);
}
else if (mLoginStatus == LoginStatus.EXPIRED_PREMIUM)
{
availableProperty().set(true);
premiumAccountProperty().set(false);
}
else
{
availableProperty().set(false);
premiumAccountProperty().set(false);
}
}
catch(RadioReferenceException rre)
{
accountExpiresProperty().setValue(null);
mLoginStatus = LoginStatus.UNKNOWN;
availableProperty().set(false);
premiumAccountProperty().set(false);
}
}