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