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.RecyclerView; 36 37 import com.android.pump.R; 38 import com.android.pump.activity.GenreDetailsActivity; 39 import com.android.pump.db.Genre; 40 import com.android.pump.db.MediaDb; 41 import com.android.pump.util.Globals; 42 43 import java.util.List; 44 45 @UiThread 46 public class GenreFragment extends Fragment { 47 private RecyclerView mRecyclerView; 48 newInstance()49 public static @NonNull Fragment newInstance() { 50 return new GenreFragment(); 51 } 52 53 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)54 public @NonNull View onCreateView(@NonNull LayoutInflater inflater, 55 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 56 View view = inflater.inflate(R.layout.fragment_genre, container, false); 57 mRecyclerView = view.findViewById(R.id.fragment_genre_recycler_view); 58 mRecyclerView.setHasFixedSize(true); 59 mRecyclerView.setAdapter(new GenreAdapter(requireContext())); 60 mRecyclerView.addItemDecoration(new SpaceItemDecoration(4, 16)); 61 62 GridLayoutManager gridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); 63 gridLayoutManager.setSpanSizeLookup( 64 new HeaderSpanSizeLookup(gridLayoutManager.getSpanCount())); 65 66 // TODO(b/123707260) Enable view caching 67 //mRecyclerView.setItemViewCacheSize(0); 68 //mRecyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); 69 return view; 70 } 71 72 private static class GenreAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 73 implements MediaDb.UpdateCallback { 74 private final MediaDb mMediaDb; 75 private final List<Genre> mGenres; // TODO(b/123710968) Use android.support.v7.util.SortedList/android.support.v7.widget.util.SortedListAdapterCallback instead 76 GenreAdapter(@onNull Context context)77 private GenreAdapter(@NonNull Context context) { 78 setHasStableIds(true); 79 mMediaDb = Globals.getMediaDb(context); 80 mGenres = mMediaDb.getGenres(); 81 } 82 onAttachedToRecyclerView(@onNull RecyclerView recyclerView)83 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 84 mMediaDb.addGenreUpdateCallback(this); 85 } 86 onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)87 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 88 mMediaDb.removeGenreUpdateCallback(this); 89 } 90 91 @Override onCreateViewHolder( @onNull ViewGroup parent, int viewType)92 public @NonNull RecyclerView.ViewHolder onCreateViewHolder( 93 @NonNull ViewGroup parent, int viewType) { 94 if (viewType == R.layout.header) { 95 return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()) 96 .inflate(viewType, parent, false)) { }; 97 } else { 98 return new GenreViewHolder(LayoutInflater.from(parent.getContext()) 99 .inflate(viewType, parent, false)); 100 } 101 } 102 103 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)104 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 105 if (position == 0) { 106 // TODO Handle header view 107 } else { 108 Genre genre = mGenres.get(position - 1); 109 mMediaDb.loadData(genre); // TODO Where should we call this? In bind()? 110 ((GenreViewHolder) holder).bind(genre); 111 } 112 } 113 114 @Override getItemCount()115 public int getItemCount() { 116 return mGenres.size() + 1; 117 } 118 119 @Override getItemId(int position)120 public long getItemId(int position) { 121 return position == 0 ? -1 : mGenres.get(position - 1).getId(); 122 } 123 124 @Override getItemViewType(int position)125 public int getItemViewType(int position) { 126 return position == 0 ? R.layout.header : R.layout.genre; 127 } 128 129 @Override onItemsInserted(int index, int count)130 public void onItemsInserted(int index, int count) { 131 notifyItemRangeInserted(index + 1, count); 132 } 133 134 @Override onItemsUpdated(int index, int count)135 public void onItemsUpdated(int index, int count) { 136 notifyItemRangeChanged(index + 1, count); 137 } 138 139 @Override onItemsRemoved(int index, int count)140 public void onItemsRemoved(int index, int count) { 141 notifyItemRangeRemoved(index + 1, count); 142 } 143 } 144 145 private static class GenreViewHolder extends RecyclerView.ViewHolder { GenreViewHolder(@onNull View itemView)146 private GenreViewHolder(@NonNull View itemView) { 147 super(itemView); 148 } 149 bind(@onNull Genre genre)150 private void bind(@NonNull Genre genre) { 151 ImageView imageView = itemView.findViewById(R.id.genre_image); 152 TextView textView = itemView.findViewById(R.id.genre_text); 153 154 // TODO imageView.setImageURI(xxx); 155 textView.setText(genre.getName()); 156 157 itemView.setOnClickListener((view) -> 158 GenreDetailsActivity.start(view.getContext(), genre)); 159 } 160 } 161 162 private static class SpaceItemDecoration extends RecyclerView.ItemDecoration { 163 private final int mXOffset; 164 private final int mYOffset; 165 SpaceItemDecoration(int xOffset, int yOffset)166 private SpaceItemDecoration(int xOffset, int yOffset) { 167 mXOffset = xOffset; 168 mYOffset = yOffset; 169 } 170 171 @Override getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)172 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 173 DisplayMetrics displayMetrics = new DisplayMetrics(); 174 ViewCompat.getDisplay(parent).getMetrics(displayMetrics); 175 outRect.left = outRect.right = (int) Math.ceil(mXOffset * displayMetrics.density); 176 if (parent.getChildAdapterPosition(view) > 0) { 177 outRect.bottom = (int) Math.ceil(mYOffset * displayMetrics.density); 178 } 179 } 180 } 181 182 private static class HeaderSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { 183 private final int mSpanCount; 184 HeaderSpanSizeLookup(int spanCount)185 private HeaderSpanSizeLookup(int spanCount) { 186 mSpanCount = spanCount; 187 } 188 189 @Override getSpanSize(int position)190 public int getSpanSize(int position) { 191 return position == 0 ? mSpanCount : 1; 192 } 193 194 @Override getSpanIndex(int position, int spanCount)195 public int getSpanIndex(int position, int spanCount) { 196 return position == 0 ? 0 : (position - 1) % spanCount; 197 } 198 199 @Override getSpanGroupIndex(int adapterPosition, int spanCount)200 public int getSpanGroupIndex(int adapterPosition, int spanCount) { 201 return adapterPosition == 0 ? 0 : ((adapterPosition - 1) / spanCount) + 1; 202 } 203 } 204 } 205