Simplified gpodder login process

This commit is contained in:
ByteHamster 2020-10-03 12:32:05 +02:00
parent d1426f9774
commit 7b8208a2a7
15 changed files with 689 additions and 729 deletions

View File

@ -292,19 +292,6 @@
</activity>
<activity
android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
android:configChanges="orientation"
android:label="@string/gpodnet_auth_label">
<intent-filter>
<action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
</activity>
<receiver android:name=".receiver.ConnectivityActionReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

View File

@ -1,395 +0,0 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ViewFlipper;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
/**
* Guides the user through the authentication process
* Step 1: Request username and password from user
* Step 2: Choose device from a list of available devices or create a new one
* Step 3: Choose from a list of actions
*/
public class GpodnetAuthenticationActivity extends AppCompatActivity {
private static final String TAG = "GpodnetAuthActivity";
private ViewFlipper viewFlipper;
private static final int STEP_DEFAULT = -1;
private static final int STEP_LOGIN = 0;
private static final int STEP_DEVICE = 1;
private static final int STEP_FINISH = 2;
private int currentStep = -1;
private GpodnetService service;
private volatile String username;
private volatile String password;
private volatile GpodnetDevice selectedDevice;
private View[] views;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.gpodnetauth_activity);
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
viewFlipper = findViewById(R.id.viewflipper);
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
views = new View[]{
inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false),
inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false),
inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false)
};
for (View view : views) {
viewFlipper.addView(view);
}
advance();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void setupLoginView(View view) {
final EditText username = view.findViewById(R.id.etxtUsername);
final EditText password = view.findViewById(R.id.etxtPassword);
final Button login = view.findViewById(R.id.butLogin);
final TextView txtvError = view.findViewById(R.id.txtvError);
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
password.setOnEditorActionListener((v, actionID, event) ->
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String usernameStr = username.getText().toString();
final String passwordStr = password.getText().toString();
if (usernameHasUnwantedChars(usernameStr)) {
txtvError.setText(R.string.gpodnetsync_username_characters_error);
txtvError.setVisibility(View.VISIBLE);
return;
}
if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials");
AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() {
volatile Exception exception;
@Override
protected void onPreExecute() {
super.onPreExecute();
login.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
// hide the keyboard
InputMethodManager inputManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(login.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
login.setEnabled(true);
progressBar.setVisibility(View.GONE);
if (exception == null) {
advance();
} else {
txtvError.setText(exception.getCause().getMessage());
txtvError.setVisibility(View.VISIBLE);
}
}
@Override
protected Void doInBackground(GpodnetService... params) {
try {
params[0].authenticate(usernameStr, passwordStr);
GpodnetAuthenticationActivity.this.username = usernameStr;
GpodnetAuthenticationActivity.this.password = passwordStr;
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
}
return null;
}
};
authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service);
}
});
}
private void setupDeviceView(View view) {
final EditText deviceID = view.findViewById(R.id.etxtDeviceID);
final EditText caption = view.findViewById(R.id.etxtCaption);
final Button createNewDevice = view.findViewById(R.id.butCreateNewDevice);
final Button chooseDevice = view.findViewById(R.id.butChooseExistingDevice);
final TextView txtvError = view.findViewById(R.id.txtvError);
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
final Spinner spinnerDevices = view.findViewById(R.id.spinnerChooseDevice);
// load device list
final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>();
new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
chooseDevice.setEnabled(false);
spinnerDevices.setEnabled(false);
createNewDevice.setEnabled(false);
}
@Override
protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
super.onPostExecute(gpodnetDevices);
if (gpodnetDevices != null) {
List<String> deviceNames = new ArrayList<>();
for (GpodnetDevice device : gpodnetDevices) {
deviceNames.add(device.getCaption());
}
spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this,
android.R.layout.simple_spinner_dropdown_item, deviceNames));
spinnerDevices.setEnabled(true);
if (!deviceNames.isEmpty()) {
chooseDevice.setEnabled(true);
}
devices.set(gpodnetDevices);
deviceID.setText(generateDeviceID(gpodnetDevices));
createNewDevice.setEnabled(true);
}
}
@Override
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
try {
return params[0].getDevices();
} catch (GpodnetServiceException e) {
e.printStackTrace();
return null;
}
}
}.execute(service);
createNewDevice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) {
final String deviceStr = deviceID.getText().toString();
final String captionStr = caption.getText().toString();
new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
private volatile Exception exception;
@Override
protected void onPreExecute() {
super.onPreExecute();
createNewDevice.setEnabled(false);
chooseDevice.setEnabled(false);
progBarCreateDevice.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
}
@Override
protected void onPostExecute(GpodnetDevice result) {
super.onPostExecute(result);
createNewDevice.setEnabled(true);
chooseDevice.setEnabled(true);
progBarCreateDevice.setVisibility(View.GONE);
if (exception == null) {
selectedDevice = result;
advance();
} else {
txtvError.setText(exception.getMessage());
txtvError.setVisibility(View.VISIBLE);
}
}
@Override
protected GpodnetDevice doInBackground(GpodnetService... params) {
try {
params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
}
return null;
}
}.execute(service);
}
}
});
chooseDevice.setOnClickListener(v -> {
final int position = spinnerDevices.getSelectedItemPosition();
if (position != AdapterView.INVALID_POSITION) {
selectedDevice = devices.get().get(position);
advance();
}
});
}
private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) {
// devices names must be of a certain form:
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
// This is more restrictive than needed, but I think it makes for more readable names.
String baseId = Build.MODEL.replaceAll("\\W", "");
String id = baseId;
int num = 0;
while (isDeviceWithIdInList(id, gpodnetDevices)) {
id = baseId + "_" + num;
num++;
}
return id;
}
private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) {
if (gpodnetDevices == null) {
return false;
}
for (GpodnetDevice device : gpodnetDevices) {
if (device.getId().equals(id)) {
return true;
}
}
return false;
}
private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) {
String text = deviceID.getText().toString();
if (text.length() == 0) {
txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
txtvError.setVisibility(View.VISIBLE);
return false;
} else if (caption.length() == 0) {
txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty);
txtvError.setVisibility(View.VISIBLE);
return false;
} else {
if (devices != null) {
if (isDeviceWithIdInList(text, devices)) {
txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
txtvError.setVisibility(View.VISIBLE);
return false;
}
txtvError.setVisibility(View.GONE);
return true;
}
return true;
}
}
private void setupFinishView(View view) {
final Button sync = view.findViewById(R.id.butSyncNow);
final Button back = view.findViewById(R.id.butGoMainscreen);
sync.setOnClickListener(v -> {
finish();
SyncService.sync(getApplicationContext());
});
back.setOnClickListener(v -> {
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
});
}
private void writeLoginCredentials() {
if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials");
GpodnetPreferences.setUsername(username);
GpodnetPreferences.setPassword(password);
GpodnetPreferences.setDeviceID(selectedDevice.getId());
}
private void advance() {
if (currentStep < STEP_FINISH) {
View view = views[currentStep + 1];
if (currentStep == STEP_DEFAULT) {
setupLoginView(view);
} else if (currentStep == STEP_LOGIN) {
if (username == null || password == null) {
throw new IllegalStateException("Username and password must not be null here");
} else {
setupDeviceView(view);
}
} else if (currentStep == STEP_DEVICE) {
if (selectedDevice == null) {
throw new IllegalStateException("Device must not be null here");
} else {
writeLoginCredentials();
setupFinishView(view);
}
}
if (currentStep != STEP_DEFAULT) {
viewFlipper.showNext();
}
currentStep++;
} else {
finish();
}
}
private boolean usernameHasUnwantedChars(String username) {
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
Matcher containsUnwantedChars = special.matcher(username);
return containsUnwantedChars.find();
}
}

