Java tutorial
/* * Copyright 2014 Google Inc. All rights reserved. * * 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 koma.movieapp.ui; import android.app.LoaderManager; import android.content.AsyncTaskLoader; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.v4.view.ViewCompat; import android.support.v7.widget.Toolbar; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.bumptech.glide.request.bitmap.RequestListener; import com.bumptech.glide.request.target.Target; import com.uwetrottmann.tmdb.Tmdb; import com.uwetrottmann.tmdb.entities.AppendToResponse; import com.uwetrottmann.tmdb.entities.Credits; import com.uwetrottmann.tmdb.entities.Genre; import com.uwetrottmann.tmdb.entities.Movie; import com.uwetrottmann.tmdb.enumerations.AppendToResponseItem; import com.uwetrottmann.tmdb.services.MoviesService; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import koma.movieapp.Config; import koma.movieapp.R; import koma.movieapp.ui.widget.CheckableFrameLayout; import koma.movieapp.ui.widget.ObservableScrollView; import koma.movieapp.util.ImageLoader; import koma.movieapp.util.LogUtils; import koma.movieapp.util.UIUtils; import static koma.movieapp.util.LogUtils.LOGD; import static koma.movieapp.util.LogUtils.LOGE; /** * An activity that shows detail information for a session, including session title, abstract, * time information, speaker photos and bios, etc. */ public class MovieDetailActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Movie>, ObservableScrollView.Callbacks { private static final String TAG = LogUtils.makeLogTag(MovieDetailActivity.class); private static final float PHOTO_ASPECT_RATIO = 1.7777777f; public static final String TRANSITION_NAME_PHOTO = "photo"; private Handler mHandler = new Handler(); private static final int TIME_HINT_UPDATE_INTERVAL = 10000; // 10 sec private int mMovieId; private Bundle mArguments; private String mTitleString; private String mHashTag; private String mUrl; private boolean mStarred; private View mScrollViewChild; private TextView mTitle; private TextView mMovieRating; private TextView mMovieRuntime; private ObservableScrollView mScrollView; private CheckableFrameLayout mAddScheduleButton; private TextView mMovieOverview; private ViewGroup mMovieGenresContainer; private LinearLayout mMovieGenres; private LinearLayout mMovieDirectors; private LinearLayout mMovieWriters; private LinearLayout mMovieProducers; private LinearLayout mMovieCast; private View mHeaderBox; private View mDetailsContainer; private boolean mSessionCursor = false; private boolean mSpeakersCursor = false; private boolean mHasSummaryContent = false; private ImageLoader mBackdropImageLoader, mNoPlaceholderImageLoader; private List<Runnable> mDeferredUiOperations = new ArrayList<Runnable>(); private StringBuilder mBuffer = new StringBuilder(); private int mPhotoHeightPixels; private int mHeaderHeightPixels; private int mAddScheduleButtonHeightPixels; private boolean mHasPhoto; private View mPhotoViewContainer; private ImageView mPhotoView; private int mMovieColor; private Runnable mTimeHintUpdaterRunnable = null; // this set stores the session IDs for which the user has dismissed the // "give feedback" card. This information is kept for the duration of the app's execution // so that if they say "No, thanks", we don't show the card again for that session while // the app is still executing. private static HashSet<String> sDismissedFeedbackCard = new HashSet<String>(); private TextView mSubmitFeedbackView; private float mMaxHeaderElevation; private float mFABElevation; private int mTagColorDotSize; @Override protected void onCreate(Bundle savedInstanceState) { //UIUtils.tryTranslateHttpIntent(this); boolean shouldBeFloatingWindow = shouldBeFloatingWindow(); if (shouldBeFloatingWindow) { setupFloatingWindow(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_movie_detail); final Toolbar toolbar = getActionBarToolbar(); toolbar.setNavigationIcon(shouldBeFloatingWindow ? R.drawable.ic_ab_close : R.drawable.ic_up); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { finishAfterTransition(); } }); mHandler.post(new Runnable() { @Override public void run() { toolbar.setTitle(""); } }); if (savedInstanceState == null) { Uri sessionUri = getIntent().getData(); } mArguments = getIntent().getExtras(); // // if (extras == null) { // return; // } // // mMovieId = Integer.parseInt(extras.getString("movieId")); mFABElevation = getResources().getDimensionPixelSize(R.dimen.fab_elevation); mMaxHeaderElevation = getResources().getDimensionPixelSize(R.dimen.session_detail_max_header_elevation); mTagColorDotSize = getResources().getDimensionPixelSize(R.dimen.tag_color_dot_size); mHandler = new Handler(); if (mBackdropImageLoader == null) { mBackdropImageLoader = new ImageLoader(this, R.drawable.person_image_empty); } if (mNoPlaceholderImageLoader == null) { mNoPlaceholderImageLoader = new ImageLoader(this); } mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); mScrollView.addCallbacks(this); ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { vto.addOnGlobalLayoutListener(mGlobalLayoutListener); } mScrollViewChild = findViewById(R.id.scroll_view_child); mScrollViewChild.setVisibility(View.INVISIBLE); mDetailsContainer = findViewById(R.id.details_container); mHeaderBox = findViewById(R.id.header_movie); mTitle = (TextView) findViewById(R.id.movie_title); mMovieRating = (TextView) findViewById(R.id.movie_rating); mMovieRuntime = (TextView) findViewById(R.id.movie_runtime); mPhotoViewContainer = findViewById(R.id.movie_photo_container); mPhotoView = (ImageView) findViewById(R.id.movie_backdrop); mMovieOverview = (TextView) findViewById(R.id.movie_overview); mMovieDirectors = (LinearLayout) findViewById(R.id.movie_directors_container); //mMovieWriters = (LinearLayout) findViewById(R.id.movie_writers_container); //mMovieProducers = (LinearLayout) findViewById(R.id.movie_producers_container); mMovieCast = (LinearLayout) findViewById(R.id.movie_cast_container); mMovieGenres = (LinearLayout) findViewById(R.id.movie_genre); mMovieGenresContainer = (ViewGroup) findViewById(R.id.movie_genres_container); mAddScheduleButton = (CheckableFrameLayout) findViewById(R.id.add_schedule_button); //ViewCompat.setTransitionName(mPhotoView, TRANSITION_NAME_PHOTO); LoaderManager manager = getLoaderManager(); manager.initLoader(0, mArguments, this); } @Override public Intent getParentActivityIntent() { // TODO(mangini): make this Activity navigate up to the right screen depending on how it was launched return new Intent(this, PopularMoviesActivity.class); } private void setupFloatingWindow() { // configure this Activity as a floating window, dimming the background WindowManager.LayoutParams params = getWindow().getAttributes(); params.width = getResources().getDimensionPixelSize(R.dimen.session_details_floating_width); params.height = getResources().getDimensionPixelSize(R.dimen.session_details_floating_height); params.alpha = 1; params.dimAmount = 0.4f; params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; getWindow().setAttributes(params); } private boolean shouldBeFloatingWindow() { Resources.Theme theme = getTheme(); TypedValue floatingWindowFlag = new TypedValue(); if (theme == null || !theme.resolveAttribute(R.attr.isFloatingWindow, floatingWindowFlag, true)) { // isFloatingWindow flag is not defined in theme return false; } return (floatingWindowFlag.data != 0); } private void recomputePhotoAndScrollingMetrics() { mHeaderHeightPixels = mHeaderBox.getHeight(); mPhotoHeightPixels = 0; if (mHasPhoto) { mPhotoHeightPixels = (int) (mPhotoView.getWidth() / PHOTO_ASPECT_RATIO); mPhotoHeightPixels = Math.min(mPhotoHeightPixels, mScrollView.getHeight() * 2 / 3); } ViewGroup.LayoutParams lp; lp = mPhotoViewContainer.getLayoutParams(); if (lp.height != mPhotoHeightPixels) { lp.height = mPhotoHeightPixels; mPhotoViewContainer.setLayoutParams(lp); } ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) mDetailsContainer.getLayoutParams(); if (mlp.topMargin != mHeaderHeightPixels + mPhotoHeightPixels) { mlp.topMargin = mHeaderHeightPixels + mPhotoHeightPixels; mDetailsContainer.setLayoutParams(mlp); } onScrollChanged(0, 0); // trigger scroll handling } @Override protected void onDestroy() { super.onDestroy(); if (mScrollView == null) { return; } ViewTreeObserver vto = mScrollView.getViewTreeObserver(); if (vto.isAlive()) { vto.removeGlobalOnLayoutListener(mGlobalLayoutListener); } } private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mAddScheduleButtonHeightPixels = mAddScheduleButton.getHeight(); recomputePhotoAndScrollingMetrics(); } }; @Override public void onScrollChanged(int deltaX, int deltaY) { // 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(); float newTop = Math.max(mPhotoHeightPixels, scrollY); mHeaderBox.setTranslationY(newTop); mAddScheduleButton.setTranslationY(newTop + mHeaderHeightPixels - mAddScheduleButtonHeightPixels / 2); float gapFillProgress = 1; if (mPhotoHeightPixels != 0) { gapFillProgress = Math.min(Math.max(UIUtils.getProgress(scrollY, 0, mPhotoHeightPixels), 0), 1); } ViewCompat.setElevation(mHeaderBox, gapFillProgress * mMaxHeaderElevation); ViewCompat.setElevation(mAddScheduleButton, gapFillProgress * mMaxHeaderElevation + mFABElevation); // Move background photo (parallax effect) mPhotoViewContainer.setTranslationY(scrollY * 0.5f); } @Override public void onResume() { super.onResume(); //updatePlusOneButton(); // if (mTimeHintUpdaterRunnable != null) { // mHandler.postDelayed(mTimeHintUpdaterRunnable, TIME_HINT_UPDATE_INTERVAL); // } // Refresh whether or not feedback has been submitted getLoaderManager().restartLoader(0, mArguments, this); } @Override public void onStop() { super.onStop(); } @Override public void onPause() { super.onPause(); if (mTimeHintUpdaterRunnable != null) { mHandler.removeCallbacks(mTimeHintUpdaterRunnable); } } // private void showStarredDeferred(final boolean starred, final boolean allowAnimate) { // mDeferredUiOperations.add(new Runnable() { // @Override // public void run() { // showStarred(starred, allowAnimate); // } // }); // //tryExecuteDeferredUiOperations(); // } private void showStarred(boolean starred, boolean allowAnimate) { mStarred = starred; mAddScheduleButton.setChecked(mStarred, allowAnimate); ImageView iconView = (ImageView) mAddScheduleButton.findViewById(R.id.add_schedule_icon); getLUtils().setOrAnimatePlusCheckIcon(iconView, starred, allowAnimate); mAddScheduleButton.setContentDescription( getString(starred ? R.string.remove_from_schedule_desc : R.string.add_to_schedule_desc)); } // private void updateEmptyView() { // findViewById(android.R.id.empty).setVisibility( // (mSpeakersCursor && mSessionCursor && !mHasSummaryContent) // ? View.VISIBLE // : View.GONE); // } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); //getMenuInflater().inflate(R.menu.session_detail, menu); // mShareMenuItem = menu.findItem(R.id.menu_share); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { default: break; } return false; } @Override public Loader<Movie> onCreateLoader(int id, Bundle data) { int movieId = Integer.parseInt(data.getString("movieId")); return new MovieLoader(this, movieId); } @Override public void onLoadFinished(Loader<Movie> loader, Movie movie) { mTitleString = movie.title; mMovieColor = getResources().getColor(R.color.default_movie_color); mHeaderBox.setBackgroundColor(mMovieColor); getLUtils().setStatusBarColor(UIUtils.scaleColor(mMovieColor, 0.8f, false)); final String movieRating = movie.vote_average.toString(); final String movieRuntime = movie.runtime.toString(); final String movieBackdropPath = movie.backdrop_path; final String movieOverview = movie.overview; /* Title */ final int titleLength = mTitleString.length(); final int textDimension; if (titleLength < Config.DETAILS_SHORT_TITLE) { textDimension = R.dimen.text_title_size_xxlarge; } else if (titleLength < Config.DETAILS_MEDIUM_TITLE) { textDimension = R.dimen.text_title_size_xlarge; } else if (titleLength < Config.DETAILS_LONG_TITLE) { textDimension = R.dimen.text_title_size_large; } else { textDimension = R.dimen.text_title_size_medium; } mTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(textDimension)); LOGD(TAG, "Title size: " + mTitle.getTextSize()); mTitle.setText(mTitleString); /* Rating */ if (!movieRating.isEmpty()) { mMovieRating.setVisibility(View.VISIBLE); mMovieRating.setText(movieRating + "/10"); } /* Runtime */ if (!movieRuntime.isEmpty()) { mMovieRuntime.setVisibility(View.VISIBLE); mMovieRuntime.setText(movieRuntime + " " + getResources().getString(R.string.minutes)); } /* Overview */ if (!movieOverview.isEmpty()) { mMovieOverview.setVisibility(View.VISIBLE); mMovieOverview.setText(movieOverview); } /* Backdrop */ final String baseBackdropUrl = Config.TMDB_IMAGE_BASE_URL + Config.TMDB_IMAGE_SIZE; final String backdropUrl = baseBackdropUrl + movieBackdropPath; if (movieBackdropPath != null) { mHasPhoto = true; mNoPlaceholderImageLoader.loadImage(backdropUrl, mPhotoView, new RequestListener<String>() { @Override public void onException(Exception e, String url, Target target) { mHasPhoto = false; recomputePhotoAndScrollingMetrics(); } @Override public void onImageReady(String url, Target target, boolean b, boolean b2) { // Trigger image transition recomputePhotoAndScrollingMetrics(); } }); recomputePhotoAndScrollingMetrics(); } else { mHasPhoto = false; recomputePhotoAndScrollingMetrics(); } /* Crew */ /* Director */ Credits creditsList = movie.credits; if (creditsList != null) { List<Credits.CrewMember> crewList = creditsList.crew; List<Credits.CastMember> castList = creditsList.cast; if (!crewList.isEmpty()) { ArrayList<Credits.CrewMember> directorList = new ArrayList<>(); //ArrayList<Credits.CrewMember> writerList = new ArrayList<>(); //ArrayList<Credits.CrewMember> producerList = new ArrayList<>(); for (Credits.CrewMember crewMember : crewList) { if (crewMember.department.equals("Directing")) { directorList.add(crewMember); } // if (crewMember.department.equals("Writing")) { // writerList.add(crewMember); // } // if (crewMember.department.equals("Production")) { // producerList.add(crewMember); // } } if (!directorList.isEmpty()) { mMovieDirectors.setVisibility(View.VISIBLE); TextView directorHeader = (TextView) findViewById(R.id.movie_director_header); directorHeader.setVisibility(View.VISIBLE); mMovieDirectors.removeAllViews(); LayoutInflater inflater = LayoutInflater.from(this); for (Credits.CrewMember director : directorList) { TextView chipView = (TextView) inflater.inflate(R.layout.include_cast_crew_member, mMovieDirectors, false); chipView.setText(director.name); mMovieDirectors.addView(chipView); } } // if (!writerList.isEmpty()) { // mMovieWriters.setVisibility(View.VISIBLE); // LayoutInflater inflater = LayoutInflater.from(this); // // for (Credits.CrewMember writer : writerList) { // TextView chipView = (TextView) inflater.inflate( // R.layout.include_cast_crew_member, mMovieWriters, false); // chipView.setText(writer.name); // mMovieWriters.addView(chipView); // } // } // if (!producerList.isEmpty()) { // mMovieProducers.setVisibility(View.VISIBLE); // LayoutInflater inflater = LayoutInflater.from(this); // // for (Credits.CrewMember producer : producerList) { // TextView chipView = (TextView) inflater.inflate( // R.layout.include_cast_crew_member, mMovieProducers, false); // chipView.setText(producer.name); // mMovieProducers.addView(chipView); // } // } } if (!castList.isEmpty()) { mMovieCast.setVisibility(View.VISIBLE); mMovieCast.removeAllViews(); TextView castHeader = (TextView) findViewById(R.id.movie_cast_header); castHeader.setVisibility(View.VISIBLE); LayoutInflater inflater = LayoutInflater.from(this); for (Credits.CastMember castMember : castList) { if (castMember.order < 5) { TextView chipView = (TextView) inflater.inflate(R.layout.include_cast_crew_member, mMovieCast, false); chipView.setText(castMember.name); mMovieCast.addView(chipView); } } } } /* Genres */ List<Genre> genresList = movie.genres; if (!genresList.isEmpty()) { mMovieGenresContainer.setVisibility(View.VISIBLE); mMovieGenres.removeAllViews(); LayoutInflater inflater = LayoutInflater.from(this); for (Genre genre : genresList) { TextView chipView = (TextView) inflater.inflate(R.layout.include_session_tag_chip, mMovieGenres, false); chipView.setText(genre.name); mMovieGenres.addView(chipView); } } //updateEmptyView(); mAddScheduleButton.setVisibility(View.VISIBLE); mHandler.post(new Runnable() { @Override public void run() { onScrollChanged(0, 0); // trigger scroll handling mScrollViewChild.setVisibility(View.VISIBLE); //mMovieOverview.setTextIsSelectable(true); } }); // mTimeHintUpdaterRunnable = new Runnable() { // @Override // public void run() { // updateTimeBasedUi(); // mHandler.postDelayed(mTimeHintUpdaterRunnable, TIME_HINT_UPDATE_INTERVAL); // } // }; //mHandler.postDelayed(mTimeHintUpdaterRunnable, TIME_HINT_UPDATE_INTERVAL); } @Override public void onLoaderReset(Loader<Movie> loader) { } private static class MovieLoader extends AsyncTaskLoader<Movie> { Movie mMovie; Tmdb tmdb; int mMovieId; public MovieLoader(Context context, int movieId) { super(context); mMovieId = movieId; tmdb = new Tmdb(); tmdb.setApiKey(Config.TMDB_API_KEY); } /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */ @Override public Movie loadInBackground() { MoviesService moviesService = null; Movie movie = null; try { moviesService = tmdb.moviesService(); movie = moviesService.summary(mMovieId, null, new AppendToResponse(AppendToResponseItem.CREDITS)); } catch (Exception e) { LOGE(TAG, "Network error"); } // Done! return movie; } /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */ @Override public void deliverResult(Movie movie) { if (isReset()) { // An async query came in while the loader is stopped. We // don't need the result. if (movie != null) { onReleaseResources(movie); } } Movie oldMovie = mMovie; mMovie = movie; if (isStarted()) { // If the Loader is currently started, we can immediately // deliver its results. super.deliverResult(movie); } // At this point we can release the resources associated with // 'oldMovies' if needed; now that the new result is delivered we // know that it is no longer in use. if (oldMovie != null) { onReleaseResources(oldMovie); } } /** * Handles a request to start the Loader. */ @Override protected void onStartLoading() { if (mMovie != null) { // If we currently have a result available, deliver it // immediately. deliverResult(mMovie); } if (takeContentChanged() || mMovie == null) { // If the data has changed since the last time it was loaded // or is not currently available, start a load. forceLoad(); } } /** * Handles a request to stop the Loader. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to cancel a load. */ @Override public void onCanceled(Movie movie) { super.onCanceled(movie); // At this point we can release the resources associated with 'movie' // if needed. onReleaseResources(movie); } /** * Handles a request to completely reset the Loader. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'movies' // if needed. if (mMovie != null) { onReleaseResources(mMovie); mMovie = null; } } /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */ protected void onReleaseResources(Movie movie) { // For a simple ArrayList<> there is nothing to do. For something // like a Cursor, we would close it here. } } }