You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/caldav/CaldavAccountSettingsActivi...

464 lines
14 KiB
Java

package org.tasks.caldav;
import static android.text.TextUtils.isEmpty;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProviders;
import at.bitfire.dav4jvm.exception.HttpException;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnFocusChange;
import butterknife.OnTextChanged;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.rey.material.widget.ProgressView;
import com.todoroo.astrid.helper.UUIDHelper;
import com.todoroo.astrid.service.TaskDeleter;
import java.net.ConnectException;
import java.net.IDN;
import java.net.URI;
import java.net.URISyntaxException;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.activities.AddCaldavAccountViewModel;
import org.tasks.activities.UpdateCaldavAccountViewModel;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking.Events;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ForApplication;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import org.tasks.security.Encryption;
import org.tasks.sync.SyncAdapters;
import org.tasks.ui.DisplayableException;
import org.tasks.ui.MenuColorizer;
import timber.log.Timber;
public class CaldavAccountSettingsActivity extends ThemedInjectingAppCompatActivity
implements Toolbar.OnMenuItemClickListener {
public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$
private static final String PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
@Inject @ForApplication Context context;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject Tracker tracker;
@Inject CaldavDao caldavDao;
@Inject SyncAdapters syncAdapters;
@Inject TaskDeleter taskDeleter;
@Inject Encryption encryption;
@Inject CaldavClient client;
@BindView(R.id.root_layout)
LinearLayout root;
@BindView(R.id.name)
TextInputEditText name;
@BindView(R.id.url)
TextInputEditText url;
@BindView(R.id.user)
TextInputEditText user;
@BindView(R.id.password)
TextInputEditText password;
@BindView(R.id.name_layout)
TextInputLayout nameLayout;
@BindView(R.id.url_layout)
TextInputLayout urlLayout;
@BindView(R.id.user_layout)
TextInputLayout userLayout;
@BindView(R.id.password_layout)
TextInputLayout passwordLayout;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.progress_bar)
ProgressView progressView;
@BindView(R.id.repeat)
SwitchCompat repeat;
private CaldavAccount caldavAccount;
private AddCaldavAccountViewModel addCaldavAccountViewModel;
private UpdateCaldavAccountViewModel updateCaldavAccountViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_caldav_account_settings);
ButterKnife.bind(this);
addCaldavAccountViewModel = ViewModelProviders.of(this).get(AddCaldavAccountViewModel.class);
updateCaldavAccountViewModel =
ViewModelProviders.of(this).get(UpdateCaldavAccountViewModel.class);
caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
if (savedInstanceState == null) {
if (caldavAccount != null) {
name.setText(caldavAccount.getName());
url.setText(caldavAccount.getUrl());
user.setText(caldavAccount.getUsername());
if (!isEmpty(caldavAccount.getPassword())) {
password.setText(PASSWORD_MASK);
}
repeat.setChecked(caldavAccount.isSuppressRepeatingTasks());
}
}
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setTitle(
caldavAccount == null ? getString(R.string.add_account) : caldavAccount.getName());
toolbar.setNavigationIcon(
ContextCompat.getDrawable(
this,
backButtonSavesTask
? R.drawable.ic_outline_clear_24px
: R.drawable.ic_outline_save_24px));
toolbar.setNavigationOnClickListener(
v -> {
if (backButtonSavesTask) {
discard();
} else {
save();
}
});
toolbar.inflateMenu(R.menu.menu_caldav_account_settings);
toolbar.setOnMenuItemClickListener(this);
toolbar.showOverflowMenu();
MenuColorizer.colorToolbar(this, toolbar);
if (caldavAccount == null) {
toolbar.getMenu().findItem(R.id.remove).setVisible(false);
name.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT);
}
addCaldavAccountViewModel.observe(this, this::addAccount, this::requestFailed);
updateCaldavAccountViewModel.observe(this, this::updateAccount, this::requestFailed);
}
private void showProgressIndicator() {
progressView.setVisibility(View.VISIBLE);
}
private void hideProgressIndicator() {
progressView.setVisibility(View.GONE);
}
private boolean requestInProgress() {
return progressView.getVisibility() == View.VISIBLE;
}
@OnTextChanged(R.id.name)
void onNameChanged(CharSequence text) {
nameLayout.setError(null);
}
@OnTextChanged(R.id.url)
void onUrlChanged(CharSequence text) {
urlLayout.setError(null);
}
@OnTextChanged(R.id.user)
void onUserChanged(CharSequence text) {
userLayout.setError(null);
}
@OnTextChanged(R.id.password)
void onPasswordChanged(CharSequence text) {
passwordLayout.setError(null);
}
@OnFocusChange(R.id.password)
void onPasswordFocused(boolean hasFocus) {
if (hasFocus) {
if (PASSWORD_MASK.equals(password.getText().toString())) {
password.setText("");
}
} else {
if (isEmpty(password.getText()) && caldavAccount != null) {
password.setText(PASSWORD_MASK);
}
}
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);
}
private String getNewName() {
return name.getText().toString().trim();
}
private String getNewURL() {
return url.getText().toString().trim();
}
private String getNewUsername() {
return user.getText().toString().trim();
}
private boolean passwordChanged() {
return caldavAccount == null || !PASSWORD_MASK.equals(password.getText().toString().trim());
}
private String getNewPassword() {
String input = password.getText().toString().trim();
return PASSWORD_MASK.equals(input) ? encryption.decrypt(caldavAccount.getPassword()) : input;
}
private void save() {
if (requestInProgress()) {
return;
}
String name = getNewName();
String username = getNewUsername();
String url = getNewURL();
String password = getNewPassword();
boolean failed = false;
if (isEmpty(name)) {
nameLayout.setError(getString(R.string.name_cannot_be_empty));
failed = true;
} else {
CaldavAccount accountByName = caldavDao.getAccountByName(name);
if (accountByName != null && !accountByName.equals(caldavAccount)) {
nameLayout.setError(getString(R.string.duplicate_name));
failed = true;
}
}
if (isEmpty(url)) {
urlLayout.setError(getString(R.string.url_required));
failed = true;
} else {
Uri baseURL = Uri.parse(url);
String scheme = baseURL.getScheme();
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
String host = baseURL.getHost();
if (isEmpty(host)) {
urlLayout.setError(getString(R.string.url_host_name_required));
failed = true;
} else {
try {
host = IDN.toASCII(host);
} catch (Exception e) {
Timber.e(e);
}
String path = baseURL.getEncodedPath();
int port = baseURL.getPort();
try {
new URI(scheme, null, host, port, path, null, null);
} catch (URISyntaxException e) {
urlLayout.setError(e.getLocalizedMessage());
failed = true;
}
}
} else {
urlLayout.setError(getString(R.string.url_invalid_scheme));
failed = true;
}
}
if (isEmpty(username)) {
userLayout.setError(getString(R.string.username_required));
failed = true;
}
if (isEmpty(password)) {
passwordLayout.setError(getString(R.string.password_required));
failed = true;
}
if (failed) {
return;
}
if (caldavAccount == null) {
showProgressIndicator();
addCaldavAccountViewModel.addAccount(client, url, username, password);
} else if (needsValidation()) {
showProgressIndicator();
updateCaldavAccountViewModel.updateCaldavAccount(client, url, username, password);
} else if (hasChanges()) {
updateAccount(caldavAccount.getUrl());
} else {
finish();
}
}
private void addAccount(String principal) {
Timber.d("Found principal: %s", principal);
CaldavAccount newAccount = new CaldavAccount();
newAccount.setName(getNewName());
newAccount.setUrl(principal);
newAccount.setUsername(getNewUsername());
newAccount.setPassword(encryption.encrypt(getNewPassword()));
newAccount.setUuid(UUIDHelper.newUUID());
newAccount.setId(caldavDao.insert(newAccount));
tracker.reportEvent(Events.CALDAV_ACCOUNT_ADDED);
setResult(RESULT_OK);
finish();
}
private void updateAccount(String principal) {
caldavAccount.setName(getNewName());
caldavAccount.setUrl(principal);
caldavAccount.setUsername(getNewUsername());
caldavAccount.setError("");
if (passwordChanged()) {
caldavAccount.setPassword(encryption.encrypt(getNewPassword()));
}
caldavAccount.setSuppressRepeatingTasks(repeat.isChecked());
caldavDao.update(caldavAccount);
setResult(RESULT_OK);
finish();
}
private void requestFailed(Throwable t) {
hideProgressIndicator();
if (t instanceof HttpException) {
showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) {
showSnackbar(((DisplayableException) t).getResId());
} else if (t instanceof ConnectException) {
showSnackbar(R.string.network_error);
} else {
Timber.e(t);
showSnackbar(R.string.error_adding_account, t.getMessage());
}
}
private void showSnackbar(int resId, Object... formatArgs) {
showSnackbar(getString(resId, formatArgs));
}
private void showSnackbar(String message) {
Snackbar snackbar =
Snackbar.make(root, message, 8000)
.setTextColor(ContextCompat.getColor(context, R.color.snackbar_text_color))
.setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_action_color));
snackbar
.getView()
.setBackgroundColor(ContextCompat.getColor(this, R.color.snackbar_background));
snackbar.show();
}
private boolean hasChanges() {
if (caldavAccount == null) {
return !isEmpty(getNewName())
|| !isEmpty(getNewPassword())
|| !isEmpty(getNewURL())
|| !isEmpty(getNewUsername())
|| repeat.isChecked();
}
return needsValidation()
|| !getNewName().equals(caldavAccount.getName())
|| repeat.isChecked() != caldavAccount.isSuppressRepeatingTasks();
}
private boolean needsValidation() {
return !getNewURL().equals(caldavAccount.getUrl())
|| !getNewUsername().equals(caldavAccount.getUsername())
|| passwordChanged();
}
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(name.getWindowToken(), 0);
super.finish();
}
@Override
public void onBackPressed() {
if (preferences.backButtonSavesTask()) {
save();
} else {
discard();
}
}
private void removeAccount() {
if (requestInProgress()) {
return;
}
dialogBuilder
.newDialog()
.setMessage(R.string.logout_warning, caldavAccount.getName())
.setPositiveButton(
R.string.remove,
(dialog, which) -> {
taskDeleter.delete(caldavAccount);
tracker.reportEvent(Events.CALDAV_ACCOUNT_REMOVED);
setResult(RESULT_OK);
finish();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void discard() {
if (requestInProgress()) {
return;
}
if (!hasChanges()) {
finish();
} else {
dialogBuilder
.newDialog(R.string.discard_changes)
.setPositiveButton(R.string.discard, (dialog, which) -> finish())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.help:
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://tasks.org/caldav")));
break;
case R.id.remove:
removeAccount();
break;
}
return onOptionsItemSelected(item);
}
}