Add connection check button to Mailbox status UI

and update the last connection timestamp accordingly
This commit is contained in:
Torsten Grote
2022-04-01 12:58:06 -03:00
parent 5e7891d78a
commit 5b648cbd35
6 changed files with 167 additions and 48 deletions

View File

@@ -47,11 +47,11 @@ public class MailboxActivity extends BriarActivity {
setContentView(R.layout.activity_mailbox); setContentView(R.layout.activity_mailbox);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
if (viewModel.getState().getValue() == null) { if (viewModel.getPairingState().getValue() == null) {
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
} }
viewModel.getState().observeEvent(this, state -> { viewModel.getPairingState().observeEvent(this, state -> {
if (state instanceof MailboxState.NotSetup) { if (state instanceof MailboxState.NotSetup) {
onNotSetup(); onNotSetup();
} else if (state instanceof MailboxState.ShowDownload) { } else if (state instanceof MailboxState.ShowDownload) {
@@ -85,7 +85,7 @@ public class MailboxActivity extends BriarActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
MailboxState s = viewModel.getState().getLastValue(); MailboxState s = viewModel.getPairingState().getLastValue();
if (s instanceof MailboxState.Pairing) { if (s instanceof MailboxState.Pairing) {
// don't go back in the flow if we are already pairing // don't go back in the flow if we are already pairing
// with the mailbox. We provide a try-again button instead. // with the mailbox. We provide a try-again button instead.

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.mailbox; package org.briarproject.briar.android.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
class MailboxState { class MailboxState {
@@ -29,11 +28,6 @@ class MailboxState {
} }
static class IsPaired extends MailboxState { static class IsPaired extends MailboxState {
final MailboxStatus mailboxStatus;
IsPaired(MailboxStatus mailboxStatus) {
this.mailboxStatus = mailboxStatus;
}
} }
} }

View File

@@ -2,11 +2,17 @@ package org.briarproject.briar.android.mailbox;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView; 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -18,9 +24,13 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; 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.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.formatDate;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -32,6 +42,9 @@ public class MailboxStatusFragment extends Fragment {
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel; private MailboxViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper());
private TextView statusInfoView;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -54,11 +67,47 @@ public class MailboxStatusFragment extends Fragment {
@Override @Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState); super.onViewCreated(v, savedInstanceState);
MailboxState.IsPaired state =
(MailboxState.IsPaired) viewModel.getState().getLastValue(); Button checkButton = v.findViewById(R.id.checkButton);
requireNonNull(state); // TODO check assumption ProgressBar checkProgress = v.findViewById(R.id.checkProgress);
TextView statusInfoView = v.findViewById(R.id.statusInfoView); checkButton.setOnClickListener(view -> {
long lastSuccess = state.mailboxStatus.getTimeOfLastSuccess(); 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; String lastConnectionText;
if (lastSuccess < 0) { if (lastSuccess < 0) {
lastConnectionText = lastConnectionText =
@@ -69,18 +118,12 @@ public class MailboxStatusFragment extends Fragment {
String statusInfoText = getString( String statusInfoText = getString(
R.string.mailbox_status_connected_info, lastConnectionText); R.string.mailbox_status_connected_info, lastConnectionText);
statusInfoView.setText(statusInfoText); 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 private void refreshLastConnection() {
public void onStart() { MailboxStatus status = viewModel.getStatus().getValue();
super.onStart(); if (status != null) onMailboxStateChanged(status);
requireActivity().setTitle(R.string.mailbox_status_title); handler.postDelayed(this::refreshLastConnection, MIN_DATE_RESOLUTION);
} }
} }

View File

@@ -7,12 +7,16 @@ import com.google.zxing.Result;
import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager; 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.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxStatus; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
@@ -32,6 +36,8 @@ import javax.inject.Inject;
import androidx.annotation.AnyThread; import androidx.annotation.AnyThread;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -39,17 +45,21 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@NotNullByDefault @NotNullByDefault
class MailboxViewModel extends DbViewModel class MailboxViewModel extends DbViewModel
implements QrCodeDecoder.ResultCallback, Consumer<MailboxPairingState> { implements QrCodeDecoder.ResultCallback, Consumer<MailboxPairingState>,
EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(MailboxViewModel.class.getName()); getLogger(MailboxViewModel.class.getName());
private final EventBus eventBus;
private final QrCodeDecoder qrCodeDecoder; private final QrCodeDecoder qrCodeDecoder;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final MailboxManager mailboxManager; private final MailboxManager mailboxManager;
private final MutableLiveEvent<MailboxState> state = private final MutableLiveEvent<MailboxState> pairingState =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveData<MailboxStatus> status =
new MutableLiveData<>();
@Nullable @Nullable
private MailboxPairingTask pairingTask = null; private MailboxPairingTask pairingTask = null;
@@ -60,19 +70,23 @@ class MailboxViewModel extends DbViewModel
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
TransactionManager db, TransactionManager db,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
EventBus eventBus,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
PluginManager pluginManager, PluginManager pluginManager,
MailboxManager mailboxManager) { MailboxManager mailboxManager) {
super(app, dbExecutor, lifecycleManager, db, androidExecutor); super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.eventBus = eventBus;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.mailboxManager = mailboxManager; this.mailboxManager = mailboxManager;
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
eventBus.addListener(this);
checkIfSetup(); checkIfSetup();
} }
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
eventBus.removeListener(this);
MailboxPairingTask task = pairingTask; MailboxPairingTask task = pairingTask;
if (task != null) { if (task != null) {
task.removeObserver(this); task.removeObserver(this);
@@ -89,9 +103,10 @@ class MailboxViewModel extends DbViewModel
if (isPaired) { if (isPaired) {
MailboxStatus mailboxStatus = MailboxStatus mailboxStatus =
mailboxManager.getMailboxStatus(txn); mailboxManager.getMailboxStatus(txn);
state.postEvent(new MailboxState.IsPaired(mailboxStatus)); pairingState.postEvent(new MailboxState.IsPaired());
status.postValue(mailboxStatus);
} else { } else {
state.postEvent(new NotSetup()); pairingState.postEvent(new NotSetup());
} }
}, this::handleException); }, this::handleException);
} else { } 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 @UiThread
void onScanButtonClicked() { void onScanButtonClicked() {
if (isTorActive()) { if (isTorActive()) {
state.setEvent(new MailboxState.ScanningQrCode()); pairingState.setEvent(new MailboxState.ScanningQrCode());
} else { } else {
state.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new MailboxState.OfflineWhenPairing());
} }
} }
@UiThread @UiThread
void onCameraError() { void onCameraError() {
state.setEvent(new MailboxState.CameraError()); pairingState.setEvent(new MailboxState.CameraError());
} }
@Override @Override
@@ -127,7 +152,7 @@ class MailboxViewModel extends DbViewModel
pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask = mailboxManager.startPairingTask(qrCodePayload);
pairingTask.addObserver(this); pairingTask.addObserver(this);
} else { } else {
state.postEvent(new MailboxState.OfflineWhenPairing()); pairingState.postEvent(new MailboxState.OfflineWhenPairing());
} }
} }
@@ -138,7 +163,7 @@ class MailboxViewModel extends DbViewModel
LOG.info("New pairing state: " + LOG.info("New pairing state: " +
mailboxPairingState.getClass().getSimpleName()); mailboxPairingState.getClass().getSimpleName());
} }
state.setEvent(new MailboxState.Pairing(mailboxPairingState)); pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState));
} }
private boolean isTorActive() { private boolean isTorActive() {
@@ -148,7 +173,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void showDownloadFragment() { void showDownloadFragment() {
state.setEvent(new MailboxState.ShowDownload()); pairingState.setEvent(new MailboxState.ShowDownload());
} }
@UiThread @UiThread
@@ -156,8 +181,29 @@ class MailboxViewModel extends DbViewModel
return qrCodeDecoder; return qrCodeDecoder;
} }
LiveData<Boolean> checkConnection() {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
mailboxManager.checkConnection(result ->
onConnectionCheckFinished(liveData, result));
return liveData;
}
@IoExecutor
private void onConnectionCheckFinished(MutableLiveData<Boolean> liveData,
boolean success) {
if (LOG.isLoggable(INFO)) {
LOG.info("Got result from connection check: " + success);
}
liveData.postValue(success);
}
@UiThread @UiThread
LiveEvent<MailboxState> getState() { LiveEvent<MailboxState> getPairingState() {
return state; return pairingState;
}
@UiThread
LiveData<MailboxStatus> getStatus() {
return status;
} }
} }

