blob: 2485ad96a2339a6cd0735e29e303a613380effdf [file] [log] [blame]
Steve McKayef16f5f2015-12-22 18:15:31 -08001/*
2 * Copyright (C) 2015 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
17package com.android.documentsui.dirlist;
18
19import static com.android.internal.util.Preconditions.checkArgument;
20
Steve McKay76be6202016-01-12 11:14:33 -080021import android.content.Context;
22import android.database.Cursor;
Steve McKayef16f5f2015-12-22 18:15:31 -080023import android.support.v7.widget.GridLayoutManager;
24import android.support.v7.widget.RecyclerView.AdapterDataObserver;
25import android.util.SparseArray;
26import android.view.ViewGroup;
Steve McKay76be6202016-01-12 11:14:33 -080027import android.widget.Space;
28
29import com.android.documentsui.R;
30import com.android.documentsui.State;
Steve McKayef16f5f2015-12-22 18:15:31 -080031
32import java.util.List;
33
34/**
35 * Adapter wrapper that inserts a sort of line break item between directories and regular files.
36 * Only needs to be used in GRID mode...at this time.
37 */
38final class SectionBreakDocumentsAdapterWrapper extends DocumentsAdapter {
39
40 private static final String TAG = "SectionBreakDocumentsAdapterWrapper";
41 private static final int ITEM_TYPE_SECTION_BREAK = Integer.MAX_VALUE;
42
43 private final Environment mEnv;
44 private final DocumentsAdapter mDelegate;
45
46 private int mBreakPosition = -1;
47
48 SectionBreakDocumentsAdapterWrapper(Environment environment, DocumentsAdapter delegate) {
49 mEnv = environment;
50 mDelegate = delegate;
51
Steve McKay955e46d2016-01-05 12:53:35 -080052 // Relay events published by our delegate to our listeners (presumably RecyclerView)
53 // with adjusted positions.
54 mDelegate.registerAdapterDataObserver(new EventRelay());
Steve McKayef16f5f2015-12-22 18:15:31 -080055 }
56
57 public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
58 return new GridLayoutManager.SpanSizeLookup() {
59 @Override
60 public int getSpanSize(int position) {
61 // Make layout whitespace span the grid. This has the effect of breaking
62 // grid rows whenever layout whitespace is encountered.
63 if (getItemViewType(position) == ITEM_TYPE_SECTION_BREAK) {
64 return mEnv.getColumnCount();
65 } else {
66 return 1;
67 }
68 }
69 };
70 }
71
72 @Override
73 public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
74 if (viewType == ITEM_TYPE_SECTION_BREAK) {
75 return new EmptyDocumentHolder(mEnv.getContext());
76 } else {
77 return mDelegate.createViewHolder(parent, viewType);
78 }
79 }
80
81 @Override
82 public void onBindViewHolder(DocumentHolder holder, int p, List<Object> payload) {
83 if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
84 mDelegate.onBindViewHolder(holder, toDelegatePosition(p), payload);
Ben Kwa90bf9f52016-01-08 13:31:11 -080085 } else {
86 ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
Steve McKayef16f5f2015-12-22 18:15:31 -080087 }
88 }
89
90 @Override
91 public void onBindViewHolder(DocumentHolder holder, int p) {
92 if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
93 mDelegate.onBindViewHolder(holder, toDelegatePosition(p));
Ben Kwa90bf9f52016-01-08 13:31:11 -080094 } else {
95 ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
Steve McKayef16f5f2015-12-22 18:15:31 -080096 }
97 }
98
99 @Override
100 public int getItemCount() {
101 return mBreakPosition == -1
102 ? mDelegate.getItemCount()
103 : mDelegate.getItemCount() + 1;
104 }
105
106 @Override
107 public void onModelUpdate(Model model) {
108 mDelegate.onModelUpdate(model);
109 mBreakPosition = -1;
110
111 // Walk down the list of IDs till we encounter something that's not a directory, and
112 // insert a whitespace element - this introduces a visual break in the grid between
113 // folders and documents.
114 // TODO: This code makes assumptions about the model, namely, that it performs a
115 // bucketed sort where directories will always be ordered before other files. CBB.
116 List<String> modelIds = mDelegate.getModelIds();
117 for (int i = 0; i < modelIds.size(); i++) {
118 if (!isDirectory(model, i)) {
Ben Kwaf5ce1032016-01-08 08:41:34 -0800119 // If the break is the first thing in the list, then there are actually no
120 // directories. In that case, don't insert a break at all.
121 if (i > 0) {
122 mBreakPosition = i;
123 }
Steve McKayef16f5f2015-12-22 18:15:31 -0800124 break;
125 }
126 }
127 }
128
129 @Override
130 public void onModelUpdateFailed(Exception e) {
131 mDelegate.onModelUpdateFailed(e);
132 }
133
134 @Override
135 public int getItemViewType(int p) {
136 if (p == mBreakPosition) {
137 return ITEM_TYPE_SECTION_BREAK;
138 } else {
139 return mDelegate.getItemViewType(toDelegatePosition(p));
140 }
141 }
142
143 /**
144 * Returns the position of an item in the delegate, adjusting
145 * values that are greater than the break position.
146 *
147 * @param p Position within the view
148 * @return Position within the delegate
149 */
150 private int toDelegatePosition(int p) {
151 return (mBreakPosition != -1 && p > mBreakPosition) ? p - 1 : p;
152 }
153
154 /**
155 * Returns the position of an item in the view, adjusting
156 * values that are greater than the break position.
157 *
158 * @param p Position within the delegate
159 * @return Position within the view
160 */
161 private int toViewPosition(int p) {
162 // If position is greater than or equal to the break, increase by one.
163 return (mBreakPosition != -1 && p >= mBreakPosition) ? p + 1 : p;
164 }
165
166 @Override
167 public SparseArray<String> hide(String... ids) {
168 // NOTE: We hear about these changes and adjust break position
169 // in our AdapterDataObserver.
170 return mDelegate.hide(ids);
171 }
172
173 @Override
174 void unhide(SparseArray<String> ids) {
175 // NOTE: We hear about these changes and adjust break position
176 // in our AdapterDataObserver.
177 mDelegate.unhide(ids);
178 }
179
180 @Override
181 List<String> getModelIds() {
182 return mDelegate.getModelIds();
183 }
184
185 @Override
186 String getModelId(int p) {
187 return (p == mBreakPosition) ? null : mDelegate.getModelId(toDelegatePosition(p));
188 }
189
190 @Override
Steve McKay955e46d2016-01-05 12:53:35 -0800191 public void onItemSelectionChanged(String id) {
192 mDelegate.onItemSelectionChanged(id);
193 }
194
195 // Listener we add to our delegate. This allows us to relay events published
196 // by the delegate to our listeners (presumably RecyclerView) with adjusted positions.
197 private final class EventRelay extends AdapterDataObserver {
198 public void onChanged() {
199 throw new UnsupportedOperationException();
200 }
201
202 public void onItemRangeChanged(int positionStart, int itemCount) {
203 throw new UnsupportedOperationException();
204 }
205
206 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
207 checkArgument(itemCount == 1);
208 notifyItemRangeChanged(toViewPosition(positionStart), itemCount, payload);
209 }
210
211 public void onItemRangeInserted(int positionStart, int itemCount) {
212 checkArgument(itemCount == 1);
213 if (positionStart < mBreakPosition) {
214 mBreakPosition++;
215 }
216 notifyItemRangeInserted(toViewPosition(positionStart), itemCount);
217 }
218
219 public void onItemRangeRemoved(int positionStart, int itemCount) {
220 checkArgument(itemCount == 1);
221 if (positionStart < mBreakPosition) {
222 mBreakPosition--;
223 }
224 notifyItemRangeRemoved(toViewPosition(positionStart), itemCount);
225 }
226
227 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
228 throw new UnsupportedOperationException();
229 }
Steve McKayef16f5f2015-12-22 18:15:31 -0800230 }
Steve McKay76be6202016-01-12 11:14:33 -0800231
232 /**
233 * The most elegant transparent blank box that spans N rows ever conceived.
234 */
235 private static final class EmptyDocumentHolder extends DocumentHolder {
236 final int mVisibleHeight;
237
238 public EmptyDocumentHolder(Context context) {
239 super(context, new Space(context));
240
241 // Per UX spec, this puts a bigger gap between the folders and documents in the grid.
242 mVisibleHeight = context.getResources().getDimensionPixelSize(
243 R.dimen.grid_item_margin);
244 }
245
246 public void bind(State state) {
247 bind(null, null, state);
248 }
249
250 @Override
251 public void bind(Cursor cursor, String modelId, State state) {
252 if (state.derivedMode == State.MODE_GRID) {
253 itemView.setMinimumHeight(mVisibleHeight);
254 } else {
255 itemView.setMinimumHeight(0);
256 }
257 return;
258 }
259 }
Steve McKayef16f5f2015-12-22 18:15:31 -0800260}