package com.kickstarter.ui.viewholders;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.kickstarter.R;
import com.kickstarter.libs.KSString;
import com.kickstarter.libs.transformations.CircleTransformation;
import com.kickstarter.libs.utils.DateTimeUtils;
import com.kickstarter.libs.utils.ObjectUtils;
import com.kickstarter.libs.utils.ProjectUtils;
import com.kickstarter.libs.utils.SocialUtils;
import com.kickstarter.libs.utils.ViewUtils;
import com.kickstarter.models.Project;
import com.kickstarter.viewmodels.ProjectCardHolderViewModel;
import com.squareup.picasso.Picasso;
import org.joda.time.DateTime;
import butterknife.Bind;
import butterknife.BindDimen;
import butterknife.BindDrawable;
import butterknife.BindString;
import butterknife.ButterKnife;
import static com.kickstarter.libs.rx.transformers.Transformers.observeForUI;
import static com.kickstarter.libs.utils.ViewUtils.getScreenDensity;
import static com.kickstarter.libs.utils.ViewUtils.getScreenWidthDp;
public final class ProjectCardViewHolder extends KSViewHolder {
private final ProjectCardHolderViewModel.ViewModel viewModel;
private Delegate delegate;
private KSString ksString;
protected @Bind(R.id.backers_count) TextView backersCountTextView;
protected @Bind(R.id.backing_group) ViewGroup backingViewGroup;
protected @Bind(R.id.blurb) TextView blurbTextView;
protected @Bind(R.id.category) TextView categoryTextView;
protected @Bind(R.id.deadline_countdown) TextView deadlineCountdownTextView;
protected @Bind(R.id.deadline_countdown_unit) TextView deadlineCountdownUnitTextView;
protected @Bind(R.id.featured) TextView featuredTextView;
protected @Bind(R.id.featured_group) ViewGroup featuredViewGroup;
protected @Bind(R.id.friend_backing_avatar) ImageView friendBackingAvatarImageView;
protected @Bind(R.id.friend_backing_message) TextView friendBackingMessageTextView;
protected @Bind(R.id.friend_backing_group) ViewGroup friendBackingViewGroup;
protected @Bind(R.id.funding_unsuccessful_text_view) TextView fundingUnsuccessfulTextView;
protected @Nullable @Bind(R.id.land_card_view_group) ViewGroup landCardViewGroup;
protected @Bind(R.id.name) TextView nameTextView;
protected @Bind(R.id.percent) TextView percentTextView;
protected @Bind(R.id.percentage_funded) ProgressBar percentageFundedProgressBar;
protected @Bind(R.id.photo) ImageView photoImageView;
protected @Bind(R.id.potd_view_group) ViewGroup potdViewGroup;
protected @Bind(R.id.project_card_view_group) ViewGroup projectCardViewGroup;
protected @Bind(R.id.project_metadata_view_group) ViewGroup projectMetadataViewGroup;
protected @Bind(R.id.project_state_view_group) ViewGroup projectStateViewGroup;
protected @Bind(R.id.starred_view_group) ViewGroup starredViewGroup;
protected @Bind(R.id.successfully_funded_text_view) TextView successfullyFundedTextView;
protected @BindDimen(R.dimen.grid_1) int grid1Dimen;
protected @BindDimen(R.dimen.grid_2) int grid2Dimen;
protected @BindDimen(R.dimen.grid_3) int grid3Dimen;
protected @BindDimen(R.dimen.grid_4) int grid4Dimen;
protected @BindDrawable(R.drawable.gray_gradient) Drawable grayGradientDrawable;
protected @BindString(R.string.discovery_baseball_card_status_banner_canceled_date) String bannerCanceledDateString;
protected @BindString(R.string.discovery_baseball_card_status_banner_suspended_date) String bannerSuspendedDateString;
protected @BindString(R.string.discovery_baseball_card_status_banner_funding_unsuccessful_date) String fundingUnsuccessfulDateString;
protected @BindString(R.string.discovery_baseball_card_status_banner_successful_date) String bannerSuccessfulDateString;
protected @BindString(R.string.discovery_baseball_card_metadata_featured_project) String featuredInString;
protected @BindString(R.string.discovery_baseball_card_stats_pledged_of_goal) String pledgedOfGoalString;
public interface Delegate {
void projectCardViewHolderClick(ProjectCardViewHolder viewHolder, Project project);
}
public ProjectCardViewHolder(final @NonNull View view, final @NonNull Delegate delegate) {
super(view);
this.delegate = delegate;
this.ksString = environment().ksString();
this.viewModel = new ProjectCardHolderViewModel.ViewModel(environment());
ButterKnife.bind(this, view);
this.viewModel.outputs.backersCountTextViewText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this.backersCountTextView::setText);
this.viewModel.outputs.backingViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.backingViewGroup));
this.viewModel.outputs.blurbText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this.blurbTextView::setText);
this.viewModel.outputs.categoryNameTextViewText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this.categoryTextView::setText);
this.viewModel.outputs.deadlineCountdownText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this.deadlineCountdownTextView::setText);
this.viewModel.outputs.featuredViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.featuredViewGroup));
this.viewModel.outputs.friendAvatarUrl()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setFriendAvatarUrl);
this.viewModel.outputs.friendBackingViewIsHidden()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.friendBackingViewGroup));
this.viewModel.outputs.friendsForNamepile()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(friends ->
friendBackingMessageTextView.setText(SocialUtils.projectCardFriendNamepile(friends, ksString))
);
this.viewModel.outputs.fundingUnsuccessfulTextViewIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(fundingUnsuccessfulTextView));
this.viewModel.outputs.imageIsInvisible()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setInvisible(this.photoImageView));
this.viewModel.outputs.nameText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(nameTextView::setText);
this.viewModel.outputs.notifyDelegateOfProjectClick()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(p -> delegate.projectCardViewHolderClick(this, p));
this.viewModel.outputs.percentageFundedProgressBarIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(percentageFundedProgressBar));
this.viewModel.outputs.percentageFundedTextViewText()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(percentTextView::setText);
this.viewModel.outputs.potdViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.potdViewGroup));
this.viewModel.outputs.percentageFunded()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(percentageFundedProgressBar::setProgress);
this.viewModel.outputs.photoUrl()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::resizeProjectImage);
this.viewModel.outputs.projectCanceledAt()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setCanceledTextView);
this.viewModel.outputs.projectFailedAt()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setFailedAtTextView);
this.viewModel.outputs.projectForDeadlineCountdownDetail()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setDeadlineCountdownText);
this.viewModel.outputs.projectStateViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.projectStateViewGroup));
this.viewModel.outputs.projectSuccessfulAt()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setSuccessfullyFundedTextView);
this.viewModel.outputs.projectSuspendedAt()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setSuspendedAtTextView);
this.viewModel.outputs.rootCategoryNameForFeatured()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(rootCategory ->
this.featuredTextView.setText(this.ksString.format(this.featuredInString, "category_name", rootCategory))
);
this.viewModel.outputs.metadataViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.projectMetadataViewGroup));
this.viewModel.outputs.starredViewGroupIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.starredViewGroup));
this.viewModel.outputs.successfullyFundedTextViewIsGone()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(ViewUtils.setGone(this.successfullyFundedTextView));
this.viewModel.outputs.setDefaultTopPadding()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe(this::setDefaultTopPadding);
}
@Override
public void bindData(final @Nullable Object data) throws Exception {
final Project project = ObjectUtils.requireNonNull((Project) data);
this.viewModel.inputs.configureWith(project);
}
private void resizeProjectImage(final @Nullable String avatarUrl) {
final int targetImageWidth = (int) (getScreenWidthDp(context()) * getScreenDensity(context()) - grid4Dimen);
final int targetImageHeight = ProjectUtils.photoHeightFromWidthRatio(targetImageWidth);
photoImageView.setMaxHeight(targetImageHeight);
Picasso.with(this.context())
.load(avatarUrl)
.resize(targetImageWidth, targetImageHeight) // required to fit properly into apis < 18
.centerCrop()
.placeholder(grayGradientDrawable)
.into(photoImageView);
}
private void setDeadlineCountdownText(final @NonNull Project project) {
deadlineCountdownUnitTextView.setText(ProjectUtils.deadlineCountdownDetail(project, context(), ksString));
}
private void setFriendAvatarUrl(final @NonNull String avatarUrl) {
Picasso.with(context()).load(avatarUrl)
.transform(new CircleTransformation())
.into(friendBackingAvatarImageView);
}
private void setDefaultTopPadding(final boolean setDefaultPadding) {
if (setDefaultPadding) {
adjustLandscapeTopPadding(landCardViewGroup, grid2Dimen, grid2Dimen, grid2Dimen, grid2Dimen);
adjustViewGroupTopMargin(projectCardViewGroup, 0);
} else {
adjustLandscapeTopPadding(landCardViewGroup, grid2Dimen, grid3Dimen, grid2Dimen, grid2Dimen);
adjustViewGroupTopMargin(projectCardViewGroup, grid1Dimen);
}
}
@Override
public void onClick(final @NonNull View view) {
this.viewModel.inputs.projectClicked();
}
/**
* Adjust spacing between cards when metadata label is present.
*/
private void adjustViewGroupTopMargin(final @NonNull ViewGroup viewGroup, final int topMargin) {
final RelativeLayout.MarginLayoutParams marginParams = new RelativeLayout.MarginLayoutParams(
viewGroup.getLayoutParams()
);
marginParams.setMargins(0, topMargin, 0, 0);
viewGroup.setLayoutParams(marginParams);
}
/**
* Adjust card content spacing when metadata label is present.
*/
private void adjustLandscapeTopPadding(final @Nullable ViewGroup landscapeViewGroup, final int left, final int top,
final int right, final int bottom) {
if (landscapeViewGroup != null) {
landscapeViewGroup.setPadding(left, top, right, bottom);
}
}
private void setCanceledTextView(final @NonNull DateTime projectCanceledAt) {
fundingUnsuccessfulTextView.setText(ksString.format(bannerCanceledDateString,
"date", DateTimeUtils.relative(context(), ksString, projectCanceledAt)
));
}
private void setSuccessfullyFundedTextView(final @NonNull DateTime projectSuccessfulAt) {
successfullyFundedTextView.setText(ksString.format(bannerSuccessfulDateString,
"date", DateTimeUtils.relative(context(), ksString, projectSuccessfulAt)
));
}
private void setFailedAtTextView(final @NonNull DateTime projectFailedAt) {
fundingUnsuccessfulTextView.setText(ksString.format(fundingUnsuccessfulDateString,
"date", DateTimeUtils.relative(context(), ksString, projectFailedAt)
));
}
private void setSuspendedAtTextView(final @NonNull DateTime projectSuspendedAt) {
fundingUnsuccessfulTextView.setText(ksString.format(bannerSuspendedDateString,
"date", DateTimeUtils.relative(context(), ksString, projectSuspendedAt)
));
}
}