diff --git a/app/build.gradle b/app/build.gradle index 31dd148..75264ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,6 +104,10 @@ dependencies { implementation "com.jakewharton:butterknife:$butterKnifeVersion" annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion" + // RxBinding + implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0' + implementation 'com.jakewharton.rxbinding4:rxbinding-core:4.0.0' + // support libraries implementation "androidx.appcompat:appcompat:$appCompatVersion" implementation 'com.google.android.material:material:1.2.0-alpha03' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24acbbd..64cded9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,9 @@ android:roundIcon="@drawable/ic_mercury_icon" android:supportsRtl="true" android:theme="@style/Theme.Mercury"> - + + + @@ -35,6 +37,7 @@ android:label="@string/title_activity_settings" /> + diff --git a/app/src/main/java/org/mercury_im/messenger/android/MercuryImApplication.java b/app/src/main/java/org/mercury_im/messenger/android/MercuryImApplication.java index 7528646..305ec44 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/MercuryImApplication.java +++ b/app/src/main/java/org/mercury_im/messenger/android/MercuryImApplication.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.os.Build; import org.jivesoftware.smack.android.AndroidSmackInitializer; -import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager; import org.mercury_im.messenger.android.di.component.DaggerAppComponent; import org.mercury_im.messenger.android.util.AndroidLoggingHandler; import org.mercury_im.messenger.core.Messenger; diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/AndroidIkeyBackupCreationViewModel.java similarity index 81% rename from app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java rename to app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/AndroidIkeyBackupCreationViewModel.java index 0e40e92..beb7586 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/AndroidIkeyBackupCreationViewModel.java @@ -1,4 +1,4 @@ -package org.mercury_im.messenger.android.ui.ikey; +package org.mercury_im.messenger.android.crypto.ikey; import android.graphics.Bitmap; @@ -11,6 +11,7 @@ import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel; import org.mercury_im.messenger.android.util.QrCodeGenerator; import org.mercury_im.messenger.core.SchedulersFacade; +import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.ikey.IkeySecretKeyBackupCreationViewModel; @@ -22,9 +23,9 @@ import javax.inject.Inject; import io.reactivex.Observable; -public class IkeyBackupCreationAndroidViewModel extends ViewModel implements MercuryAndroidViewModel { +public class AndroidIkeyBackupCreationViewModel extends ViewModel implements MercuryAndroidViewModel { - private static final Logger LOGGER = Logger.getLogger(IkeyBackupCreationAndroidViewModel.class.getName()); + private static final Logger LOGGER = Logger.getLogger(AndroidIkeyBackupCreationViewModel.class.getName()); MutableLiveData passphrase = new MutableLiveData<>(); MutableLiveData passphraseAsQrCode = new MutableLiveData<>(); @@ -32,10 +33,13 @@ public class IkeyBackupCreationAndroidViewModel extends ViewModel implements Mer @Inject IkeySecretKeyBackupCreationViewModel commonViewModel; + @Inject + OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator; + @Inject SchedulersFacade schedulers; - public IkeyBackupCreationAndroidViewModel() { + public AndroidIkeyBackupCreationViewModel() { MercuryImApplication.getApplication().getAppComponent().inject(this); } @@ -43,8 +47,8 @@ public class IkeyBackupCreationAndroidViewModel extends ViewModel implements Mer getCommonViewModel().setAccountId(accountId); Observable> passphraseObservable = - //getCommonViewModel().getPassphrase() - Observable.just(new Optional<>(new OpenPgpSecretKeyBackupPassphrase("71ZA-Y416-UA7A-7NCE-3SNM-88EF"))); + getCommonViewModel().getPassphrase(); + //Observable.just(new Optional<>(passphraseGenerator.generateBackupPassphrase())); addDisposable(passphraseObservable .subscribeOn(schedulers.getIoScheduler()) diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupCreationFragment.java similarity index 88% rename from app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java rename to app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupCreationFragment.java index 502c52f..058cd3f 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupCreationFragment.java @@ -1,4 +1,4 @@ -package org.mercury_im.messenger.android.ui.ikey; +package org.mercury_im.messenger.android.crypto.ikey; import androidx.lifecycle.ViewModelProvider; @@ -23,7 +23,7 @@ import butterknife.ButterKnife; public class IkeyBackupCreationFragment extends Fragment { - private IkeyBackupCreationAndroidViewModel viewModel; + private AndroidIkeyBackupCreationViewModel viewModel; @BindView(R.id.backup_code) TextView backupCode; @@ -52,7 +52,7 @@ public class IkeyBackupCreationFragment extends Fragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - viewModel = new ViewModelProvider(this).get(IkeyBackupCreationAndroidViewModel.class); + viewModel = new ViewModelProvider(this).get(AndroidIkeyBackupCreationViewModel.class); viewModel.initialize(accountId); viewModel.getPassphrase().observe(getViewLifecycleOwner(), passphrase -> backupCode.setText(passphrase)); diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupRestoreActivity.java similarity index 72% rename from app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java rename to app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupRestoreActivity.java index 2bb2f21..b061230 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/android/crypto/ikey/IkeyBackupRestoreActivity.java @@ -1,4 +1,4 @@ -package org.mercury_im.messenger.android.ui.ikey; +package org.mercury_im.messenger.android.crypto.ikey; import androidx.appcompat.app.AppCompatActivity; @@ -11,6 +11,6 @@ public class IkeyBackupRestoreActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_ikey_backup_restore); + setContentView(R.layout.fragment_ikey_backup_restore); } } diff --git a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java index ea2fbc7..e78d2f3 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java +++ b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java @@ -4,8 +4,12 @@ import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule; import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule; import org.mercury_im.messenger.android.ui.account.detail.AndroidAccountDetailsViewModel; +import org.mercury_im.messenger.android.ui.account.login.AddAccountActivity; +import org.mercury_im.messenger.android.ui.account.login.EnterAccountDetailsFragment; +import org.mercury_im.messenger.android.ui.account.login.IkeySetupViewModel; import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel; -import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationAndroidViewModel; +import org.mercury_im.messenger.android.crypto.ikey.AndroidIkeyBackupCreationViewModel; +import org.mercury_im.messenger.core.di.module.IkeyModule; import org.mercury_im.messenger.core.di.module.OpenPgpModule; import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule; import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule; @@ -50,6 +54,7 @@ import dagger.Component; XmppTcpConnectionFactoryModule.class, RxMercuryMessageStoreFactoryModule.class, OpenPgpModule.class, + IkeyModule.class, RxMercuryRosterStoreFactoryModule.class, StanzaIdSourceFactoryModule.class }) @@ -70,7 +75,7 @@ public interface AppComponent { void inject(ContactDetailActivity contactDetailActivity); - + void inject(EnterAccountDetailsFragment enterAccountDetailsFragment); // ViewModels @@ -94,10 +99,13 @@ public interface AppComponent { void inject(AndroidAccountDetailsViewModel accountDetailsViewModel); - void inject(IkeyBackupCreationAndroidViewModel ikeyBackupCreationAndroidViewModel); + void inject(AndroidIkeyBackupCreationViewModel androidIkeyBackupCreationViewModel); + + void inject(IkeySetupViewModel ikeySetupViewModel); + // void inject(AndroidOxSecretKeyBackupRestoreViewModel androidOxSecretKeyBackupRestoreViewModel); - // Common VMs + // Common VMs void inject(LoginViewModel loginViewModel); void inject(AccountListViewModel accountsViewModel); @@ -108,4 +116,5 @@ public interface AppComponent { void inject(MercuryForegroundService service); void inject(MercuryEntityCapsStore store); + } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java index 924b119..f94db8a 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java @@ -11,11 +11,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.RecyclerView; @@ -24,7 +23,7 @@ import com.google.android.material.card.MaterialCardView; import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.MercuryImApplication; -import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationFragment; +import org.mercury_im.messenger.android.crypto.ikey.IkeyBackupCreationFragment; import org.mercury_im.messenger.android.ui.openpgp.ToggleableFingerprintsAdapter; import org.mercury_im.messenger.android.ui.openpgp.OpenPgpV4FingerprintFormatter; import org.mercury_im.messenger.core.util.Optional; @@ -46,6 +45,9 @@ public class AccountDetailsFragment extends Fragment { @BindView(R.id.jid) TextView jid; + @BindView(R.id.btn_backup) + Button localKeyCreateBackupButton; + @BindView(R.id.btn_share) Button localFingerprintShareButton; @@ -154,6 +156,15 @@ public class AccountDetailsFragment extends Fragment { getParentFragmentManager().beginTransaction() .replace(R.id.fragment, IkeyBackupCreationFragment.newInstance(accountId)).commit(); }); + + localKeyCreateBackupButton.setOnClickListener(v -> { + Toast.makeText(getContext(), "Not yet implemented.", Toast.LENGTH_SHORT).show(); + }); + + viewModel.isAccountAuthenticated().observe(this, authenticated -> { + ikeyCreateBackupButton.setEnabled(authenticated); + localKeyCreateBackupButton.setEnabled(authenticated); + }); } private void startShareFingerprintIntent(OpenPgpV4Fingerprint fingerprint) { diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java index f39f3f9..1310274 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java @@ -18,6 +18,7 @@ import org.mercury_im.messenger.core.util.DefaultUtil; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; +import org.mercury_im.messenger.entity.Account; import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.ArrayList; @@ -46,6 +47,8 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements private MutableLiveData localFingerprint = new MutableLiveData<>(); private MutableLiveData> remoteFingerprints = new MutableLiveData<>(new ArrayList<>()); private MutableLiveData jid = new MutableLiveData<>(DefaultUtil.defaultJid()); + private MutableLiveData accountEnabled = new MutableLiveData<>(false); + private MutableLiveData accountAuthenticated = new MutableLiveData<>(false); public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) { super(application); @@ -75,6 +78,18 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements () -> LOGGER.log(Level.INFO, "observing remote fingerprint onComplete."))); addDisposable(getCommonViewModel().getJid(accountId).subscribe(jid::postValue)); + + addDisposable(getCommonViewModel().isAccountEnabled(accountId) + .compose(schedulers.executeUiSafeObservable()) + .subscribe(bool -> accountEnabled.postValue(bool), + e -> LOGGER.log(Level.SEVERE, "Error subscribing to account enabled state", e), + () -> LOGGER.log(Level.INFO, "observing account enabled state onComplete."))); + + addDisposable(getCommonViewModel().isAccountAuthenticated(accountId) + .compose(schedulers.executeUiSafeObservable()) + .subscribe(bool -> accountAuthenticated.postValue(bool), + e -> LOGGER.log(Level.SEVERE, "Error subscribing to account authenticated state", e), + () -> LOGGER.log(Level.INFO, "observing account authenticated state onComplete."))); } @Override @@ -112,10 +127,10 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements public void onGenerateIkey() { addDisposable(getCommonViewModel().generateIkey(accountId) - .subscribeOn(schedulers.getNewThread()) - .observeOn(schedulers.getUiScheduler()) - .subscribe(() -> LOGGER.log(Level.INFO, "IKey generated for account " + accountId), - e -> LOGGER.log(Level.SEVERE, "Could not generate Ikey", e))); + .subscribeOn(schedulers.getNewThread()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(() -> LOGGER.log(Level.INFO, "IKey generated for account " + accountId), + e -> LOGGER.log(Level.SEVERE, "Could not generate Ikey", e))); } public void onDeleteIkey() { @@ -134,6 +149,14 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements e -> LOGGER.log(Level.SEVERE, "Could not restore Ikey backup", e))); } + public LiveData isAccountEnabled() { + return accountEnabled; + } + + public LiveData isAccountAuthenticated() { + return accountAuthenticated; + } + public static class AndroidAccountDetailsViewModelFactory implements ViewModelProvider.Factory { private final Application application; diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListFragment.java index 8c94905..8145d91 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListFragment.java @@ -1,6 +1,7 @@ package org.mercury_im.messenger.android.ui.account.list; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -16,6 +17,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.ui.account.OnAccountListItemClickListener; +import org.mercury_im.messenger.android.ui.account.login.AddAccountActivity; import org.mercury_im.messenger.android.ui.account.login.AddAccountDialogFragment; import java.util.ArrayList; @@ -58,7 +60,10 @@ public class AccountListFragment extends Fragment implements SearchView.OnQueryT public void onResume() { super.onResume(); observeViewModel(); - fab.setOnClickListener(v -> displayAddAccountDialog()); + //fab.setOnClickListener(v -> displayAddAccountDialog()); + fab.setOnClickListener(v -> { + startActivity(new Intent(getContext(), AddAccountActivity.class)); + }); } private void observeViewModel() { diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListRecyclerViewAdapter.java index 7ebd4ef..aea7cd3 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListRecyclerViewAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/list/AccountListRecyclerViewAdapter.java @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.jetbrains.annotations.NotNull; import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.ui.account.OnAccountListItemClickListener; +import org.mercury_im.messenger.core.connection.state.ConnectivityState; import org.mercury_im.messenger.core.viewmodel.account.list.AccountViewItem; import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable; @@ -62,8 +63,9 @@ public class AccountListRecyclerViewAdapter extends RecyclerView.Adapter { - if (!compoundButton.isPressed()) { + holder.enabled.setEnabled(shouldSwitchBeEnabled(viewItem.getConnectivityState())); + holder.enabled.setOnCheckedChangeListener((switch_, checked) -> { + if (!switch_.isPressed()) { return; } viewModel.setAccountEnabled(account, checked); @@ -78,6 +80,12 @@ public class AccountListRecyclerViewAdapter extends RecyclerView.Adapter onAccountClickListener.onAccountListItemClick(account)); } + private static boolean shouldSwitchBeEnabled(ConnectivityState connectivity) { + return connectivity == ConnectivityState.authenticated || + connectivity == ConnectivityState.disconnected || + connectivity == ConnectivityState.disconnectedOnError; + } + class ViewHolder extends RecyclerView.ViewHolder { final View mView; final ImageView avatar; diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountActivity.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountActivity.java new file mode 100644 index 0000000..6e2e8e3 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountActivity.java @@ -0,0 +1,109 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import org.mercury_im.messenger.R; +import org.mercury_im.messenger.android.MercuryImApplication; +import org.mercury_im.messenger.android.crypto.ikey.IkeyBackupCreationFragment; +import org.mercury_im.messenger.android.di.component.AppComponent; +import org.mercury_im.messenger.core.util.Optional; +import org.mercury_im.messenger.entity.Account; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +import butterknife.BindView; +import butterknife.ButterKnife; +import lombok.Getter; + +public class AddAccountActivity extends AppCompatActivity { + + @BindView(R.id.viewpager) + ViewPager2 viewPager; + + private SetupPagerAdapter pagerAdapter; + + private AppComponent appComponent; + private IkeySetupViewModel ikeyViewModel; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.layout_viewpager2); + ButterKnife.bind(this); + appComponent = MercuryImApplication.getApplication().getAppComponent(); + + pagerAdapter = new SetupPagerAdapter(this); + viewPager.setAdapter(pagerAdapter); + viewPager.setUserInputEnabled(false); // disable swiping + + ikeyViewModel = new ViewModelProvider(this).get(IkeySetupViewModel.class); // shared between fragments + } + + public void loginFinished(Optional optionalAccount) { + if (optionalAccount.isPresent()) { + pagerAdapter.getFragments().put(1, IkeySetupFragment.newInstance(optionalAccount.getItem().getId())); + pagerAdapter.notifyDataSetChanged(); + viewPager.setCurrentItem(1); + } + } + + public void skipIkeySetup() { + finish(); + } + + public void setupIkey(UUID accountId) { + int pos = pagerAdapter.getItemCount(); + pagerAdapter.getFragments().put(pos, IkeyBackupRestoreOrSkipFragment.newInstance(accountId)); + pagerAdapter.notifyDataSetChanged(); + viewPager.setCurrentItem(pos); + } + + public void restoreIkeyBackup(UUID accountId) { + int pos = pagerAdapter.getItemCount(); + pagerAdapter.getFragments().put(pos, IkeyBackupRestoreSuccessfulFragment.newInstance(accountId)); + pagerAdapter.notifyDataSetChanged(); + viewPager.setCurrentItem(pos); + } + + public void generateIkeyBackup(UUID accountId) { + int pos = pagerAdapter.getItemCount(); + pagerAdapter.getFragments().put(pos, IkeyBackupCreationFragment.newInstance(accountId)); + pagerAdapter.notifyDataSetChanged(); + viewPager.setCurrentItem(pos); + } + + private class SetupPagerAdapter extends FragmentStateAdapter { + + @Getter + private final Map fragments = new LinkedHashMap<>(); + + SetupPagerAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + + fragments.put(0, EnterAccountDetailsFragment.newInstance(appComponent)); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragments.get(position); + } + + @Override + public int getItemCount() { + return fragments.size(); + } + } + +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountDialogFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountDialogFragment.java index 8919bf7..9fd8e8e 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountDialogFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AddAccountDialogFragment.java @@ -44,7 +44,7 @@ public class AddAccountDialogFragment extends AppCompatDialogFragment { @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { LayoutInflater inflater = requireActivity().getLayoutInflater(); - View dialogView = inflater.inflate(R.layout.dialog_login, null); + View dialogView = inflater.inflate(R.layout.view_account_credentials, null); ButterKnife.bind(this, dialogView); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); @@ -83,8 +83,8 @@ public class AddAccountDialogFragment extends AppCompatDialogFragment { viewModel.getLoginPasswordError().observe(this, error -> passwordLayout.setError(error)); viewModel.isLoginButtonEnabled().observe(this, positiveButton::setEnabled); - viewModel.isLoginFinished().observe(this, finished -> { - if (finished) { + viewModel.isLoginFinished().observe(this, optAccount -> { + if (optAccount.isPresent()) { dismiss(); } }); diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AndroidLoginViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AndroidLoginViewModel.java index 25fe418..76156ab 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AndroidLoginViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/AndroidLoginViewModel.java @@ -14,6 +14,7 @@ import org.mercury_im.messenger.core.account.error.PasswordError; import org.mercury_im.messenger.core.account.error.UsernameError; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel; +import org.mercury_im.messenger.entity.Account; import javax.inject.Inject; @@ -24,7 +25,7 @@ public class AndroidLoginViewModel extends AndroidViewModel implements MercuryAn 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); + private final MutableLiveData> loginFinished = new MutableLiveData<>(new Optional<>()); @Inject LoginViewModel commonViewModel; @@ -99,8 +100,8 @@ public class AndroidLoginViewModel extends AndroidViewModel implements MercuryAn getCommonViewModel().dispose(); } - public void onLoginButtonClicked() { - getCommonViewModel().login(); + public Account onLoginButtonClicked() { + return getCommonViewModel().login(); } public LiveData getLoginUsernameError() { @@ -115,7 +116,7 @@ public class AndroidLoginViewModel extends AndroidViewModel implements MercuryAn return loginButtonEnabled; } - public LiveData isLoginFinished() { + public LiveData> isLoginFinished() { return loginFinished; } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/EnterAccountDetailsFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/EnterAccountDetailsFragment.java new file mode 100644 index 0000000..005900a --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/EnterAccountDetailsFragment.java @@ -0,0 +1,117 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import org.mercury_im.messenger.R; +import org.mercury_im.messenger.android.MercuryImApplication; +import org.mercury_im.messenger.android.di.component.AppComponent; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class EnterAccountDetailsFragment extends Fragment { + + @BindView(R.id.username_layout) + TextInputLayout usernameLayout; + + @BindView(R.id.username) + TextInputEditText username; + + @BindView(R.id.password_layout) + TextInputLayout passwordLayout; + + @BindView(R.id.password) + TextInputEditText password; + + @BindView(R.id.btn_login) + Button loginButton; + + @BindView(R.id.btn_cancel) + Button cancelButton; + + @BindView(R.id.progress) + ProgressBar progressBar; + + @Inject + AndroidLoginViewModel viewModel; + + public EnterAccountDetailsFragment(AppComponent appComponent) { + appComponent.inject(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_enter_account_credentials, container, false); + ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new AndroidLoginViewModel(MercuryImApplication.getApplication()); + + loginButton.setOnClickListener(v -> viewModel.onLoginButtonClicked()); + + cancelButton.setOnClickListener(v -> getActivity().finish()); + + viewModel.getLoginUsernameError().observe(this, error -> usernameLayout.setError(error)); + viewModel.getLoginPasswordError().observe(this, error -> passwordLayout.setError(error)); + viewModel.isLoginButtonEnabled().observe(this, loginButton::setEnabled); + viewModel.isLoginButtonEnabled().observe(this, enabled -> + progressBar.setVisibility(enabled ? View.GONE : View.VISIBLE)); + + viewModel.isLoginFinished().observe(this, optAccount -> + ((AddAccountActivity) getActivity()).loginFinished(optAccount)); + + username.addTextChangedListener(viewModel.getUsernameTextChangedListener()); + password.addTextChangedListener(viewModel.getPasswordTextChangedListener()); + + username.setOnEditorActionListener(focusPasswordFieldOnEnterPressed); + password.setOnEditorActionListener(loginOnEnterPressed); + } + + private final TextView.OnEditorActionListener focusPasswordFieldOnEnterPressed = new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_NEXT) { + password.requestFocus(); + return true; + } + return false; + } + }; + + private final TextView.OnEditorActionListener loginOnEnterPressed = new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) { + viewModel.onLoginButtonClicked(); + return true; + } + return false; + } + }; + + public static EnterAccountDetailsFragment newInstance(AppComponent appComponent) { + return new EnterAccountDetailsFragment(appComponent); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreOrSkipFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreOrSkipFragment.java new file mode 100644 index 0000000..3e17d0d --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreOrSkipFragment.java @@ -0,0 +1,68 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import org.mercury_im.messenger.R; + +import java.util.UUID; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class IkeyBackupRestoreOrSkipFragment extends Fragment { + + @BindView(R.id.btn_restore) + Button restoreButton; + + @BindView(R.id.btn_new_key) + Button regenerateButton; + + @BindView(R.id.edit_backup_code) + EditText backupCodeEditText; + + @BindView(R.id.btn_scan) + ImageButton scanButton; + + private final UUID accountId; + + private IkeySetupViewModel viewModel; + + IkeyBackupRestoreOrSkipFragment(UUID accountId) { + this.accountId = accountId; + } + + public static IkeyBackupRestoreOrSkipFragment newInstance(UUID accountId) { + return new IkeyBackupRestoreOrSkipFragment(accountId); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_ikey_backup_restore, container, false); + ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class); + + restoreButton.setOnClickListener(v -> + ((AddAccountActivity) getActivity()).restoreIkeyBackup(accountId)); + regenerateButton.setOnClickListener(v -> + ((AddAccountActivity) getActivity()).generateIkeyBackup(accountId)); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreSuccessfulFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreSuccessfulFragment.java new file mode 100644 index 0000000..2cca734 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeyBackupRestoreSuccessfulFragment.java @@ -0,0 +1,56 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import org.mercury_im.messenger.R; + +import java.util.UUID; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class IkeyBackupRestoreSuccessfulFragment extends Fragment { + + @BindView(R.id.btn_done) + Button doneButton; + + @BindView(R.id.text) + TextView fingerprint; + + private final UUID accountId; + private IkeySetupViewModel viewModel; + + IkeyBackupRestoreSuccessfulFragment(UUID accountId) { + this.accountId = accountId; + } + + public static IkeyBackupRestoreSuccessfulFragment newInstance(UUID accountId) { + return new IkeyBackupRestoreSuccessfulFragment(accountId); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_ikey_backup_restore_success, container); + ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class); + + doneButton.setOnClickListener(v -> getActivity().finish()); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupFragment.java new file mode 100644 index 0000000..76b5bcc --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupFragment.java @@ -0,0 +1,74 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import org.mercury_im.messenger.R; + +import java.util.UUID; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class IkeySetupFragment extends Fragment { + + @BindView(R.id.btn_skip) + Button skipButton; + + @BindView(R.id.btn_continue) + Button continueButton; + + private final UUID accountId; + private IkeySetupViewModel viewModel; + + public IkeySetupFragment(UUID accountId) { + this.accountId = accountId; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_ikey_setup, container, false); + ButterKnife.bind(this, view); + return view; + } + + public static IkeySetupFragment newInstance(UUID accountId) { + return new IkeySetupFragment(accountId); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class); + viewModel.init(accountId); + + skipButton.setOnClickListener(v -> + getActivity().finish()); + + continueButton.setOnClickListener(v -> { + viewModel.fetchBackupElement() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess(opt -> { + if (opt.isPresent()) { + ((AddAccountActivity) getActivity()).setupIkey(accountId); + } else { + ((AddAccountActivity) getActivity()).generateIkeyBackup(accountId); + } + }) + .subscribe(); + }); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupViewModel.java new file mode 100644 index 0000000..1c1f51b --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/login/IkeySetupViewModel.java @@ -0,0 +1,73 @@ +package org.mercury_im.messenger.android.ui.account.login; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ikey.IkeyManager; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; +import org.jivesoftware.smackx.pubsub.PubSubException; +import org.mercury_im.messenger.android.MercuryImApplication; +import org.mercury_im.messenger.core.connection.MercuryConnection; +import org.mercury_im.messenger.core.connection.MercuryConnectionManager; +import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer; +import org.mercury_im.messenger.core.util.Optional; + +import java.util.UUID; + +import javax.inject.Inject; + +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.Single; + +public class IkeySetupViewModel extends AndroidViewModel { + + @Inject + MercuryConnectionManager connectionManager; + + @Inject + IkeyInitializer ikeyInitializer; + + IkeyManager ikeyManager; + private SecretkeyElement secretkeyElement; + + public IkeySetupViewModel(@NonNull Application application) { + super(application); + MercuryImApplication.getApplication().getAppComponent().inject(this); + } + + public void init(UUID accountId) { + MercuryConnection connection = connectionManager.getConnection(accountId); + ikeyManager = ikeyInitializer.initFor(connection); + } + + public Single> fetchBackupElement() { + return fetchMaybeBackupElement() + .map(Optional::new) + .toSingle(new Optional<>()); + } + + private Maybe fetchMaybeBackupElement() { + return new Maybe() { + @Override + protected void subscribeActual(MaybeObserver observer) { + try { + secretkeyElement = ikeyManager.fetchSecretIdentityKey(); + if (secretkeyElement != null) { + observer.onSuccess(secretkeyElement); + } else { + observer.onComplete(); + } + } catch (PubSubException.NotALeafNodeException e) { + observer.onComplete(); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + observer.onError(e); + } + } + }; + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/AndroidOxSecretKeyBackupRestoreViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/AndroidOxSecretKeyBackupRestoreViewModel.java index 02de237..fb14ae5 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/AndroidOxSecretKeyBackupRestoreViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/AndroidOxSecretKeyBackupRestoreViewModel.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MutableLiveData; +import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.openpgp.OxBackupRestoreError; @@ -14,12 +15,14 @@ import org.mercury_im.messenger.core.viewmodel.openpgp.OxSecretKeyBackupRestoreV import java.util.logging.Level; import java.util.logging.Logger; +import javax.inject.Inject; + public class AndroidOxSecretKeyBackupRestoreViewModel extends AndroidViewModel implements MercuryAndroidViewModel { private static final Logger LOGGER = Logger.getLogger(AndroidOxSecretKeyBackupRestoreViewModel.class.getName()); - //@Inject + // @Inject OxSecretKeyBackupRestoreViewModel commonViewModel; private MutableLiveData> restoreError = @@ -27,7 +30,7 @@ public class AndroidOxSecretKeyBackupRestoreViewModel extends AndroidViewModel public AndroidOxSecretKeyBackupRestoreViewModel(@NonNull Application application) { super(application); - //MercuryImApplication.getApplication().getAppComponent().inject(this); + // MercuryImApplication.getApplication().getAppComponent().inject(this); addDisposable(getCommonViewModel().observeBackupRestoreError() .subscribe(opt -> restoreError.postValue(opt), diff --git a/app/src/main/res/layout/fragment_enter_account_credentials.xml b/app/src/main/res/layout/fragment_enter_account_credentials.xml new file mode 100644 index 0000000..56cb61f --- /dev/null +++ b/app/src/main/res/layout/fragment_enter_account_credentials.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + +