From 5b648cbd356b17352d87cce69e40b43c0cdc6f65 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 1 Apr 2022 12:58:06 -0300 Subject: [PATCH] Add connection check button to Mailbox status UI and update the last connection timestamp accordingly --- .../android/mailbox/MailboxActivity.java | 6 +- .../briar/android/mailbox/MailboxState.java | 6 -- .../mailbox/MailboxStatusFragment.java | 75 +++++++++++++++---- .../android/mailbox/MailboxViewModel.java | 70 ++++++++++++++--- .../res/layout/fragment_mailbox_status.xml | 56 +++++++++++--- briar-android/src/main/res/values/strings.xml | 2 + 6 files changed, 167 insertions(+), 48 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java index df58ace2d..46ac84fcb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxActivity.java @@ -47,11 +47,11 @@ public class MailboxActivity extends BriarActivity { setContentView(R.layout.activity_mailbox); progressBar = findViewById(R.id.progressBar); - if (viewModel.getState().getValue() == null) { + if (viewModel.getPairingState().getValue() == null) { progressBar.setVisibility(VISIBLE); } - viewModel.getState().observeEvent(this, state -> { + viewModel.getPairingState().observeEvent(this, state -> { if (state instanceof MailboxState.NotSetup) { onNotSetup(); } else if (state instanceof MailboxState.ShowDownload) { @@ -85,7 +85,7 @@ public class MailboxActivity extends BriarActivity { @Override public void onBackPressed() { - MailboxState s = viewModel.getState().getLastValue(); + MailboxState s = viewModel.getPairingState().getLastValue(); if (s instanceof MailboxState.Pairing) { // don't go back in the flow if we are already pairing // with the mailbox. We provide a try-again button instead. diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxState.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxState.java index 7d2ef097e..e69a94209 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxState.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxState.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.mailbox; import org.briarproject.bramble.api.mailbox.MailboxPairingState; -import org.briarproject.bramble.api.mailbox.MailboxStatus; class MailboxState { @@ -29,11 +28,6 @@ class MailboxState { } static class IsPaired extends MailboxState { - final MailboxStatus mailboxStatus; - - IsPaired(MailboxStatus mailboxStatus) { - this.mailboxStatus = mailboxStatus; - } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxStatusFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxStatusFragment.java index 378354d0e..561532beb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxStatusFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxStatusFragment.java @@ -2,11 +2,17 @@ package org.briarproject.briar.android.mailbox; import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; +import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; @@ -18,9 +24,13 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; -import static java.util.Objects.requireNonNull; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static androidx.transition.TransitionManager.beginDelayedTransition; import static org.briarproject.briar.android.AppModule.getAndroidComponent; +import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION; import static org.briarproject.briar.android.util.UiUtils.formatDate; +import static org.briarproject.briar.android.util.UiUtils.observeOnce; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -32,6 +42,9 @@ public class MailboxStatusFragment extends Fragment { ViewModelProvider.Factory viewModelFactory; private MailboxViewModel viewModel; + private final Handler handler = new Handler(Looper.getMainLooper()); + + private TextView statusInfoView; @Override public void onAttach(Context context) { @@ -54,11 +67,47 @@ public class MailboxStatusFragment extends Fragment { @Override public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { super.onViewCreated(v, savedInstanceState); - MailboxState.IsPaired state = - (MailboxState.IsPaired) viewModel.getState().getLastValue(); - requireNonNull(state); // TODO check assumption - TextView statusInfoView = v.findViewById(R.id.statusInfoView); - long lastSuccess = state.mailboxStatus.getTimeOfLastSuccess(); + + Button checkButton = v.findViewById(R.id.checkButton); + ProgressBar checkProgress = v.findViewById(R.id.checkProgress); + checkButton.setOnClickListener(view -> { + beginDelayedTransition((ViewGroup) v); + checkButton.setVisibility(INVISIBLE); + checkProgress.setVisibility(VISIBLE); + observeOnce(viewModel.checkConnection(), this, result -> { + beginDelayedTransition((ViewGroup) v); + checkButton.setVisibility(VISIBLE); + checkProgress.setVisibility(INVISIBLE); + }); + }); + + statusInfoView = v.findViewById(R.id.statusInfoView); + viewModel.getStatus() + .observe(getViewLifecycleOwner(), this::onMailboxStateChanged); + + // TODO + // * detect problems and show them #2175 + // * add "Unlink" button confirmation dialog and functionality #2173 + Button unlinkButton = v.findViewById(R.id.unlinkButton); + unlinkButton.setOnClickListener(view -> Toast.makeText(requireContext(), + "NOT IMPLEMENTED", Toast.LENGTH_SHORT).show()); + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.mailbox_status_title); + handler.postDelayed(this::refreshLastConnection, MIN_DATE_RESOLUTION); + } + + @Override + public void onStop() { + super.onStop(); + handler.removeCallbacks(this::refreshLastConnection); + } + + private void onMailboxStateChanged(MailboxStatus status) { + long lastSuccess = status.getTimeOfLastSuccess(); String lastConnectionText; if (lastSuccess < 0) { lastConnectionText = @@ -69,18 +118,12 @@ public class MailboxStatusFragment extends Fragment { String statusInfoText = getString( R.string.mailbox_status_connected_info, lastConnectionText); statusInfoView.setText(statusInfoText); - // TODO - // * react to status changes - // * detect problems and show them - // * update connection time periodically like conversation timestamps - // * add "Check connection" button - // * add "Unlink" button with confirmation dialog } - @Override - public void onStart() { - super.onStart(); - requireActivity().setTitle(R.string.mailbox_status_title); + private void refreshLastConnection() { + MailboxStatus status = viewModel.getStatus().getValue(); + if (status != null) onMailboxStateChanged(status); + handler.postDelayed(this::refreshLastConnection, MIN_DATE_RESOLUTION); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java index 2a8e15a6b..28efd6415 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxViewModel.java @@ -7,12 +7,16 @@ import com.google.zxing.Result; import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxStatus; +import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.PluginManager; @@ -32,6 +36,8 @@ import javax.inject.Inject; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; @@ -39,17 +45,21 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; @NotNullByDefault class MailboxViewModel extends DbViewModel - implements QrCodeDecoder.ResultCallback, Consumer { + implements QrCodeDecoder.ResultCallback, Consumer, + EventListener { private static final Logger LOG = getLogger(MailboxViewModel.class.getName()); + private final EventBus eventBus; private final QrCodeDecoder qrCodeDecoder; private final PluginManager pluginManager; private final MailboxManager mailboxManager; - private final MutableLiveEvent state = + private final MutableLiveEvent pairingState = new MutableLiveEvent<>(); + private final MutableLiveData status = + new MutableLiveData<>(); @Nullable private MailboxPairingTask pairingTask = null; @@ -60,19 +70,23 @@ class MailboxViewModel extends DbViewModel LifecycleManager lifecycleManager, TransactionManager db, AndroidExecutor androidExecutor, + EventBus eventBus, @IoExecutor Executor ioExecutor, PluginManager pluginManager, MailboxManager mailboxManager) { super(app, dbExecutor, lifecycleManager, db, androidExecutor); + this.eventBus = eventBus; this.pluginManager = pluginManager; this.mailboxManager = mailboxManager; qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); + eventBus.addListener(this); checkIfSetup(); } @Override protected void onCleared() { super.onCleared(); + eventBus.removeListener(this); MailboxPairingTask task = pairingTask; if (task != null) { task.removeObserver(this); @@ -89,9 +103,10 @@ class MailboxViewModel extends DbViewModel if (isPaired) { MailboxStatus mailboxStatus = mailboxManager.getMailboxStatus(txn); - state.postEvent(new MailboxState.IsPaired(mailboxStatus)); + pairingState.postEvent(new MailboxState.IsPaired()); + status.postValue(mailboxStatus); } else { - state.postEvent(new NotSetup()); + pairingState.postEvent(new NotSetup()); } }, this::handleException); } else { @@ -100,18 +115,28 @@ class MailboxViewModel extends DbViewModel } } + @UiThread + @Override + public void eventOccurred(Event e) { + if (e instanceof OwnMailboxConnectionStatusEvent) { + MailboxStatus status = + ((OwnMailboxConnectionStatusEvent) e).getStatus(); + this.status.setValue(status); + } + } + @UiThread void onScanButtonClicked() { if (isTorActive()) { - state.setEvent(new MailboxState.ScanningQrCode()); + pairingState.setEvent(new MailboxState.ScanningQrCode()); } else { - state.setEvent(new MailboxState.OfflineWhenPairing()); + pairingState.setEvent(new MailboxState.OfflineWhenPairing()); } } @UiThread void onCameraError() { - state.setEvent(new MailboxState.CameraError()); + pairingState.setEvent(new MailboxState.CameraError()); } @Override @@ -127,7 +152,7 @@ class MailboxViewModel extends DbViewModel pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask.addObserver(this); } else { - state.postEvent(new MailboxState.OfflineWhenPairing()); + pairingState.postEvent(new MailboxState.OfflineWhenPairing()); } } @@ -138,7 +163,7 @@ class MailboxViewModel extends DbViewModel LOG.info("New pairing state: " + mailboxPairingState.getClass().getSimpleName()); } - state.setEvent(new MailboxState.Pairing(mailboxPairingState)); + pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState)); } private boolean isTorActive() { @@ -148,7 +173,7 @@ class MailboxViewModel extends DbViewModel @UiThread void showDownloadFragment() { - state.setEvent(new MailboxState.ShowDownload()); + pairingState.setEvent(new MailboxState.ShowDownload()); } @UiThread @@ -156,8 +181,29 @@ class MailboxViewModel extends DbViewModel return qrCodeDecoder; } + LiveData checkConnection() { + MutableLiveData liveData = new MutableLiveData<>(); + mailboxManager.checkConnection(result -> + onConnectionCheckFinished(liveData, result)); + return liveData; + } + + @IoExecutor + private void onConnectionCheckFinished(MutableLiveData liveData, + boolean success) { + if (LOG.isLoggable(INFO)) { + LOG.info("Got result from connection check: " + success); + } + liveData.postValue(success); + } + @UiThread - LiveEvent getState() { - return state; + LiveEvent getPairingState() { + return pairingState; + } + + @UiThread + LiveData getStatus() { + return status; } } diff --git a/briar-android/src/main/res/layout/fragment_mailbox_status.xml b/briar-android/src/main/res/layout/fragment_mailbox_status.xml index 608edd3ba..58096f487 100644 --- a/briar-android/src/main/res/layout/fragment_mailbox_status.xml +++ b/briar-android/src/main/res/layout/fragment_mailbox_status.xml @@ -10,13 +10,14 @@ android:id="@+id/imageView" android:layout_width="32dp" android:layout_height="32dp" - android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/statusTitleView" + android:layout_marginHorizontal="16dp" + app:layout_constraintBottom_toTopOf="@+id/statusTitleView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.25" app:layout_constraintVertical_chainStyle="packed" app:srcCompat="@drawable/ic_check_circle_outline" app:tint="@color/briar_brand_green" @@ -26,14 +27,37 @@ android:id="@+id/statusTitleView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginHorizontal="16dp" + android:layout_margin="16dp" android:text="@string/mailbox_status_connected_title" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" app:layout_constrainedWidth="true" - app:layout_constraintBottom_toBottomOf="@+id/imageView" + app:layout_constraintBottom_toTopOf="@+id/checkButton" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/imageView" - app:layout_constraintTop_toTopOf="@+id/imageView" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView" /> + +