package fr.eyal.datalib.sample.netflix.fragment; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Calendar; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Looper; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.GridView; import android.widget.RelativeLayout; import android.widget.TextView; import fr.eyal.datalib.sample.netflix.R; import fr.eyal.datalib.sample.netflix.data.model.movieimage.MovieImage; import fr.eyal.datalib.sample.netflix.data.service.NetflixService; import fr.eyal.datalib.sample.netflix.fragment.adapter.NetflixListAdapter; import fr.eyal.datalib.sample.netflix.fragment.model.MovieItem; import fr.eyal.datalib.sample.netflix.fragment.model.MovieItemResponse; import fr.eyal.lib.data.model.ResponseBusinessObject; import fr.eyal.lib.data.processor.Processor; import fr.eyal.lib.data.service.ServiceHelper; import fr.eyal.lib.data.service.model.BusinessResponse; import fr.eyal.lib.data.service.model.ComplexOptions; import fr.eyal.lib.util.Out; public abstract class NetflixListFragment extends NetflixFragment implements OnScrollListener { RelativeLayout mRootView; GridView mGridView; View mEmptyView; NetflixListAdapter mAdapter; SparseArray<MovieItem> mPendingItem; ArrayList<MovieItem> mPendingItemCache; int mScrollState; private static Object sharedLock = new Object(); //the shared lock float mItemHeight; @Override public void onCreate(Bundle savedInstanceState) { mAdapter = new NetflixListAdapter(this); mPendingItem = new SparseArray<MovieItem>(); mPendingItemCache = new ArrayList<MovieItem>(); super.onCreate(savedInstanceState); setRetainInstance(true); mItemHeight = getResources().getDimension(R.dimen.item_height_small); try { int requestId = callDataCache(null, null); if(requestId == ServiceHelper.BAD_REQUEST_ID) requestId = callDataNetwork(null, null); if(requestId == ServiceHelper.BAD_REQUEST_ID) mRequestIds.add(requestId); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // if(container != null) // mGridView = (GridView) inflater.inflate(R.layout.fgmt_new, null, false); // else // mGridView = (GridView) inflater.inflate(R.layout.fgmt_new, container); // View emptyView = inflater.inflate(R.layout.empty_grid, null); // getActivity().addContentView(emptyView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // mGridView.setEmptyView(emptyView); if(container != null) mRootView = (RelativeLayout) inflater.inflate(R.layout.fgmt_new, null, false); else mRootView = (RelativeLayout) inflater.inflate(R.layout.fgmt_new, container); mEmptyView = mRootView.findViewById(android.R.id.empty); mGridView = (GridView) mRootView.findViewById(R.id.gridview); mGridView.setEmptyView(mEmptyView); // if(mEmptyView instanceof TextView){ // ((TextView)mEmptyView).setText(getResources().getString(R.string.loading)); // } mGridView.setAdapter(mAdapter); mGridView.setOnScrollListener(this); return mRootView; } /** * Ask to display the movie's poster asynchronously * * @param item the item concerned by the display */ public void loadMoviePoster(MovieItem item){ synchronized (mPendingItemCache) { if(mPendingItemCache.contains(item)){ Out.e("", "UPDATE " + "No item " + item.getLabel(-1)); return; } try { synchronized (sharedLock) { Out.e("", "UPDATE " + "Request Cache"); launchMovieImageCacheRequest(item, 1, true); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } /** * Launch a cache request for movie image * * @param item the {@link MovieItem} object associated to the request * @param inSampleSize bitmap option the sample size * @param inJustDecodeBounds ask to just decode bounds of the bitmap * * @throws UnsupportedEncodingException */ public void launchMovieImageCacheRequest(MovieItem item, int inSampleSize, boolean inJustDecodeBounds) throws UnsupportedEncodingException { ComplexOptions options = new ComplexOptions(); BitmapFactory.Options bmpOption = new BitmapFactory.Options(); bmpOption.inSampleSize = inSampleSize; bmpOption.inJustDecodeBounds = inJustDecodeBounds; options.putBitmapOptions(bmpOption); Out.w("", "ITEM "+item); int requestId = callImageCache(item.getImageUrl(), options, null); mRequestIds.add(requestId); mPendingItem.append(requestId, item); mPendingItemCache.add(item); } /* * OnDataListener management */ @Override public void onCacheRequestFinished(int requestId, ResponseBusinessObject response) { mRequestIds.remove(Integer.valueOf(requestId)); mDataManager.removeOnDataListener(requestId, this); if(response instanceof MovieItemResponse){ MovieItemResponse movie = (MovieItemResponse) response; if (Looper.getMainLooper().getThread() == Thread.currentThread()){ updateMovie(movie, false); Out.d("TEST", "UI THREADDDDD!!!!!!!!!!!!! Cache"); } else { getActivity().runOnUiThread(new UpdateMovie((MovieItemResponse) movie, false)); Out.d("TEST", "NO UI THREAD :-( Cache"); } //we update the page content //we compute the update time Calendar updateTime = Calendar.getInstance(); updateTime.setTimeInMillis(movie.getUpdatedAt().getTimeInMillis()); updateTime.add(Calendar.MINUTE, movie.getTtl()); //we update the content if the ttl is consumed if(updateTime.compareTo(Calendar.getInstance()) <= 0) { try { callDataNetwork(null, null); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } else if(response instanceof MovieImage){ MovieImage movieImage = (MovieImage) response; MovieItem item = getItemAndTreatPendings(requestId); //if we didn't receive the image's soft reference if(movieImage.image == null && item != null){ BitmapFactory.Options options = movieImage.lastOptions; if(options != null && options.outHeight != 0){ //we calculate the sample size int sampleSize = (int) (options.outHeight/mItemHeight); Out.d("", "SAMPLE SIZE"+sampleSize); //then we ask for the image content including the sample size try { launchMovieImageCacheRequest(item, sampleSize, false); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return; } } //if the cache object does not contains the good information if(movieImage.image == null || movieImage.image.get() == null){ //if the list is scrolling we don't ask for a network request if(mScrollState == OnScrollListener.SCROLL_STATE_FLING){ String title = (item != null) ? item.getLabel(-1) : ""; Out.e("", "UPDATE " + "Scrolling so stop " + title); return; } //we ask for a network request try { if(item != null){ int id = callImageNetwork(item.getImageUrl(), null, null); mRequestIds.add(id); mPendingItem.append(id, item); } else { } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } else { //we ask to update the ImageView updateMovieImage(item, movieImage); } } } private MovieItem getItemAndTreatPendings(int requestId) { MovieItem item = null; synchronized (sharedLock) { item = mPendingItem.get(requestId); mPendingItem.remove(requestId); if(item != null){ mPendingItemCache.remove(item); } } return item; } @Override public void onDataFromDatabase(int code, ArrayList<?> data) { } @Override public void onRequestFinished(int requestId, boolean suceed, BusinessResponse response) { mRequestIds.remove(Integer.valueOf(requestId)); mDataManager.removeOnDataListener(requestId, this); if(!suceed){ if(response.statusMessage.equals(Processor.PARSING_ERROR_MESSAGE)){ if (Looper.getMainLooper().getThread() == Thread.currentThread()){ updateErrorMessage(getString(R.string.parsing_error)); } else { getActivity().runOnUiThread(new UpdateMovieError(getString(R.string.parsing_error))); } } return; } switch (response.webserviceType) { case NetflixService.WEBSERVICE_MOVIEIMAGE: MovieItem item = getItemAndTreatPendings(requestId); updateMovieImage(item, (MovieImage) response.response); break; default: if(response.response instanceof MovieItemResponse) if (Looper.getMainLooper().getThread() == Thread.currentThread()){ updateMovie((MovieItemResponse)response.response, true); Out.d("TEST", "UI THREADDDDD!!!!!!!!!!!!! Network"); } else { getActivity().runOnUiThread(new UpdateMovie((MovieItemResponse) response.response, true)); Out.d("TEST", "NO UI THREAD :-( Network"); } break; } } /* * Content update */ /** * Update the label with an error message * * @param string */ private void updateErrorMessage(String string) { ((TextView)mEmptyView).setText(getResources().getString(R.string.parsing_error)); } /** * Update the movies list * * @param response the {@link MovieItemResponse} content */ @SuppressWarnings("unchecked") private void updateMovie(MovieItemResponse response, boolean updateLabel) { ArrayList<MovieItem> items = (ArrayList<MovieItem>) response.getItems(); if(updateLabel && (items == null || (items.size() == 0 && mEmptyView instanceof TextView))){ ((TextView)mEmptyView).setText(getResources().getString(R.string.search_error)); } else { mAdapter.setData((ArrayList<MovieItem>) response.getItems()); mAdapter.notifyDataSetChanged(); } } /** * Update the movie image * * @param requestId the request id to find the corresponding movie item * @param response the {@link MovieImage} received * * @return the {@linMovieItem00} item concerned by the updating or null if it is not found */ private MovieItem updateMovieImage(MovieItem item, MovieImage response) { //we update the object if(item == null || response == null) return null; item.setImage(response); //we update the current displayed list mAdapter.updatePoster(item); return item; } private class UpdateMovieError implements Runnable{ String mMessage; public UpdateMovieError(String message){ mMessage = message; } @Override public void run() { updateErrorMessage(mMessage); } } private class UpdateMovie implements Runnable{ MovieItemResponse mResponse; boolean mUpdateLabel; public UpdateMovie(MovieItemResponse response, boolean updateLabel){ mResponse = response; mUpdateLabel = updateLabel; } @Override public void run() { updateMovie(mResponse, mUpdateLabel); } } /* * Scroll management */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //if the list finish a fling if(mScrollState == OnScrollListener.SCROLL_STATE_FLING && scrollState != mScrollState){ mScrollState = scrollState; int first = mGridView.getFirstVisiblePosition(); int size = mGridView.getChildCount(); for (int i = 0; i < size; i++) { MovieItem item = (MovieItem) mAdapter.getItem(first+i); if(item != null){ View v = mGridView.getChildAt(i); if(v != null){ NetflixListAdapter.ItemViewHolder holder = (NetflixListAdapter.ItemViewHolder) v.getTag(); if(holder != null){ boolean isImageSet = mAdapter.setImageFromItemOrCache(holder, item); if(!isImageSet) loadMoviePoster(item); } } } } } mScrollState = scrollState; } /** * Function called when the data of the page are loaded from the cache * * @return the requestId generated by the request */ protected abstract int callDataCache(ComplexOptions complexOptionsCache, ComplexOptions complexOptionsNetwork) throws UnsupportedEncodingException; /** * Function called when the data of the page are loaded from the network * * @return the requestId generated by the request */ protected abstract int callDataNetwork(ComplexOptions complexOptionsCache, ComplexOptions complexOptionsNetwork) throws UnsupportedEncodingException; /** * Function called when an image of the page is loaded from the cache * * @return the requestId generated by the request */ protected abstract int callImageCache(String url, ComplexOptions complexOptionsCache, ComplexOptions complexOptionsNetwork) throws UnsupportedEncodingException; /** * Function called when an image of the page is loaded from the network * * @return the requestId generated by the request */ protected abstract int callImageNetwork(String url, ComplexOptions complexOptionsCache, ComplexOptions complexOptionsNetwork) throws UnsupportedEncodingException; }