From 9ed9c36fa3458fb2bee6bb9b5964cea64b220099 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 6 Jun 2020 16:45:20 +0200 Subject: [PATCH] Account Management: Move ViewModel logic mostly to domain --- app/src/main/AndroidManifest.xml | 3 - .../messenger/di/component/AppComponent.java | 23 ++- .../messenger/ui/MercuryAndroidViewModel.java | 18 ++ .../ui/account/AccountsFragment.java | 19 +- .../account/AccountsRecyclerViewAdapter.java | 4 +- .../ui/account/AccountsViewModel.java | 67 ------- .../ui/account/AddAccountDialogFragment.java | 123 +++++++++++++ .../ui/account/AndroidAccountsViewModel.java | 52 ++++++ .../ui/account/AndroidLoginViewModel.java | 155 ++++++++++++++++ .../messenger/ui/account/LoginActivity.java | 158 ----------------- .../messenger/ui/account/LoginViewModel.java | 167 ------------------ .../{activity_login.xml => dialog_login.xml} | 11 +- app/src/main/res/values/strings.xml | 2 + .../org/mercury_im/messenger/Messenger.java | 14 +- .../account/error/PasswordError.java | 2 - .../account/error/UsernameError.java | 3 +- .../messenger/di/module/ViewModelModule.java | 36 ++++ .../messenger/viewmodel/MercuryViewModel.java | 17 ++ .../viewmodel/accounts/AccountsViewModel.java | 62 +++++++ .../viewmodel/accounts/LoginViewModel.java | 162 +++++++++++++++++ .../xmpp/MercuryConnectionManager.java | 13 +- 21 files changed, 667 insertions(+), 444 deletions(-) create mode 100644 app/src/main/java/org/mercury_im/messenger/ui/MercuryAndroidViewModel.java delete mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/AccountsViewModel.java create mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/AddAccountDialogFragment.java create mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/AndroidAccountsViewModel.java create mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/AndroidLoginViewModel.java delete mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/LoginActivity.java delete mode 100644 app/src/main/java/org/mercury_im/messenger/ui/account/LoginViewModel.java rename app/src/main/res/layout/{activity_login.xml => dialog_login.xml} (85%) create mode 100644 domain/src/main/java/org/mercury_im/messenger/di/module/ViewModelModule.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/viewmodel/MercuryViewModel.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/viewmodel/accounts/AccountsViewModel.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/viewmodel/accounts/LoginViewModel.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 221bfc4..0a60f99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,9 +32,6 @@ - diff --git a/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java b/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java index d834ef4..b64b939 100644 --- a/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java +++ b/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java @@ -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); diff --git a/app/src/main/java/org/mercury_im/messenger/ui/MercuryAndroidViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/MercuryAndroidViewModel.java new file mode 100644 index 0000000..52bc288 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/ui/MercuryAndroidViewModel.java @@ -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 getCommonViewModel(); + + default void dispose() { + getCommonViewModel().dispose(); + } + + default void addDisposable(Disposable disposable) { + getCommonViewModel().addDisposable(disposable); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsFragment.java index 109a19e..852af97 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsFragment.java @@ -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"); + } } diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsRecyclerViewAdapter.java index 6656eb0..fac0b9a 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsRecyclerViewAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsRecyclerViewAdapter.java @@ -26,9 +26,9 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter 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; } diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsViewModel.java deleted file mode 100644 index 6359150..0000000 --- a/app/src/main/java/org/mercury_im/messenger/ui/account/AccountsViewModel.java +++ /dev/null @@ -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 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 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) - ); - } -} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AddAccountDialogFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AddAccountDialogFragment.java new file mode 100644 index 0000000..e8c7ef5 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/ui/account/AddAccountDialogFragment.java @@ -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; + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidAccountsViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidAccountsViewModel.java new file mode 100644 index 0000000..18a5fdf --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidAccountsViewModel.java @@ -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 { + + private static final Logger LOGGER = Logger.getLogger(AndroidAccountsViewModel.class.getName()); + + private final MutableLiveData 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 getConnectionPool() { + return connectionPool; + } + + public void setAccountEnabled(Account accountModel, boolean enabled) { + getCommonViewModel().onToggleAccountEnabled(accountModel, enabled); + } + + @Override + public AccountsViewModel getCommonViewModel() { + return viewModel; + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidLoginViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidLoginViewModel.java new file mode 100644 index 0000000..c7a592c --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/ui/account/AndroidLoginViewModel.java @@ -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 { + + private static final Logger LOGGER = Logger.getLogger(AndroidLoginViewModel.class.getName()); + + + private final MutableLiveData loginUsernameError = new MutableLiveData<>(); + private final MutableLiveData loginPasswordError = new MutableLiveData<>(); + private final MutableLiveData loginButtonEnabled = new MutableLiveData<>(true); + private final MutableLiveData 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> mapUsernameErrorCodeToMessage(Observable> 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> mapPasswordErrorCodeToMessage(Observable> 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 getLoginUsernameError() { + return loginUsernameError; + } + + public LiveData getLoginPasswordError() { + return loginPasswordError; + } + + public LiveData isLoginButtonEnabled() { + return loginButtonEnabled; + } + + public LiveData 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()); + } + }; + +} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/LoginActivity.java b/app/src/main/java/org/mercury_im/messenger/ui/account/LoginActivity.java deleted file mode 100644 index 300f9d3..0000000 --- a/app/src/main/java/org/mercury_im/messenger/ui/account/LoginActivity.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/app/src/main/java/org/mercury_im/messenger/ui/account/LoginViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/account/LoginViewModel.java deleted file mode 100644 index d3439e1..0000000 --- a/app/src/main/java/org/mercury_im/messenger/ui/account/LoginViewModel.java +++ /dev/null @@ -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 usernameError = new MutableLiveData<>(new Error()); - private MutableLiveData passwordError = new MutableLiveData<>(new Error()); - private MutableLiveData loginButtonEnabled = new MutableLiveData<>(false); - private MutableLiveData 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 getPasswordError() { - return passwordError; - } - - public LiveData getUsernameError() { - return usernameError; - } - - public LiveData isLoginSuccessful() { - return loginCompleted; - } - - public LiveData 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; - } - } -} diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/dialog_login.xml similarity index 85% rename from app/src/main/res/layout/activity_login.xml rename to app/src/main/res/layout/dialog_login.xml index c2a4264..9d69b95 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/dialog_login.xml @@ -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"> -