Java tutorial
/* * Copyright 2014 Gleb Godonoga. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.andrada.sitracker.ui.fragment; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.MenuItemCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.TextView; import com.andrada.sitracker.BuildConfig; import com.andrada.sitracker.Constants; import com.andrada.sitracker.R; import com.andrada.sitracker.contracts.AppUriContract; import com.andrada.sitracker.contracts.SIPrefs_; import com.andrada.sitracker.db.beans.Publication; import com.andrada.sitracker.db.dao.PublicationDao; import com.andrada.sitracker.db.manager.SiDBHelper; import com.andrada.sitracker.events.RatingResultEvent; import com.andrada.sitracker.exceptions.SharePublicationException; import com.andrada.sitracker.reader.SamlibPublicationPageReader; import com.andrada.sitracker.ui.BaseActivity; import com.andrada.sitracker.ui.widget.CheckableFrameLayout; import com.andrada.sitracker.ui.widget.MessageCardView; import com.andrada.sitracker.ui.widget.ObservableScrollView; import com.andrada.sitracker.util.AnalyticsHelper; import com.andrada.sitracker.util.SamlibPageHelper; import com.andrada.sitracker.util.ShareHelper; import com.andrada.sitracker.util.UIUtils; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.github.amlcurran.showcaseview.ShowcaseView; import com.github.amlcurran.showcaseview.targets.PointTarget; import com.github.amlcurran.showcaseview.targets.ViewTarget; import com.github.kevinsawicki.http.HttpRequest; import com.google.android.gms.plus.PlusOneButton; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorListenerAdapter; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ObjectAnimator; import com.nineoldandroids.view.ViewHelper; import com.viewpagerindicator.CirclePageIndicator; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.InstanceState; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenuItem; import org.androidannotations.annotations.OrmLiteDao; import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.sharedpreferences.Pref; import org.androidannotations.api.BackgroundExecutor; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; import de.greenrobot.event.EventBus; import de.keyboardsurfer.android.widget.crouton.Configuration; import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Style; import static com.nineoldandroids.view.ViewPropertyAnimator.animate; @EFragment(R.layout.fragment_pub_details) @OptionsMenu(R.menu.publication_info_menu) public class PublicationInfoFragment extends Fragment implements ObservableScrollView.Callbacks { private static final float PHOTO_ASPECT_RATIO = 1.7777777f; private static final float GAP_FILL_DISTANCE_MULTIPLIER = 1.5f; @OrmLiteDao(helper = SiDBHelper.class) PublicationDao publicationsDao; Publication currentRecord; @Pref SIPrefs_ prefs; @ViewById(R.id.scroll_view_child) View mScrollViewChild; @ViewById(R.id.pub_title) TextView mTitle; @ViewById(R.id.pub_subtitle) TextView mSubtitle; @ViewById(R.id.scroll_view) ObservableScrollView mScrollView; @ViewById(R.id.pub_abstract) TextView mAbstract; @ViewById(R.id.publication_rating_block) ViewGroup mRatingContainer; @ViewById(R.id.publication_rating_text) TextView mPubRating; @ViewById(R.id.publication_rating_count) TextView mPubRatingCount; @ViewById(R.id.publication_rating) RatingBar mRatingBar; @ViewById(R.id.voted_on_field) TextView mVotedOnField; @ViewById(R.id.header_pub) View mHeaderBox; @ViewById(R.id.header_pub_contents) View mHeaderContentBox; @ViewById(R.id.header_background) View mHeaderBackgroundBox; @ViewById(R.id.header_shadow) View mHeaderShadow; @ViewById(R.id.details_container) View mDetailsContainer; @ViewById(R.id.pub_photo_container) View mPhotoViewContainer; @ViewById(R.id.pager) ViewPager pager; @ViewById(R.id.pagerIndicators) CirclePageIndicator pagerIndicators; @ViewById(R.id.read_pub_button) CheckableFrameLayout mReadPubButton; @ViewById(R.id.plus_one_button) PlusOneButton mPlusOneButton; @OptionsMenuItem(R.id.action_mark_read) MenuItem mMarkAsReadAction; @OptionsMenuItem(R.id.action_force_download) MenuItem mForceDownloadAction; @InstanceState boolean mIsDownloading = false; private boolean mDownloaded; private ViewGroup mRootView; private Handler mHandler = new Handler(); private Uri mPublicationUri; private long mPublicationId; private boolean mHasPhoto; private boolean mGapFillShown; private float newTop; private int mHeaderTopClearance; private int mPhotoHeightPixels; private int mHeaderHeightPixels; private int mReadPubButtonHeightPixels; private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mReadPubButtonHeightPixels = mReadPubButton.getHeight(); recomputePhotoAndScrollingMetrics(); } }; private boolean delayedScrollCheck; private boolean rateShowcaseShown; private boolean mRatingVisible; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments()); mPublicationUri = intent.getData(); if (mPublicationUri == null) { return; } mPublicationId = AppUriContract.getPublicationId(mPublicationUri); mHandler = new Handler(); rateShowcaseShown = prefs.ratingShowcaseShotDone().get(); } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mRootView = (ViewGroup) inflater.inflate(R.layout.fragment_pub_details, container, false); return mRootView; } @Override public void onResume() { super.onResume(); EventBus.getDefault().register(this); loadData(); } @Override public void onPause() { super.onPause(); EventBus.getDefault().unregister(this); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onDestroyView() { super.onDestroyView(); if (mScrollView == null) { return; } ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { //noinspection deprecation if (UIUtils.hasJellyBean()) { vto.removeOnGlobalLayoutListener(mGlobalLayoutListener); } else { vto.removeGlobalOnLayoutListener(mGlobalLayoutListener); } } } @Override public void onDestroy() { OpenHelperManager.releaseHelper(); BackgroundExecutor.cancelAll("publicationDownload", true); super.onDestroy(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (mIsDownloading) { MenuItemCompat.setActionView(mForceDownloadAction, R.layout.ab_download_progress); } mMarkAsReadAction.setVisible(currentRecord != null && currentRecord.getNew()); } @AfterViews public void afterViews() { mScrollViewChild.setVisibility(View.INVISIBLE); pager.setAdapter(new PublicationImagesAdapter(getActivity())); pagerIndicators.setViewPager(pager); pagerIndicators.setSnap(true); setupCustomScrolling(); } @Background void loadData() { currentRecord = publicationsDao.getPublicationForId(mPublicationId); bindData(); } @Background(id = "publicationDownload") void downloadPublication(boolean forceDownload, boolean startActivity) { int errorMessage = -1; try { final Intent intent = ShareHelper.fetchPublication(getActivity(), currentRecord, prefs.downloadFolder().get(), forceDownload); markCurrentPublicationRead(); if (startActivity && getActivity() != null) { mIsDownloading = false; mHandler.post(new Runnable() { @Override public void run() { showPublicationState(PublicationState.READY_FOR_READING, true); } }); mHandler.postDelayed(new Runnable() { @Override public void run() { if (getActivity() != null) { getActivity().startActivity(intent); } } }, 1000); } } catch (SharePublicationException e) { if (getActivity() == null) { //Fragment is detached, we won't be abel to show anything return; } switch (e.getError()) { case COULD_NOT_PERSIST: errorMessage = R.string.publication_error_save; break; case STORAGE_NOT_ACCESSIBLE_FOR_PERSISTANCE: errorMessage = R.string.publication_error_storage; break; case ERROR_UNKOWN: errorMessage = R.string.publication_error_unknown; break; case COULD_NOT_LOAD: errorMessage = R.string.cannot_download_publication; break; case WRONG_PUBLICATION_URL: errorMessage = R.string.publication_error_url; break; default: break; } final String msg = getResources().getString(errorMessage); mHandler.post(new Runnable() { @Override public void run() { showPublicationState(PublicationState.WAITING_REFRESH, false); showCustomPositionedCrouton(msg, false); } }); } finally { mIsDownloading = false; if (getActivity() != null) { mHandler.post(new Runnable() { @Override public void run() { MenuItemCompat.setActionView(mForceDownloadAction, null); } }); } } } @UiThread(propagation = UiThread.Propagation.REUSE) void bindData() { String mTitleString = currentRecord.getName(); String subtitle = currentRecord.getCategory(); mTitle.setText(mTitleString); mSubtitle.setText(subtitle); mReadPubButton.setVisibility(View.VISIBLE); mPhotoViewContainer.setBackgroundColor(UIUtils.scaleColor(0xe8552c, 0.65f, false)); updatePlusOneButton(); if (currentRecord.getUpdatesIgnored()) { showEnableUpdatesBackCard(); } else { final MessageCardView messageCardView = (MessageCardView) mRootView .findViewById(R.id.message_card_view); if (messageCardView != null) { messageCardView.setVisibility(View.GONE); } } updateRating(); String imagesUrl = currentRecord.getImagePageUrl(); if (!TextUtils.isEmpty(imagesUrl) && prefs.displayPubImages().get() && //TODO remove Gingerbread check on next release UIUtils.hasGingerbreadMR1()) { //Do a network request to detect number of images loadImageList(imagesUrl); //Add images //Add image view to pager. } else { mHasPhoto = false; delayedScrollCheck = true; recomputePhotoAndScrollingMetrics(); } //Check if file is new version of pub is loaded. if (mIsDownloading) { showPublicationState(PublicationState.DOWNLOADING, false); } else { boolean isRefreshable = false; if (getActivity() != null) { isRefreshable = ShareHelper.shouldRefreshPublication(getActivity(), currentRecord, prefs.downloadFolder().get()); } if (isRefreshable) { showPublicationState(PublicationState.WAITING_REFRESH, false); } else { showPublicationState(PublicationState.READY_FOR_READING, false); } } String pubAbstract = SamlibPageHelper.stripDescriptionOfImages(currentRecord.getDescription()); if (!TextUtils.isEmpty(pubAbstract)) { UIUtils.setTextMaybeHtml(mAbstract, pubAbstract); mAbstract.setMovementMethod(LinkMovementMethod.getInstance()); mAbstract.setVisibility(View.VISIBLE); } else { mAbstract.setVisibility(View.GONE); } mHandler.post(new Runnable() { @Override public void run() { onScrollChanged(0, 0); // trigger scroll handling mScrollViewChild.setVisibility(View.VISIBLE); } }); } private void updateRating() { String ratingString = currentRecord.getRating(); if (!TextUtils.isEmpty(ratingString) && ratingString.split("\\*").length == 2) { float rating = Float.valueOf(ratingString.split("\\*")[0]); int ratingCount = Integer.valueOf(ratingString.split("\\*")[1]); mRatingContainer.setVisibility(View.VISIBLE); mPubRating.setText(String.valueOf(rating)); mPubRatingCount.setText(String.valueOf(ratingCount)); //Handle personal rating if (!TextUtils.isEmpty(currentRecord.getVoteCookie()) && currentRecord.getVoteDate() != null) { mRatingBar.setRating(currentRecord.getMyVote()); mVotedOnField.setVisibility(View.VISIBLE); String formattedVoteDate = DateUtils .getRelativeTimeSpanString(currentRecord.getVoteDate().getTime(), new Date().getTime(), DateUtils.MINUTE_IN_MILLIS) .toString(); mVotedOnField.setText(getString(R.string.publication_rating_vote_date, formattedVoteDate)); } else { mRatingBar.setRating(rating); mVotedOnField.setVisibility(View.GONE); } mRatingVisible = true; } else { mRatingContainer.setVisibility(View.GONE); mRatingVisible = false; } } private void updatePlusOneButton() { if (mPlusOneButton == null) { return; } if (currentRecord == null) { return; } if (!TextUtils.isEmpty(currentRecord.getUrl())) { mPlusOneButton.initialize(currentRecord.getUrl(), 0); mPlusOneButton.setVisibility(View.VISIBLE); } else { mPlusOneButton.setVisibility(View.GONE); } } @Background void loadImageList(String url) { try { final HttpRequest request = HttpRequest .get(SamlibPublicationPageReader.SAMLIB_URL_PREFIX + currentRecord.getImagePageUrl()); //Tolerate 10 days request.getConnection().addRequestProperty("Cache-Control", "max-stale=" + (60 * 60 * 24 * 10)); String data = request.body(); List<Pair<String, String>> results = new SamlibPublicationPageReader() .readPublicationImageUrlsAndDescriptions(data); addImagesToList(results); } catch (HttpRequest.HttpRequestException e) { AnalyticsHelper.getInstance().sendException("Could not load publication image list", e); addImagesToList(new ArrayList<Pair<String, String>>()); } } @Background void markCurrentPublicationRead() { try { publicationsDao.markPublicationRead(currentRecord); } catch (SQLException e) { AnalyticsHelper.getInstance().sendException(e); } } @Background void markPublicationAsIgnored(boolean ignored) { if (currentRecord != null) { try { currentRecord.setUpdatesIgnored(ignored); publicationsDao.update(currentRecord); bindData(); } catch (SQLException e) { AnalyticsHelper.getInstance().sendException(e); } } } @Background void saveVoteResult(String voteCookie, int rating) { try { currentRecord.setMyVote(rating); currentRecord.setVoteCookie(voteCookie); currentRecord.setVoteDate(new Date()); publicationsDao.update(currentRecord); bindData(); } catch (SQLException e) { AnalyticsHelper.getInstance().sendException(e); } } @UiThread(propagation = UiThread.Propagation.REUSE) void addImagesToList(List<Pair<String, String>> results) { if (results.size() == 0) { mHasPhoto = false; } else { mHasPhoto = true; if (results.size() == 1) { pagerIndicators.setVisibility(View.GONE); } else if (results.size() > 15) { results = results.subList(0, 15); } PublicationImagesAdapter adapter = (PublicationImagesAdapter) pager.getAdapter(); adapter.removeAllItems(); for (Pair<String, String> res : results) { adapter.addImage(res.first); } attemptToShowShowcaseForImageSettings(); } recomputePhotoAndScrollingMetrics(); } @UiThread(delay = 500) void attemptToShowShowcaseForImageSettings() { if (getActivity() != null) { Rect pointOnImage = new Rect(); mPhotoViewContainer.getHitRect(pointOnImage); int x = (int) (pointOnImage.left + pointOnImage.right / 1.3); int y = pointOnImage.top + pointOnImage.bottom / 2; ShowcaseView.Builder bldr = new ShowcaseView.Builder(getActivity()).setTarget(new PointTarget(x, y)) .setContentTitle(getString(R.string.showcase_pub_detail_image_title)) .setContentText(getString(R.string.showcase_pub_detail_image_detail)) .setStyle(R.style.ShowcaseView_Base_Overlayed); if (!BuildConfig.DEBUG) { bldr.singleShot(Constants.SHOWCASE_PUBLICATION_DETAIL_IMAGES_SHOT_ID); } bldr.build(); } } @UiThread(delay = 500) void attemptToShowShowcaseForRatings() { if (!rateShowcaseShown && mRatingVisible) { rateShowcaseShown = true; prefs.ratingShowcaseShotDone().put(true); ShowcaseView.Builder bldr = new ShowcaseView.Builder(getActivity()) .setTarget(new ViewTarget(mRatingBar)) .setContentTitle(getString(R.string.showcase_pub_detail_ratings_title)) .setContentText(getString(R.string.showcase_pub_detail_ratings_detail)) .setStyle(R.style.ShowcaseView_Base_Overlayed); if (!BuildConfig.DEBUG) { bldr.singleShot(Constants.SHOWCASE_PUBLICATION_DETAIL_RATING_SHOT_ID); } bldr.build(); } } @OptionsItem(R.id.action_mark_read) void menuMarkAsReadSelected() { markCurrentPublicationRead(); mHandler.post(new Runnable() { @Override public void run() { mMarkAsReadAction.setVisible(false); } }); } @OptionsItem(R.id.action_open_pub_in_browser) void menuOpenInBrowserSelected() { if (currentRecord != null) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(currentRecord.getUrl())); getActivity().startActivity(intent); } } @OptionsItem(R.id.action_force_download) void menuForceDownloadSelected() { if (currentRecord != null && !mIsDownloading) { mIsDownloading = true; //Change action view MenuItemCompat.setActionView(mForceDownloadAction, R.layout.ab_download_progress); //Start downloading in a background thread //Do force, no activity start downloadPublication(true, false); AnalyticsHelper.getInstance().sendEvent(Constants.GA_ADMIN_CATEGORY, Constants.GA_EVENT_PUB_MANUAL_REFRESH, Constants.GA_EVENT_AUTHOR_PUB_OPEN); } } @OptionsItem(R.id.action_ignore_updates) void ignoreUpdatesSelected() { AnalyticsHelper.getInstance().sendEvent(Constants.GA_ADMIN_CATEGORY, Constants.GA_EVENT_PUB_IGNORED, Constants.GA_EVENT_PUB_IGNORED); markPublicationAsIgnored(true); } @Click(R.id.read_pub_button) void downloadAndReadButtonClicked() { if (mIsDownloading) { return; } AnalyticsHelper.getInstance().sendEvent(Constants.GA_READ_CATEGORY, Constants.GA_EVENT_FAB_CLICK, Constants.GA_EVENT_AUTHOR_PUB_OPEN); if (!mDownloaded) { //Start download here mIsDownloading = true; showPublicationState(PublicationState.DOWNLOADING, true); downloadPublication(false, true); } else { //Open the pub right away downloadPublication(false, true); } } @Click(R.id.publication_rating_block) void voteForPubClicked() { if (currentRecord != null && getActivity() != null) { AnalyticsHelper.getInstance().sendView(Constants.GA_SCREEN_RATING_DIALOG); FragmentManager fm = this.getActivity().getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment prev = fm.findFragmentByTag(RatePublicationDialog.FRAGMENT_TAG); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); RatePublicationDialog dg; if (currentRecord.getVoteDate() != null && !TextUtils.isEmpty(currentRecord.getVoteCookie())) { dg = RatePublicationDialog_.builder().publicationUrl(currentRecord.getUrl()) .currentRating(currentRecord.getMyVote()).votingCookie(currentRecord.getVoteCookie()) .build(); } else { dg = RatePublicationDialog_.builder().publicationUrl(currentRecord.getUrl()).build(); } dg.show(ft, AboutDialog.FRAGMENT_TAG); } } public void onEventMainThread(RatingResultEvent result) { String msg; if (result.ratingSubmissionResult) { msg = getString(R.string.publication_rating_submit_success); saveVoteResult(result.voteCookie, result.ratingValue); } else { msg = getString(R.string.publication_rating_submit_error); } showCustomPositionedCrouton(msg, result.ratingSubmissionResult); } private void showEnableUpdatesBackCard() { final MessageCardView messageCardView = (MessageCardView) mRootView.findViewById(R.id.message_card_view); messageCardView.show(); messageCardView.setListener(new MessageCardView.OnMessageCardButtonClicked() { @Override public void onMessageCardButtonClicked(String tag) { if ("ENABLE_UPDATES_BACK".equals(tag)) { AnalyticsHelper.getInstance().sendEvent(Constants.GA_ADMIN_CATEGORY, Constants.GA_EVENT_ENABLE_UPDATES_BACK, Constants.GA_EVENT_ENABLE_UPDATES_BACK); markPublicationAsIgnored(false); } else { messageCardView.dismiss(true); } } }); } private void showCustomPositionedCrouton(String message, boolean success) { if (getActivity() == null) { return; } View view = getLayoutInflater(null).inflate(R.layout.crouton_custom_pos_textview, null); if (success) { view.findViewById(android.R.id.background).setBackgroundColor(Style.holoGreenLight); } else { view.findViewById(android.R.id.background).setBackgroundColor(Style.holoRedLight); } int topPadding = UIUtils.calculateActionBarSize(getActivity()); if (!mHasPhoto) { topPadding = (int) (newTop + mHeaderHeightPixels); } view.setPadding(view.getPaddingLeft(), topPadding, view.getPaddingRight(), view.getPaddingBottom()); TextView tv = (TextView) view.findViewById(android.R.id.text1); tv.setText(message); Crouton cr = Crouton.make(getActivity(), view); cr.setConfiguration(new Configuration.Builder().setDuration(Configuration.DURATION_LONG).build()); cr.show(); } private void showPublicationState(PublicationState state, boolean allowAnimate) { if (!isDetached()) { mDownloaded = state.equals(PublicationState.READY_FOR_READING); if (!mIsDownloading) { mReadPubButton.setChecked(mDownloaded, allowAnimate); } ImageView iconView = (ImageView) mReadPubButton.findViewById(R.id.read_pub_icon); setOrAnimateReadPubIcon(iconView, state, allowAnimate); } } private void setupCustomScrolling() { mScrollView.addCallbacks(this); ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { vto.addOnGlobalLayoutListener(mGlobalLayoutListener); } } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onScrollChanged(int deltaX, int deltaY) { final BaseActivity activity = (BaseActivity) getActivity(); if (activity == null) { return; } // Reposition the header bar -- it's normally anchored to the top of the content, // but locks to the top of the screen on scroll int scrollY = mScrollView.getScrollY(); if (!rateShowcaseShown && mRatingVisible && delayedScrollCheck) { //Check for bottom scroll //Showcase if bottom View view = mScrollView.getChildAt(mScrollView.getChildCount() - 1); int diff = (view.getBottom() - (mScrollView.getHeight() + mScrollView.getScrollY())); if (diff <= 0) { attemptToShowShowcaseForRatings(); } } newTop = Math.max(mPhotoHeightPixels, scrollY + mHeaderTopClearance); if (UIUtils.hasHoneycombMR1()) { mHeaderBox.setTranslationY(newTop); mReadPubButton.setTranslationY(newTop + mHeaderHeightPixels - mReadPubButtonHeightPixels / 2); mHeaderBackgroundBox.setPivotY(mHeaderHeightPixels); } else { ViewHelper.setTranslationY(mHeaderBox, newTop); //TODO get rid of this on next release with API 14 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) mReadPubButton.getLayoutParams(); if (mlp.topMargin != Math.round(newTop + mHeaderHeightPixels - mReadPubButtonHeightPixels / 2)) { mlp.topMargin = Math.round(newTop + mHeaderHeightPixels - mReadPubButtonHeightPixels / 2); mReadPubButton.setLayoutParams(mlp); } ViewHelper.setPivotY(mHeaderBackgroundBox, mHeaderHeightPixels); } int gapFillDistance = (int) (mHeaderTopClearance * GAP_FILL_DISTANCE_MULTIPLIER); boolean showGapFill = !mHasPhoto || (scrollY > (mPhotoHeightPixels - gapFillDistance)); float desiredHeaderScaleY = showGapFill ? ((mHeaderHeightPixels + gapFillDistance + 1) * 1f / mHeaderHeightPixels) : 1f; if (!mHasPhoto) { if (UIUtils.hasHoneycombMR1()) { mHeaderBackgroundBox.setScaleY(desiredHeaderScaleY); } else { ViewHelper.setScaleY(mHeaderBackgroundBox, desiredHeaderScaleY); } } else if (mGapFillShown != showGapFill) { if (UIUtils.hasICS()) { mHeaderBackgroundBox.animate().scaleY(desiredHeaderScaleY) .setInterpolator(new DecelerateInterpolator(2f)).setDuration(250).start(); } else { //TODO get rid of this on next release with API 14 animate(mHeaderBackgroundBox).scaleY(desiredHeaderScaleY) .setInterpolator(new DecelerateInterpolator(2f)).setDuration(250); } } mGapFillShown = showGapFill; mHeaderShadow.setVisibility(View.VISIBLE); if (mHeaderTopClearance != 0) { // Fill the gap between status bar and header bar with color float gapFillProgress = Math.min(Math.max(UIUtils.getProgress(scrollY, mPhotoHeightPixels - mHeaderTopClearance * 2, mPhotoHeightPixels - mHeaderTopClearance), 0), 1); if (UIUtils.hasHoneycombMR1()) { mHeaderShadow.setAlpha(gapFillProgress); } else { ViewHelper.setAlpha(mHeaderShadow, gapFillProgress); } } // Move background photo (parallax effect) if (UIUtils.hasHoneycombMR1()) { mPhotoViewContainer.setTranslationY(scrollY * 0.3f); } else { //TODO get rid of this on next release with API 14 ViewHelper.setTranslationY(mPhotoViewContainer, scrollY * 0.3f); } } private void recomputePhotoAndScrollingMetrics() { final int actionBarSize = UIUtils.calculateActionBarSize(getActivity()); mHeaderTopClearance = actionBarSize - mHeaderContentBox.getPaddingTop(); mHeaderHeightPixels = mHeaderContentBox.getHeight(); mPhotoHeightPixels = mHeaderTopClearance; if (mHasPhoto) { mPhotoHeightPixels = (int) (mPhotoViewContainer.getWidth() / PHOTO_ASPECT_RATIO); mPhotoHeightPixels = Math.min(mPhotoHeightPixels, mRootView.getHeight() * 2 / 3); } ViewGroup.LayoutParams lp; lp = mPhotoViewContainer.getLayoutParams(); if (lp.height != mPhotoHeightPixels) { lp.height = mPhotoHeightPixels; mPhotoViewContainer.setLayoutParams(lp); } lp = mHeaderBackgroundBox.getLayoutParams(); if (lp.height != mHeaderHeightPixels) { lp.height = mHeaderHeightPixels; mHeaderBackgroundBox.setLayoutParams(lp); } if (UIUtils.hasHoneycombMR1()) { ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) mDetailsContainer.getLayoutParams(); if (mlp.topMargin != mHeaderHeightPixels + mPhotoHeightPixels) { mlp.topMargin = mHeaderHeightPixels + mPhotoHeightPixels; mDetailsContainer.setLayoutParams(mlp); } } else { //Set padding instead int paddTop = mDetailsContainer.getPaddingTop(); if (paddTop != mHeaderHeightPixels + mPhotoHeightPixels + 16) { mDetailsContainer.setPadding(mDetailsContainer.getPaddingLeft(), mHeaderHeightPixels + mPhotoHeightPixels + 16, mDetailsContainer.getPaddingRight(), mDetailsContainer.getPaddingBottom()); } } onScrollChanged(0, 0); // trigger scroll handling } private void setOrAnimateReadPubIcon(final ImageView imageView, PublicationState currentState, boolean allowAnimate) { final int imageResId = currentState.equals(PublicationState.READY_FOR_READING) ? R.drawable.read_pub_button_icon_checked : currentState.equals(PublicationState.DOWNLOADING) ? R.drawable.download_pub_icon_fab_up : R.drawable.read_pub_button_icon_unchecked; if (imageView.getTag() != null) { if (imageView.getTag() instanceof Animator) { Animator anim = (Animator) imageView.getTag(); anim.end(); ViewHelper.setAlpha(imageView, 1f); } } /* if (imageView.getBackground() instanceof AnimationDrawable) { AnimationDrawable frameAnimation = (AnimationDrawable) imageView.getBackground(); frameAnimation.stop(); imageView.setBackgroundResource(0); }*/ if (allowAnimate && currentState.equals(PublicationState.DOWNLOADING)) { int duration = getResources().getInteger(android.R.integer.config_shortAnimTime); Animator outAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 0f); outAnimator.setDuration(duration); outAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageView.setImageDrawable(null); imageView.setBackgroundResource(imageResId); Drawable frameAnimation = imageView.getBackground(); if (frameAnimation instanceof AnimationDrawable) { ((AnimationDrawable) frameAnimation).start(); } } }); ObjectAnimator inAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1f); inAnimator.setDuration(duration * 2); final AnimatorSet set = new AnimatorSet(); set.playSequentially(outAnimator, inAnimator); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageView.setTag(null); } }); imageView.setTag(set); set.start(); } else if (allowAnimate && currentState.equals(PublicationState.READY_FOR_READING)) { int duration = getResources().getInteger(android.R.integer.config_shortAnimTime); Animator outAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 0f); outAnimator.setDuration(duration); outAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageView.setBackgroundResource(0); imageView.setImageResource(imageResId); } }); ObjectAnimator inAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1f); inAnimator.setDuration(duration * 2); final AnimatorSet set = new AnimatorSet(); set.playSequentially(outAnimator, inAnimator); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageView.setTag(null); } }); imageView.setTag(set); set.start(); } else if (!allowAnimate && currentState.equals(PublicationState.DOWNLOADING)) { mHandler.post(new Runnable() { @Override public void run() { imageView.setImageDrawable(null); imageView.setBackgroundResource(imageResId); AnimationDrawable frameAnimation = (AnimationDrawable) imageView.getBackground(); frameAnimation.start(); } }); } else { mHandler.post(new Runnable() { @Override public void run() { imageView.setBackgroundResource(0); imageView.setImageResource(imageResId); } }); } } enum PublicationState { WAITING_REFRESH, DOWNLOADING, READY_FOR_READING } class PublicationImagesAdapter extends PagerAdapter { Context context; List<ImageView> mImages; public PublicationImagesAdapter(Context context) { this.context = context; mImages = new ArrayList<ImageView>(); } public void addImage(String url) { final ImageView img = new ImageView(context); img.setScaleType(ImageView.ScaleType.CENTER_CROP); mImages.add(img); if (getActivity() != null) { Glide.with(getActivity()).load(url).placeholder(R.drawable.placeholder_img_art).centerCrop() .crossFade().listener(new RequestListener<String, GlideDrawable>() { @Override public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { // Trigger image transition mHandler.post(new Runnable() { @Override public void run() { recomputePhotoAndScrollingMetrics(); } }); return false; } }).into(img); } notifyDataSetChanged(); } public void removeAllItems() { mImages.clear(); notifyDataSetChanged(); } @Override public int getCount() { return mImages.size(); } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView v = mImages.get(position); container.addView(v); return v; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } } }