syncthing.android.ui.login.LoginPresenter.java Source code

Java tutorial

Introduction

Here is the source code for syncthing.android.ui.login.LoginPresenter.java

Source

/*
 * Copyright (c) 2015 OpenSilk Productions LLC
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package syncthing.android.ui.login;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.databinding.Bindable;
import android.databinding.PropertyChangeRegistry;
import android.databinding.adapters.ViewBindingAdapter;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Pair;
import android.view.View;
import android.view.inputmethod.InputMethodManager;

import org.apache.commons.lang3.StringUtils;
import org.opensilk.common.core.dagger2.ForApplication;
import org.opensilk.common.core.dagger2.ScreenScope;
import org.opensilk.common.core.rx.RxUtils;
import org.opensilk.common.ui.mortar.ActionBarConfig;
import org.opensilk.common.ui.mortar.ActivityResultsController;
import org.opensilk.common.ui.mortar.DialogPresenter;
import org.opensilk.common.ui.mortar.ToolbarOwner;

import java.util.concurrent.atomic.AtomicReference;

import javax.inject.Inject;

import mortar.ViewPresenter;
import rx.Scheduler;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
import syncthing.android.R;
import syncthing.android.service.SyncthingUtils;
import syncthing.android.settings.AppSettings;
import syncthing.android.ui.ManageActivity;
import syncthing.android.ui.binding.BindingSubscriptionsHolder;
import syncthing.api.Credentials;
import syncthing.api.Session;
import syncthing.api.SessionManager;
import syncthing.api.SynchingApiWrapper;
import syncthing.api.SyncthingApi;
import syncthing.api.SyncthingApiConfig;
import syncthing.api.model.DeviceConfig;
import timber.log.Timber;

/**
* Created by drew on 3/11/15.
*/
@ScreenScope
public class LoginPresenter extends ViewPresenter<CoordinatorLayout>
        implements android.databinding.Observable, BindingSubscriptionsHolder {

    enum State {
        NONE, LOADING, ERROR, SUCCESS
    }

    final Context appContext;
    final ActivityResultsController activityResultsController;
    final AppSettings settings;
    final SessionManager manager;
    final DialogPresenter dialogPresenter;
    final ToolbarOwner toolbarOwner;

    final AtomicReference<Credentials> credentials = new AtomicReference<>();
    final SyncthingApiConfig.Builder configBuilder = SyncthingApiConfig.builder();
    final PropertyChangeRegistry registry = new PropertyChangeRegistry();

    String alias = "";
    String host = "";
    String port = "8384";
    String user = "";
    String pass = "";
    boolean tls;
    String errorHost;

    CompositeSubscription bindingSubscriptions;
    Subscription subscription;
    String error;
    Session session;
    State state = State.NONE;
    boolean wasPreviouslyLoaded;

    @Inject
    public LoginPresenter(@ForApplication Context context, Credentials initialCredentials,
            ActivityResultsController activityResultsController, SessionManager manager, AppSettings settings,
            DialogPresenter dialogPresenter, ToolbarOwner toolbarOwner) {
        this.appContext = context;
        this.credentials.set(initialCredentials);
        this.activityResultsController = activityResultsController;
        this.settings = settings;
        this.manager = manager;
        if (initialCredentials != Credentials.NONE) {
            configBuilder.forCredentials(initialCredentials);
        }
        this.dialogPresenter = dialogPresenter;
        this.toolbarOwner = toolbarOwner;
    }

    @Override
    protected void onExitScope() {
        Timber.d("onExitScope");
        super.onExitScope();
        if (subscription != null) {
            subscription.unsubscribe();
        }
        if (session != null) {
            manager.release(session);
        }
    }

    @Override
    protected void onLoad(Bundle savedInstanceState) {
        super.onLoad(savedInstanceState);
        if (!wasPreviouslyLoaded && savedInstanceState != null) {
            credentials.set(savedInstanceState.getParcelable("creds"));
        } else if (!wasPreviouslyLoaded) {
            if (credentials.get() != Credentials.NONE) {
                Credentials c = credentials.get();
                alias = c.alias;
                host = SyncthingUtils.extractHost(c.url);
                port = SyncthingUtils.extractPort(c.url);
                tls = SyncthingUtils.isHttps(c.url);
            }
        }
        switch (state) {
        case ERROR:
            showError();
            break;
        case LOADING:
            showLoading();
            break;
        case SUCCESS:
            exitSuccess();
            break;
        }
    }

    @Override
    protected void onSave(Bundle outState) {
        super.onSave(outState);
        outState.putParcelable("creds", credentials.get());
    }

    @Override
    public CompositeSubscription bindingSubscriptions() {
        return (bindingSubscriptions != null) ? bindingSubscriptions
                : (bindingSubscriptions = new CompositeSubscription());
    }

    @Override
    public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        registry.add(callback);
    }

    @Override
    public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        registry.remove(callback);
    }

    private void notifyChange(int id) {
        registry.notifyChange(this, id);
    }

    public void submit(View btn) {
        dismissKeyboard(btn);
        if (isInputInvalid()) {
            dialogPresenter.showDialog(context -> new AlertDialog.Builder(context).setTitle(R.string.input_error)
                    .setMessage(R.string.input_error_message).setPositiveButton(android.R.string.cancel, null)
                    .setNegativeButton(R.string.save, (d, w) -> {
                        fetchApiKey();
                    }).create());
        } else {
            fetchApiKey();
        }
    }

    public void cancel(View btn) {
        exitCanceled();
    }

    void fetchApiKey() {
        if (!hasView())
            return;
        state = State.LOADING;
        showLoading();
        String url = SyncthingUtils.buildUrl(host, //TODO undocumented behavior (default port)
                StringUtils.isEmpty(port) ? (tls ? "443" : "80") : port, tls);
        Timber.d("Logging into %s", url);
        credentials.set(credentials.get().buildUpon().setUrl(url).build());
        configBuilder.setUrl(url);
        configBuilder.setAuth(SyncthingUtils.buildAuthorization(user, pass));
        configBuilder.setDebug(true);
        if (session != null) {
            manager.release(session);
        }
        session = manager.acquire(configBuilder.build());
        final SyncthingApi api = SynchingApiWrapper.wrap(session.api(), Schedulers.io());
        subscription = rx.Observable.zip(api.config(), api.system(), Pair::create)
                .observeOn(AndroidSchedulers.mainThread()).subscribe(pair -> {
                    Credentials.Builder builder = credentials.get().buildUpon();
                    builder.setId(pair.second.myID);
                    builder.setApiKey(pair.first.gui.apiKey);
                    if (StringUtils.isEmpty(alias)) {
                        for (DeviceConfig d : pair.first.devices) {
                            if (StringUtils.equals(d.deviceID, pair.second.myID)) {
                                builder.setAlias(SyncthingUtils.getDisplayName(d));
                                break;
                            }
                        }
                    } else {
                        builder.setAlias(alias);
                    }
                    credentials.set(builder.build());
                }, this::notifyLoginError, this::saveKeyAndFinish);
    }

    void notifyLoginError(Throwable t) {
        state = State.ERROR;
        error = t.getMessage();
        if (hasView()) {
            showError();
        }
    }

    void saveKeyAndFinish() {
        state = State.SUCCESS;
        settings.saveCredentials(credentials.get());
        if (hasView()) {
            exitSuccess();
        }
    }

    void cancelLogin() {
        if (subscription != null) {
            final Subscription s = subscription;
            subscription = null;
            final Scheduler.Worker worker = Schedulers.io().createWorker();
            worker.schedule(() -> {
                s.unsubscribe();
                worker.unsubscribe();
            });
        }
    }

    void showLoading() {
        dialogPresenter.showDialog(context -> {
            ProgressDialog loadingProgress = new ProgressDialog(context);
            loadingProgress.setMessage(context.getString(R.string.fetching_api_key_dots));
            loadingProgress.setCancelable(false);
            loadingProgress.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel),
                    (dialog, which) -> {
                        cancelLogin();
                        dialog.dismiss();
                    });
            return loadingProgress;
        });
    }

    void showError() {
        dialogPresenter.showDialog(context -> new AlertDialog.Builder(context).setTitle(R.string.login_failure)
                .setMessage(error).setPositiveButton(android.R.string.ok, null).create());
    }

    void exitSuccess() {
        Intent intent = new Intent().putExtra(ManageActivity.EXTRA_CREDENTIALS, (Parcelable) credentials.get());
        activityResultsController.setResultAndFinish(Activity.RESULT_OK, intent);
    }

    public void exitCanceled() {
        activityResultsController.setResultAndFinish(Activity.RESULT_CANCELED, new Intent());
    }

    void dismissKeyboard(View v) {
        final InputMethodManager imm = (InputMethodManager) v.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
        }
    }

    boolean isInputInvalid() {
        boolean invalid = false;
        invalid |= errorHost != null;
        return invalid;
    }

    @Bindable
    public String getAlias() {
        return alias;
    }

    public final Action1<CharSequence> actionSetAlias = new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            String s = StringUtils.isEmpty(charSequence) ? "" : charSequence.toString();
            if (!StringUtils.equals(s, alias)) {
                alias = s;
            }
        }
    };

    @Bindable
    public String getHost() {
        return host;
    }

    @Bindable
    public String getErrorHost() {
        return errorHost;
    }

    public final Action1<CharSequence> actionSetHost = new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            String s = StringUtils.isEmpty(charSequence) ? "" : charSequence.toString();
            boolean invalid = false;
            if (StringUtils.isEmpty(s)) {
                invalid = true;
            }
            if (hasView()) {
                String err = (invalid ? getView().getContext().getString(R.string.input_error) : null);
                if (!StringUtils.equals(err, errorHost)) {
                    errorHost = err;
                    notifyChange(syncthing.android.BR.errorHost);
                }
            }
            if (!invalid && !StringUtils.equals(s, host)) {
                host = s;
            }
        }
    };

    @Bindable
    public String getPort() {
        return port;
    }

    public final Action1<CharSequence> actionSetPort = new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            String s = StringUtils.isEmpty(charSequence) ? "" : charSequence.toString();
            if (!StringUtils.equals(s, port)) {
                port = s;
            }
        }
    };

    @Bindable
    public String getUser() {
        return user;
    }

    public final Action1<CharSequence> actionSetUser = new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            String s = StringUtils.isEmpty(charSequence) ? "" : charSequence.toString();
            if (!StringUtils.equals(s, user)) {
                user = s;
            }
        }
    };

    @Bindable
    public String getPass() {
        return pass;
    }

    public final Action1<CharSequence> actionSetPass = new Action1<CharSequence>() {
        @Override
        public void call(CharSequence charSequence) {
            String s = StringUtils.isEmpty(charSequence) ? "" : charSequence.toString();
            if (!StringUtils.equals(s, pass)) {
                pass = s;
            }
        }
    };

    @Bindable
    public boolean isTls() {
        return tls;
    }

    public final Action1<Boolean> actionSetTls = new Action1<Boolean>() {
        @Override
        public void call(Boolean aBoolean) {
            tls = aBoolean;
        }
    };

    public final ViewBindingAdapter.OnViewAttachedToWindow toolbarAttachedListener = new ViewBindingAdapter.OnViewAttachedToWindow() {
        @Override
        public void onViewAttachedToWindow(View v) {
            Timber.d("attachingtoolbar");
            Toolbar toolbar = (Toolbar) v;
            toolbarOwner.attachToolbar(toolbar);
            toolbarOwner.setConfig(ActionBarConfig.builder().setTitle(R.string.login).build());
        }
    };

    public final ViewBindingAdapter.OnViewDetachedFromWindow toolbarDetachedListener = new ViewBindingAdapter.OnViewDetachedFromWindow() {
        @Override
        public void onViewDetachedFromWindow(View v) {
            Timber.d("detachingToolbar");
            Toolbar toolbar = (Toolbar) v;
            toolbarOwner.detachToolbar(toolbar);
        }
    };

    public final ViewBindingAdapter.OnViewDetachedFromWindow dropViewListener = new ViewBindingAdapter.OnViewDetachedFromWindow() {
        @Override
        public void onViewDetachedFromWindow(View v) {
            Timber.d("Dropping view %s");
            dropView((CoordinatorLayout) v);
            RxUtils.unsubscribe(bindingSubscriptions);
            bindingSubscriptions = null;
        }
    };

}