View File

@ -1,59 +0,0 @@
package de.danoeh.antennapod.dialog;
import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import android.text.Editable;
import android.text.InputType;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
/**
* Creates a dialog that lets the user change the hostname for the gpodder.net service.
*/
public class GpodnetSetHostnameDialog {
private GpodnetSetHostnameDialog(){}
private static final String TAG = "GpodnetSetHostnameDialog";
public static AlertDialog createDialog(final Context context) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
final EditText et = new EditText(context);
et.setText(GpodnetPreferences.getHostname());
et.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
dialog.setTitle(R.string.pref_gpodnet_sethostname_title)
.setView(setupContentView(context, et))
.setPositiveButton(R.string.confirm_label, (dialog1, which) -> {
final Editable e = et.getText();
if (e != null) {
GpodnetPreferences.setHostname(e.toString());
}
dialog1.dismiss();
})
.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel())
.setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> {
GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
dialog1.dismiss();
})
.setCancelable(true);
return dialog.show();
}
private static View setupContentView(Context context, EditText et) {
LinearLayout ll = new LinearLayout(context);
ll.addView(et);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) et.getLayoutParams();
if (params != null) {
params.setMargins(8, 8, 8, 8);
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
return ll;
}
}

View File

@ -0,0 +1,322 @@
package de.danoeh.antennapod.fragment.preferences;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.ViewFlipper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputLayout;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.core.util.FileNameGenerator;
import de.danoeh.antennapod.core.util.IntentUtils;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Guides the user through the authentication process.
*/
public class GpodderAuthenticationFragment extends DialogFragment {
public static final String TAG = "GpodnetAuthActivity";
private ViewFlipper viewFlipper;
private static final int STEP_DEFAULT = -1;
private static final int STEP_HOSTNAME = 0;
private static final int STEP_LOGIN = 1;
private static final int STEP_DEVICE = 2;
private static final int STEP_FINISH = 3;
private int currentStep = -1;
private GpodnetService service;
private volatile String username;
private volatile String password;
private volatile GpodnetDevice selectedDevice;
private List<GpodnetDevice> devices;
private List<List<String>> synchronizedDevices;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST);
dialog.setNegativeButton(R.string.cancel_label, null);
dialog.setCancelable(false);
this.setCancelable(false);
View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null);
viewFlipper = root.findViewById(R.id.viewflipper);
advance();
dialog.setView(root);
return dialog.create();
}
private void setupHostView(View view) {
final Button selectHost = view.findViewById(R.id.chooseHostButton);
final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup);
final EditText serverUrlText = view.findViewById(R.id.serverUrlText);
if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHostname())) {
serverUrlText.setText(GpodnetPreferences.getHostname());
}
final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput);
serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE);
});
selectHost.setOnClickListener(v -> {
if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) {
GpodnetPreferences.setHostname(serverUrlText.getText().toString());
} else {
GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
}
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
getDialog().setTitle(GpodnetPreferences.getHostname());
advance();
});
}
private void setupLoginView(View view) {
final EditText username = view.findViewById(R.id.etxtUsername);
final EditText password = view.findViewById(R.id.etxtPassword);
final Button login = view.findViewById(R.id.butLogin);
final TextView txtvError = view.findViewById(R.id.credentialsError);
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
final TextView createAccount = view.findViewById(R.id.createAccountButton);
createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/"));
password.setOnEditorActionListener((v, actionID, event) ->
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
login.setOnClickListener(v -> {
final String usernameStr = username.getText().toString();
final String passwordStr = password.getText().toString();
if (usernameHasUnwantedChars(usernameStr)) {
txtvError.setText(R.string.gpodnetsync_username_characters_error);
txtvError.setVisibility(View.VISIBLE);
return;
}
login.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
InputMethodManager inputManager = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
Completable.fromAction(() -> {
service.authenticate(usernameStr, passwordStr);
devices = service.getDevices();
synchronizedDevices = service.getSynchronizedDevices();
GpodderAuthenticationFragment.this.username = usernameStr;
GpodderAuthenticationFragment.this.password = passwordStr;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
login.setEnabled(true);
progressBar.setVisibility(View.GONE);
advance();
}, error -> {
login.setEnabled(true);
progressBar.setVisibility(View.GONE);
txtvError.setText(error.getCause().getMessage());
txtvError.setVisibility(View.VISIBLE);
});
});
}
private void setupDeviceView(View view) {
final EditText deviceName = view.findViewById(R.id.deviceName);
final LinearLayout deviceGroups = view.findViewById(R.id.deviceGroups);
deviceName.setText(generateDeviceName());
MaterialButton newGroupButton = view.findViewById(R.id.startNewGroupButton);
newGroupButton.setOnClickListener(v -> createDevice(view, null));
for (List<String> syncGroup : synchronizedDevices) {
StringBuilder devicesString = new StringBuilder();
for (int i = 0; i < syncGroup.size(); i++) {
String deviceId = syncGroup.get(i);
devicesString.append(findDevice(deviceId).getCaption());
if (i != syncGroup.size() - 1) {
devicesString.append("\n");
}
}
View groupView = View.inflate(getContext(), R.layout.gpodnetauth_device_group, null);
((TextView) groupView.findViewById(R.id.groupContents)).setText(devicesString.toString());
groupView.findViewById(R.id.selectGroupButton).setOnClickListener(v -> createDevice(view, syncGroup));
deviceGroups.addView(groupView);
}
}
private void createDevice(View view, List<String> group) {
final EditText deviceName = view.findViewById(R.id.deviceName);
final TextView txtvError = view.findViewById(R.id.deviceSelectError);
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
final LinearLayout deviceGroups = view.findViewById(R.id.deviceGroups);
final MaterialButton newGroupButton = view.findViewById(R.id.startNewGroupButton);
String deviceNameStr = deviceName.getText().toString();
if (isDeviceInList(deviceNameStr)) {
return;
}
deviceGroups.setVisibility(View.GONE);
progBarCreateDevice.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
newGroupButton.setVisibility(View.GONE);
deviceName.setEnabled(false);
Observable.fromCallable(() -> {
String deviceId = generateDeviceId(deviceNameStr);
service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE);
if (group != null) {
List<String> newGroup = new ArrayList<>(group);
newGroup.add(deviceId);
service.linkDevices(newGroup);
}
return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(device -> {
progBarCreateDevice.setVisibility(View.GONE);
selectedDevice = device;
advance();
}, error -> {
deviceName.setEnabled(true);
newGroupButton.setVisibility(View.VISIBLE);
deviceGroups.setVisibility(View.VISIBLE);
progBarCreateDevice.setVisibility(View.GONE);
txtvError.setText(error.getMessage());
txtvError.setVisibility(View.VISIBLE);
});
}
private String generateDeviceName() {
String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL);
String name = baseName;
int num = 1;
while (isDeviceInList(name)) {
name = baseName + " (" + num + ")";
num++;
}
return name;
}
private String generateDeviceId(String name) {
// devices names must be of a certain form:
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US);
}
private boolean isDeviceInList(String name) {
if (devices == null) {
return false;
}
String id = generateDeviceId(name);
for (GpodnetDevice device : devices) {
if (device.getId().equals(id) || device.getCaption().equals(name)) {
return true;
}
}
return false;
}
private GpodnetDevice findDevice(String id) {
if (devices == null) {
return null;
}
for (GpodnetDevice device : devices) {
if (device.getId().equals(id)) {
return device;
}
}
return null;
}
private void setupFinishView(View view) {
final Button sync = view.findViewById(R.id.butSyncNow);
sync.setOnClickListener(v -> {
dismiss();
SyncService.sync(getContext());
});
}
private void writeLoginCredentials() {
GpodnetPreferences.setUsername(username);
GpodnetPreferences.setPassword(password);
GpodnetPreferences.setDeviceID(selectedDevice.getId());
}
private void advance() {
if (currentStep < STEP_FINISH) {
View view = viewFlipper.getChildAt(currentStep + 1);
if (currentStep == STEP_DEFAULT) {
setupHostView(view);
} else if (currentStep == STEP_HOSTNAME) {
setupLoginView(view);
} else if (currentStep == STEP_LOGIN) {
if (username == null || password == null) {
throw new IllegalStateException("Username and password must not be null here");
} else {
setupDeviceView(view);
}
} else if (currentStep == STEP_DEVICE) {
if (selectedDevice == null) {
throw new IllegalStateException("Device must not be null here");
} else {
writeLoginCredentials();
setupFinishView(view);
}
}
if (currentStep != STEP_DEFAULT) {
viewFlipper.showNext();
}
currentStep++;
} else {
dismiss();
}
}
private boolean usernameHasUnwantedChars(String username) {
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
Matcher containsUnwantedChars = special.matcher(username);
return containsUnwantedChars.find();
}
}

