Troubleshooting wizard for mailbox

This commit is contained in:
Torsten Grote
2022-04-26 15:18:07 -03:00
parent df22df22a0
commit 5254efb630
11 changed files with 625 additions and 37 deletions

View File

@@ -45,6 +45,7 @@ import org.briarproject.briar.android.hotspot.QrHotspotFragment;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.mailbox.ErrorFragment;
import org.briarproject.briar.android.mailbox.ErrorWizardFragment;
import org.briarproject.briar.android.mailbox.MailboxScanFragment;
import org.briarproject.briar.android.mailbox.MailboxStatusFragment;
import org.briarproject.briar.android.mailbox.OfflineFragment;
@@ -257,4 +258,6 @@ public interface AndroidComponent
void inject(ErrorFragment errorFragment);
void inject(MailboxStatusFragment mailboxStatusFragment);
void inject(ErrorWizardFragment errorWizardFragment);
}

View File

@@ -0,0 +1,202 @@
package org.briarproject.briar.android.mailbox;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import com.google.android.material.animation.ArgbEvaluatorCompat;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.BriarButton;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.transition.AutoTransition;
import androidx.transition.Transition;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ErrorWizardFragment extends Fragment {
static final String TAG = ErrorWizardFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel;
private ScrollView scrollView;
private ValueAnimator colorAnim;
private final Transition transition = new AutoTransition();
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(MailboxViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_mailbox_error_wizard,
container, false);
scrollView = (ScrollView) v;
int startColor =
getColor(v.getContext(), R.color.briar_accent);
int endColor = getColor(v.getContext(), R.color.window_background);
colorAnim = ValueAnimator
.ofObject(new ArgbEvaluatorCompat(), startColor, endColor);
colorAnim.setDuration(2500);
return v;
}
@Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
RadioGroup radioGroup1 = v.findViewById(R.id.radioGroup1);
List<RadioButton> radioButtons1 = new ArrayList<>(3);
radioButtons1.add(v.findViewById(R.id.radioButton1));
radioButtons1.add(v.findViewById(R.id.radioButton2));
radioButtons1.add(v.findViewById(R.id.radioButton3));
List<View> views1 = new ArrayList<>(3);
View info1 = v.findViewById(R.id.info1);
views1.add(info1);
views1.add(v.findViewById(R.id.info2));
View info3 = v.findViewById(R.id.info3);
views1.add(info3);
setUpRadioGroup(radioGroup1, radioButtons1, views1);
RadioGroup radioGroup1_1 = info1.findViewById(R.id.radioGroup1_1);
List<RadioButton> radioButtons1_1 = new ArrayList<>(3);
radioButtons1_1.add(info1.findViewById(R.id.radioButton1_1));
radioButtons1_1.add(info1.findViewById(R.id.radioButton1_2));
radioButtons1_1.add(info1.findViewById(R.id.radioButton1_3));
radioButtons1_1.add(info1.findViewById(R.id.radioButton1_4));
List<View> views1_1 = new ArrayList<>(3);
views1_1.add(info1.findViewById(R.id.info1_1_1));
views1_1.add(info1.findViewById(R.id.info1_1_2));
views1_1.add(info1.findViewById(R.id.info1_1_3));
views1_1.add(info1.findViewById(R.id.info1_1_4));
setUpRadioGroup(radioGroup1_1, radioButtons1_1, views1_1);
// set up unlink buttons
BriarButton button3 = info3.findViewById(R.id.button3);
BriarButton button1_1_1 = info1.findViewById(R.id.button1_1_1);
BriarButton button1_1_2 = info1.findViewById(R.id.button1_1_2);
button3.setOnClickListener(this::onUnlinkButtonClicked);
button1_1_1.setOnClickListener(this::onUnlinkButtonClicked);
button1_1_2.setOnClickListener(this::onUnlinkButtonClicked);
// set up check connection button
BriarButton button1_1_3 = info1.findViewById(R.id.button1_1_3);
button1_1_3.setOnClickListener(this::onCheckConnectionButtonClicked);
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.mailbox_error_wizard_title);
}
private void setUpRadioGroup(RadioGroup radioGroup,
List<RadioButton> radioButtons, List<View> views) {
if (radioButtons.size() != views.size()) {
throw new IllegalArgumentException();
}
radioGroup.setOnCheckedChangeListener((group, checkedId) -> {
onCheckedChanged();
for (int i = 0; i < radioButtons.size(); i++) {
RadioButton radioButton = radioButtons.get(i);
View view = views.get(i);
if (checkedId == radioButton.getId()) {
animateColor(view);
view.setVisibility(VISIBLE);
} else {
view.setVisibility(GONE);
}
}
});
}
private void onUnlinkButtonClicked(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
R.style.BriarDialogTheme);
builder.setTitle(R.string.mailbox_status_unlink_dialog_title);
builder.setMessage(R.string.mailbox_status_unlink_dialog_question);
builder.setPositiveButton(R.string.cancel,
(dialog, which) -> dialog.cancel());
builder.setNegativeButton(R.string.mailbox_status_unlink_button,
(dialog, which) -> viewModel.unlink());
builder.setOnCancelListener(dialog -> ((BriarButton) v).reset());
builder.show();
}
private void onCheckConnectionButtonClicked(View v) {
viewModel.checkConnectionFromWizard();
}
private void animateColor(View v) {
if (colorAnim.isRunning()) colorAnim.end();
colorAnim.removeAllUpdateListeners();
colorAnim.addUpdateListener(animation ->
v.setBackgroundColor((int) animation.getAnimatedValue())
);
colorAnim.start();
}
private void onCheckedChanged() {
transition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionStart(@NonNull Transition transition) {
}
@Override
public void onTransitionEnd(@NonNull Transition transition) {
scrollView.fullScroll(FOCUS_DOWN);
}
@Override
public void onTransitionCancel(@NonNull Transition transition) {
}
@Override
public void onTransitionPause(@NonNull Transition transition) {
}
@Override
public void onTransitionResume(@NonNull Transition transition) {
}
});
beginDelayedTransition(scrollView, transition);
}
}

