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