View File

@ -14,19 +14,16 @@ import de.danoeh.antennapod.core.event.SyncServiceEvent;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.dialog.AuthenticationDialog;
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information";
private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync";
private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync";
private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout";
private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -51,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void syncStatusChanged(SyncServiceEvent event) {
updateGpodnetPreferenceScreen();
if (!GpodnetPreferences.loggedIn()) {
return;
}
@ -66,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
private void setupGpodderScreen() {
final Activity activity = getActivity();
findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> {
new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG);
return true;
});
findPreference(PREF_GPODNET_SETLOGIN_INFORMATION)
.setOnPreferenceClickListener(preference -> {
AuthenticationDialog dialog = new AuthenticationDialog(activity,
@ -94,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
updateGpodnetPreferenceScreen();
return true;
});
findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
dialog -> updateGpodnetPreferenceScreen());
return true;
});
}
private void updateGpodnetPreferenceScreen() {
@ -119,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
} else {
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
}
findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
}
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewFlipper
android:id="@+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>

View File

@ -1,96 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ImageView
android:id="@id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/gpodder_icon" />
android:orientation="vertical">
<TextView
android:id="@id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_login_descr"
android:layout_below="@id/icon"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<EditText
android:id="@+id/etxtUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username_label"
android:layout_below="@id/txtvDescription"
android:focusable="true"
android:focusableInTouchMode="true"
android:cursorVisible="true"
android:maxLines="1"
android:inputType="text"
android:imeOptions="actionNext|flagNoFullscreen"
android:nextFocusForward="@id/etxtPassword"/>
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/gpodder_icon"/>
<EditText
android:id="@+id/etxtPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_label"
android:layout_below="@id/etxtUsername"
android:inputType="textPassword"
android:focusable="true"
android:focusableInTouchMode="true"
android:cursorVisible="true"
android:imeOptions="actionGo|flagNoFullscreen"
android:imeActionLabel="@string/gpodnetauth_login_butLabel"/>
<TextView
android:id="@+id/createAccountButton"
android:layout_width="0dp"
android:textAlignment="textEnd"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:textColor="?colorAccent"
android:layout_weight="1"
android:layout_gravity="center_vertical|end"
android:text="@string/create_account"/>
</LinearLayout>
<Button
android:id="@+id/butLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/etxtPassword"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:text="@string/gpodnetauth_login_butLabel"/>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/etxtPassword"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@id/butLogin"
android:layout_toStartOf="@id/butLogin"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_small"
android:maxLines="2"
android:ellipsize="end"
android:gravity="center"
android:layout_margin="16dp"
tools:text="Error message"
tools:background="@android:color/holo_green_dark" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etxtUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username_label"
android:lines="1"
android:imeOptions="actionNext|flagNoFullscreen"/>
<ProgressBar
android:id="@+id/progBarLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_alignTop="@+id/butLogin"
android:layout_toLeftOf="@+id/butLogin"
android:layout_toStartOf="@+id/butLogin"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginTop="16dp"
android:text="@string/gpodnetauth_login_register"
android:autoLink="web"
android:layout_below="@id/butLogin"/>
</RelativeLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etxtPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_label"
android:inputType="textPassword"
android:lines="1"
android:imeOptions="actionNext|flagNoFullscreen"
android:imeActionLabel="@string/gpodnetauth_login_butLabel"/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end|center_vertical">
<TextView
android:id="@+id/credentialsError"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_small"
android:maxLines="2"
android:ellipsize="end"
android:gravity="center"
tools:text="Error message"
tools:background="@android:color/holo_green_dark"/>
<ProgressBar
android:id="@+id/progBarLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="right"/>
<Button
android:id="@+id/butLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_login_butLabel"/>
</LinearLayout>
</LinearLayout>