View File

@@ -17,6 +17,7 @@ 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;
import org.briarproject.briar.android.view.BriarButton;
import javax.inject.Inject;
@@ -29,6 +30,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.core.content.ContextCompat.getColor;
@@ -38,6 +40,7 @@ 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;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -58,6 +61,7 @@ public class MailboxStatusFragment extends Fragment {
private ImageView imageView;
private TextView statusTitleView;
private TextView statusInfoView;
private Button wizardButton;
private Button unlinkButton;
private ProgressBar unlinkProgress;
@@ -83,18 +87,11 @@ public class MailboxStatusFragment extends Fragment {
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
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);
});
});
BriarButton checkButton = v.findViewById(R.id.checkButton);
checkButton.setOnClickListener(view ->
observeOnce(viewModel.checkConnection(), this, result ->
checkButton.reset()
));
imageView = v.findViewById(R.id.imageView);
statusTitleView = v.findViewById(R.id.statusTitleView);
@@ -102,8 +99,13 @@ public class MailboxStatusFragment extends Fragment {
viewModel.getStatus()
.observe(getViewLifecycleOwner(), this::onMailboxStateChanged);
// TODO
// * Implement UI for warning user when mailbox is unreachable #2175
wizardButton = v.findViewById(R.id.wizardButton);
wizardButton.setOnClickListener(view -> {
Fragment f = new ErrorWizardFragment();
String tag = ErrorWizardFragment.TAG;
showFragment(getParentFragmentManager(), f, tag, false);
});
unlinkButton = v.findViewById(R.id.unlinkButton);
unlinkProgress = v.findViewById(R.id.unlinkProgress);
unlinkButton.setOnClickListener(view ->
@@ -135,16 +137,19 @@ public class MailboxStatusFragment extends Fragment {
title = getString(R.string.mailbox_status_connected_title);
tintRes = R.color.briar_brand_green;
showUnlinkWarning = true;
wizardButton.setVisibility(GONE);
} else if (status.getAttemptsSinceSuccess() < NUM_FAILURES) {
iconRes = R.drawable.ic_help_outline_white;
title = getString(R.string.mailbox_status_problem_title);
tintRes = R.color.briar_orange_500;
showUnlinkWarning = false;
wizardButton.setVisibility(GONE);
} else {
tintRes = R.color.briar_red_500;
title = getString(R.string.mailbox_status_failure_title);
iconRes = R.drawable.alerts_and_states_error;
showUnlinkWarning = false;
wizardButton.setVisibility(VISIBLE);
}
imageView.setImageResource(iconRes);
int color = getColor(requireContext(), tintRes);

View File

@@ -204,11 +204,7 @@ class MailboxViewModel extends DbViewModel
LiveData<Boolean> checkConnection() {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
ioExecutor.execute(() -> {
boolean success = mailboxManager.checkConnection();
if (LOG.isLoggable(INFO)) {
LOG.info("Got result from connection check: " + success);
}
checkConnection(success -> {
liveData.postValue(success);
if (!success) { // force failure screen
MailboxStatus lastStatus = status.getValue();
@@ -221,6 +217,23 @@ class MailboxViewModel extends DbViewModel
return liveData;
}
void checkConnectionFromWizard() {
checkConnection(success -> {
boolean isOnline = isTorActive();
pairingState.postEvent(new MailboxState.IsPaired(isOnline));
});
}
private void checkConnection(@IoExecutor Consumer<Boolean> consumer) {
ioExecutor.execute(() -> {
boolean success = mailboxManager.checkConnection();
if (LOG.isLoggable(INFO)) {
LOG.info("Got result from connection check: " + success);
}
consumer.accept(success);
});
}
@UiThread
void unlink() {
ioExecutor.execute(() -> {

View File

@@ -0,0 +1,75 @@
package org.briarproject.briar.android.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.AppCompatButton;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static androidx.transition.TransitionManager.beginDelayedTransition;
@NotNullByDefault
public class BriarButton extends FrameLayout {
private final Button button;
private final ProgressBar progressBar;
public BriarButton(Context context) {
this(context, null);
}
public BriarButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BriarButton(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.briar_button, this, true);
TypedArray attributes =
context.obtainStyledAttributes(attrs, R.styleable.BriarButton);
String text = attributes.getString(R.styleable.BriarButton_text);
int style = attributes
.getResourceId(R.styleable.BriarButton_buttonStyle, 0);
attributes.recycle();
ContextThemeWrapper wrapper = new ContextThemeWrapper(context, style);
button = new AppCompatButton(wrapper, null, style);
button.setText(text);
addView(button);
progressBar = findViewById(R.id.briar_button_progress_bar);
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
if (l == null) button.setOnClickListener(null);
else {
button.setOnClickListener(v -> {
beginDelayedTransition(this);
progressBar.setVisibility(VISIBLE);
button.setVisibility(INVISIBLE);
l.onClick(this);
});
}
}
public void reset() {
beginDelayedTransition(this);
progressBar.setVisibility(INVISIBLE);
button.setVisibility(VISIBLE);
}
}