View File

@@ -10,13 +10,14 @@
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_marginStart="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginLeft="16dp" app:layout_constraintBottom_toTopOf="@+id/statusTitleView"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/statusTitleView" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_check_circle_outline" app:srcCompat="@drawable/ic_check_circle_outline"
app:tint="@color/briar_brand_green" app:tint="@color/briar_brand_green"
@@ -26,14 +27,37 @@
android:id="@+id/statusTitleView" android:id="@+id/statusTitleView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_margin="16dp"
android:text="@string/mailbox_status_connected_title" android:text="@string/mailbox_status_connected_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/imageView" app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/imageView" />
<Button
android:id="@+id/checkButton"
style="@style/BriarButtonFlat.Neutral"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/mailbox_status_check_button"
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView" />
<ProgressBar
android:id="@+id/checkProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="@+id/checkButton"
app:layout_constraintStart_toStartOf="@+id/checkButton"
app:layout_constraintTop_toTopOf="@+id/checkButton"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/statusInfoView" android:id="@+id/statusInfoView"
@@ -41,11 +65,21 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:gravity="center" android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView" app:layout_constraintTop_toBottomOf="@+id/checkButton"
app:layout_constraintVertical_bias="0.0"
tools:text="@string/mailbox_status_connected_info" /> tools:text="@string/mailbox_status_connected_info" />
<Button
android:id="@+id/unlinkButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/mailbox_status_unlink_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -643,10 +643,12 @@
<string name="tor_offline_button_check">Check connection settings</string> <string name="tor_offline_button_check">Check connection settings</string>
<string name="mailbox_status_title">Mailbox status</string> <string name="mailbox_status_title">Mailbox status</string>
<string name="mailbox_status_connected_title">Mailbox is running</string> <string name="mailbox_status_connected_title">Mailbox is running</string>
<string name="mailbox_status_check_button">Check Connection</string>
<!-- Example for string substitution: Last connection: 3min ago--> <!-- Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Last connection: %s</string> <string name="mailbox_status_connected_info">Last connection: %s</string>
<!-- Indicates that there never was a connection to the mailbox. Last connection: Never --> <!-- Indicates that there never was a connection to the mailbox. Last connection: Never -->
<string name="mailbox_status_connected_never">Never</string> <string name="mailbox_status_connected_never">Never</string>
<string name="mailbox_status_unlink_button">Unlink</string>
<!-- Conversation Settings --> <!-- Conversation Settings -->
<string name="disappearing_messages_title">Disappearing messages</string> <string name="disappearing_messages_title">Disappearing messages</string>