package org.mercury_im.messenger.core.viewmodel.account; 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.core.SchedulersFacade; import org.mercury_im.messenger.core.account.error.PasswordError; import org.mercury_im.messenger.core.account.error.UsernameError; import org.mercury_im.messenger.core.connection.MercuryConnectionManager; import org.mercury_im.messenger.core.connection.exception.InvalidCredentialsException; import org.mercury_im.messenger.core.connection.exception.ServerUnreachableException; import org.mercury_im.messenger.core.connection.state.ConnectionState; import org.mercury_im.messenger.core.connection.state.ConnectivityState; import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.MercuryViewModel; import org.mercury_im.messenger.entity.Account; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import io.reactivex.Observable; 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 SchedulersFacade schedulers; private BehaviorSubject> loginUsernameError; private BehaviorSubject> loginPasswordError; private BehaviorSubject isLoginPossible; private BehaviorSubject> isLoginSuccessful; private BehaviorSubject displayProgressBar; private EntityBareJid loginUsernameValue; private String loginPasswordValue; private Account account; @Inject public LoginViewModel(MercuryConnectionManager connectionManager, AccountRepository accountRepository, SchedulersFacade schedulers) { this.connectionManager = connectionManager; this.accountRepository = accountRepository; this.schedulers = schedulers; reset(); } public void reset() { loginUsernameError = BehaviorSubject.createDefault(new Optional<>()); loginPasswordError = BehaviorSubject.createDefault(new Optional<>()); isLoginPossible = BehaviorSubject.createDefault(false); isLoginSuccessful = BehaviorSubject.createDefault(new Optional<>()); displayProgressBar = BehaviorSubject.createDefault(false); loginUsernameValue = null; loginPasswordValue = null; } public Observable> getLoginUsernameError() { return loginUsernameError; } public Observable> getLoginPasswordError() { return loginPasswordError; } public Observable isLoginPossible() { return isLoginPossible; } public Observable> isLoginSuccessful() { return isLoginSuccessful; } public synchronized void onLoginUsernameChanged(String 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 Account login() { displayProgressBar.onNext(true); if (!isLoginPossible.getValue()) { // Prevent race condition where account would be logged in twice return account; } isLoginPossible.onNext(false); account = createAccountEntity(); //MercuryConnection connection = connectionManager.createConnection(account); addDisposable(accountRepository.upsertAccount(account) //.andThen(connection.connect()) //.andThen(connection.login()) //.andThen(connectionManager.registerConnection(connection)) .subscribeOn(schedulers.getNewThread()) .observeOn(schedulers.getUiScheduler()) .subscribe( a -> LOGGER.log(Level.INFO, "Account " + a + " successfully inserted."), this::onLoginFailed )); addDisposable(connectionManager.observeConnectionPool() .map(cp -> { ConnectionState state = cp.getConnectionStates().get(account.getId()); if (state == null) { return ConnectivityState.disconnected; } else { return state.getConnectivity(); } }) .filter(s -> s == ConnectivityState.disconnectedOnError || s == ConnectivityState.authenticated) .firstOrError() .compose(schedulers.executeUiSafeSingle()) .subscribe(connectivityState -> { displayProgressBar.onNext(false); if (connectivityState == ConnectivityState.disconnectedOnError) { onLoginFailed(new Exception()); } else { onLoginSuccessful(account); } })); return account; } 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.INFO, "Login successful."); isLoginSuccessful.onNext(new Optional<>(account)); } private void onLoginFailed(Throwable error) { LOGGER.log(Level.INFO, "Login failed!"); isLoginPossible.onNext(true); if (error instanceof InvalidCredentialsException) { loginPasswordError.onNext(new Optional<>(PasswordError.incorrectPassword)); } else if (error instanceof ServerUnreachableException) { loginUsernameError.onNext(new Optional<>(UsernameError.unreachableServer)); } } public Observable isDisplayProgressBar() { return displayProgressBar; } }