1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pump.fragment; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.os.Bundle; 22 import android.util.DisplayMetrics; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.ImageView; 27 import android.widget.TextView; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.UiThread; 32 import androidx.core.view.ViewCompat; 33 import androidx.fragment.app.Fragment; 34 import androidx.recyclerview.widget.GridLayoutManager; 35 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; 36 import androidx.recyclerview.widget.RecyclerView; 37 import androidx.recyclerview.widget.SimpleItemAnimator; 38 39 import com.android.pump.R; 40 import com.android.pump.activity.SeriesDetailsActivity; 41 import com.android.pump.db.MediaDb; 42 import com.android.pump.db.Series; 43 import com.android.pump.util.Globals; 44 45 import java.util.List; 46 47 @UiThread 48 public class SeriesFragment extends Fragment { 49 private RecyclerView mRecyclerView; 50 newInstance()51 public static @NonNull Fragment newInstance() { 52 return new SeriesFragment(); 53 } 54 55 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)56 public @NonNull View onCreateView(@NonNull LayoutInflater inflater, 57 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 58 View view = inflater.inflate(R.layout.fragment_series, container, false); 59 mRecyclerView = view.findViewById(R.id.fragment_series_recycler_view); 60 mRecyclerView.setHasFixedSize(true); 61 mRecyclerView.setAdapter(new SeriesAdapter(requireContext())); 62 mRecyclerView.addItemDecoration(new SpaceItemDecoration(4, 16)); 63 64 GridLayoutManager gridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); 65 gridLayoutManager.setSpanSizeLookup( 66 new HeaderSpanSizeLookup(gridLayoutManager.getSpanCount())); 67 68 ((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 69 70 // TODO(b/123707260) Enable view caching 71 //mRecyclerView.setItemViewCacheSize(0); 72 //mRecyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); 73 return view; 74 } 75 76 private static class SeriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 77 implements MediaDb.UpdateCallback { 78 private final MediaDb mMediaDb; 79 private final List<Series> mSeries; // TODO(b/123710968) Use android.support.v7.util.SortedList/android.support.v7.widget.util.SortedListAdapterCallback instead 80 SeriesAdapter(@onNull Context context)81 private SeriesAdapter(@NonNull Context context) { 82 // TODO setHasStableIds(true); 83 mMediaDb = Globals.getMediaDb(context); 84 mSeries = mMediaDb.getSeries(); 85 } 86 onAttachedToRecyclerView(@onNull RecyclerView recyclerView)87 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 88 mMediaDb.addSeriesUpdateCallback(this); 89 } 90 onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)91 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 92 mMediaDb.removeSeriesUpdateCallback(this); 93 } 94 95 @Override onCreateViewHolder( @onNull ViewGroup parent, int viewType)96 public @NonNull RecyclerView.ViewHolder onCreateViewHolder( 97 @NonNull ViewGroup parent, int viewType) { 98 if (viewType == R.layout.header) { 99 return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()) 100 .inflate(viewType, parent, false)) { }; 101 } else { 102 return new SeriesViewHolder(LayoutInflater.from(parent.getContext()) 103 .inflate(viewType, parent, false)); 104 } 105 } 106 107 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)108 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 109 if (position == 0) { 110 // TODO Handle header view 111 } else { 112 Series series = mSeries.get(position - 1); 113 mMediaDb.loadData(series); // TODO Where should we call this? In bind()? 114 ((SeriesViewHolder) holder).bind(series); 115 } 116 } 117 118 @Override getItemCount()119 public int getItemCount() { 120 return mSeries.size() + 1; 121 } 122 123 @Override getItemId(int position)124 public long getItemId(int position) { 125 return 0; // TODO return position == 0 ? -1 : mSeries.get(position - 1).getId(); 126 } 127 128 @Override getItemViewType(int position)129 public int getItemViewType(int position) { 130 return position == 0 ? R.layout.header : R.layout.series; 131 } 132 133 @Override onItemsInserted(int index, int count)134 public void onItemsInserted(int index, int count) { 135 notifyItemRangeInserted(index + 1, count); 136 } 137 138 @Override onItemsUpdated(int index, int count)139 public void onItemsUpdated(int index, int count) { 140 notifyItemRangeChanged(index + 1, count); 141 } 142 143 @Override onItemsRemoved(int index, int count)144 public void onItemsRemoved(int index, int count) { 145 notifyItemRangeRemoved(index + 1, count); 146 } 147 } 148 149 private static class SeriesViewHolder extends RecyclerView.ViewHolder { SeriesViewHolder(@onNull View itemView)150 private SeriesViewHolder(@NonNull View itemView) { 151 super(itemView); 152 } 153 bind(@onNull Series series)154 private void bind(@NonNull Series series) { 155 ImageView imageView = itemView.findViewById(R.id.series_image); 156 TextView textView = itemView.findViewById(R.id.series_text); 157 158 imageView.setImageURI(series.getPosterUri()); 159 textView.setText(series.getTitle()); 160 161 itemView.setOnClickListener((view) -> 162 SeriesDetailsActivity.start(view.getContext(), series)); 163 } 164 } 165 166 private static class SpaceItemDecoration extends RecyclerView.ItemDecoration { 167 private final int mXOffset; 168 private final int mYOffset; 169 SpaceItemDecoration(int xOffset, int yOffset)170 private SpaceItemDecoration(int xOffset, int yOffset) { 171 mXOffset = xOffset; 172 mYOffset = yOffset; 173 } 174 175 @Override getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)176 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 177 DisplayMetrics displayMetrics = new DisplayMetrics(); 178 ViewCompat.getDisplay(parent).getMetrics(displayMetrics); 179 outRect.left = outRect.right = (int) Math.ceil(mXOffset * displayMetrics.density); 180 if (parent.getChildAdapterPosition(view) > 0) { 181 outRect.bottom = (int) Math.ceil(mYOffset * displayMetrics.density); 182 } 183 } 184 } 185 186 private static class HeaderSpanSizeLookup extends SpanSizeLookup { 187 private final int mSpanCount; 188 HeaderSpanSizeLookup(int spanCount)189 private HeaderSpanSizeLookup(int spanCount) { 190 mSpanCount = spanCount; 191 } 192 193 @Override getSpanSize(int position)194 public int getSpanSize(int position) { 195 return position == 0 ? mSpanCount : 1; 196 } 197 198 @Override getSpanIndex(int position, int spanCount)199 public int getSpanIndex(int position, int spanCount) { 200 return position == 0 ? 0 : (position - 1) % spanCount; 201 } 202 203 @Override getSpanGroupIndex(int adapterPosition, int spanCount)204 public int getSpanGroupIndex(int adapterPosition, int spanCount) { 205 return adapterPosition == 0 ? 0 : ((adapterPosition - 1) / spanCount) + 1; 206 } 207 } 208 } 209