Skip to content

Commit

Permalink
[Android][UPM] Add Cancel button to the Loading Modal
Browse files Browse the repository at this point in the history
This CL adds a button to the Loading Modal Dialog that
dismisses the dialog instantly as CANCELLED.

The view is trivial and doesn't change after inflation.
Therefore, this CL adds only integration tests instead of
view tests.

Since Handlers can't be mocked or overridden (because all
post* methods are final), this CL adds a method to skip
animations for tests.

Screenshots are in the linked bug.

Bug: 1318902
Change-Id: I732a17cd1e278152bbfc6142967010f5fce8beab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3620381
Reviewed-by: Ioana Pandele <ioanap@chromium.org>
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Reviewed-by: Boris Sazonov <bsazonov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1001549}
  • Loading branch information
FHorschig authored and Chromium LUCI CQ committed May 10, 2022
1 parent 2f8a7a8 commit 7bd9a8c
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 20 deletions.
1 change: 1 addition & 0 deletions chrome/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,7 @@ android_library("chrome_test_java") {
"//chrome/browser/language/android:java",
"//chrome/browser/language/android:javatests",
"//chrome/browser/lens:java",
"//chrome/browser/loading_modal/android:javatests",
"//chrome/browser/locale:java",
"//chrome/browser/notifications:java",
"//chrome/browser/notifications:javatests",
Expand Down
21 changes: 21 additions & 0 deletions chrome/browser/loading_modal/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ android_resources("java_resources") {
deps = [ "//ui/android:ui_java_resources" ]
sources = [
"java/res/layout/loading_modal.xml",
"java/res/layout/loading_modal_button_bar.xml",
"java/res/values/dimens.xml",
]
}
Expand All @@ -48,3 +49,23 @@ java_library("junit") {
"//ui/android:ui_full_java",
]
}

