Commit 709b1fd9 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Commit Bot

[Autofill Assistant] Split DetailsCoordinator into Coordinator + ViewBinder.

Change-Id: I3e9579e7e0fdf3bb90e1a6d806045a6a1207a230
Reviewed-on: https://chromium-review.googlesource.com/c/1430009
Commit-Queue: Jordan Demeulenaere <jdemeulenaere@chromium.org>
Reviewed-by: default avatarStephane Zermatten <szermatt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625361}
parent f2e0092d
...@@ -4,97 +4,39 @@ ...@@ -4,97 +4,39 @@
package org.chromium.chrome.browser.autofill_assistant.details; package org.chromium.chrome.browser.autofill_assistant.details;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.ThumbnailUtils;
import android.os.Build;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.autofill_assistant.R; import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.cached_image_fetcher.CachedImageFetcher; import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetailsViewBinder.ViewHolder;
import org.chromium.chrome.browser.compositor.animation.CompositorAnimator; import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/** /**
* Coordinator responsible for showing details. * Coordinator responsible for showing details.
*/ */
public class AssistantDetailsCoordinator { public class AssistantDetailsCoordinator {
private static final int IMAGE_BORDER_RADIUS = 4;
private static final int PULSING_DURATION_MS = 1_000;
private static final String DETAILS_TIME_FORMAT = "H:mma";
private static final String DETAILS_DATE_FORMAT = "EEE, MMM d";
private final Context mContext;
@Nullable @Nullable
private Runnable mOnVisibilityChanged; private Runnable mOnVisibilityChanged;
private final View mView; private final View mView;
private final GradientDrawable mDefaultImage;
private final ImageView mImageView;
private final TextView mTitleView;
private final TextView mSubtextView;
private final int mImageWidth;
private final int mImageHeight;
private final int mPulseAnimationStartColor;
private final int mPulseAnimationEndColor;
private final Set<View> mViewsToAnimate = new HashSet<>();
private ValueAnimator mPulseAnimation;
public AssistantDetailsCoordinator(ChromeActivity activity, AssistantDetailsModel model) { public AssistantDetailsCoordinator(ChromeActivity activity, AssistantDetailsModel model) {
mContext = activity;
mView = LayoutInflater.from(activity).inflate( mView = LayoutInflater.from(activity).inflate(
R.layout.autofill_assistant_details, /* root= */ null); R.layout.autofill_assistant_details, /* root= */ null);
ViewHolder viewHolder = new ViewHolder(activity, mView);
mDefaultImage = (GradientDrawable) activity.getResources().getDrawable( AssistantDetailsViewBinder viewBinder = new AssistantDetailsViewBinder(activity);
R.drawable.autofill_assistant_default_details); PropertyModelChangeProcessor.create(model, viewHolder, viewBinder);
mImageView = mView.findViewById(R.id.details_image);
mTitleView = mView.findViewById(R.id.details_title);
mSubtextView = mView.findViewById(R.id.details_text);
mImageWidth = mContext.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mImageHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mPulseAnimationStartColor = mContext.getResources().getColor(R.color.modern_grey_100);
mPulseAnimationEndColor = mContext.getResources().getColor(R.color.modern_grey_50);
// Details view is initially hidden. // Details view is initially hidden.
setVisible(false); setVisible(false);
// Observe details in model to hide or show this coordinator view.
model.addObserver((source, propertyKey) -> { model.addObserver((source, propertyKey) -> {
if (AssistantDetailsModel.DETAILS == propertyKey) { if (AssistantDetailsModel.DETAILS == propertyKey) {
AssistantDetails details = model.get(AssistantDetailsModel.DETAILS); AssistantDetails details = model.get(AssistantDetailsModel.DETAILS);
if (details != null) { if (details != null) {
showDetails(details); setVisible(true);
} else { } else {
setVisible(false); setVisible(false);
} }
...@@ -132,172 +74,4 @@ public class AssistantDetailsCoordinator { ...@@ -132,172 +74,4 @@ public class AssistantDetailsCoordinator {
mOnVisibilityChanged = listener; mOnVisibilityChanged = listener;
} }
/**
* Update the details.
*/
private void showDetails(AssistantDetails details) {
String detailsText = makeDetailsText(details);
mTitleView.setText(details.getTitle());
mSubtextView.setText(detailsText);
if (mImageView.getDrawable() == null) {
// Set default image if no image was set before.
mImageView.setImageDrawable(mDefaultImage);
}
setTextStyles(details);
// Download image and then set it in the model.
if (!details.getUrl().isEmpty()) {
CachedImageFetcher.getInstance().fetchImage(details.getUrl(), image -> {
if (image != null) {
mImageView.setImageDrawable(getRoundedImage(image));
}
});
}
setVisible(true);
}
private void setTextStyles(AssistantDetails details) {
setTitleStyle(details);
setSubtextStyle(details);
}
private void setTitleStyle(AssistantDetails details) {
boolean animateBackground = false;
if (details.getUserApprovalRequired() && !details.getHighlightTitle()) {
// De-emphasize title if user needs to accept details but the title should not be
// highlighted.
mTitleView.setTextColor(ApiCompatibilityUtils.getColor(
mContext.getResources(), R.color.modern_grey_300));
} else {
// Normal style: bold black text.
ApiCompatibilityUtils.setTextAppearance(
mTitleView, R.style.TextAppearance_BlackCaptionDefault);
mTitleView.setTypeface(mTitleView.getTypeface(), Typeface.BOLD);
if (mTitleView.length() == 0 && details.getShowPlaceholdersForEmptyFields()) {
animateBackground = true;
}
}
if (animateBackground) {
addViewToAnimation(mTitleView);
} else {
removeViewFromAnimation(mTitleView);
}
}
private void setSubtextStyle(AssistantDetails details) {
boolean animateBackground = false;
if (details.getUserApprovalRequired()) {
if (details.getHighlightDate()) {
// Emphasized style.
mSubtextView.setTypeface(mSubtextView.getTypeface(), Typeface.BOLD_ITALIC);
} else {
// De-emphasized style.
mSubtextView.setTextColor(ApiCompatibilityUtils.getColor(
mContext.getResources(), R.color.modern_grey_300));
}
} else {
// Normal style.
ApiCompatibilityUtils.setTextAppearance(
mSubtextView, R.style.TextAppearance_BlackCaption);
if (mSubtextView.length() == 0 && details.getShowPlaceholdersForEmptyFields()) {
animateBackground = true;
}
}
if (animateBackground) {
addViewToAnimation(mSubtextView);
} else {
removeViewFromAnimation(mSubtextView);
}
}
private String makeDetailsText(AssistantDetails details) {
List<String> parts = new ArrayList<>();
Date date = details.getDate();
if (date != null) {
parts.add(formatDetailsTime(date));
parts.add(formatDetailsDate(date));
}
String description = details.getDescription();
if (description != null && !description.isEmpty()) {
parts.add(description);
}
// TODO(crbug.com/806868): Use a view instead of this dot text.
return TextUtils.join(" • ", parts);
}
private Locale getLocale() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? mContext.getResources().getConfiguration().getLocales().get(0)
: mContext.getResources().getConfiguration().locale;
}
private String formatDetailsTime(Date date) {
DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault());
String timeFormatPattern = (df instanceof SimpleDateFormat)
? ((SimpleDateFormat) df).toPattern()
: DETAILS_TIME_FORMAT;
return new SimpleDateFormat(timeFormatPattern, getLocale()).format(date);
}
private String formatDetailsDate(Date date) {
return new SimpleDateFormat(DETAILS_DATE_FORMAT, getLocale()).format(date);
}
private Drawable getRoundedImage(Bitmap bitmap) {
RoundedBitmapDrawable roundedBitmap =
RoundedBitmapDrawableFactory.create(mContext.getResources(),
ThumbnailUtils.extractThumbnail(bitmap, mImageWidth, mImageHeight));
roundedBitmap.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
IMAGE_BORDER_RADIUS, mContext.getResources().getDisplayMetrics()));
return roundedBitmap;
}
private void addViewToAnimation(View view) {
if (mViewsToAnimate.add(view) && mPulseAnimation == null) {
mPulseAnimation =
ValueAnimator.ofInt(mPulseAnimationStartColor, mPulseAnimationEndColor);
mPulseAnimation.setDuration(PULSING_DURATION_MS);
mPulseAnimation.setEvaluator(new ArgbEvaluator());
mPulseAnimation.setRepeatCount(ValueAnimator.INFINITE);
mPulseAnimation.setRepeatMode(ValueAnimator.REVERSE);
mPulseAnimation.setInterpolator(CompositorAnimator.ACCELERATE_INTERPOLATOR);
mPulseAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
mTitleView.setBackgroundColor(Color.WHITE);
mSubtextView.setBackgroundColor(Color.WHITE);
mDefaultImage.setColor(mPulseAnimationStartColor);
}
});
mPulseAnimation.addUpdateListener(animation -> {
int animatedValue = (int) animation.getAnimatedValue();
for (View viewToAnimate : mViewsToAnimate) {
viewToAnimate.setBackgroundColor(animatedValue);
}
mDefaultImage.setColor(animatedValue);
});
mPulseAnimation.start();
}
}
private void removeViewFromAnimation(View view) {
if (mViewsToAnimate.remove(view) && mPulseAnimation != null) {
mPulseAnimation.cancel();
mPulseAnimation = null;
}
// Reset background to white.
view.setBackgroundColor(Color.WHITE);
}
} }
// Copyright 2018 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.
package org.chromium.chrome.browser.autofill_assistant.details;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.ThumbnailUtils;
import android.os.Build;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.cached_image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* This class is responsible for pushing updates to the Autofill Assistant details view. These
* updates are pulled from the {@link AssistantDetailsModel} when a notification of an update is
* received.
*/
class AssistantDetailsViewBinder
implements PropertyModelChangeProcessor.ViewBinder<AssistantDetailsModel,
AssistantDetailsViewBinder.ViewHolder, PropertyKey> {
private static final int IMAGE_BORDER_RADIUS = 4;
private static final int PULSING_DURATION_MS = 1_000;
private static final String DETAILS_TIME_FORMAT = "H:mma";
private static final String DETAILS_DATE_FORMAT = "EEE, MMM d";
/**
* A wrapper class that holds the different views of the header.
*/
static class ViewHolder {
final GradientDrawable mDefaultImage;
final ImageView mImageView;
final TextView mTitleView;
final TextView mSubtextView;
public ViewHolder(Context context, View detailsView) {
mDefaultImage = (GradientDrawable) context.getResources().getDrawable(
R.drawable.autofill_assistant_default_details);
mImageView = detailsView.findViewById(R.id.details_image);
mTitleView = detailsView.findViewById(R.id.details_title);
mSubtextView = detailsView.findViewById(R.id.details_text);
}
}
private final Context mContext;
private final int mImageWidth;
private final int mImageHeight;
private final int mPulseAnimationStartColor;
private final int mPulseAnimationEndColor;
private final Set<View> mViewsToAnimate = new HashSet<>();
private ValueAnimator mPulseAnimation;
AssistantDetailsViewBinder(Context context) {
mContext = context;
mImageWidth = context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mImageHeight = context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mPulseAnimationStartColor = context.getResources().getColor(R.color.modern_grey_100);
mPulseAnimationEndColor = context.getResources().getColor(R.color.modern_grey_50);
}
@Override
public void bind(AssistantDetailsModel model, ViewHolder view, PropertyKey propertyKey) {
if (AssistantDetailsModel.DETAILS == propertyKey) {
AssistantDetails details = model.get(AssistantDetailsModel.DETAILS);
if (details == null) {
// Handled by the AssistantDetailsCoordinator.
return;
}
setDetails(details, view);
} else {
assert false : "Unhandled property detected in AssistantDetailsViewBinder!";
}
}
private void setDetails(AssistantDetails details, ViewHolder viewHolder) {
String detailsText = makeDetailsText(details);
viewHolder.mTitleView.setText(details.getTitle());
viewHolder.mSubtextView.setText(detailsText);
if (viewHolder.mImageView.getDrawable() == null) {
// Set default image if no image was set before.
viewHolder.mImageView.setImageDrawable(viewHolder.mDefaultImage);
}
setTextStyles(details, viewHolder);
// Download image and then set it in the model.
if (!details.getUrl().isEmpty()) {
CachedImageFetcher.getInstance().fetchImage(details.getUrl(), image -> {
if (image != null) {
viewHolder.mImageView.setImageDrawable(getRoundedImage(image));
}
});
}
}
private void setTextStyles(AssistantDetails details, ViewHolder viewHolder) {
setTitleStyle(details, viewHolder);
setSubtextStyle(details, viewHolder);
}
private void setTitleStyle(AssistantDetails details, ViewHolder viewHolder) {
boolean animateBackground = false;
TextView titleView = viewHolder.mTitleView;
if (details.getUserApprovalRequired() && !details.getHighlightTitle()) {
// De-emphasize title if user needs to accept details but the title should not be
// highlighted.
titleView.setTextColor(ApiCompatibilityUtils.getColor(
mContext.getResources(), R.color.modern_grey_300));
} else {
// Normal style: bold black text.
ApiCompatibilityUtils.setTextAppearance(
titleView, R.style.TextAppearance_BlackCaptionDefault);
titleView.setTypeface(titleView.getTypeface(), Typeface.BOLD);
if (titleView.length() == 0 && details.getShowPlaceholdersForEmptyFields()) {
animateBackground = true;
}
}
if (animateBackground) {
addViewToAnimation(titleView, viewHolder);
} else {
removeViewFromAnimation(titleView);
}
}
private void setSubtextStyle(AssistantDetails details, ViewHolder viewHolder) {
boolean animateBackground = false;
TextView subtextView = viewHolder.mSubtextView;
if (details.getUserApprovalRequired()) {
if (details.getHighlightDate()) {
// Emphasized style.
subtextView.setTypeface(subtextView.getTypeface(), Typeface.BOLD_ITALIC);
} else {
// De-emphasized style.
subtextView.setTextColor(ApiCompatibilityUtils.getColor(
mContext.getResources(), R.color.modern_grey_300));
}
} else {
// Normal style.
ApiCompatibilityUtils.setTextAppearance(
subtextView, R.style.TextAppearance_BlackCaption);
if (subtextView.length() == 0 && details.getShowPlaceholdersForEmptyFields()) {
animateBackground = true;
}
}
if (animateBackground) {
addViewToAnimation(subtextView, viewHolder);
} else {
removeViewFromAnimation(subtextView);
}
}
private String makeDetailsText(AssistantDetails details) {
List<String> parts = new ArrayList<>();
Date date = details.getDate();
if (date != null) {
parts.add(formatDetailsTime(date));
parts.add(formatDetailsDate(date));
}
String description = details.getDescription();
if (description != null && !description.isEmpty()) {
parts.add(description);
}
// TODO(crbug.com/806868): Use a view instead of this dot text.
return TextUtils.join(" • ", parts);
}
private Locale getLocale() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? mContext.getResources().getConfiguration().getLocales().get(0)
: mContext.getResources().getConfiguration().locale;
}
private String formatDetailsTime(Date date) {
DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault());
String timeFormatPattern = (df instanceof SimpleDateFormat)
? ((SimpleDateFormat) df).toPattern()
: DETAILS_TIME_FORMAT;
return new SimpleDateFormat(timeFormatPattern, getLocale()).format(date);
}
private String formatDetailsDate(Date date) {
return new SimpleDateFormat(DETAILS_DATE_FORMAT, getLocale()).format(date);
}
private Drawable getRoundedImage(Bitmap bitmap) {
RoundedBitmapDrawable roundedBitmap =
RoundedBitmapDrawableFactory.create(mContext.getResources(),
ThumbnailUtils.extractThumbnail(bitmap, mImageWidth, mImageHeight));
roundedBitmap.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
IMAGE_BORDER_RADIUS, mContext.getResources().getDisplayMetrics()));
return roundedBitmap;
}
private void addViewToAnimation(View view, ViewHolder viewHolder) {
mViewsToAnimate.add(view);
if (mPulseAnimation == null) {
mPulseAnimation =
ValueAnimator.ofInt(mPulseAnimationStartColor, mPulseAnimationEndColor);
mPulseAnimation.setDuration(PULSING_DURATION_MS);
mPulseAnimation.setEvaluator(new ArgbEvaluator());
mPulseAnimation.setRepeatCount(ValueAnimator.INFINITE);
mPulseAnimation.setRepeatMode(ValueAnimator.REVERSE);
mPulseAnimation.setInterpolator(CompositorAnimator.ACCELERATE_INTERPOLATOR);
mPulseAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
viewHolder.mTitleView.setBackgroundColor(Color.WHITE);
viewHolder.mSubtextView.setBackgroundColor(Color.WHITE);
viewHolder.mDefaultImage.setColor(mPulseAnimationStartColor);
}
});
mPulseAnimation.addUpdateListener(animation -> {
int animatedValue = (int) animation.getAnimatedValue();
for (View viewToAnimate : mViewsToAnimate) {
viewToAnimate.setBackgroundColor(animatedValue);
}
viewHolder.mDefaultImage.setColor(animatedValue);
});
mPulseAnimation.start();
}
}
private void removeViewFromAnimation(View view) {
mViewsToAnimate.remove(view);
if (mViewsToAnimate.isEmpty() && mPulseAnimation != null) {
mPulseAnimation.cancel();
mPulseAnimation = null;
}
}
}
...@@ -155,6 +155,7 @@ chrome_java_sources = [ ...@@ -155,6 +155,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java", "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java", "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java", "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java", "java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java", "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java", "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment