|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko.home; |
|
7 |
|
8 import org.mozilla.gecko.R; |
|
9 import org.mozilla.gecko.db.BrowserContract.HomeItems; |
|
10 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; |
|
11 import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig; |
|
12 import org.mozilla.gecko.home.HomeConfig.ItemHandler; |
|
13 import org.mozilla.gecko.home.HomeConfig.PanelConfig; |
|
14 import org.mozilla.gecko.home.HomeConfig.ViewConfig; |
|
15 import org.mozilla.gecko.util.StringUtils; |
|
16 |
|
17 import android.content.Context; |
|
18 import android.database.Cursor; |
|
19 import android.os.Parcel; |
|
20 import android.os.Parcelable; |
|
21 import android.text.TextUtils; |
|
22 import android.util.Log; |
|
23 import android.util.SparseArray; |
|
24 import android.view.KeyEvent; |
|
25 import android.view.LayoutInflater; |
|
26 import android.view.View; |
|
27 import android.view.ViewGroup; |
|
28 import android.widget.FrameLayout; |
|
29 import android.widget.ImageView; |
|
30 import android.widget.LinearLayout; |
|
31 import android.widget.TextView; |
|
32 |
|
33 import java.lang.ref.SoftReference; |
|
34 import java.util.EnumSet; |
|
35 import java.util.LinkedList; |
|
36 import java.util.Map; |
|
37 import java.util.WeakHashMap; |
|
38 |
|
39 import com.squareup.picasso.Picasso; |
|
40 |
|
41 /** |
|
42 * {@code PanelLayout} is the base class for custom layouts to be |
|
43 * used in {@code DynamicPanel}. It provides the basic framework |
|
44 * that enables custom layouts to request and reset datasets and |
|
45 * create panel views. Furthermore, it automates most of the process |
|
46 * of binding panel views with their respective datasets. |
|
47 * |
|
48 * {@code PanelLayout} abstracts the implemention details of how |
|
49 * datasets are actually loaded through the {@DatasetHandler} interface. |
|
50 * {@code DatasetHandler} provides two operations: request and reset. |
|
51 * The results of the dataset requests done via the {@code DatasetHandler} |
|
52 * are delivered to the {@code PanelLayout} with the {@code deliverDataset()} |
|
53 * method. |
|
54 * |
|
55 * Subclasses of {@code PanelLayout} should simply use the utilities |
|
56 * provided by {@code PanelLayout}. Namely: |
|
57 * |
|
58 * {@code requestDataset()} - To fetch datasets and auto-bind them to |
|
59 * the existing panel views backed by them. |
|
60 * |
|
61 * {@code resetDataset()} - To release any resources associated with a |
|
62 * previously loaded dataset. |
|
63 * |
|
64 * {@code createPanelView()} - To create a panel view for a ViewConfig |
|
65 * associated with the panel. |
|
66 * |
|
67 * {@code disposePanelView()} - To dispose any dataset references associated |
|
68 * with the given view. |
|
69 * |
|
70 * {@code PanelLayout} subclasses should always use {@code createPanelView()} |
|
71 * to create the views dynamically created based on {@code ViewConfig}. This |
|
72 * allows {@code PanelLayout} to auto-bind datasets with panel views. |
|
73 * {@code PanelLayout} subclasses are free to have any type of views to arrange |
|
74 * the panel views in different ways. |
|
75 */ |
|
76 abstract class PanelLayout extends FrameLayout { |
|
77 private static final String LOGTAG = "GeckoPanelLayout"; |
|
78 |
|
79 protected final SparseArray<ViewState> mViewStates; |
|
80 private final PanelConfig mPanelConfig; |
|
81 private final DatasetHandler mDatasetHandler; |
|
82 private final OnUrlOpenListener mUrlOpenListener; |
|
83 private final ContextMenuRegistry mContextMenuRegistry; |
|
84 |
|
85 /** |
|
86 * To be used by panel views to express that they are |
|
87 * backed by datasets. |
|
88 */ |
|
89 public interface DatasetBacked { |
|
90 public void setDataset(Cursor cursor); |
|
91 public void setFilterManager(FilterManager manager); |
|
92 } |
|
93 |
|
94 /** |
|
95 * To be used by requests made to {@code DatasetHandler}s to couple dataset ID with current |
|
96 * filter for queries on the database. |
|
97 */ |
|
98 public static class DatasetRequest implements Parcelable { |
|
99 public enum Type implements Parcelable { |
|
100 DATASET_LOAD, |
|
101 FILTER_PUSH, |
|
102 FILTER_POP; |
|
103 |
|
104 @Override |
|
105 public int describeContents() { |
|
106 return 0; |
|
107 } |
|
108 |
|
109 @Override |
|
110 public void writeToParcel(Parcel dest, int flags) { |
|
111 dest.writeInt(ordinal()); |
|
112 } |
|
113 |
|
114 public static final Creator<Type> CREATOR = new Creator<Type>() { |
|
115 @Override |
|
116 public Type createFromParcel(final Parcel source) { |
|
117 return Type.values()[source.readInt()]; |
|
118 } |
|
119 |
|
120 @Override |
|
121 public Type[] newArray(final int size) { |
|
122 return new Type[size]; |
|
123 } |
|
124 }; |
|
125 } |
|
126 |
|
127 private final int mViewIndex; |
|
128 private final Type mType; |
|
129 private final String mDatasetId; |
|
130 private final FilterDetail mFilterDetail; |
|
131 |
|
132 private DatasetRequest(Parcel in) { |
|
133 this.mViewIndex = in.readInt(); |
|
134 this.mType = (Type) in.readParcelable(getClass().getClassLoader()); |
|
135 this.mDatasetId = in.readString(); |
|
136 this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader()); |
|
137 } |
|
138 |
|
139 public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) { |
|
140 this(index, Type.DATASET_LOAD, datasetId, filterDetail); |
|
141 } |
|
142 |
|
143 public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) { |
|
144 this.mViewIndex = index; |
|
145 this.mType = type; |
|
146 this.mDatasetId = datasetId; |
|
147 this.mFilterDetail = filterDetail; |
|
148 } |
|
149 |
|
150 public int getViewIndex() { |
|
151 return mViewIndex; |
|
152 } |
|
153 |
|
154 public Type getType() { |
|
155 return mType; |
|
156 } |
|
157 |
|
158 public String getDatasetId() { |
|
159 return mDatasetId; |
|
160 } |
|
161 |
|
162 public String getFilter() { |
|
163 return (mFilterDetail != null ? mFilterDetail.filter : null); |
|
164 } |
|
165 |
|
166 public FilterDetail getFilterDetail() { |
|
167 return mFilterDetail; |
|
168 } |
|
169 |
|
170 @Override |
|
171 public int describeContents() { |
|
172 return 0; |
|
173 } |
|
174 |
|
175 @Override |
|
176 public void writeToParcel(Parcel dest, int flags) { |
|
177 dest.writeInt(mViewIndex); |
|
178 dest.writeParcelable(mType, 0); |
|
179 dest.writeString(mDatasetId); |
|
180 dest.writeParcelable(mFilterDetail, 0); |
|
181 } |
|
182 |
|
183 public String toString() { |
|
184 return "{ index: " + mViewIndex + |
|
185 ", type: " + mType + |
|
186 ", dataset: " + mDatasetId + |
|
187 ", filter: " + mFilterDetail + |
|
188 " }"; |
|
189 } |
|
190 |
|
191 public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() { |
|
192 public DatasetRequest createFromParcel(Parcel in) { |
|
193 return new DatasetRequest(in); |
|
194 } |
|
195 |
|
196 public DatasetRequest[] newArray(int size) { |
|
197 return new DatasetRequest[size]; |
|
198 } |
|
199 }; |
|
200 } |
|
201 |
|
202 /** |
|
203 * Defines the contract with the component that is responsible |
|
204 * for handling datasets requests. |
|
205 */ |
|
206 public interface DatasetHandler { |
|
207 /** |
|
208 * Requests a dataset to be fetched and auto-bound to the |
|
209 * panel views backed by it. |
|
210 */ |
|
211 public void requestDataset(DatasetRequest request); |
|
212 |
|
213 /** |
|
214 * Releases any resources associated with a panel view. It will |
|
215 * do nothing if the view with the given index been created |
|
216 * before. |
|
217 */ |
|
218 public void resetDataset(int viewIndex); |
|
219 } |
|
220 |
|
221 public interface PanelView { |
|
222 public void setOnItemOpenListener(OnItemOpenListener listener); |
|
223 public void setOnKeyListener(OnKeyListener listener); |
|
224 public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory); |
|
225 } |
|
226 |
|
227 public interface FilterManager { |
|
228 public FilterDetail getPreviousFilter(); |
|
229 public boolean canGoBack(); |
|
230 public void goBack(); |
|
231 } |
|
232 |
|
233 public interface ContextMenuRegistry { |
|
234 public void register(View view); |
|
235 } |
|
236 |
|
237 public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, |
|
238 OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) { |
|
239 super(context); |
|
240 mViewStates = new SparseArray<ViewState>(); |
|
241 mPanelConfig = panelConfig; |
|
242 mDatasetHandler = datasetHandler; |
|
243 mUrlOpenListener = urlOpenListener; |
|
244 mContextMenuRegistry = contextMenuRegistry; |
|
245 } |
|
246 |
|
247 @Override |
|
248 public void onDetachedFromWindow() { |
|
249 super.onDetachedFromWindow(); |
|
250 |
|
251 final int count = mViewStates.size(); |
|
252 for (int i = 0; i < count; i++) { |
|
253 final ViewState viewState = mViewStates.valueAt(i); |
|
254 |
|
255 final View view = viewState.getView(); |
|
256 if (view != null) { |
|
257 maybeSetDataset(view, null); |
|
258 } |
|
259 } |
|
260 mViewStates.clear(); |
|
261 } |
|
262 |
|
263 /** |
|
264 * Delivers the dataset as a {@code Cursor} to be bound to the |
|
265 * panel view backed by it. This is used by the {@code DatasetHandler} |
|
266 * in response to a dataset request. |
|
267 */ |
|
268 public final void deliverDataset(DatasetRequest request, Cursor cursor) { |
|
269 Log.d(LOGTAG, "Delivering request: " + request); |
|
270 final ViewState viewState = mViewStates.get(request.getViewIndex()); |
|
271 if (viewState == null) { |
|
272 return; |
|
273 } |
|
274 |
|
275 switch (request.getType()) { |
|
276 case FILTER_PUSH: |
|
277 viewState.pushFilter(request.getFilterDetail()); |
|
278 break; |
|
279 case FILTER_POP: |
|
280 viewState.popFilter(); |
|
281 break; |
|
282 } |
|
283 |
|
284 final View activeView = viewState.getActiveView(); |
|
285 if (activeView == null) { |
|
286 throw new IllegalStateException("No active view for view state: " + viewState.getIndex()); |
|
287 } |
|
288 |
|
289 final ViewConfig viewConfig = viewState.getViewConfig(); |
|
290 |
|
291 final View newView; |
|
292 if (cursor == null || cursor.getCount() == 0) { |
|
293 newView = createEmptyView(viewConfig); |
|
294 maybeSetDataset(activeView, null); |
|
295 } else { |
|
296 newView = createPanelView(viewConfig); |
|
297 maybeSetDataset(newView, cursor); |
|
298 } |
|
299 |
|
300 if (activeView != newView) { |
|
301 replacePanelView(activeView, newView); |
|
302 } |
|
303 } |
|
304 |
|
305 /** |
|
306 * Releases any references to the given dataset from all |
|
307 * existing panel views. |
|
308 */ |
|
309 public final void releaseDataset(int viewIndex) { |
|
310 Log.d(LOGTAG, "Releasing dataset: " + viewIndex); |
|
311 final ViewState viewState = mViewStates.get(viewIndex); |
|
312 if (viewState == null) { |
|
313 return; |
|
314 } |
|
315 |
|
316 final View view = viewState.getView(); |
|
317 if (view != null) { |
|
318 maybeSetDataset(view, null); |
|
319 } |
|
320 } |
|
321 |
|
322 /** |
|
323 * Requests a dataset to be loaded and bound to any existing |
|
324 * panel view backed by it. |
|
325 */ |
|
326 protected final void requestDataset(DatasetRequest request) { |
|
327 Log.d(LOGTAG, "Requesting request: " + request); |
|
328 if (mViewStates.get(request.getViewIndex()) == null) { |
|
329 return; |
|
330 } |
|
331 |
|
332 mDatasetHandler.requestDataset(request); |
|
333 } |
|
334 |
|
335 /** |
|
336 * Releases any resources associated with a panel view. |
|
337 * e.g. close any associated {@code Cursor}. |
|
338 */ |
|
339 protected final void resetDataset(int viewIndex) { |
|
340 Log.d(LOGTAG, "Resetting view with index: " + viewIndex); |
|
341 if (mViewStates.get(viewIndex) == null) { |
|
342 return; |
|
343 } |
|
344 |
|
345 mDatasetHandler.resetDataset(viewIndex); |
|
346 } |
|
347 |
|
348 /** |
|
349 * Factory method to create instance of panels from a given |
|
350 * {@code ViewConfig}. All panel views defined in {@code PanelConfig} |
|
351 * should be created using this method so that {@PanelLayout} can |
|
352 * keep track of panel views and their associated datasets. |
|
353 */ |
|
354 protected final View createPanelView(ViewConfig viewConfig) { |
|
355 Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType()); |
|
356 |
|
357 ViewState viewState = mViewStates.get(viewConfig.getIndex()); |
|
358 if (viewState == null) { |
|
359 viewState = new ViewState(viewConfig); |
|
360 mViewStates.put(viewConfig.getIndex(), viewState); |
|
361 } |
|
362 |
|
363 View view = viewState.getView(); |
|
364 if (view == null) { |
|
365 switch(viewConfig.getType()) { |
|
366 case LIST: |
|
367 view = new PanelListView(getContext(), viewConfig); |
|
368 break; |
|
369 |
|
370 case GRID: |
|
371 view = new PanelGridView(getContext(), viewConfig); |
|
372 break; |
|
373 |
|
374 default: |
|
375 throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName()); |
|
376 } |
|
377 |
|
378 PanelView panelView = (PanelView) view; |
|
379 panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState)); |
|
380 panelView.setOnKeyListener(new PanelKeyListener(viewState)); |
|
381 panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() { |
|
382 @Override |
|
383 public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { |
|
384 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); |
|
385 info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL)); |
|
386 info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE)); |
|
387 return info; |
|
388 } |
|
389 }); |
|
390 |
|
391 mContextMenuRegistry.register(view); |
|
392 |
|
393 if (view instanceof DatasetBacked) { |
|
394 DatasetBacked datasetBacked = (DatasetBacked) view; |
|
395 datasetBacked.setFilterManager(new PanelFilterManager(viewState)); |
|
396 |
|
397 // XXX: Disabled because of bug 1010986 |
|
398 // if (viewConfig.isRefreshEnabled()) { |
|
399 // view = new PanelRefreshLayout(getContext(), view, |
|
400 // mPanelConfig.getId(), viewConfig.getIndex()); |
|
401 // } |
|
402 } |
|
403 |
|
404 viewState.setView(view); |
|
405 } |
|
406 |
|
407 return view; |
|
408 } |
|
409 |
|
410 /** |
|
411 * Dispose any dataset references associated with the |
|
412 * given view. |
|
413 */ |
|
414 protected final void disposePanelView(View view) { |
|
415 Log.d(LOGTAG, "Disposing panel view"); |
|
416 final int count = mViewStates.size(); |
|
417 for (int i = 0; i < count; i++) { |
|
418 final ViewState viewState = mViewStates.valueAt(i); |
|
419 |
|
420 if (viewState.getView() == view) { |
|
421 maybeSetDataset(view, null); |
|
422 mViewStates.remove(viewState.getIndex()); |
|
423 break; |
|
424 } |
|
425 } |
|
426 } |
|
427 |
|
428 private void maybeSetDataset(View view, Cursor cursor) { |
|
429 if (view instanceof DatasetBacked) { |
|
430 final DatasetBacked dsb = (DatasetBacked) view; |
|
431 dsb.setDataset(cursor); |
|
432 } |
|
433 } |
|
434 |
|
435 private View createEmptyView(ViewConfig viewConfig) { |
|
436 Log.d(LOGTAG, "Creating empty view: " + viewConfig.getType()); |
|
437 |
|
438 ViewState viewState = mViewStates.get(viewConfig.getIndex()); |
|
439 if (viewState == null) { |
|
440 throw new IllegalStateException("No view state found for view index: " + viewConfig.getIndex()); |
|
441 } |
|
442 |
|
443 View view = viewState.getEmptyView(); |
|
444 if (view == null) { |
|
445 view = LayoutInflater.from(getContext()).inflate(R.layout.home_empty_panel, null); |
|
446 |
|
447 final EmptyViewConfig emptyViewConfig = viewConfig.getEmptyViewConfig(); |
|
448 |
|
449 // XXX: Refactor this into a custom view (bug 985134) |
|
450 final String text = (emptyViewConfig == null) ? null : emptyViewConfig.getText(); |
|
451 final TextView textView = (TextView) view.findViewById(R.id.home_empty_text); |
|
452 if (TextUtils.isEmpty(text)) { |
|
453 textView.setText(R.string.home_default_empty); |
|
454 } else { |
|
455 textView.setText(text); |
|
456 } |
|
457 |
|
458 final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl(); |
|
459 final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image); |
|
460 |
|
461 if (TextUtils.isEmpty(imageUrl)) { |
|
462 imageView.setImageResource(R.drawable.icon_home_empty_firefox); |
|
463 } else { |
|
464 Picasso.with(getContext()) |
|
465 .load(imageUrl) |
|
466 .error(R.drawable.icon_home_empty_firefox) |
|
467 .into(imageView); |
|
468 } |
|
469 |
|
470 viewState.setEmptyView(view); |
|
471 } |
|
472 |
|
473 return view; |
|
474 } |
|
475 |
|
476 private void replacePanelView(View currentView, View newView) { |
|
477 final ViewGroup parent = (ViewGroup) currentView.getParent(); |
|
478 parent.addView(newView, parent.indexOfChild(currentView), currentView.getLayoutParams()); |
|
479 parent.removeView(currentView); |
|
480 } |
|
481 |
|
482 /** |
|
483 * Must be implemented by {@code PanelLayout} subclasses to define |
|
484 * what happens then the layout is first loaded. Should set initial |
|
485 * UI state and request any necessary datasets. |
|
486 */ |
|
487 public abstract void load(); |
|
488 |
|
489 /** |
|
490 * Represents a 'live' instance of a panel view associated with |
|
491 * the {@code PanelLayout}. Is responsible for tracking the history stack of filters. |
|
492 */ |
|
493 protected class ViewState { |
|
494 private final ViewConfig mViewConfig; |
|
495 private SoftReference<View> mView; |
|
496 private SoftReference<View> mEmptyView; |
|
497 private LinkedList<FilterDetail> mFilterStack; |
|
498 |
|
499 public ViewState(ViewConfig viewConfig) { |
|
500 mViewConfig = viewConfig; |
|
501 mView = new SoftReference<View>(null); |
|
502 mEmptyView = new SoftReference<View>(null); |
|
503 } |
|
504 |
|
505 public ViewConfig getViewConfig() { |
|
506 return mViewConfig; |
|
507 } |
|
508 |
|
509 public int getIndex() { |
|
510 return mViewConfig.getIndex(); |
|
511 } |
|
512 |
|
513 public View getView() { |
|
514 return mView.get(); |
|
515 } |
|
516 |
|
517 public void setView(View view) { |
|
518 mView = new SoftReference<View>(view); |
|
519 } |
|
520 |
|
521 public View getEmptyView() { |
|
522 return mEmptyView.get(); |
|
523 } |
|
524 |
|
525 public void setEmptyView(View view) { |
|
526 mEmptyView = new SoftReference<View>(view); |
|
527 } |
|
528 |
|
529 public View getActiveView() { |
|
530 final View view = getView(); |
|
531 if (view != null && view.getParent() != null) { |
|
532 return view; |
|
533 } |
|
534 |
|
535 final View emptyView = getEmptyView(); |
|
536 if (emptyView != null && emptyView.getParent() != null) { |
|
537 return emptyView; |
|
538 } |
|
539 |
|
540 return null; |
|
541 } |
|
542 |
|
543 public String getDatasetId() { |
|
544 return mViewConfig.getDatasetId(); |
|
545 } |
|
546 |
|
547 public ItemHandler getItemHandler() { |
|
548 return mViewConfig.getItemHandler(); |
|
549 } |
|
550 |
|
551 /** |
|
552 * Get the current filter that this view is displaying, or null if none. |
|
553 */ |
|
554 public FilterDetail getCurrentFilter() { |
|
555 if (mFilterStack == null) { |
|
556 return null; |
|
557 } else { |
|
558 return mFilterStack.peek(); |
|
559 } |
|
560 } |
|
561 |
|
562 /** |
|
563 * Get the previous filter that this view was displaying, or null if none. |
|
564 */ |
|
565 public FilterDetail getPreviousFilter() { |
|
566 if (!canPopFilter()) { |
|
567 return null; |
|
568 } |
|
569 |
|
570 return mFilterStack.get(1); |
|
571 } |
|
572 |
|
573 /** |
|
574 * Adds a filter to the history stack for this view. |
|
575 */ |
|
576 public void pushFilter(FilterDetail filter) { |
|
577 if (mFilterStack == null) { |
|
578 mFilterStack = new LinkedList<FilterDetail>(); |
|
579 |
|
580 // Initialize with the initial filter. |
|
581 mFilterStack.push(new FilterDetail(mViewConfig.getFilter(), |
|
582 mPanelConfig.getTitle())); |
|
583 } |
|
584 |
|
585 mFilterStack.push(filter); |
|
586 } |
|
587 |
|
588 /** |
|
589 * Remove the most recent filter from the stack. |
|
590 * |
|
591 * @return whether the filter was popped |
|
592 */ |
|
593 public boolean popFilter() { |
|
594 if (!canPopFilter()) { |
|
595 return false; |
|
596 } |
|
597 |
|
598 mFilterStack.pop(); |
|
599 return true; |
|
600 } |
|
601 |
|
602 public boolean canPopFilter() { |
|
603 return (mFilterStack != null && mFilterStack.size() > 1); |
|
604 } |
|
605 } |
|
606 |
|
607 static class FilterDetail implements Parcelable { |
|
608 final String filter; |
|
609 final String title; |
|
610 |
|
611 private FilterDetail(Parcel in) { |
|
612 this.filter = in.readString(); |
|
613 this.title = in.readString(); |
|
614 } |
|
615 |
|
616 public FilterDetail(String filter, String title) { |
|
617 this.filter = filter; |
|
618 this.title = title; |
|
619 } |
|
620 |
|
621 @Override |
|
622 public int describeContents() { |
|
623 return 0; |
|
624 } |
|
625 |
|
626 @Override |
|
627 public void writeToParcel(Parcel dest, int flags) { |
|
628 dest.writeString(filter); |
|
629 dest.writeString(title); |
|
630 } |
|
631 |
|
632 public static final Creator<FilterDetail> CREATOR = new Creator<FilterDetail>() { |
|
633 public FilterDetail createFromParcel(Parcel in) { |
|
634 return new FilterDetail(in); |
|
635 } |
|
636 |
|
637 public FilterDetail[] newArray(int size) { |
|
638 return new FilterDetail[size]; |
|
639 } |
|
640 }; |
|
641 } |
|
642 |
|
643 /** |
|
644 * Pushes filter to {@code ViewState}'s stack and makes request for new filter value. |
|
645 */ |
|
646 private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) { |
|
647 final int index = viewState.getIndex(); |
|
648 final String datasetId = viewState.getDatasetId(); |
|
649 |
|
650 mDatasetHandler.requestDataset(new DatasetRequest(index, |
|
651 DatasetRequest.Type.FILTER_PUSH, |
|
652 datasetId, |
|
653 filterDetail)); |
|
654 } |
|
655 |
|
656 /** |
|
657 * Pops filter from {@code ViewState}'s stack and makes request for previous filter value. |
|
658 * |
|
659 * @return whether the filter has changed |
|
660 */ |
|
661 private boolean popFilterOnView(ViewState viewState) { |
|
662 if (viewState.canPopFilter()) { |
|
663 final int index = viewState.getIndex(); |
|
664 final String datasetId = viewState.getDatasetId(); |
|
665 final FilterDetail filterDetail = viewState.getPreviousFilter(); |
|
666 |
|
667 mDatasetHandler.requestDataset(new DatasetRequest(index, |
|
668 DatasetRequest.Type.FILTER_POP, |
|
669 datasetId, |
|
670 filterDetail)); |
|
671 |
|
672 return true; |
|
673 } else { |
|
674 return false; |
|
675 } |
|
676 } |
|
677 |
|
678 public interface OnItemOpenListener { |
|
679 public void onItemOpen(String url, String title); |
|
680 } |
|
681 |
|
682 private class PanelOnItemOpenListener implements OnItemOpenListener { |
|
683 private ViewState mViewState; |
|
684 |
|
685 public PanelOnItemOpenListener(ViewState viewState) { |
|
686 mViewState = viewState; |
|
687 } |
|
688 |
|
689 @Override |
|
690 public void onItemOpen(String url, String title) { |
|
691 if (StringUtils.isFilterUrl(url)) { |
|
692 FilterDetail filterDetail = new FilterDetail(StringUtils.getFilterFromUrl(url), title); |
|
693 pushFilterOnView(mViewState, filterDetail); |
|
694 } else { |
|
695 EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class); |
|
696 if (mViewState.getItemHandler() == ItemHandler.INTENT) { |
|
697 flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT); |
|
698 } |
|
699 |
|
700 mUrlOpenListener.onUrlOpen(url, flags); |
|
701 } |
|
702 } |
|
703 } |
|
704 |
|
705 private class PanelKeyListener implements View.OnKeyListener { |
|
706 private ViewState mViewState; |
|
707 |
|
708 public PanelKeyListener(ViewState viewState) { |
|
709 mViewState = viewState; |
|
710 } |
|
711 |
|
712 @Override |
|
713 public boolean onKey(View v, int keyCode, KeyEvent event) { |
|
714 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { |
|
715 return popFilterOnView(mViewState); |
|
716 } |
|
717 |
|
718 return false; |
|
719 } |
|
720 } |
|
721 |
|
722 private class PanelFilterManager implements FilterManager { |
|
723 private final ViewState mViewState; |
|
724 |
|
725 public PanelFilterManager(ViewState viewState) { |
|
726 mViewState = viewState; |
|
727 } |
|
728 |
|
729 @Override |
|
730 public FilterDetail getPreviousFilter() { |
|
731 return mViewState.getPreviousFilter(); |
|
732 } |
|
733 |
|
734 @Override |
|
735 public boolean canGoBack() { |
|
736 return mViewState.canPopFilter(); |
|
737 } |
|
738 |
|
739 @Override |
|
740 public void goBack() { |
|
741 popFilterOnView(mViewState); |
|
742 } |
|
743 } |
|
744 } |