mirror of
https://codeberg.org/Mercury-IM/Mercury-IM
synced 2024-06-18 09:34:52 +02:00
Account Management: Move ViewModel logic mostly to domain
This commit is contained in:
parent
91084ad2a7
commit
9ed9c36fa3
|
@ -32,9 +32,6 @@
|
|||
<activity
|
||||
android:name=".ui.settings.SettingsActivity"
|
||||
android:label="@string/title_activity_settings" />
|
||||
<activity
|
||||
android:name=".ui.account.LoginActivity"
|
||||
android:label="@string/title_activity_login" />
|
||||
<activity android:name=".ui.roster.contacts.detail.ContactDetailActivity" />
|
||||
|
||||
<service android:name=".service.MercuryConnectionService" />
|
||||
|
|
|
@ -4,20 +4,22 @@ import org.mercury_im.messenger.MercuryImApplication;
|
|||
import org.mercury_im.messenger.data.di.RepositoryModule;
|
||||
import org.mercury_im.messenger.di.module.AndroidPersistenceModule;
|
||||
import org.mercury_im.messenger.di.module.AppModule;
|
||||
import org.mercury_im.messenger.di.module.ViewModelModule;
|
||||
import org.mercury_im.messenger.service.MercuryConnectionService;
|
||||
import org.mercury_im.messenger.store.MercuryEntityCapsStore;
|
||||
import org.mercury_im.messenger.ui.MainActivity;
|
||||
import org.mercury_im.messenger.ui.account.AndroidAccountsViewModel;
|
||||
import org.mercury_im.messenger.ui.account.AndroidLoginViewModel;
|
||||
import org.mercury_im.messenger.ui.chat.ChatActivity;
|
||||
import org.mercury_im.messenger.ui.chat.ChatInputFragment;
|
||||
import org.mercury_im.messenger.ui.chat.ChatInputViewModel;
|
||||
import org.mercury_im.messenger.ui.chat.ChatViewModel;
|
||||
import org.mercury_im.messenger.ui.chatlist.ChatListViewModel;
|
||||
import org.mercury_im.messenger.ui.account.AccountsViewModel;
|
||||
import org.mercury_im.messenger.ui.account.LoginActivity;
|
||||
import org.mercury_im.messenger.ui.account.LoginViewModel;
|
||||
import org.mercury_im.messenger.ui.roster.contacts.ContactListViewModel;
|
||||
import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailActivity;
|
||||
import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailViewModel;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.AccountsViewModel;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.LoginViewModel;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
|
@ -32,7 +34,8 @@ import dagger.Component;
|
|||
modules = {
|
||||
AppModule.class,
|
||||
AndroidPersistenceModule.class,
|
||||
RepositoryModule.class
|
||||
RepositoryModule.class,
|
||||
ViewModelModule.class
|
||||
})
|
||||
public interface AppComponent {
|
||||
|
||||
|
@ -45,8 +48,6 @@ public interface AppComponent {
|
|||
|
||||
void inject(MainActivity mainActivity);
|
||||
|
||||
void inject(LoginActivity loginActivity);
|
||||
|
||||
void inject(ChatActivity chatActivity);
|
||||
|
||||
void inject(ChatInputFragment chatInputFragment);
|
||||
|
@ -63,15 +64,21 @@ public interface AppComponent {
|
|||
|
||||
void inject(ChatInputViewModel chatInputViewModel);
|
||||
|
||||
void inject(LoginViewModel loginViewModel);
|
||||
void inject(AndroidLoginViewModel androidLoginViewModel);
|
||||
|
||||
void inject(AccountsViewModel accountsViewModel);
|
||||
void inject(AndroidAccountsViewModel androidAccountsViewModel);
|
||||
|
||||
void inject(ChatListViewModel chatListViewModel);
|
||||
|
||||
void inject(ContactDetailViewModel contactDetailViewModel);
|
||||
|
||||
|
||||
// Common VMs
|
||||
void inject(LoginViewModel loginViewModel);
|
||||
|
||||
void inject(AccountsViewModel accountsViewModel);
|
||||
|
||||
|
||||
// Services
|
||||
|
||||
void inject(MercuryConnectionService service);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.mercury_im.messenger.ui;
|
||||
|
||||
import org.mercury_im.messenger.viewmodel.MercuryViewModel;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public interface MercuryAndroidViewModel<VM extends MercuryViewModel> {
|
||||
|
||||
VM getCommonViewModel();
|
||||
|
||||
default void dispose() {
|
||||
getCommonViewModel().dispose();
|
||||
}
|
||||
|
||||
default void addDisposable(Disposable disposable) {
|
||||
getCommonViewModel().addDisposable(disposable);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -16,12 +15,10 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
|
|||
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
import org.mercury_im.messenger.xmpp.state.ConnectionState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -36,7 +33,7 @@ public class AccountsFragment extends Fragment {
|
|||
|
||||
private OnAccountListItemClickListener accountClickListener;
|
||||
|
||||
AccountsViewModel viewModel;
|
||||
AndroidAccountsViewModel viewModel;
|
||||
|
||||
private AccountsRecyclerViewAdapter adapter;
|
||||
|
||||
|
@ -53,7 +50,7 @@ public class AccountsFragment extends Fragment {
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(AccountsViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AndroidAccountsViewModel.class);
|
||||
this.adapter = new AccountsRecyclerViewAdapter(viewModel, accountClickListener);
|
||||
}
|
||||
|
||||
|
@ -61,6 +58,7 @@ public class AccountsFragment extends Fragment {
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
observeViewModel();
|
||||
fab.setOnClickListener(v -> displayAddAccountDialog());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
|
@ -75,7 +73,6 @@ public class AccountsFragment extends Fragment {
|
|||
ButterKnife.bind(this, view);
|
||||
|
||||
Context context = view.getContext();
|
||||
fab.setOnClickListener(v -> startActivity(new Intent(context, LoginActivity.class)));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
@ -115,4 +112,10 @@ public class AccountsFragment extends Fragment {
|
|||
|
||||
void onAccountListItemLongClick(Account item);
|
||||
}
|
||||
|
||||
private void displayAddAccountDialog() {
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "DISPLAY FRAGMENT!!!");
|
||||
AddAccountDialogFragment addAccountDialogFragment = new AddAccountDialogFragment();
|
||||
addAccountDialogFragment.show(getParentFragmentManager(), "addAccount");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRe
|
|||
|
||||
private final List<ConnectionState> connectionStates = new ArrayList<>();
|
||||
private final OnAccountListItemClickListener onAccountClickListener;
|
||||
private final AccountsViewModel viewModel;
|
||||
private final AndroidAccountsViewModel viewModel;
|
||||
|
||||
public AccountsRecyclerViewAdapter(AccountsViewModel viewModel, OnAccountListItemClickListener listener) {
|
||||
public AccountsRecyclerViewAdapter(AndroidAccountsViewModel viewModel, OnAccountListItemClickListener listener) {
|
||||
onAccountClickListener = listener;
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.Messenger;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
import org.mercury_im.messenger.xmpp.state.ConnectionPoolState;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class AccountsViewModel extends AndroidViewModel {
|
||||
|
||||
@Inject
|
||||
AccountRepository repository;
|
||||
|
||||
@Inject
|
||||
Messenger messenger;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AccountsViewModel.class.getName());
|
||||
|
||||
private final MutableLiveData<ConnectionPoolState> connectionPool = new MutableLiveData<>();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
public AccountsViewModel(Application application) {
|
||||
super(application);
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
compositeDisposable.add(messenger.getConnectionManager()
|
||||
.observeConnectionPool()
|
||||
.subscribe(connectionPool::postValue,
|
||||
error -> LOGGER.log(Level.SEVERE, "Error observing connections", error)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
public LiveData<ConnectionPoolState> getConnectionPool() {
|
||||
return connectionPool;
|
||||
}
|
||||
|
||||
public void setAccountEnabled(Account accountModel, boolean enabled) {
|
||||
accountModel.setEnabled(enabled);
|
||||
repository.upsertAccount(accountModel)
|
||||
.subscribe(
|
||||
success -> LOGGER.log(Level.FINER, "Account " + accountModel.getAddress() + " " + (enabled ? "enabled" : "disabled")),
|
||||
error -> LOGGER.log(Level.SEVERE, "Account " + accountModel.getAddress() + " cannot be " + (enabled ? "enabled" : "disabled"), error)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AddAccountDialogFragment extends AppCompatDialogFragment implements TextView.OnEditorActionListener {
|
||||
|
||||
@BindView(R.id.username)
|
||||
TextInputEditText addressView;
|
||||
|
||||
@BindView(R.id.password)
|
||||
TextInputEditText passwordView;
|
||||
|
||||
private AndroidLoginViewModel viewModel;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
LayoutInflater inflater = requireActivity().getLayoutInflater();
|
||||
View dialogView = inflater.inflate(R.layout.dialog_login, null);
|
||||
ButterKnife.bind(this, dialogView);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setTitle(R.string.action_add_account)
|
||||
.setView(dialogView)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.action_sign_in, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// We later overwrite in onResume, so that the dialog does not automatically
|
||||
// dismiss when button is clicked.
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
AddAccountDialogFragment.this.onCancel(dialog);
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final AlertDialog d = (AlertDialog)getDialog();
|
||||
if(d == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel = new AndroidLoginViewModel(MercuryImApplication.getApplication());
|
||||
|
||||
Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> viewModel.onLoginButtonClicked());
|
||||
|
||||
viewModel.getLoginUsernameError().observe(this, error -> addressView.setError(error));
|
||||
viewModel.getLoginPasswordError().observe(this, error -> passwordView.setError(error));
|
||||
viewModel.isLoginButtonEnabled().observe(this, positiveButton::setEnabled);
|
||||
|
||||
viewModel.isLoginFinished().observe(this, finished -> {
|
||||
if (finished) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
addressView.setOnEditorActionListener(this);
|
||||
passwordView.setOnEditorActionListener(this);
|
||||
|
||||
addressView.addTextChangedListener(viewModel.getUsernameTextChangedListener());
|
||||
passwordView.addTextChangedListener(viewModel.getPasswordTextChangedListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
switch (v.getId()) {
|
||||
case R.id.username:
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
passwordView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.password:
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
viewModel.getCommonViewModel().login();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.ui.MercuryAndroidViewModel;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.AccountsViewModel;
|
||||
import org.mercury_im.messenger.xmpp.state.ConnectionPoolState;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class AndroidAccountsViewModel extends AndroidViewModel implements MercuryAndroidViewModel<AccountsViewModel> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AndroidAccountsViewModel.class.getName());
|
||||
|
||||
private final MutableLiveData<ConnectionPoolState> connectionPool = new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
AccountsViewModel viewModel;
|
||||
|
||||
public AndroidAccountsViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
|
||||
addDisposable(getCommonViewModel().getConnectionPool()
|
||||
.subscribe(connectionPool::postValue,
|
||||
error -> LOGGER.log(Level.SEVERE, "Error observing connections", error)));
|
||||
}
|
||||
|
||||
public LiveData<ConnectionPoolState> getConnectionPool() {
|
||||
return connectionPool;
|
||||
}
|
||||
|
||||
public void setAccountEnabled(Account accountModel, boolean enabled) {
|
||||
getCommonViewModel().onToggleAccountEnabled(accountModel, enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountsViewModel getCommonViewModel() {
|
||||
return viewModel;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.account.error.PasswordError;
|
||||
import org.mercury_im.messenger.account.error.UsernameError;
|
||||
import org.mercury_im.messenger.ui.MercuryAndroidViewModel;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.util.TextChangedListener;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.LoginViewModel;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
public class AndroidLoginViewModel extends AndroidViewModel implements MercuryAndroidViewModel<LoginViewModel> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AndroidLoginViewModel.class.getName());
|
||||
|
||||
|
||||
private final MutableLiveData<String> loginUsernameError = new MutableLiveData<>();
|
||||
private final MutableLiveData<String> loginPasswordError = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> loginButtonEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> loginFinished = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
LoginViewModel commonViewModel;
|
||||
|
||||
@Inject
|
||||
public AndroidLoginViewModel(Application application) {
|
||||
super(application);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
commonViewModel.reset();
|
||||
addDisposable(mapUsernameErrorCodeToMessage(getCommonViewModel().getLoginUsernameError())
|
||||
.subscribe(e -> loginUsernameError.setValue(e.isPresent() ? e.getItem() : null)));
|
||||
addDisposable(mapPasswordErrorCodeToMessage(getCommonViewModel().getLoginPasswordError())
|
||||
.subscribe(e -> loginPasswordError.setValue(e.isPresent() ? e.getItem() : null)));
|
||||
addDisposable(getCommonViewModel().isLoginPossible()
|
||||
.subscribe(loginButtonEnabled::setValue));
|
||||
addDisposable(getCommonViewModel().isLoginSuccessful()
|
||||
.subscribe(loginFinished::setValue));
|
||||
}
|
||||
|
||||
private Observable<Optional<String>> mapUsernameErrorCodeToMessage(Observable<Optional<UsernameError>> errorCodeObservable) {
|
||||
return errorCodeObservable.map(optional -> {
|
||||
if (!optional.isPresent()){
|
||||
return new Optional<>(null);
|
||||
}
|
||||
UsernameError error = optional.getItem();
|
||||
int resourceId;
|
||||
switch (error) {
|
||||
case emptyUsername:
|
||||
resourceId = R.string.error_field_required;
|
||||
break;
|
||||
case invalidUsername:
|
||||
resourceId = R.string.error_invalid_username;
|
||||
break;
|
||||
case unreachableServer:
|
||||
resourceId = R.string.error_unreachable_server;
|
||||
break;
|
||||
default:
|
||||
resourceId = R.string.error_uknown_error;
|
||||
}
|
||||
return new Optional<>(getApplication().getResources().getString(resourceId));
|
||||
});
|
||||
}
|
||||
|
||||
private Observable<Optional<String>> mapPasswordErrorCodeToMessage(Observable<Optional<PasswordError>> errorCodeObservable) {
|
||||
return errorCodeObservable.map(optional -> {
|
||||
if (!optional.isPresent()){
|
||||
return new Optional<>(null);
|
||||
}
|
||||
PasswordError error = optional.getItem();
|
||||
int resourceId;
|
||||
switch (error) {
|
||||
case emptyPassword:
|
||||
resourceId = R.string.error_field_required;
|
||||
break;
|
||||
case incorrectPassword:
|
||||
resourceId = R.string.error_incorrect_password;
|
||||
break;
|
||||
default:
|
||||
resourceId = R.string.error_uknown_error;
|
||||
}
|
||||
return new Optional<>(getApplication().getResources().getString(resourceId));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
getCommonViewModel().dispose();
|
||||
}
|
||||
|
||||
public void onLoginButtonClicked() {
|
||||
getCommonViewModel().login();
|
||||
}
|
||||
|
||||
public LiveData<String> getLoginUsernameError() {
|
||||
return loginUsernameError;
|
||||
}
|
||||
|
||||
public LiveData<String> getLoginPasswordError() {
|
||||
return loginPasswordError;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginButtonEnabled() {
|
||||
return loginButtonEnabled;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginFinished() {
|
||||
return loginFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginViewModel getCommonViewModel() {
|
||||
return commonViewModel;
|
||||
}
|
||||
|
||||
public TextChangedListener getUsernameTextChangedListener() {
|
||||
return usernameTextChangedListener;
|
||||
}
|
||||
|
||||
public TextChangedListener getPasswordTextChangedListener() {
|
||||
return passwordTextChangedListener;
|
||||
}
|
||||
|
||||
private final TextChangedListener usernameTextChangedListener = new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
getCommonViewModel().onLoginUsernameChanged(s.toString());
|
||||
}
|
||||
};
|
||||
|
||||
private final TextChangedListener passwordTextChangedListener = new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
getCommonViewModel().onLoginPasswordChanged(s.toString());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.account.error.PasswordError;
|
||||
import org.mercury_im.messenger.account.error.UsernameError;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.util.TextChangedListener;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
|
||||
/**
|
||||
* A login screen that offers login via email/password.
|
||||
*/
|
||||
public class LoginActivity extends AppCompatActivity implements TextView.OnEditorActionListener {
|
||||
|
||||
@BindView(R.id.username)
|
||||
TextInputEditText addressView;
|
||||
|
||||
@BindView(R.id.password)
|
||||
TextInputEditText passwordView;
|
||||
|
||||
@BindView(R.id.sign_in_button)
|
||||
Button loginButton;
|
||||
|
||||
private LoginViewModel viewModel;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_login);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
|
||||
observeViewModel();
|
||||
setupTextInputListeners();
|
||||
loginButton.setOnClickListener(view -> viewModel.login());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
observeUsernameError();
|
||||
observePasswordError();
|
||||
finishOnceLoginWasSuccessful();
|
||||
enableLoginButtonOncePossible();
|
||||
}
|
||||
|
||||
private void observeUsernameError() {
|
||||
viewModel.getUsernameError().observe(this, usernameError -> {
|
||||
addressView.setError(usernameError.isError() ? usernameError.getErrorMessage() : null);
|
||||
});
|
||||
}
|
||||
|
||||
private void observePasswordError() {
|
||||
viewModel.getPasswordError().observe(this, passwordError -> {
|
||||
passwordView.setError(passwordError.isError() ? passwordError.getErrorMessage() : null);
|
||||
});
|
||||
}
|
||||
|
||||
private void finishOnceLoginWasSuccessful() {
|
||||
viewModel.isLoginSuccessful().observe(this, successful -> {
|
||||
if (Boolean.TRUE.equals(successful)) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enableLoginButtonOncePossible() {
|
||||
viewModel.isLoginEnabled().observe(this, isEnabled -> {
|
||||
loginButton.setEnabled(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<String> getUsernameError(UsernameError usernameError) {
|
||||
switch (usernameError) {
|
||||
case none:
|
||||
return new Optional<>(null);
|
||||
case emptyUsername:
|
||||
return new Optional<>(getResources().getString(R.string.error_field_required));
|
||||
case invalidUsername:
|
||||
return new Optional<>(getResources().getString(R.string.error_invalid_username));
|
||||
case unknownUsername:
|
||||
return new Optional<>("Unknown Username!");
|
||||
default:
|
||||
throw new AssertionError("Unknown UsernameError enum value.");
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getPasswordError(PasswordError passwordError) {
|
||||
switch (passwordError) {
|
||||
case none:
|
||||
return new Optional<>(null);
|
||||
case emptyPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_field_required));
|
||||
case invalidPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_invalid_password));
|
||||
case incorrectPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_incorrect_password));
|
||||
default:
|
||||
throw new AssertionError("Unknown PasswordError enum value.");
|
||||
}
|
||||
}
|
||||
|
||||
private void setupTextInputListeners() {
|
||||
addressView.setOnEditorActionListener(this);
|
||||
passwordView.setOnEditorActionListener(this);
|
||||
|
||||
addressView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.setUsername(charSequence.toString());
|
||||
}
|
||||
});
|
||||
|
||||
passwordView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.setPassword(charSequence.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
switch (v.getId()) {
|
||||
case R.id.username:
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
passwordView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.password:
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
//viewModel.login();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.Messenger;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class LoginViewModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<Error> usernameError = new MutableLiveData<>(new Error());
|
||||
private MutableLiveData<Error> passwordError = new MutableLiveData<>(new Error());
|
||||
private MutableLiveData<Boolean> loginButtonEnabled = new MutableLiveData<>(false);
|
||||
private MutableLiveData<Boolean> loginCompleted = new MutableLiveData<>(false);
|
||||
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private EntityBareJid username;
|
||||
private String password;
|
||||
|
||||
@Inject
|
||||
Messenger messenger;
|
||||
|
||||
@Inject
|
||||
AccountRepository accountRepository;
|
||||
|
||||
public LoginViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
((MercuryImApplication) application).getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
this.username = null;
|
||||
usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
|
||||
} else {
|
||||
try {
|
||||
this.username = JidCreate.entityBareFrom(username);
|
||||
} catch (XmppStringprepException e) {
|
||||
this.username = null;
|
||||
usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_invalid_username)));
|
||||
}
|
||||
}
|
||||
updateLoginButtonState();
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (password == null || password.isEmpty()) {
|
||||
this.password = null;
|
||||
passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
|
||||
} else {
|
||||
this.password = password;
|
||||
}
|
||||
updateLoginButtonState();
|
||||
}
|
||||
|
||||
private void updateLoginButtonState() {
|
||||
loginButtonEnabled.setValue(username != null && !(password == null || password.isEmpty()));
|
||||
}
|
||||
|
||||
public synchronized void login() {
|
||||
Boolean loginEnabled = loginButtonEnabled.getValue();
|
||||
if (loginEnabled != null && !loginEnabled) {
|
||||
// Prevent race condition where account would be logged in twice
|
||||
return;
|
||||
}
|
||||
loginButtonEnabled.setValue(false);
|
||||
Account account = new Account();
|
||||
account.setAddress(username.asUnescapedString());
|
||||
account.setPassword(password);
|
||||
account.setEnabled(true);
|
||||
MercuryConnection connection = messenger.getConnectionManager().createConnection(account);
|
||||
disposable.add(connection.connect()
|
||||
.andThen(connection.login())
|
||||
.andThen(Completable.fromAction(() -> messenger.getConnectionManager().registerConnection(connection)))
|
||||
.andThen(accountRepository.insertAccount(account).ignoreElement())
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnComplete(this::signalLoginSuccessful)
|
||||
.doOnError(this::signalLoginError)
|
||||
.subscribe(() -> Log.d("Mercury-IM", "Login successful for " + account.getAddress()),
|
||||
e -> Log.e("Mercury-IM", "Login failed for " + account.getAddress(), e)));
|
||||
}
|
||||
|
||||
private void signalLoginSuccessful() {
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "Signal Login Successful");
|
||||
loginCompleted.setValue(true);
|
||||
}
|
||||
|
||||
private void signalLoginError(Throwable error) {
|
||||
if (error instanceof SASLErrorException) {
|
||||
passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_incorrect_password)));
|
||||
loginButtonEnabled.setValue(true);
|
||||
} else {
|
||||
Toast.makeText(getApplication(), "A connection error occurred", Toast.LENGTH_LONG).show();
|
||||
loginButtonEnabled.setValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Error> getPasswordError() {
|
||||
return passwordError;
|
||||
}
|
||||
|
||||
public LiveData<Error> getUsernameError() {
|
||||
return usernameError;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginSuccessful() {
|
||||
return loginCompleted;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginEnabled() {
|
||||
return loginButtonEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
disposable.clear();
|
||||
}
|
||||
|
||||
public static class Error {
|
||||
|
||||
private String message;
|
||||
|
||||
public Error() {
|
||||
|
||||
}
|
||||
|
||||
public Error(String errorMessage) {
|
||||
this.message = errorMessage;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return message != null;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,7 @@
|
|||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".ui.account.LoginActivity">
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/jid_login_form"
|
||||
|
@ -55,13 +54,5 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/sign_in_button"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/action_sign_in" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -138,4 +138,6 @@
|
|||
<string name="error_account_not_connected">Account not connected</string>
|
||||
<string name="error_contact_already_added">Contact already added</string>
|
||||
<string name="error_invalid_address">Invalid address</string>
|
||||
<string name="error_unreachable_server">Server unreachable</string>
|
||||
<string name="error_uknown_error">Unknown error</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,16 +3,13 @@ package org.mercury_im.messenger;
|
|||
import org.jivesoftware.smack.SmackConfiguration;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.Chat;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.roster.RosterEntry;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.mercury_im.messenger.data.repository.Repositories;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
import org.mercury_im.messenger.exception.ConnectionNotFoundException;
|
||||
import org.mercury_im.messenger.exception.ContactAlreadyAddedException;
|
||||
|
@ -27,7 +24,6 @@ import javax.inject.Inject;
|
|||
import javax.inject.Singleton;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
|
@ -61,17 +57,9 @@ public class Messenger {
|
|||
LOGGER.log(Level.INFO, "Perform initial login.");
|
||||
disposable.add(repositories.getAccountRepository().observeAllAccounts().firstOrError()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(connectionManager::registerConnections));
|
||||
.subscribe(connectionManager::doRegisterConnections));
|
||||
}
|
||||
|
||||
public Account createAccount(String username, String service, String password) {
|
||||
Account account = new Account();
|
||||
account.setAddress(username + "@" + service);
|
||||
account.setPassword(password);
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
public Completable addContact(UUID accountId, String contactAddress) {
|
||||
return Completable.fromAction(() -> doAddContact(accountId, contactAddress));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package org.mercury_im.messenger.account.error;
|
||||
|
||||
public enum PasswordError {
|
||||
none,
|
||||
emptyPassword,
|
||||
invalidPassword,
|
||||
incorrectPassword
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package org.mercury_im.messenger.account.error;
|
||||
|
||||
public enum UsernameError {
|
||||
none,
|
||||
emptyUsername,
|
||||
invalidUsername,
|
||||
unknownUsername
|
||||
unreachableServer
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package org.mercury_im.messenger.di.module;
|
||||
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.AccountsViewModel;
|
||||
import org.mercury_im.messenger.viewmodel.accounts.LoginViewModel;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import io.reactivex.Scheduler;
|
||||
|
||||
@Module
|
||||
public class ViewModelModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static LoginViewModel provideLoginViewModel(MercuryConnectionManager connectionManager,
|
||||
AccountRepository accountRepository,
|
||||
@Named(ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
|
||||
return new LoginViewModel(connectionManager, accountRepository, ioScheduler, uiScheduler);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static AccountsViewModel provideAccountsViewModel(MercuryConnectionManager connectionManager,
|
||||
AccountRepository accountRepository,
|
||||
@Named(ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
|
||||
return new AccountsViewModel(connectionManager, accountRepository, ioScheduler, uiScheduler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.mercury_im.messenger.viewmodel;
|
||||
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public interface MercuryViewModel {
|
||||
|
||||
CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
default void dispose() {
|
||||
compositeDisposable.dispose();
|
||||
}
|
||||
|
||||
default void addDisposable(Disposable disposable) {
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.mercury_im.messenger.viewmodel.accounts;
|
||||
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
import org.mercury_im.messenger.viewmodel.MercuryViewModel;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
|
||||
import org.mercury_im.messenger.xmpp.state.ConnectionPoolState;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
|
||||
public class AccountsViewModel implements MercuryViewModel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AccountsViewModel.class.getName());
|
||||
|
||||
private final MercuryConnectionManager connectionManager;
|
||||
private final AccountRepository accountRepository;
|
||||
private final Scheduler ioScheduler;
|
||||
private final Scheduler uiScheduler;
|
||||
|
||||
public AccountsViewModel(MercuryConnectionManager connectionManager,
|
||||
AccountRepository accountRepository,
|
||||
@Named(ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
|
||||
|
||||
this.connectionManager = connectionManager;
|
||||
this.accountRepository = accountRepository;
|
||||
this.ioScheduler = ioScheduler;
|
||||
this.uiScheduler = uiScheduler;
|
||||
}
|
||||
|
||||
|
||||
public Observable<ConnectionPoolState> getConnectionPool() {
|
||||
return connectionManager.observeConnectionPool();
|
||||
}
|
||||
|
||||
public void onToggleAccountEnabled(Account account, boolean enabled) {
|
||||
account.setEnabled(enabled);
|
||||
addDisposable(accountRepository.upsertAccount(account)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(uiScheduler)
|
||||
.subscribe(
|
||||
success -> logAccountToggledSuccess(success, enabled),
|
||||
error -> logAccountToggleError(account, enabled, error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void logAccountToggledSuccess(Account account, boolean enabled) {
|
||||
LOGGER.log(Level.FINER, "Account " + account.getAddress() + (enabled ? " enabled" : " disabled"));
|
||||
}
|
||||
|
||||
private void logAccountToggleError(Account account, boolean enabled, Throwable error) {
|
||||
LOGGER.log(Level.SEVERE, "Account " + account.getAddress() + " could not be " + (enabled ? "enabled" : "disabled"), error);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package org.mercury_im.messenger.viewmodel.accounts;
|
||||
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.mercury_im.messenger.account.error.PasswordError;
|
||||
import org.mercury_im.messenger.account.error.UsernameError;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
import org.mercury_im.messenger.viewmodel.MercuryViewModel;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
|
||||
import org.mercury_im.messenger.xmpp.exception.InvalidCredentialsException;
|
||||
import org.mercury_im.messenger.xmpp.exception.ServerUnreachableException;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
|
||||
public class LoginViewModel implements MercuryViewModel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(LoginViewModel.class.getName());
|
||||
|
||||
private final MercuryConnectionManager connectionManager;
|
||||
private final AccountRepository accountRepository;
|
||||
|
||||
private final Scheduler ioScheduler;
|
||||
private final Scheduler uiScheduler;
|
||||
|
||||
private BehaviorSubject<Optional<UsernameError>> loginUsernameError;
|
||||
private BehaviorSubject<Optional<PasswordError>> loginPasswordError;
|
||||
private BehaviorSubject<Boolean> isLoginPossible;
|
||||
private BehaviorSubject<Boolean> isLoginSuccessful;
|
||||
|
||||
private EntityBareJid loginUsernameValue;
|
||||
private String loginPasswordValue;
|
||||
|
||||
@Inject
|
||||
public LoginViewModel(MercuryConnectionManager connectionManager,
|
||||
AccountRepository accountRepository,
|
||||
@Named(ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.accountRepository = accountRepository;
|
||||
this.ioScheduler = ioScheduler;
|
||||
this.uiScheduler = uiScheduler;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
loginUsernameError = BehaviorSubject.createDefault(new Optional<>());
|
||||
loginPasswordError = BehaviorSubject.createDefault(new Optional<>());
|
||||
isLoginPossible = BehaviorSubject.createDefault(false);
|
||||
isLoginSuccessful = BehaviorSubject.createDefault(false);
|
||||
|
||||
loginUsernameValue = null;
|
||||
loginPasswordValue = null;
|
||||
}
|
||||
|
||||
public Observable<Optional<UsernameError>> getLoginUsernameError() {
|
||||
return loginUsernameError;
|
||||
}
|
||||
|
||||
public Observable<Optional<PasswordError>> getLoginPasswordError() {
|
||||
return loginPasswordError;
|
||||
}
|
||||
|
||||
public Observable<Boolean> isLoginPossible() {
|
||||
return isLoginPossible;
|
||||
}
|
||||
|
||||
public Observable<Boolean> isLoginSuccessful() {
|
||||
return isLoginSuccessful;
|
||||
}
|
||||
|
||||
public synchronized void onLoginUsernameChanged(String username) {
|
||||
LOGGER.log(Level.INFO, "USERNAME CHANGED: " + username);
|
||||
if (StringUtils.isNullOrEmpty(username)) {
|
||||
loginUsernameValue = null;
|
||||
loginUsernameError.onNext(new Optional<>(UsernameError.emptyUsername));
|
||||
} else {
|
||||
try {
|
||||
loginUsernameValue = JidCreate.entityBareFrom(username);
|
||||
loginUsernameError.onNext(new Optional<>());
|
||||
} catch (XmppStringprepException e) {
|
||||
loginUsernameValue = null;
|
||||
loginUsernameError.onNext(new Optional<>(UsernameError.invalidUsername));
|
||||
}
|
||||
}
|
||||
updateLoginPossible();
|
||||
}
|
||||
|
||||
public synchronized void onLoginPasswordChanged(String password) {
|
||||
if (StringUtils.isNullOrEmpty(password)) {
|
||||
loginPasswordValue = null;
|
||||
loginPasswordError.onNext(new Optional<>(PasswordError.emptyPassword));
|
||||
} else {
|
||||
loginPasswordValue = password;
|
||||
loginPasswordError.onNext(new Optional<>());
|
||||
}
|
||||
updateLoginPossible();
|
||||
}
|
||||
|
||||
private synchronized void updateLoginPossible() {
|
||||
isLoginPossible.onNext(loginUsernameValue != null && loginPasswordValue != null);
|
||||
}
|
||||
|
||||
public synchronized void login() {
|
||||
if (!isLoginPossible.getValue()) {
|
||||
// Prevent race condition where account would be logged in twice
|
||||
return;
|
||||
}
|
||||
isLoginPossible.onNext(false);
|
||||
|
||||
Account account = createAccountEntity();
|
||||
|
||||
MercuryConnection connection = connectionManager.createConnection(account);
|
||||
addDisposable(connection.connect()
|
||||
.andThen(connection.login())
|
||||
.andThen(connectionManager.registerConnection(connection))
|
||||
.andThen(accountRepository.insertAccount(account))
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(uiScheduler)
|
||||
.subscribe(
|
||||
this::onLoginSuccessful,
|
||||
this::onLoginFailed
|
||||
));
|
||||
}
|
||||
|
||||
private Account createAccountEntity() {
|
||||
Account account = new Account();
|
||||
account.setAddress(loginUsernameValue.asEntityBareJidString());
|
||||
account.setPassword(loginPasswordValue);
|
||||
account.setEnabled(true);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private void onLoginSuccessful(Account account) {
|
||||
LOGGER.log(Level.FINER, "Successfully added new account " + account);
|
||||
isLoginSuccessful.onNext(true);
|
||||
}
|
||||
|
||||
private void onLoginFailed(Throwable error) {
|
||||
isLoginPossible.onNext(true);
|
||||
if (error instanceof InvalidCredentialsException) {
|
||||
loginPasswordError.onNext(new Optional<>(PasswordError.incorrectPassword));
|
||||
} else if (error instanceof ServerUnreachableException) {
|
||||
loginUsernameError.onNext(new Optional<>(UsernameError.unreachableServer));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import java.util.logging.Logger;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
@ -73,7 +74,7 @@ public class MercuryConnectionManager {
|
|||
|
||||
public void start() {
|
||||
accountRepository.observeAllAccounts()
|
||||
.subscribe(accounts -> registerConnections(accounts));
|
||||
.subscribe(accounts -> doRegisterConnections(accounts));
|
||||
}
|
||||
|
||||
public List<MercuryConnection> getConnections() {
|
||||
|
@ -97,16 +98,20 @@ public class MercuryConnectionManager {
|
|||
return new MercuryConnection(connectionFactory.createConnection(account), account);
|
||||
}
|
||||
|
||||
public void registerConnections(List<Account> accounts) {
|
||||
public void doRegisterConnections(List<Account> accounts) {
|
||||
for (Account account : accounts) {
|
||||
if (!connectionsMap.containsKey(account.getId())) {
|
||||
MercuryConnection connection = createConnection(account);
|
||||
registerConnection(connection);
|
||||
doRegisterConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerConnection(MercuryConnection connection) {
|
||||
public Completable registerConnection(MercuryConnection connection) {
|
||||
return Completable.fromAction(() -> doRegisterConnection(connection));
|
||||
}
|
||||
|
||||
public void doRegisterConnection(MercuryConnection connection) {
|
||||
LOGGER.log(Level.INFO, "Register Connection " + connection.getAccountId());
|
||||
putConnection(connection);
|
||||
disposable.add(accountRepository
|
||||
|
|
Loading…
Reference in a new issue