android_library("javatests") {
testonly = true

sources = [ "javatests/src/org/chromium/chrome/browser/loading_modal/LoadingModalDialogIntegrationTest.java" ]

deps = [
":java",
"//base:base_java",
"//base:base_java_test_support",
"//chrome/test/android:chrome_java_test_support",
"//content/public/test/android:content_java_test_support",
"//third_party/androidx:androidx_test_runner_java",
"//third_party/hamcrest:hamcrest_library_java",
"//third_party/junit:junit",
"//third_party/mockito:mockito_java",
"//ui/android:ui_java_test_support",
"//ui/android:ui_no_recycler_view_java",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<android.widget.ProgressBar
android:layout_width="@dimen/loading_modal_loading_indicator_size"
android:layout_height="@dimen/loading_modal_loading_indicator_size"
android:layout_centerInParent="true"/>
android:indeterminate="true"
android:layout_centerInParent="true" />

</RelativeLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2022 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<org.chromium.ui.widget.ButtonCompat
android:id="@+id/cancel_loading_modal"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/password_generation_dialog_cancel_button"
android:layout_centerHorizontal="true"
style="@style/TextButton"/>

</RelativeLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
package org.chromium.chrome.browser.loading_modal;

import android.content.Context;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
import org.chromium.ui.modelutil.PropertyModel;

import java.lang.annotation.Retention;
Expand All @@ -26,6 +31,7 @@
public class LoadingModalDialogCoordinator {
private final LoadingModalDialogMediator mMediator;
private final RelativeLayout mCustomView;
private final View mButtonsView;

// Used to indicate the current loading dialog state.
@IntDef({State.READY, State.LOADING_DELAYED, State.LOADING_SHOWN, State.FINISHED_SHOWN,
Expand Down Expand Up @@ -56,11 +62,20 @@ public class LoadingModalDialogCoordinator {
*/
public static LoadingModalDialogCoordinator create(
ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier, Context context) {
return create(modalDialogManagerSupplier, context, new Handler());
}

@VisibleForTesting
static LoadingModalDialogCoordinator create(
ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier, Context context,
Handler handler) {
LoadingModalDialogMediator dialogMediator =
new LoadingModalDialogMediator(modalDialogManagerSupplier);
new LoadingModalDialogMediator(modalDialogManagerSupplier, handler);
RelativeLayout dialogView =
(RelativeLayout) LayoutInflater.from(context).inflate(R.layout.loading_modal, null);
return new LoadingModalDialogCoordinator(dialogMediator, dialogView);
RelativeLayout buttonsView = (RelativeLayout) LayoutInflater.from(context).inflate(
R.layout.loading_modal_button_bar, null);
return new LoadingModalDialogCoordinator(dialogMediator, dialogView, buttonsView);
}

/**
Expand All @@ -70,19 +85,24 @@ public static LoadingModalDialogCoordinator create(
* @param dialogView The custom view with dialog content.
*/
private LoadingModalDialogCoordinator(@NonNull LoadingModalDialogMediator dialogMediator,
@NonNull RelativeLayout dialogView) {
@NonNull RelativeLayout dialogView, @Nullable View buttonsView) {
mMediator = dialogMediator;
mCustomView = dialogView;
mButtonsView = buttonsView;
}

/** Shows the loading modal dialog. */
public void show() {
PropertyModel dialogModel = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.FULLSCREEN_DIALOG, true)
.with(ModalDialogProperties.EXCEED_MAX_HEIGHT, true)
.with(ModalDialogProperties.CONTROLLER, mMediator)
.with(ModalDialogProperties.CUSTOM_VIEW, mCustomView)
.build();
PropertyModel dialogModel =
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.FULLSCREEN_DIALOG, true)
.with(ModalDialogProperties.EXCEED_MAX_HEIGHT, true)
.with(ModalDialogProperties.CONTROLLER, mMediator)
.with(ModalDialogProperties.CUSTOM_VIEW, mCustomView)
.with(ModalDialogProperties.CUSTOM_BUTTON_BAR_VIEW, mButtonsView)
.build();
mButtonsView.findViewById(R.id.cancel_loading_modal)
.setOnClickListener(view -> mMediator.onClick(dialogModel, ButtonType.NEGATIVE));
mMediator.showDialog(dialogModel);
}

Expand All @@ -97,4 +117,14 @@ public void dismiss() {
public @State int getState() {
return mMediator.getState();
}

@VisibleForTesting
void skipDelayForTesting() {
mMediator.skipDelays();
}

@VisibleForTesting
View getButtonsView() {
return mButtonsView;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LoadingModalDialogMediator
private static final long SHOW_DELAY_TIME_MS = 500L;
private static final long MINIMUM_SHOW_TIME_MS = 500L;

private final Handler mHandler = new Handler();
private final Handler mHandler;
private final ObservableSupplier<ModalDialogManager> mDialogManagerSupplier;

private ModalDialogManager mDialogManager;
Expand All @@ -34,12 +34,12 @@ class LoadingModalDialogMediator
private long mShownAtMs;

private @LoadingModalDialogCoordinator.State int mState;
private boolean mSkipDelay;

/** ModalDialogProperties.Controller implementation */
@Override
public void onClick(PropertyModel model, @ButtonType int buttonType) {
// TODO(crbug.com/1311674): Dismiss as follows after button is added
// dismissDialogImmediately(DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
dismissDialogImmediately(DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
}

@Override
Expand Down Expand Up @@ -69,10 +69,13 @@ public void onDialogAdded(PropertyModel model) {
mShownAtMs = Long.valueOf(SystemClock.elapsedRealtime());
}

LoadingModalDialogMediator(ObservableSupplier<ModalDialogManager> dialogManagerSupplier) {
LoadingModalDialogMediator(
ObservableSupplier<ModalDialogManager> dialogManagerSupplier, Handler handler) {
assert dialogManagerSupplier != null;
assert handler != null;
mDialogManagerSupplier = dialogManagerSupplier;
mState = LoadingModalDialogCoordinator.State.READY;
mHandler = handler;
}

/**
Expand All @@ -91,7 +94,7 @@ void showDialog(PropertyModel model) {
mDialogManager = dialogManager;
mModel = model;
mState = LoadingModalDialogCoordinator.State.LOADING_DELAYED;
mHandler.postDelayed(this::showDialogImmediately, SHOW_DELAY_TIME_MS);
postDelayed(this::showDialogImmediately, SHOW_DELAY_TIME_MS);
}

/**
Expand All @@ -113,10 +116,10 @@ void dismissDialog() {
&& mShownAtMs + MINIMUM_SHOW_TIME_MS > currentTimeMs) {
// Dialog dismiss should be postponed to prevent UI flicker.
mState = LoadingModalDialogCoordinator.State.FINISHED_SHOWN;
Runnable dismissRunnable =
() -> dismissDialogImmediately(DialogDismissalCause.ACTION_ON_DIALOG_COMPLETED);
mHandler.postDelayed(
dismissRunnable, mShownAtMs + MINIMUM_SHOW_TIME_MS - currentTimeMs);
postDelayed(()
-> dismissDialogImmediately(
DialogDismissalCause.ACTION_ON_DIALOG_COMPLETED),
mShownAtMs + MINIMUM_SHOW_TIME_MS - currentTimeMs);
} else {
// Dialog is not yet shown or has been visible long enough.
dismissDialogImmediately(DialogDismissalCause.ACTION_ON_DIALOG_COMPLETED);
Expand All @@ -131,6 +134,10 @@ int getState() {
return mState;
}

void skipDelays() {
mSkipDelay = true;
}

/** Immediately shows the dialog. */
private void showDialogImmediately() {
if (mState != LoadingModalDialogCoordinator.State.LOADING_DELAYED) return;
Expand All @@ -152,4 +159,12 @@ private void dismissDialogImmediately(@DialogDismissalCause int dismissalCause)
}
mDialogManager.dismissDialog(mModel, dismissalCause);
}

private void postDelayed(Runnable r, long delay) {
if (mSkipDelay) {
r.run();
} else {
mHandler.postDelayed(r, delay);
}
}
}

0 comments on commit 7bd9a8c

Please sign in to comment.