/*
* Copyright 2015 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 com.google.samples.apps.ourstreets.fragment;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.ViewCompat;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v7.widget.RecyclerView;
import android.transition.Explode;
import android.transition.Fade;
import android.transition.TransitionSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.samples.apps.ourstreets.R;
import com.google.samples.apps.ourstreets.data.DataView;
import com.google.samples.apps.ourstreets.data.GalleryPresenter;
import com.google.samples.apps.ourstreets.model.Gallery;
import com.google.samples.apps.ourstreets.transition.Elevation;
import com.google.samples.apps.ourstreets.view.GalleryAdapter;
import com.google.samples.apps.ourstreets.view.GalleryDivider;
import com.google.samples.apps.ourstreets.view.GalleryViewHolder;
import com.google.samples.apps.ourstreets.view.RecyclerItemClickListener;
import com.google.samples.apps.ourstreets.view.ViewUtils;
import java.util.List;
/**
* A {@link Fragment} that displays an overview of all available StreetView galleries.
*/
public class GalleryFragment extends Fragment implements DataView<Gallery> {
public static final String TAG = "GalleryFragment";
private static final FastOutLinearInInterpolator INTERPOLATOR =
new FastOutLinearInInterpolator();
private List<Gallery> mGalleries;
private RecyclerView.Adapter mAdapter;
private RecyclerView mRecyclerView;
private View mEmptyView;
private View mGalleryContent;
private boolean mAnimateViewSwap;
public GalleryFragment() {
final Fade fade = new Fade();
fade.addTarget(R.id.appbar);
Explode explode = new Explode();
explode.excludeTarget(R.id.appbar, true);
Elevation elevation = new Elevation();
elevation.addTarget(R.id.gallery_card);
elevation.setStartDelay(250); // arbitrarily chosen delay
TransitionSet exit = new TransitionSet();
exit.addTransition(fade);
exit.addTransition(explode);
exit.addTransition(elevation);
setExitTransition(exit);
}
public static GalleryFragment newInstance() {
return new GalleryFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewUtils.setStatusBarColor(getActivity(), R.color.background);
return inflater.inflate(R.layout.fragment_gallery, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
ViewCompat.requestApplyInsets(view);
mEmptyView = view.findViewById(R.id.progress);
mGalleryContent = view.findViewById(R.id.gallery_content);
initRecyclerView(view);
mAnimateViewSwap = savedInstanceState == null && mGalleries == null;
if (mGalleries == null) {
new GalleryPresenter(this).getData();
} else {
showData(mGalleries);
}
}
@Override
public void onDestroyView() {
mRecyclerView = null;
mEmptyView = null;
mGalleryContent = null;
super.onDestroyView();
}
@Override
public void showData(List<Gallery> galleries) {
mGalleries = galleries;
initRecyclerViewAdapter();
showContent(mAnimateViewSwap);
}
@Override
public void showError() {
mGalleryContent.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
//noinspection ConstantConditions
View failedContainer = getView().findViewById(R.id.failedStub);
if (failedContainer == null) {
getView().findViewById(R.id.failed).setVisibility(View.VISIBLE);
// Loading already failed before. Everything else is already inflated and set up.
return;
}
setupAndInflate((ViewStub) failedContainer);
}
private void initRecyclerView(View contentView) {
mRecyclerView = (RecyclerView) contentView.findViewById(R.id.galleries);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(getActivity()) {
@Override
public void onItemClick(RecyclerView.ViewHolder holder, int position) {
showDetailFragment((GalleryViewHolder) holder,
mGalleries.get(position));
}
});
final int itemMargin = getResources().getDimensionPixelSize(R.dimen.grid_single);
mRecyclerView.addItemDecoration(new GalleryDivider(itemMargin));
}
private void initRecyclerViewAdapter() {
if (mRecyclerView == null || mGalleries == null) {
Log.d(TAG, "initRecyclerViewAdapter: Missing data, not initializing.");
return;
}
if (mAdapter == null) {
mAdapter = new GalleryAdapter(mGalleries, getContext());
}
mRecyclerView.setAdapter(mAdapter);
}
private void showContent(boolean animate) {
if (!isAdded()) {
Log.d(TAG, "showData: Not showing data");
return;
}
if (animate) {
safelyAnimateProgressToContent();
} else {
swapEmptyWithContentView(false);
}
}
private void showDetailFragment(@NonNull final GalleryViewHolder holder,
@NonNull final Gallery gallery) {
// Turn of transition grouping for clicked item view to break card structure.
((ViewGroup) holder.itemView).setTransitionGroup(false);
holder.mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
CameraPosition cameraPosition = googleMap.getCameraPosition();
performDetailTransition(holder, gallery, cameraPosition);
}
});
}
private void performDetailTransition(@NonNull GalleryViewHolder itemHolder,
@NonNull Gallery gallery,
CameraPosition cameraPosition) {
DetailFragment fragment = getDetailFragment(gallery, cameraPosition);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
@SuppressLint("CommitTransaction")
FragmentTransaction transaction = fragmentManager.beginTransaction()
.replace(android.R.id.content, fragment, DetailFragment.TAG);
createAndAddTransitionParticipants(itemHolder, transaction);
transaction.addToBackStack(DetailFragment.TAG).commit();
}
@NonNull
private DetailFragment getDetailFragment(Gallery gallery, CameraPosition cameraPosition) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
DetailFragment detailFragment = (DetailFragment)
fragmentManager.findFragmentByTag(DetailFragment.TAG);
if (detailFragment == null) {
detailFragment = DetailFragment.newInstance(getActivity(), gallery, cameraPosition);
} else {
detailFragment.setNewDetailInformation(gallery, cameraPosition);
}
return detailFragment;
}
private void createAndAddTransitionParticipants(@NonNull GalleryViewHolder itemHolder,
@NonNull FragmentTransaction transaction) {
transaction.addSharedElement(itemHolder.descriptionContainer,
getString(R.string.transition_description));
transaction.addSharedElement(itemHolder.mapView,
getString(R.string.transition_map));
}
/**
* Add animation safety by making sure that the fragment currently is attached.
*/
private void safelyAnimateProgressToContent() {
if (mGalleryContent.isAttachedToWindow()) {
animateProgressToContent();
} else {
mGalleryContent.addOnAttachStateChangeListener(new View
.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
v.removeOnAttachStateChangeListener(this);
animateProgressToContent();
}
@Override
public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
}
});
}
}
/**
* Perform the animation from loading progress to the actual fragment's content.
*/
private void animateProgressToContent() {
AnimatorSet animatorSet = new AnimatorSet();
FrameLayout targetView = ((FrameLayout) getView());
//noinspection ConstantConditions
animatorSet.play(createCircularReveal(targetView)).with(createColorChange(targetView));
animatorSet.start();
}
@NonNull
private Animator createCircularReveal(@NonNull View targetView) {
Animator circularReveal = ViewUtils.createCircularReveal(targetView,
R.id.progress, INTERPOLATOR);
circularReveal.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
swapEmptyWithContentView(true);
}
});
return circularReveal;
}
@NonNull
private ObjectAnimator createColorChange(@NonNull FrameLayout targetView) {
return ViewUtils.createColorChange(targetView,
R.color.blue_grey_600, android.R.color.transparent, INTERPOLATOR);
}
/**
* Replace the empty view with the gallery view.
*
* @param animate <code>true</code> if the swap should be animated, else <code>false</code>.
*/
private void swapEmptyWithContentView(boolean animate) {
if (animate) {
mEmptyView.animate()
.alpha(0f)
.setInterpolator(INTERPOLATOR)
.withEndAction(new Runnable() {
@Override
public void run() {
mEmptyView.setVisibility(View.GONE);
}
});
mGalleryContent.setAlpha(0f);
mGalleryContent.setVisibility(View.VISIBLE);
mGalleryContent.animate()
.alpha(1f)
.setInterpolator(INTERPOLATOR)
.start();
} else {
mEmptyView.setVisibility(View.GONE);
mGalleryContent.setVisibility(View.VISIBLE);
}
}
private void setupAndInflate(ViewStub failedStub) {
failedStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, final View inflated) {
inflated.findViewById(R.id.try_again).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
new GalleryPresenter(GalleryFragment.this).getData();
mEmptyView.setVisibility(View.VISIBLE);
inflated.setVisibility(View.GONE);
}
});
}
});
failedStub.inflate();
}
}