View File

@ -1,114 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/deviceName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_device_name"
android:lines="1"
android:imeOptions="actionNext|flagNoFullscreen"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_title"
android:layout_alignParentTop="true"
android:layout_marginBottom="16dp"
style="@style/AntennaPod.TextView.Heading"/>
style="@style/AntennaPod.TextView.Heading"
android:layout_marginTop="16dp"
android:text="@string/gpodnetauth_sync_groups"/>
<TextView
android:id="@+id/txtvDescription"
android:id="@+id/deviceSelectError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_descr"
android:layout_below="@id/txtvTitle"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary"/>
<EditText
android:id="@+id/etxtCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_device_caption"
android:layout_below="@id/txtvDescription"
android:imeOptions="flagNoFullscreen"/>
<TextView
android:id="@+id/txtvDeviceID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_deviceID"
android:textSize="@dimen/text_size_medium"
android:layout_below="@id/etxtCaption"/>
<EditText
android:id="@+id/etxtDeviceID"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvDeviceID"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:imeOptions="flagNoFullscreen"/>
<Button
android:id="@+id/butCreateNewDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="@id/etxtDeviceID"
android:text="@string/gpodnetauth_device_butCreateNewDevice"/>
<TextView
android:id="@+id/txtvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/etxtDeviceID"
android:layout_toLeftOf="@id/butCreateNewDevice"
android:layout_toStartOf="@id/butCreateNewDevice"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_small"
android:visibility="gone"
tools:text="Error message"
tools:background="@android:color/holo_green_dark" />
<LinearLayout
android:id="@+id/deviceGroups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<Button
android:id="@+id/startNewGroupButton"
android:text="@string/gpodnetauth_sync_group_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/materialButtonOutlinedStyle"
android:layout_gravity="right"/>
<ProgressBar
android:id="@+id/progbarCreateDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/butCreateNewDevice"
android:layout_toLeftOf="@id/butCreateNewDevice"
android:layout_toStartOf="@id/butCreateNewDevice"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_medium"
android:visibility="gone"
/>
android:visibility="gone" />
<TextView
android:id="@+id/txtvChooseExistingDevice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_chooseExistingDevice"
android:layout_below="@id/butCreateNewDevice"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"
android:layout_marginTop="32dp"/>
<Button
android:id="@+id/butChooseExistingDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_butChoose"
android:layout_below="@+id/spinnerChooseDevice"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
<Spinner
android:id="@+id/spinnerChooseDevice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvChooseExistingDevice"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:strokeWidth="2dp"
app:strokeColor="?attr/currently_playing_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/groupContents"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/selectGroupButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
style="?attr/borderlessButtonStyle"
android:text="@string/gpodnetauth_sync_group_select"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:clipToPadding="false">
<ViewFlipper
android:id="@+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inAnimation="@anim/slide_right_in"
android:outAnimation="@anim/slide_left_out">
<include layout="@layout/gpodnetauth_host" />
<include layout="@layout/gpodnetauth_credentials" />
<include layout="@layout/gpodnetauth_device" />
<include layout="@layout/gpodnetauth_finish" />
</ViewFlipper>
</ScrollView>

