From 5254efb6302b7e5d3af1f839a8f2021a4686e857 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 26 Apr 2022 15:18:07 -0300 Subject: [PATCH 1/2] Troubleshooting wizard for mailbox --- .../briar/android/AndroidComponent.java | 3 + .../android/mailbox/ErrorWizardFragment.java | 202 ++++++++++++++++++ .../mailbox/MailboxStatusFragment.java | 33 +-- .../android/mailbox/MailboxViewModel.java | 23 +- .../briar/android/view/BriarButton.java | 75 +++++++ .../src/main/res/layout/briar_button.xml | 17 ++ .../layout/fragment_mailbox_error_wizard.xml | 91 ++++++++ .../fragment_mailbox_error_wizard_access.xml | 153 +++++++++++++ .../res/layout/fragment_mailbox_status.xml | 35 +-- briar-android/src/main/res/values/attrs.xml | 11 +- briar-android/src/main/res/values/strings.xml | 19 ++ 11 files changed, 625 insertions(+), 37 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/mailbox/ErrorWizardFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/view/BriarButton.java create mode 100644 briar-android/src/main/res/layout/briar_button.xml create mode 100644 briar-android/src/main/res/layout/fragment_mailbox_error_wizard.xml create mode 100644 briar-android/src/main/res/layout/fragment_mailbox_error_wizard_access.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 0fe7c4876..340160ddc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -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); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/ErrorWizardFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/ErrorWizardFragment.java new file mode 100644 index 000000000..44f9407fd --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/ErrorWizardFragment.java @@ -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 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 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 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 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 radioButtons, List 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); + } + +} 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 555e23d79..e4c0c44d3 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 @@ -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); 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 0d61bfab5..566ee76c9 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 @@ -204,11 +204,7 @@ class MailboxViewModel extends DbViewModel LiveData checkConnection() { MutableLiveData 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 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(() -> { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/BriarButton.java b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarButton.java new file mode 100644 index 000000000..261a76c32 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarButton.java @@ -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); + } + +} diff --git a/briar-android/src/main/res/layout/briar_button.xml b/briar-android/src/main/res/layout/briar_button.xml new file mode 100644 index 000000000..780bb5b5a --- /dev/null +++ b/briar-android/src/main/res/layout/briar_button.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/briar-android/src/main/res/layout/fragment_mailbox_error_wizard.xml b/briar-android/src/main/res/layout/fragment_mailbox_error_wizard.xml new file mode 100644 index 000000000..c8e176540 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_mailbox_error_wizard.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/briar-android/src/main/res/layout/fragment_mailbox_error_wizard_access.xml b/briar-android/src/main/res/layout/fragment_mailbox_error_wizard_access.xml new file mode 100644 index 000000000..e7d3996d6 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_mailbox_error_wizard_access.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 63b2e80df..520a49f09 100644 --- a/briar-android/src/main/res/layout/fragment_mailbox_status.xml +++ b/briar-android/src/main/res/layout/fragment_mailbox_status.xml @@ -37,28 +37,17 @@ app:layout_constraintTop_toBottomOf="@+id/imageView" tools:text="@string/mailbox_status_problem_title" /> -