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.net.Uri; 22 import android.os.Bundle; 23 import android.util.DisplayMetrics; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.ImageView; 28 import android.widget.TextView; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.UiThread; 33 import androidx.core.view.ViewCompat; 34 import androidx.fragment.app.Fragment; 35 import androidx.recyclerview.widget.GridLayoutManager; 36 import androidx.recyclerview.widget.RecyclerView; 37 38 import com.android.pump.R; 39 import com.android.pump.activity.ArtistDetailsActivity; 40 import com.android.pump.db.Album; 41 import com.android.pump.db.Artist; 42 import com.android.pump.db.MediaDb; 43 import com.android.pump.util.Globals; 44 45 import java.util.List; 46 47 @UiThread 48 public class ArtistFragment extends Fragment { 49 private RecyclerView mRecyclerView; 50 newInstance()51 public static @NonNull Fragment newInstance() { 52 return new ArtistFragment(); 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_artist, container, false); 59 mRecyclerView = view.findViewById(R.id.fragment_artist_recycler_view); 60 mRecyclerView.setHasFixedSize(true); 61 mRecyclerView.setAdapter(new ArtistAdapter(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 // TODO(b/123707260) Enable view caching 69 //mRecyclerView.setItemViewCacheSize(0); 70 //mRecyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); 71 return view; 72 } 73 74 private static class ArtistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 75 implements MediaDb.UpdateCallback { 76 private final MediaDb mMediaDb; 77 private final List<Artist> mArtists; // TODO(b/123710968) Use android.support.v7.util.SortedList/android.support.v7.widget.util.SortedListAdapterCallback instead 78 ArtistAdapter(@onNull Context context)79 private ArtistAdapter(@NonNull Context context) { 80 setHasStableIds(true); 81 mMediaDb = Globals.getMediaDb(context); 82 mArtists = mMediaDb.getArtists(); 83 } 84 onAttachedToRecyclerView(@onNull RecyclerView recyclerView)85 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 86 mMediaDb.addArtistUpdateCallback(this); 87 } 88 onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)89 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 90 mMediaDb.removeArtistUpdateCallback(this); 91 } 92 93 @Override onCreateViewHolder( @onNull ViewGroup parent, int viewType)94 public @NonNull RecyclerView.ViewHolder onCreateViewHolder( 95 @NonNull ViewGroup parent, int viewType) { 96 if (viewType == R.layout.header) { 97 return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()) 98 .inflate(viewType, parent, false)) { }; 99 } else { 100 return new ArtistViewHolder(LayoutInflater.from(parent.getContext()) 101 .inflate(viewType, parent, false)); 102 } 103 } 104 105 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)106 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 107 if (position == 0) { 108 // TODO Handle header view 109 } else { 110 Artist artist = mArtists.get(position - 1); 111 mMediaDb.loadData(artist); // TODO Where should we call this? In bind()? 112 ((ArtistViewHolder) holder).bind(artist); 113 } 114 } 115 116 @Override getItemCount()117 public int getItemCount() { 118 return mArtists.size() + 1; 119 } 120 121 @Override getItemId(int position)122 public long getItemId(int position) { 123 return position == 0 ? -1 : mArtists.get(position - 1).getId(); 124 } 125 126 @Override getItemViewType(int position)127 public int getItemViewType(int position) { 128 return position == 0 ? R.layout.header : R.layout.artist; 129 } 130 131 @Override onItemsInserted(int index, int count)132 public void onItemsInserted(int index, int count) { 133 notifyItemRangeInserted(index + 1, count); 134 } 135 136 @Override onItemsUpdated(int index, int count)137 public void onItemsUpdated(int index, int count) { 138 notifyItemRangeChanged(index + 1, count); 139 } 140 141 @Override onItemsRemoved(int index, int count)142 public void onItemsRemoved(int index, int count) { 143 notifyItemRangeRemoved(index + 1, count); 144 } 145 } 146 147 private static class ArtistViewHolder extends RecyclerView.ViewHolder { ArtistViewHolder(@onNull View itemView)148 private ArtistViewHolder(@NonNull View itemView) { 149 super(itemView); 150 } 151 bind(@onNull Artist artist)152 private void bind(@NonNull Artist artist) { 153 ImageView imageView = itemView.findViewById(R.id.artist_image); 154 TextView nameView = itemView.findViewById(R.id.artist_name); 155 156 Uri artUri = null; 157 if (artist.getHeadshotUri() == null) { 158 // Fallback to album art 159 List<Album> albums = artist.getAlbums(); 160 for (Album album : albums) { 161 if (album.getAlbumArtUri() != null) { 162 artUri = album.getAlbumArtUri(); 163 break; 164 } 165 } 166 } else { 167 artUri = artist.getHeadshotUri(); 168 } 169 imageView.setImageURI(artUri); 170 nameView.setText(artist.getName()); 171 172 itemView.setOnClickListener((view) -> 173 ArtistDetailsActivity.start(view.getContext(), artist)); 174 } 175 } 176 177 private static class SpaceItemDecoration extends RecyclerView.ItemDecoration { 178 private final int mXOffset; 179 private final int mYOffset; 180 SpaceItemDecoration(int xOffset, int yOffset)181 private SpaceItemDecoration(int xOffset, int yOffset) { 182 mXOffset = xOffset; 183 mYOffset = yOffset; 184 } 185 186 @Override getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)187 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 188 DisplayMetrics displayMetrics = new DisplayMetrics(); 189 ViewCompat.getDisplay(parent).getMetrics(displayMetrics); 190 outRect.left = outRect.right = (int) Math.ceil(mXOffset * displayMetrics.density); 191 if (parent.getChildAdapterPosition(view) > 0) { 192 outRect.bottom = (int) Math.ceil(mYOffset * displayMetrics.density); 193 } 194 } 195 } 196 197 private static class HeaderSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { 198 private final int mSpanCount; 199 HeaderSpanSizeLookup(int spanCount)200 private HeaderSpanSizeLookup(int spanCount) { 201 mSpanCount = spanCount; 202 } 203 204 @Override getSpanSize(int position)205 public int getSpanSize(int position) { 206 return position == 0 ? mSpanCount : 1; 207 } 208 209 @Override getSpanIndex(int position, int spanCount)210 public int getSpanIndex(int position, int spanCount) { 211 return position == 0 ? 0 : (position - 1) % spanCount; 212 } 213 214 @Override getSpanGroupIndex(int adapterPosition, int spanCount)215 public int getSpanGroupIndex(int adapterPosition, int spanCount) { 216 return adapterPosition == 0 ? 0 : ((adapterPosition - 1) / spanCount) + 1; 217 } 218 } 219 } 220