package com.appboy.ui.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ViewSwitcher;
import com.appboy.Appboy;
import com.appboy.Constants;
import com.appboy.configuration.AppboyConfigurationProvider;
import com.appboy.enums.Channel;
import com.appboy.models.cards.Card;
import com.appboy.support.AppboyLogger;
import com.appboy.ui.AppboyNavigator;
import com.appboy.ui.R;
import com.appboy.ui.actions.ActionFactory;
import com.appboy.ui.actions.IAction;
import com.appboy.ui.actions.UriAction;
import com.appboy.ui.feed.AppboyFeedManager;
import com.appboy.ui.feed.AppboyImageSwitcher;
import com.appboy.ui.support.FrescoLibraryUtils;
import com.appboy.ui.support.ViewUtils;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.Observable;
import java.util.Observer;
/**
* Base class for Appboy feed card views
*/
public abstract class BaseCardView<T extends Card> extends RelativeLayout implements Observer {
private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, BaseCardView.class.getName());
private static Boolean unreadCardVisualIndicatorOn;
private static final float SQUARE_ASPECT_RATIO = 1f;
protected final Context mContext;
protected T mCard;
protected AppboyImageSwitcher mImageSwitcher;
private final boolean mCanUseFresco;
public BaseCardView(Context context) {
super(context);
// Note: this must be called before we inflate any views.
mCanUseFresco = FrescoLibraryUtils.canUseFresco(context);
mContext = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(getLayoutResource(), this);
// All implementing views of BaseCardView must include this switcher view in order to have the
// read/unread functionality. Views that don't have the indicator (like banner views) won't have the image switcher
// in them and thus we do the null-check below.
mImageSwitcher = (AppboyImageSwitcher) findViewById(R.id.com_appboy_newsfeed_item_read_indicator_image_switcher);
if (mImageSwitcher != null) {
mImageSwitcher.setFactory(new ViewSwitcher.ViewFactory() {
@Override
public View makeView() {
return new ImageView(mContext.getApplicationContext());
}
});
}
// If the visual indicator on cards shouldn't be on, due to the xml setting in appboy.xml, then set the
// imageSwitcher to GONE to hide the indicator UI.
// Read the setting from the appboy.xml if we don't already have a value.
if (unreadCardVisualIndicatorOn == null) {
AppboyConfigurationProvider configurationProvider = new AppboyConfigurationProvider(context);
unreadCardVisualIndicatorOn = configurationProvider.getIsNewsfeedVisualIndicatorOn();
}
// If the setting is false, then hide the indicator.
if (!unreadCardVisualIndicatorOn) {
if (mImageSwitcher != null) {
mImageSwitcher.setVisibility(GONE);
}
}
}
/**
* This method is called when the setRead() method is called on the internal Card object.
*/
@Override
public void update(Observable observable, Object data) {
setCardViewedIndicator();
}
/**
* Checks to see if the card object is viewed and if so, sets the read/unread status
* indicator image. If the card is null, does nothing.
*/
private void setCardViewedIndicator() {
if (getCard() != null) {
if (mImageSwitcher != null) {
AppboyLogger.v(TAG, "Setting the read/unread indicator for the card.");
if (getCard().isRead()) {
if (mImageSwitcher.getReadIcon() != null) {
mImageSwitcher.setImageDrawable(mImageSwitcher.getReadIcon());
} else {
mImageSwitcher.setImageResource(R.drawable.icon_read);
}
mImageSwitcher.setTag("icon_read");
} else {
if (mImageSwitcher.getUnReadIcon() != null) {
mImageSwitcher.setImageDrawable(mImageSwitcher.getUnReadIcon());
return;
} else {
mImageSwitcher.setImageResource(R.drawable.icon_unread);
}
mImageSwitcher.setTag("icon_unread");
}
}
} else {
AppboyLogger.d(TAG, "The card is null.");
}
}
protected abstract int getLayoutResource();
public void setCard(final T card) {
mCard = card;
onSetCard(card);
// Register as an observer to the card class
card.addObserver(this);
setCardViewedIndicator();
}
protected abstract void onSetCard(T card);
public Card getCard() {
return mCard;
}
void setOptionalTextView(TextView view, String value) {
if (value != null && !value.trim().equals("")) {
view.setText(value);
view.setVisibility(VISIBLE);
} else {
view.setText("");
view.setVisibility(GONE);
}
}
void safeSetBackground(Drawable background) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
setBackgroundDrawable(background);
} else {
setBackgroundNew(background);
}
}
@TargetApi(16)
private void setBackgroundNew(Drawable background) {
setBackground(background);
}
/**
* Calls setImageViewToUrl with aspect ratio set to 1f and respectAspectRatio set to false.
* @see com.appboy.ui.widget.BaseCardView#setImageViewToUrl(android.widget.ImageView, String, float, boolean)
*/
void setImageViewToUrl(final ImageView imageView, final String imageUrl) {
setImageViewToUrl(imageView, imageUrl, 1f, false);
}
/**
* Calls setImageViewToUrl with respectAspectRatio set to true.
* @see com.appboy.ui.widget.BaseCardView#setImageViewToUrl(android.widget.ImageView, String, float, boolean)
*/
void setImageViewToUrl(final ImageView imageView, final String imageUrl, final float aspectRatio) {
setImageViewToUrl(imageView, imageUrl, aspectRatio, true);
}
/**
* Asynchronously fetches the image at the given imageUrl and displays the image in the ImageView. No image will be
* displayed if the image cannot be downloaded or fetched from the cache.
*
* @param imageView the ImageView in which to display the image
* @param imageUrl the URL of the image resource
* @param aspectRatio the desired aspect ratio of the image. This should match what's being sent down from the dashboard.
* @param respectAspectRatio whether to use aspectRatio as the final aspect ratio of the imageView. When set to false,
* the aspect ratio of the imageView will match that of the downloaded image. When set to true,
* the provided aspect ratio will match aspectRatio, regardless of the actual dimensions of the
* downloaded image.
*/
void setImageViewToUrl(final ImageView imageView, final String imageUrl, final float aspectRatio, final boolean respectAspectRatio) {
if (imageUrl == null) {
AppboyLogger.w(TAG, "The image url to render is null. Not setting the card image.");
return;
}
if (aspectRatio == 0) {
AppboyLogger.w(TAG, "The image aspect ratio is 0. Not setting the card image.");
return;
}
if (!imageUrl.equals(imageView.getTag())) {
if (aspectRatio != SQUARE_ASPECT_RATIO) {
// We need to set layout params on the imageView once its layout state is visible. To do this,
// we obtain the imageView's observer and attach a listener on it for when the view's layout
// occurs. At layout time, we set the imageView's size params based on the aspect ratio
// for our card. Note that after the card's first layout, we don't want redundant resizing
// so we remove our listener after the resizing.
ViewTreeObserver viewTreeObserver = imageView.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int width = imageView.getWidth();
imageView.setLayoutParams(new LayoutParams(width, (int) (width / aspectRatio)));
ViewUtils.removeOnGlobalLayoutListenerSafe(imageView.getViewTreeObserver(), this);
}
});
}
}
imageView.setImageResource(android.R.color.transparent);
Appboy.getInstance(getContext()).fetchAndRenderImage(imageUrl, imageView, respectAspectRatio);
imageView.setTag(imageUrl);
}
}
/**
* Loads an image via url for display in a SimpleDraweeView using the Facebook Fresco library.
* By default, gif urls are set to autoplay and tap to retry is on for all images.
* @param simpleDraweeView the fresco SimpleDraweeView in which to display the image
* @param imageUrl the URL of the image resource
*/
void setSimpleDraweeToUrl(final SimpleDraweeView simpleDraweeView, final String imageUrl, final float aspectRatio, final boolean respectAspectRatio) {
if (imageUrl == null) {
AppboyLogger.w(TAG, "The image url to render is null. Not setting the card image.");
return;
}
FrescoLibraryUtils.setDraweeControllerHelper(simpleDraweeView, imageUrl, aspectRatio, respectAspectRatio);
}
/**
* Returns whether we can use the Fresco Library for newsfeed cards.
*/
boolean canUseFresco() {
return mCanUseFresco;
}
protected static void handleCardClick(Context context, Card card, IAction cardAction, String tag) {
handleCardClick(context, card, cardAction, tag, true);
}
/**
* All card views should handle new feed card clicks through this method
*/
protected static void handleCardClick(Context context, Card card, IAction cardAction, String tag, boolean markAsRead) {
if (markAsRead) {
card.setIsRead(true);
}
if (cardAction != null) {
if (card.logClick()) {
AppboyLogger.d(tag, String.format("Logged click for card %s", card.getId()));
} else {
AppboyLogger.d(tag, String.format("Logging click failed for card %s", card.getId()));
}
if (!AppboyFeedManager.getInstance().getFeedCardClickActionListener().onFeedCardClicked(context, card, cardAction)) {
if (cardAction instanceof UriAction) {
AppboyNavigator.getAppboyNavigator().gotoUri(context, (UriAction) cardAction);
} else {
// Some other action received, execute directly.
cardAction.execute(context);
}
}
}
}
protected static UriAction getUriActionForCard(Card card) {
Bundle extras = new Bundle();
for (String key : card.getExtras().keySet()) {
extras.putString(key, card.getExtras().get(key));
}
return ActionFactory.createUriActionFromUrlString(card.getUrl(), extras, card.getOpenUriInWebView(), Channel.NEWS_FEED);
}
/**
* Gets the view to display the correct card image after checking if it can use Fresco.
* @param stubLayoutId The resource Id of the stub for inflation as returned by findViewById.
* @return the view to display the image. This will either be an ImageView or DraweeView
*/
View getProperViewFromInflatedStub(int stubLayoutId) {
ViewStub imageStub = (ViewStub) findViewById(stubLayoutId);
imageStub.inflate();
if (mCanUseFresco) {
return findViewById(R.id.com_appboy_stubbed_feed_drawee_view);
} else {
return findViewById(R.id.com_appboy_stubbed_feed_image_view);
}
}
}