View File

@ -1,46 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/gpodder_icon" />
<TextView
android:id="@+id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:text="@string/gpodnetauth_finish_title"
style="@style/AntennaPod.TextView.Heading"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_finish_descr"
android:layout_below="@id/txtvTitle"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary" />
<Button
android:id="@+id/butSyncNow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvDescription"
android:layout_marginTop="16dp"
android:layout_marginTop="8dp"
android:text="@string/gpodnetauth_finish_butsyncnow"/>
<Button
android:id="@+id/butGoMainscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/butSyncNow"
android:text="@string/gpodnetauth_finish_butgomainscreen"/>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioGroup
android:id="@+id/serverRadioGroup"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioButton
android:id="@+id/officialServerRadio"
android:text="@string/gpodnetauth_server_official"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"/>
<RadioButton
android:id="@+id/customServerRadio"
android:text="@string/gpodnetauth_server_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RadioGroup>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/serverUrlTextInput"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/serverUrlText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_host"
android:inputType="textNoSuggestions"
android:lines="1"
android:imeOptions="actionNext|flagNoFullscreen" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/chooseHostButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:text="@string/gpodnetauth_select_server"/>
</LinearLayout>

View File

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
<Preference
android:key="pref_gpodnet_description"
android:icon="@drawable/gpodder_icon"
android:summary="@string/gpodnet_description"/>
<Preference
android:key="pref_gpodnet_authenticate"
android:title="@string/pref_gpodnet_authenticate_title"
android:summary="@string/pref_gpodnet_authenticate_sum">
<intent android:action=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
</PreferenceScreen>
android:summary="@string/pref_gpodnet_authenticate_sum"/>
<Preference
android:key="pref_gpodnet_setlogin_information"
android:title="@string/pref_gpodnet_setlogin_information_title"
@ -23,8 +24,5 @@
<Preference
android:key="pref_gpodnet_logout"
android:title="@string/pref_gpodnet_logout_title"/>
<Preference
android:key="pref_gpodnet_hostname"
android:title="@string/pref_gpodnet_sethostname_title"/>
</PreferenceScreen>

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.sync.gpoddernet;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.core.sync.model.EpisodeAction;
@ -36,6 +37,7 @@ import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@ -47,6 +49,7 @@ public class GpodnetService implements ISyncService {
public static final String TAG = "GpodnetService";
public static final String DEFAULT_BASE_HOST = "gpodder.net";
private static final String BASE_SCHEME = "https";
private static final int PORT = 443;
private static final int UPLOAD_BULK_SIZE = 30;
private static final MediaType TEXT = MediaType.parse("plain/text; charset=utf-8");
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
@ -71,7 +74,8 @@ public class GpodnetService implements ISyncService {
public List<GpodnetTag> getTopTags(int count) throws GpodnetServiceException {
URL url;
try {
url = new URI(BASE_SCHEME, baseHost, String.format(Locale.US, "/api/2/tags/%d.json", count), null).toURL();
url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format(Locale.US, "/api/2/tags/%d.json", count), null, null).toURL();
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
@ -104,8 +108,8 @@ public class GpodnetService implements ISyncService {
public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag, int count)
throws GpodnetServiceException {
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(Locale.US,
"/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format(Locale.US, "/api/2/tag/%s/%d.json", tag.getTag(), count), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -130,7 +134,8 @@ public class GpodnetService implements ISyncService {
}
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(Locale.US, "/toplist/%d.json", count), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format(Locale.US, "/toplist/%d.json", count), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -161,8 +166,8 @@ public class GpodnetService implements ISyncService {
}
try {
URL url = new URI(BASE_SCHEME, baseHost,
String.format(Locale.US, "/suggestions/%d.json", count), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format(Locale.US, "/suggestions/%d.json", count), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -187,7 +192,7 @@ public class GpodnetService implements ISyncService {
.format(Locale.US, "q=%s&scale_logo=%d", query, scaledLogoSize) : String
.format("q=%s", query);
try {
URL url = new URI(BASE_SCHEME, null, baseHost, -1, "/search.json",
URL url = new URI(BASE_SCHEME, null, baseHost, PORT, "/search.json",
parameters, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -214,7 +219,8 @@ public class GpodnetService implements ISyncService {
public List<GpodnetDevice> getDevices() throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/devices/%s.json", username), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/devices/%s.json", username), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
JSONArray devicesArray = new JSONArray(response);
@ -225,6 +231,45 @@ public class GpodnetService implements ISyncService {
}
}
/**
* Returns synchronization status of devices.
* <p/>
* This method requires authentication.
*
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public List<List<String>> getSynchronizedDevices() throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/sync-devices/%s.json", username), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
JSONObject syncStatus = new JSONObject(response);
List<List<String>> result = new ArrayList<>();
JSONArray synchronizedDevices = syncStatus.getJSONArray("synchronized");
for (int i = 0; i < synchronizedDevices.length(); i++) {
JSONArray groupDevices = synchronizedDevices.getJSONArray(i);
List<String> group = new ArrayList<>();
for (int j = 0; j < groupDevices.length(); j++) {
group.add(groupDevices.getString(j));
}
result.add(group);
}
JSONArray notSynchronizedDevices = syncStatus.getJSONArray("not-synchronized");
for (int i = 0; i < notSynchronizedDevices.length(); i++) {
result.add(Collections.singletonList(notSynchronizedDevices.getString(i)));
}
return result;
} catch (JSONException | MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Configures the device of a given user.
* <p/>
@ -237,8 +282,8 @@ public class GpodnetService implements ISyncService {
throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/devices/%s/%s.json", username, deviceId), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/devices/%s/%s.json", username, deviceId), null, null).toURL();
String content;
if (caption != null || type != null) {
JSONObject jsonContent = new JSONObject();
@ -261,6 +306,39 @@ public class GpodnetService implements ISyncService {
}
}
/**
* Links devices for synchronization.
* <p/>
* This method requires authentication.
*
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public void linkDevices(@NonNull List<String> deviceIds) throws GpodnetServiceException {
requireLoggedIn();
try {
final URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/sync-devices/%s.json", username), null, null).toURL();
JSONObject jsonContent = new JSONObject();
JSONArray group = new JSONArray();
for (String deviceId : deviceIds) {
group.put(deviceId);
}
JSONArray synchronizedGroups = new JSONArray();
synchronizedGroups.put(group);
jsonContent.put("synchronize", synchronizedGroups);
jsonContent.put("stop-synchronize", new JSONArray());
Log.d("aaaa", jsonContent.toString());
RequestBody body = RequestBody.create(JSON, jsonContent.toString());
Request.Builder request = new Request.Builder().post(body).url(url);
executeRequest(request);
} catch (JSONException | MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Returns the subscriptions of a specific device.
* <p/>
@ -273,8 +351,8 @@ public class GpodnetService implements ISyncService {
public String getSubscriptionsOfDevice(@NonNull String deviceId) throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/subscriptions/%s/%s.opml", username, deviceId), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/subscriptions/%s/%s.opml", username, deviceId), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
return executeRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
@ -295,7 +373,8 @@ public class GpodnetService implements ISyncService {
public String getSubscriptionsOfUser() throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format("/subscriptions/%s.opml", username), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/subscriptions/%s.opml", username), null, null).toURL();
Request.Builder request = new Request.Builder().url(url);
return executeRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
@ -319,8 +398,8 @@ public class GpodnetService implements ISyncService {
throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/subscriptions/%s/%s.txt", username, deviceId), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/subscriptions/%s/%s.txt", username, deviceId), null, null).toURL();
StringBuilder builder = new StringBuilder();
for (String s : subscriptions) {
builder.append(s);
@ -353,8 +432,8 @@ public class GpodnetService implements ISyncService {
@NonNull Collection<String> removed) throws GpodnetServiceException {
requireLoggedIn();
try {
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/subscriptions/%s/%s.json", username, deviceId), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/subscriptions/%s/%s.json", username, deviceId), null, null).toURL();
final JSONObject requestObject = new JSONObject();
requestObject.put("add", new JSONArray(added));
@ -389,8 +468,7 @@ public class GpodnetService implements ISyncService {
String params = String.format(Locale.US, "since=%d", timestamp);
String path = String.format("/api/2/subscriptions/%s/%s.json", username, deviceId);
try {
URL url = new URI(BASE_SCHEME, null, baseHost, -1, path, params,
null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT, path, params, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -432,8 +510,8 @@ public class GpodnetService implements ISyncService {
throws SyncServiceException {
try {
Log.d(TAG, "Uploading partial actions " + from + " to " + to + " of " + episodeActions.size());
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/episodes/%s.json", username), null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/episodes/%s.json", username), null, null).toURL();
final JSONArray list = new JSONArray();
for (int i = from; i < to; i++) {
@ -471,7 +549,7 @@ public class GpodnetService implements ISyncService {
String params = String.format(Locale.US, "since=%d", timestamp);
String path = String.format("/api/2/episodes/%s.json", username);
try {
URL url = new URI(BASE_SCHEME, null, baseHost, -1, path, params, null).toURL();
URL url = new URI(BASE_SCHEME, null, baseHost, PORT, path, params, null).toURL();
Request.Builder request = new Request.Builder().url(url);
String response = executeRequest(request);
@ -497,7 +575,8 @@ public class GpodnetService implements ISyncService {
public void authenticate(@NonNull String username, @NonNull String password) throws GpodnetServiceException {
URL url;
try {
url = new URI(BASE_SCHEME, baseHost, String.format("/api/2/auth/%s/login.json", username), null).toURL();
url = new URI(BASE_SCHEME, null, baseHost, PORT,
String.format("/api/2/auth/%s/login.json", username), null, null).toURL();
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
@ -567,6 +646,13 @@ public class GpodnetService implements ISyncService {
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw new GpodnetServiceAuthenticationException("Wrong username or password");
} else {
if (BuildConfig.DEBUG) {
try {
Log.d(TAG, response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
throw new GpodnetServiceBadStatusCodeException("Bad response code: " + responseCode, responseCode);
}
}

View File

@ -468,8 +468,6 @@
<string name="pref_fast_forward_sum">Customize the number of seconds to jump forward when the fast forward button is clicked</string>
<string name="pref_rewind">Rewind Skip Time</string>
<string name="pref_rewind_sum">Customize the number of seconds to jump backwards when the rewind button is clicked</string>
<string name="pref_gpodnet_sethostname_title">Set hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Use default host</string>
<string name="pref_expandNotify_title">High Notification priority</string>
<string name="pref_expandNotify_sum">This usually expands the notification to show playback buttons.</string>
<string name="pref_persistNotify_title">Persistent Playback Controls</string>
@ -631,23 +629,24 @@
<string name="gpodnet_suggestions_header">SUGGESTIONS</string>
<string name="gpodnet_search_hint">Search gpodder.net</string>
<string name="gpodnetauth_login_title">Login</string>
<string name="gpodnetauth_login_descr">Welcome to the gpodder.net login process. First, type in your login information:</string>
<string name="gpodnetauth_login_butLabel">Login</string>
<string name="gpodnetauth_login_register">If you do not have an account yet, you can create one here:\nhttps://gpodder.net/register/</string>
<string name="create_account">Create account</string>
<string name="username_label">Username</string>
<string name="password_label">Password</string>
<string name="gpodnetauth_device_title">Device Selection</string>
<string name="gpodnet_description">Gpodder.net is an open-source podcast synchronization service that is independent of the AntennaPod project.</string>
<string name="gpodnetauth_server_official">Official gpodder.net server</string>
<string name="gpodnetauth_server_custom">Custom server</string>
<string name="gpodnetauth_host">Hostname</string>
<string name="gpodnetauth_select_server">Select server</string>
<string name="gpodnetauth_device_descr">Create a new device to use for your gpodder.net account or choose an existing one:</string>
<string name="gpodnetauth_device_deviceID">Device ID:\u0020</string>
<string name="gpodnetauth_device_caption">Caption</string>
<string name="gpodnetauth_device_butCreateNewDevice">Create new device</string>
<string name="gpodnetauth_device_chooseExistingDevice">Choose existing device:</string>
<string name="gpodnetauth_device_errorEmpty">Device ID must not be empty</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Device ID already in use</string>
<string name="gpodnetauth_device_name">Device name</string>
<string name="gpodnetauth_device_name_default">AntennaPod on %1$s</string>
<string name="gpodnetauth_device_caption_errorEmpty">Caption must not be empty</string>
<string name="gpodnetauth_sync_groups">Synchronization groups</string>
<string name="gpodnetauth_sync_group_select">Select</string>
<string name="gpodnetauth_sync_group_new">Use new group</string>
<string name="gpodnetauth_device_butChoose">Choose</string>
<string name="gpodnetauth_finish_title">Login successful!</string>
<string name="gpodnetauth_finish_descr">Congratulations! Your gpodder.net account is now linked with your device. AntennaPod will from now on automatically sync subscriptions on your device with your gpodder.net account.</string>
<string name="gpodnetauth_finish_butsyncnow">Start sync now</string>
<string name="gpodnetauth_finish_butgomainscreen">Go to main screen</string>