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.AlbumDetailsActivity; 39 import com.android.pump.db.Album; 40 import com.android.pump.db.Artist; 41 import com.android.pump.db.MediaDb; 42 import com.android.pump.util.Globals; 43 44 import java.util.List; 45 46 @UiThread 47 public class AlbumFragment extends Fragment { 48 private RecyclerView mRecyclerView; 49 newInstance()50 public static @NonNull Fragment newInstance() { 51 return new AlbumFragment(); 52 } 53 54 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)55 public @NonNull View onCreateView(@NonNull LayoutInflater inflater, 56 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 57 View view = inflater.inflate(R.layout.fragment_album, container, false); 58 mRecyclerView = view.findViewById(R.id.fragment_album_recycler_view); 59 mRecyclerView.setHasFixedSize(true); 60 mRecyclerView.setAdapter(new AlbumAdapter(requireContext())); 61 mRecyclerView.addItemDecoration(new SpaceItemDecoration(4, 16)); 62 63 GridLayoutManager gridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); 64 gridLayoutManager.setSpanSizeLookup( 65 new HeaderSpanSizeLookup(gridLayoutManager.getSpanCount())); 66 67 // TODO(b/123707260) Enable view caching 68 //mRecyclerView.setItemViewCacheSize(0); 69 //mRecyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); 70 return view; 71 } 72 73 private static class AlbumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 74 implements MediaDb.UpdateCallback { 75 private final MediaDb mMediaDb; 76 private final List<Album> mAlbums; // TODO(b/123710968) Use android.support.v7.util.SortedList/android.support.v7.widget.util.SortedListAdapterCallback instead 77 AlbumAdapter(@onNull Context context)78 private AlbumAdapter(@NonNull Context context) { 79 setHasStableIds(true); 80 mMediaDb = Globals.getMediaDb(context); 81 mAlbums = mMediaDb.getAlbums(); 82 } 83 onAttachedToRecyclerView(@onNull RecyclerView recyclerView)84 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 85 mMediaDb.addAlbumUpdateCallback(this); 86 } 87 onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)88 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 89 mMediaDb.removeAlbumUpdateCallback(this); 90 } 91 92 @Override onCreateViewHolder( @onNull ViewGroup parent, int viewType)93 public @NonNull RecyclerView.ViewHolder onCreateViewHolder( 94 @NonNull ViewGroup parent, int viewType) { 95 if (viewType == R.layout.header) { 96 return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()) 97 .inflate(viewType, parent, false)) { }; 98 } else { 99 return new AlbumViewHolder(LayoutInflater.from(parent.getContext()) 100 .inflate(viewType, parent, false)); 101 } 102 } 103 104 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)105 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 106 if (position == 0) { 107 // TODO Handle header view 108 } else { 109 Album album = mAlbums.get(position - 1); 110 mMediaDb.loadData(album); // TODO Where should we call this? In bind()? 111 ((AlbumViewHolder) holder).bind(album); 112 } 113 } 114 115 @Override getItemCount()116 public int getItemCount() { 117 return mAlbums.size() + 1; 118 } 119 120 @Override getItemId(int position)121 public long getItemId(int position) { 122 return position == 0 ? -1 : mAlbums.get(position - 1).getId(); 123 } 124 125 @Override getItemViewType(int position)126 public int getItemViewType(int position) { 127 return position == 0 ? R.layout.header : R.layout.album; 128 } 129 130 @Override onItemsInserted(int index, int count)131 public void onItemsInserted(int index, int count) { 132 notifyItemRangeInserted(index + 1, count); 133 } 134 135 @Override onItemsUpdated(int index, int count)136 public void onItemsUpdated(int index, int count) { 137 notifyItemRangeChanged(index + 1, count); 138 } 139 140 @Override onItemsRemoved(int index, int count)141 public void onItemsRemoved(int index, int count) { 142 notifyItemRangeRemoved(index + 1, count); 143 } 144 } 145 146 private static class AlbumViewHolder extends RecyclerView.ViewHolder { AlbumViewHolder(@onNull View itemView)147 private AlbumViewHolder(@NonNull View itemView) { 148 super(itemView); 149 } 150 bind(@onNull Album album)151 private void bind(@NonNull Album album) { 152 ImageView imageView = itemView.findViewById(R.id.album_image); 153 TextView titleView = itemView.findViewById(R.id.album_title); 154 TextView artistView = itemView.findViewById(R.id.album_artist); 155 156 imageView.setImageURI(album.getAlbumArtUri()); 157 titleView.setText(album.getTitle()); 158 Artist artist = album.getArtist(); 159 artistView.setText(artist == null ? null : artist.getName()); 160 161 itemView.setOnClickListener((view) -> 162 AlbumDetailsActivity.start(view.getContext(), album)); 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 GridLayoutManager.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