package com.byoutline.kickmaterial.activities; import android.animation.Animator; import android.app.Activity; import android.content.Intent; import android.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.SharedElementCallback; import android.support.v4.view.ViewCompat; import android.support.v7.graphics.Palette; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.AnimationUtils; import butterknife.ButterKnife; import butterknife.OnClick; import com.byoutline.kickmaterial.KickMaterialApp; import com.byoutline.kickmaterial.R; import com.byoutline.kickmaterial.adapters.ProjectsAdapter; import com.byoutline.kickmaterial.databinding.ActivityProjectDetailsBinding; import com.byoutline.kickmaterial.events.ProjectDetailsFetchedEvent; import com.byoutline.kickmaterial.model.Project; import com.byoutline.kickmaterial.model.ProjectDetails; import com.byoutline.kickmaterial.model.ProjectIdAndSignature; import com.byoutline.kickmaterial.utils.AnimatorUtils; import com.byoutline.kickmaterial.utils.LUtils; import com.byoutline.kickmaterial.utils.LruCacheWithPlaceholders; import com.byoutline.kickmaterial.utils.PaletteAndAplaTransformation; import com.byoutline.kickmaterial.views.ObservableScrollView; import com.byoutline.observablecachedfield.ObservableCachedFieldWithArg; import com.byoutline.secretsauce.activities.WebViewActivityV7; import com.byoutline.secretsauce.activities.WebViewFlickrActivity; import com.byoutline.secretsauce.utils.ViewUtils; import com.software.shell.fab.ActionButton; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import org.parceler.Parcels; import timber.log.Timber; import javax.inject.Inject; import java.util.List; /** * @author Pawel Karczewski <pawel.karczewski at byoutline.com> on 2015-01-03 */ public class ProjectDetailsActivity extends KickMaterialBaseActivity implements ObservableScrollView.Callbacks { private static final String EXTRA_PROJECT = "DetailActivity:project"; private static final int MAX_TRANSITION_DELAY = 800; private static final int ACTION_BUTTON_VISIBILITY_ANIM_DELAY = MAX_TRANSITION_DELAY + 200; @Inject ObservableCachedFieldWithArg<ProjectDetails, ProjectIdAndSignature> projectDetailsField; @Inject Bus bus; @Inject LruCacheWithPlaceholders picassoCache; int minTitlesMarginTop; private Project project; private ProjectDetails projectDetails; private int maxTitlesMarginTop; private int maxTitlesMarginLeft; private int maxParallaxValue; private int titleFontMaxSize; private int titleFontMinSize; private int maxTitlePaddingRight; private int imageWidth; private int imageHeight; private ActivityProjectDetailsBinding binding; public static void launch(Activity context, Project project, View... sharedViews) { final Bundle options; if (LUtils.hasL()) { options = getSharedElementsBundle(context, sharedViews); } else { options = new Bundle(); } Intent intent = new Intent(context, ProjectDetailsActivity.class); Parcelable wrapped = Parcels.wrap(project); intent.putExtra(EXTRA_PROJECT, wrapped); // Preload big photo Picasso.with(context).load(project.getBigPhotoUrl()); ActivityCompat.startActivity(context, intent, options); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_project_details); ButterKnife.bind(this); KickMaterialApp.component.inject(this); binding.setProject(projectDetailsField.observable()); supportPostponeEnterTransition(); handleArguments(); injectViewsAndSetUpToolbar(); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(false); // Hide default toolbar title binding.scrollView.addCallbacks(this); minTitlesMarginTop = ViewUtils.dpToPx(32, getApplicationContext()); maxTitlesMarginTop = getResources().getDimensionPixelSize(R.dimen.titles_container_margin_top) - getResources().getDimensionPixelSize(R.dimen.status_bar_height); maxTitlesMarginLeft = ViewUtils.dpToPx(32, getApplicationContext()); maxTitlePaddingRight = ViewUtils.dpToPx(72, getApplicationContext()); maxParallaxValue = getResources().getDimensionPixelSize(R.dimen.project_details_photo_height) / 3; titleFontMaxSize = getResources().getDimensionPixelSize(R.dimen.font_21); titleFontMinSize = getResources().getDimensionPixelSize(R.dimen.font_16); imageHeight = getResources().getDimensionPixelSize(R.dimen.project_details_photo_height); imageWidth = (int) (imageHeight * ProjectsAdapter.IMAGE_RATIO); binding.detailsContainer.startAnimation(AnimationUtils.loadAnimation(ProjectDetailsActivity.this, R.anim.slide_from_bottom)); loadProjectData(); launchPostTransitionAnimations(); } private void launchPostTransitionAnimations() { if (LUtils.hasL()) { ActivityCompat.setEnterSharedElementCallback(this, new SharedElementCallback() { @Override public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) { binding.detailsContainer.postDelayed(() -> { binding.detailsContainer.startAnimation(LUtils.loadAnimationWithLInterpolator(ProjectDetailsActivity.this, R.anim.slide_from_top)); binding.scrollView.startAnimation(LUtils.loadAnimationWithLInterpolator(ProjectDetailsActivity.this, R.anim.slide_from_top_long)); }, 0); } }); } // categoriesRv.post(() -> categoriesRv.startAnimation(LUtils.loadAnimationWithLInterpolator(getApplicationContext(), R.anim.slide_from_bottom))); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.project_details, menu); return true; } private void loadProjectData() { ViewUtils.setTextOrClear(binding.projectSubtitleTv, getString(R.string.project_details_made_by, project.getAuthorName())); if (LUtils.hasL()) { animateAlphaAfterTransition(binding.projectSubtitleTv); } binding.projectBackingProgress.setText(project.isFunded() ? R.string.funded : R.string.backing_in_progress); binding.projectItemBigProgressSb.setProgress((int) project.getPercentProgress()); ProjectsAdapter.setProjectDetailsInfo(binding.projectItemBigGatheredMoneyTv, binding.projectItemBigPledgedOfTv, binding.projectItemBigDaysLeft, binding.projectItemTimeLeftTypeTv, project); // TODO: animate elevation on scroll. ViewCompat.setElevation(binding.detailsContainer, ViewUtils.convertDpToPixel(4, ProjectDetailsActivity.this)); loadProjectPhoto(); } private void loadProjectPhoto() { Bitmap bitmap = picassoCache.getPlaceholder(project.getBigPhotoUrl()); boolean bigPhotoMustBeFetched = bitmap == null; if (bigPhotoMustBeFetched) { bitmap = picassoCache.getPlaceholder(project.getPhotoUrl()); boolean placeholderAlreadyFetched = bitmap != null; if (placeholderAlreadyFetched) { binding.projectPhotoIv.setImageBitmap(bitmap); } } // Make sure that transition starts soon even if image is not ready. binding.projectPhotoIv.postDelayed(this::supportStartPostponedEnterTransition, MAX_TRANSITION_DELAY); Picasso.with(this) .load(project.getBigPhotoUrl()) .resize(imageWidth, imageHeight) .onlyScaleDown() .centerCrop() .transform(PaletteAndAplaTransformation.instance()) .into(binding.projectPhotoIv, new Callback() { @Override public void onSuccess() { Bitmap bitmap = ((BitmapDrawable) binding.projectPhotoIv.getDrawable()).getBitmap(); // Ew! Palette palette = PaletteAndAplaTransformation.getPalette(bitmap); binding.detailsContainer.setBackgroundColor(palette.getDarkVibrantColor(Color.BLACK)); supportStartPostponedEnterTransition(); } @Override public void onError() { supportStartPostponedEnterTransition(); } }); } private void animateAlphaAfterTransition(final View view) { view.setAlpha(0); ActivityCompat.setEnterSharedElementCallback(this, new SharedElementCallback() { @Override public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) { Animator alphaAnimator = AnimatorUtils.getAlphaAnimator(view); alphaAnimator.setDuration(600); alphaAnimator.start(); ActivityCompat.setEnterSharedElementCallback(ProjectDetailsActivity.this, null); } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; case R.id.back_project: showRewardList(); return true; } return super.onOptionsItemSelected(item); } private void showRewardList() { if (projectDetails == null) { ViewUtils.showToast("Getting rewards failed. Retrying"); postProjectDetails(); } else { RewardsListActivity.launch(this, projectDetails, binding.playVideoBtn); // showWebView(project.getPledgeUrl()); } } @Override public void onResume() { super.onResume(); setTitle(" "); bus.register(this); toolbar.setBackgroundColor(Color.TRANSPARENT); postProjectDetails(); } private void postProjectDetails() { ProjectIdAndSignature params = ProjectIdAndSignature.create(project.id, project.getDetailsQueryMap()); projectDetailsField.postValue(params); } @Override public void onPause() { bus.unregister(this); super.onPause(); } @Override protected void onDestroy() { Picasso.with(this).cancelRequest(binding.projectPhotoIv); super.onDestroy(); } private void handleArguments() { Bundle args = getIntent().getExtras(); if (args != null) { project = Parcels.unwrap(args.getParcelable(EXTRA_PROJECT)); } } @Override public void setToolbarAlpha(float alpha) { } @OnClick(R.id.project_comments_ll) public void onCommentsClicked() { showWebView(project.getCommentsUrl()); } @OnClick(R.id.project_updates_ll) public void onUpdatesClicked() { showWebView(project.getUpdatesUrl()); } @OnClick(R.id.read_more_btn) public void readMorePressed(View view) { int MAX_DESCRIPTION_LINES = 1000; binding.projectDescriptionTv.setMaxLines(MAX_DESCRIPTION_LINES); ViewUtils.showView(view, false); // showWebView(project.getProjectUrl()); } @OnClick(R.id.play_video_btn) public void playVideo() { if (projectDetails == null) { ViewUtils.showToast("Getting project details failed. Retrying"); postProjectDetails(); } else { VideoActivity.showActivity(this, projectDetails); } } @OnClick({R.id.project_author_name_label_tv, R.id.author_photo_iv, R.id.project_author_name_tv}) public void authorClicked() { showWebView(project.getAuthorUrl(), new Intent(this, WebViewFlickrActivity.class)); } private void showWebView(String url) { Intent intent = new Intent(this, WebViewActivityV7.class); showWebView(url, intent); } private void showWebView(String url, Intent intent) { intent.putExtra(WebViewActivityV7.BUNDLE_URL, url); startActivity(intent); } @Override public void onScrollChanged(int deltaX, int deltaY) { int scrollY = (int) (binding.scrollView.getScrollY() * 0.6f); float newTitleLeft = Math.min(maxTitlesMarginLeft, scrollY * 0.5f); float newTitleTop = Math.min(maxTitlesMarginTop, scrollY); int newTitlePaddingRight = Math.min(maxTitlePaddingRight, scrollY); binding.projectTitleTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(titleFontMaxSize - scrollY * 0.05f, titleFontMinSize)); // binding.projectTitleTv.setTextSize(TypedValue.COMPLEX_UNIT_PX,moveBase(minTitlesMarginTop,maxTitlesMarginTop,titleFontMaxSize,titleFontMinSize,scrollY)); binding.projectTitleTv.setPadding(0, 0, newTitlePaddingRight, 0); binding.projectDetailsTitleContainer.setTranslationX(newTitleLeft); binding.projectDetailsTitleContainer.setTranslationY(-newTitleTop); binding.detailsContainer.setTranslationY(-newTitleTop); binding.playVideoBtn.setTranslationY(-newTitleTop); /** Content of scroll view is hiding to early during scroll so we move it also by * changing to padding */ binding.scrollView.setPadding(0, (int) (newTitleTop * 0.6f), 0, 0); // Move background photo (parallax effect) int parallax = (int) (scrollY * .3f); if (maxParallaxValue > parallax) { binding.projectPhotoContainer.setTranslationY(-parallax); } } public int moveBase(int xmin, int xmax, int ymin, int ymax, int value) { return ymin + ((value - xmin) * (ymax - ymin) / (xmax - xmin)); } private void animateActionButtonVisibility(final boolean videoExist) { ActionButton videoBtn = binding.playVideoBtn; videoBtn.setEnabled(videoExist); Runnable r = () -> { // Button alpha may change during transition, we may have to wait until its end // to check its state. Timber.w("Action btn alpha: " + videoBtn.getAlpha()); boolean actionBtnShouldAnimate = (videoExist && videoBtn.getAlpha() != 1) || (!videoExist && videoBtn.getAlpha() != 0); if (!actionBtnShouldAnimate) { return; } Animator alphaAnimator = AnimatorUtils.getAlphaAnimator(videoBtn, !videoExist); alphaAnimator.setDuration(600); alphaAnimator.start(); }; // Wait just in case transition is still in progress. videoBtn.postDelayed(r, ACTION_BUTTON_VISIBILITY_ANIM_DELAY); } @Subscribe public void onProjectDetailsFetched(ProjectDetailsFetchedEvent event) { projectDetails = event.getResponse(); final boolean videoExist = projectDetails.video != null; animateActionButtonVisibility(videoExist); } }