com.facebook.widget.PickerFragment Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of facebook-android-api Show documentation
Show all versions of facebook-android-api Show documentation
The Mavenized Facebook Android api available in Maven Central repo.
The newest version!
/**
* Copyright 2010-present Facebook.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.widget;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.animation.AlphaAnimation;
import android.widget.*;
import com.facebook.FacebookException;
import com.facebook.Request;
import com.facebook.Session;
import com.facebook.SessionState;
import com.facebook.android.R;
import com.facebook.internal.SessionTracker;
import com.facebook.model.GraphObject;
import java.util.*;
/**
* Provides functionality common to SDK UI elements that allow the user to pick one or more
* graph objects (e.g., places, friends) from a list of possibilities. The UI is exposed as a
* Fragment to allow to it to be included in an Activity along with other Fragments. The Fragments
* can be configured by passing parameters as part of their Intent bundle, or (for certain
* properties) by specifying attributes in their XML layout files.
*
* PickerFragments support callbacks that will be called in the event of an error, when the
* underlying data has been changed, or when the set of selected graph objects changes.
*/
public abstract class PickerFragment extends Fragment {
/**
* The key for a boolean parameter in the fragment's Intent bundle to indicate whether the
* picker should show pictures (if available) for the graph objects.
*/
public static final String SHOW_PICTURES_BUNDLE_KEY = "com.facebook.widget.PickerFragment.ShowPictures";
/**
* The key for a String parameter in the fragment's Intent bundle to indicate which extra fields
* beyond the default fields should be retrieved for any graph objects in the results.
*/
public static final String EXTRA_FIELDS_BUNDLE_KEY = "com.facebook.widget.PickerFragment.ExtraFields";
/**
* The key for a boolean parameter in the fragment's Intent bundle to indicate whether the
* picker should display a title bar with a Done button.
*/
public static final String SHOW_TITLE_BAR_BUNDLE_KEY = "com.facebook.widget.PickerFragment.ShowTitleBar";
/**
* The key for a String parameter in the fragment's Intent bundle to indicate the text to
* display in the title bar.
*/
public static final String TITLE_TEXT_BUNDLE_KEY = "com.facebook.widget.PickerFragment.TitleText";
/**
* The key for a String parameter in the fragment's Intent bundle to indicate the text to
* display in the Done btuton.
*/
public static final String DONE_BUTTON_TEXT_BUNDLE_KEY = "com.facebook.widget.PickerFragment.DoneButtonText";
private static final String SELECTION_BUNDLE_KEY = "com.facebook.android.PickerFragment.Selection";
private static final String ACTIVITY_CIRCLE_SHOW_KEY = "com.facebook.android.PickerFragment.ActivityCircleShown";
private static final int PROFILE_PICTURE_PREFETCH_BUFFER = 5;
private final int layout;
private OnErrorListener onErrorListener;
private OnDataChangedListener onDataChangedListener;
private OnSelectionChangedListener onSelectionChangedListener;
private OnDoneButtonClickedListener onDoneButtonClickedListener;
private GraphObjectFilter filter;
private boolean showPictures = true;
private boolean showTitleBar = true;
private ListView listView;
HashSet extraFields = new HashSet();
GraphObjectAdapter adapter;
private final Class graphObjectClass;
private LoadingStrategy loadingStrategy;
private SelectionStrategy selectionStrategy;
private ProgressBar activityCircle;
private SessionTracker sessionTracker;
private String titleText;
private String doneButtonText;
private TextView titleTextView;
private Button doneButton;
private Drawable titleBarBackground;
private Drawable doneButtonBackground;
private boolean appEventsLogged;
PickerFragment(Class graphObjectClass, int layout, Bundle args) {
this.graphObjectClass = graphObjectClass;
this.layout = layout;
setPickerFragmentSettingsFromBundle(args);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = createAdapter();
adapter.setFilter(new GraphObjectAdapter.Filter() {
@Override
public boolean includeItem(T graphObject) {
return filterIncludesItem(graphObject);
}
});
}
@Override
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(activity, attrs, savedInstanceState);
TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.com_facebook_picker_fragment);
setShowPictures(a.getBoolean(R.styleable.com_facebook_picker_fragment_show_pictures, showPictures));
String extraFieldsString = a.getString(R.styleable.com_facebook_picker_fragment_extra_fields);
if (extraFieldsString != null) {
String[] strings = extraFieldsString.split(",");
setExtraFields(Arrays.asList(strings));
}
showTitleBar = a.getBoolean(R.styleable.com_facebook_picker_fragment_show_title_bar, showTitleBar);
titleText = a.getString(R.styleable.com_facebook_picker_fragment_title_text);
doneButtonText = a.getString(R.styleable.com_facebook_picker_fragment_done_button_text);
titleBarBackground = a.getDrawable(R.styleable.com_facebook_picker_fragment_title_bar_background);
doneButtonBackground = a.getDrawable(R.styleable.com_facebook_picker_fragment_done_button_background);
a.recycle();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup view = (ViewGroup) inflater.inflate(layout, container, false);
listView = (ListView) view.findViewById(R.id.com_facebook_picker_list_view);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View v, int position, long id) {
onListItemClick((ListView) parent, v, position);
}
});
listView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// We don't actually do anything differently on long-clicks, but setting the listener
// enables the selector transition that we have for visual consistency with the
// Facebook app's pickers.
return false;
}
});
listView.setOnScrollListener(onScrollListener);
activityCircle = (ProgressBar) view.findViewById(R.id.com_facebook_picker_activity_circle);
setupViews(view);
listView.setAdapter(adapter);
return view;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
sessionTracker = new SessionTracker(getActivity(), new Session.StatusCallback() {
@Override
public void call(Session session, SessionState state, Exception exception) {
if (!session.isOpened()) {
// When a session is closed, we want to clear out our data so it is not visible to subsequent users
clearResults();
}
}
});
setSettingsFromBundle(savedInstanceState);
loadingStrategy = createLoadingStrategy();
loadingStrategy.attach(adapter);
selectionStrategy = createSelectionStrategy();
selectionStrategy.readSelectionFromBundle(savedInstanceState, SELECTION_BUNDLE_KEY);
// Should we display a title bar? (We need to do this after we've retrieved our bundle settings.)
if (showTitleBar) {
inflateTitleBar((ViewGroup) getView());
}
if (activityCircle != null && savedInstanceState != null) {
boolean shown = savedInstanceState.getBoolean(ACTIVITY_CIRCLE_SHOW_KEY, false);
if (shown) {
displayActivityCircle();
} else {
// Should be hidden already, but just to be sure.
hideActivityCircle();
}
}
}
@Override
public void onDetach() {
super.onDetach();
listView.setOnScrollListener(null);
listView.setAdapter(null);
loadingStrategy.detach();
sessionTracker.stopTracking();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveSettingsToBundle(outState);
selectionStrategy.saveSelectionToBundle(outState, SELECTION_BUNDLE_KEY);
if (activityCircle != null) {
outState.putBoolean(ACTIVITY_CIRCLE_SHOW_KEY, activityCircle.getVisibility() == View.VISIBLE);
}
}
@Override
public void onStop() {
if (!appEventsLogged) {
logAppEvents(false);
}
super.onStop();
}
@Override
public void setArguments(Bundle args) {
super.setArguments(args);
setSettingsFromBundle(args);
}
/**
* Gets the current OnDataChangedListener for this fragment, which will be called whenever
* the underlying data being displaying in the picker has changed.
*
* @return the OnDataChangedListener, or null if there is none
*/
public OnDataChangedListener getOnDataChangedListener() {
return onDataChangedListener;
}
/**
* Sets the current OnDataChangedListener for this fragment, which will be called whenever
* the underlying data being displaying in the picker has changed.
*
* @param onDataChangedListener the OnDataChangedListener, or null if there is none
*/
public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
this.onDataChangedListener = onDataChangedListener;
}
/**
* Gets the current OnSelectionChangedListener for this fragment, which will be called
* whenever the user selects or unselects a graph object in the list.
*
* @return the OnSelectionChangedListener, or null if there is none
*/
public OnSelectionChangedListener getOnSelectionChangedListener() {
return onSelectionChangedListener;
}
/**
* Sets the current OnSelectionChangedListener for this fragment, which will be called
* whenever the user selects or unselects a graph object in the list.
*
* @param onSelectionChangedListener the OnSelectionChangedListener, or null if there is none
*/
public void setOnSelectionChangedListener(
OnSelectionChangedListener onSelectionChangedListener) {
this.onSelectionChangedListener = onSelectionChangedListener;
}
/**
* Gets the current OnDoneButtonClickedListener for this fragment, which will be called
* when the user clicks the Done button.
*
* @return the OnDoneButtonClickedListener, or null if there is none
*/
public OnDoneButtonClickedListener getOnDoneButtonClickedListener() {
return onDoneButtonClickedListener;
}
/**
* Sets the current OnDoneButtonClickedListener for this fragment, which will be called
* when the user clicks the Done button. This will only be possible if the title bar is
* being shown in this fragment.
*
* @param onDoneButtonClickedListener the OnDoneButtonClickedListener, or null if there is none
*/
public void setOnDoneButtonClickedListener(OnDoneButtonClickedListener onDoneButtonClickedListener) {
this.onDoneButtonClickedListener = onDoneButtonClickedListener;
}
/**
* Gets the current OnErrorListener for this fragment, which will be called in the event
* of network or other errors encountered while populating the graph objects in the list.
*
* @return the OnErrorListener, or null if there is none
*/
public OnErrorListener getOnErrorListener() {
return onErrorListener;
}
/**
* Sets the current OnErrorListener for this fragment, which will be called in the event
* of network or other errors encountered while populating the graph objects in the list.
*
* @param onErrorListener the OnErrorListener, or null if there is none
*/
public void setOnErrorListener(OnErrorListener onErrorListener) {
this.onErrorListener = onErrorListener;
}
/**
* Gets the current filter for this fragment, which will be called for each graph object
* returned from the service to determine if it should be displayed in the list.
* If no filter is specified, all retrieved graph objects will be displayed.
*
* @return the GraphObjectFilter, or null if there is none
*/
public GraphObjectFilter getFilter() {
return filter;
}
/**
* Sets the current filter for this fragment, which will be called for each graph object
* returned from the service to determine if it should be displayed in the list.
* If no filter is specified, all retrieved graph objects will be displayed.
*
* @param filter the GraphObjectFilter, or null if there is none
*/
public void setFilter(GraphObjectFilter filter) {
this.filter = filter;
}
/**
* Gets the Session to use for any Facebook requests this fragment will make.
*
* @return the Session that will be used for any Facebook requests, or null if there is none
*/
public Session getSession() {
return sessionTracker.getSession();
}
/**
* Sets the Session to use for any Facebook requests this fragment will make. If the
* parameter is null, the fragment will use the current active session, if any.
*
* @param session the Session to use for Facebook requests, or null to use the active session
*/
public void setSession(Session session) {
sessionTracker.setSession(session);
}
/**
* Gets whether to display pictures, if available, for displayed graph objects.
*
* @return true if pictures should be displayed, false if not
*/
public boolean getShowPictures() {
return showPictures;
}
/**
* Sets whether to display pictures, if available, for displayed graph objects.
*
* @param showPictures true if pictures should be displayed, false if not
*/
public void setShowPictures(boolean showPictures) {
this.showPictures = showPictures;
}
/**
* Gets the extra fields to request for the retrieved graph objects.
*
* @return the extra fields to request
*/
public Set getExtraFields() {
return new HashSet(extraFields);
}
/**
* Sets the extra fields to request for the retrieved graph objects.
*
* @param fields the extra fields to request
*/
public void setExtraFields(Collection fields) {
extraFields = new HashSet();
if (fields != null) {
extraFields.addAll(fields);
}
}
/**
* Sets whether to show a title bar with a Done button. This must be
* called prior to the Fragment going through its creation lifecycle to have an effect.
*
* @param showTitleBar true if a title bar should be displayed, false if not
*/
public void setShowTitleBar(boolean showTitleBar) {
this.showTitleBar = showTitleBar;
}
/**
* Gets whether to show a title bar with a Done button. The default is true.
*
* @return true if a title bar will be shown, false if not.
*/
public boolean getShowTitleBar() {
return showTitleBar;
}
/**
* Sets the text to show in the title bar, if a title bar is to be shown. This must be
* called prior to the Fragment going through its creation lifecycle to have an effect, or
* the default will be used.
*
* @param titleText the text to show in the title bar
*/
public void setTitleText(String titleText) {
this.titleText = titleText;
}
/**
* Gets the text to show in the title bar, if a title bar is to be shown.
*
* @return the text to show in the title bar
*/
public String getTitleText() {
if (titleText == null) {
titleText = getDefaultTitleText();
}
return titleText;
}
/**
* Sets the text to show in the Done button, if a title bar is to be shown. This must be
* called prior to the Fragment going through its creation lifecycle to have an effect, or
* the default will be used.
*
* @param doneButtonText the text to show in the Done button
*/
public void setDoneButtonText(String doneButtonText) {
this.doneButtonText = doneButtonText;
}
/**
* Gets the text to show in the Done button, if a title bar is to be shown.
*
* @return the text to show in the Done button
*/
public String getDoneButtonText() {
if (doneButtonText == null) {
doneButtonText = getDefaultDoneButtonText();
}
return doneButtonText;
}
/**
* Causes the picker to load data from the service and display it to the user.
*
* @param forceReload if true, data will be loaded even if there is already data being displayed (or loading);
* if false, data will not be re-loaded if it is already displayed (or loading)
*/
public void loadData(boolean forceReload) {
if (!forceReload && loadingStrategy.isDataPresentOrLoading()) {
return;
}
loadDataSkippingRoundTripIfCached();
}
/**
* Updates the properties of the PickerFragment based on the contents of the supplied Bundle;
* calling Activities may use this to pass additional configuration information to the
* PickerFragment beyond what is specified in its XML layout.
*
* @param inState a Bundle containing keys corresponding to properties of the PickerFragment
*/
public void setSettingsFromBundle(Bundle inState) {
setPickerFragmentSettingsFromBundle(inState);
}
void setupViews(ViewGroup view) {
}
boolean filterIncludesItem(T graphObject) {
if (filter != null) {
return filter.includeItem(graphObject);
}
return true;
}
List getSelectedGraphObjects() {
return adapter.getGraphObjectsById(selectionStrategy.getSelectedIds());
}
void setSelectedGraphObjects(List objectIds) {
for(String objectId : objectIds) {
if(!this.selectionStrategy.isSelected(objectId)) {
this.selectionStrategy.toggleSelection(objectId);
}
}
}
void saveSettingsToBundle(Bundle outState) {
outState.putBoolean(SHOW_PICTURES_BUNDLE_KEY, showPictures);
if (!extraFields.isEmpty()) {
outState.putString(EXTRA_FIELDS_BUNDLE_KEY, TextUtils.join(",", extraFields));
}
outState.putBoolean(SHOW_TITLE_BAR_BUNDLE_KEY, showTitleBar);
outState.putString(TITLE_TEXT_BUNDLE_KEY, titleText);
outState.putString(DONE_BUTTON_TEXT_BUNDLE_KEY, doneButtonText);
}
abstract Request getRequestForLoadData(Session session);
abstract PickerFragmentAdapter createAdapter();
abstract LoadingStrategy createLoadingStrategy();
abstract SelectionStrategy createSelectionStrategy();
void onLoadingData() {
}
String getDefaultTitleText() {
return null;
}
String getDefaultDoneButtonText() {
return getString(R.string.com_facebook_picker_done_button_text);
}
void displayActivityCircle() {
if (activityCircle != null) {
layoutActivityCircle();
activityCircle.setVisibility(View.VISIBLE);
}
}
void layoutActivityCircle() {
// If we've got no data, make the activity circle full-opacity. Otherwise we'll dim it to avoid
// cluttering the UI.
float alpha = (!adapter.isEmpty()) ? .25f : 1.0f;
setAlpha(activityCircle, alpha);
}
void hideActivityCircle() {
if (activityCircle != null) {
// We use an animation to dim the activity circle; need to clear this or it will remain visible.
activityCircle.clearAnimation();
activityCircle.setVisibility(View.INVISIBLE);
}
}
void setSelectionStrategy(SelectionStrategy selectionStrategy) {
if (selectionStrategy != this.selectionStrategy) {
this.selectionStrategy = selectionStrategy;
if (adapter != null) {
// Adapter should cause a re-render.
adapter.notifyDataSetChanged();
}
}
}
void logAppEvents(boolean doneButtonClicked) {
}
private static void setAlpha(View view, float alpha) {
// Set the alpha appropriately (setAlpha is API >= 11, this technique works on all API levels).
AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);
alphaAnimation.setDuration(0);
alphaAnimation.setFillAfter(true);
view.startAnimation(alphaAnimation);
}
private void setPickerFragmentSettingsFromBundle(Bundle inState) {
// We do this in a separate non-overridable method so it is safe to call from the constructor.
if (inState != null) {
showPictures = inState.getBoolean(SHOW_PICTURES_BUNDLE_KEY, showPictures);
String extraFieldsString = inState.getString(EXTRA_FIELDS_BUNDLE_KEY);
if (extraFieldsString != null) {
String[] strings = extraFieldsString.split(",");
setExtraFields(Arrays.asList(strings));
}
showTitleBar = inState.getBoolean(SHOW_TITLE_BAR_BUNDLE_KEY, showTitleBar);
String titleTextString = inState.getString(TITLE_TEXT_BUNDLE_KEY);
if (titleTextString != null) {
titleText = titleTextString;
if (titleTextView != null) {
titleTextView.setText(titleText);
}
}
String doneButtonTextString = inState.getString(DONE_BUTTON_TEXT_BUNDLE_KEY);
if (doneButtonTextString != null) {
doneButtonText = doneButtonTextString;
if (doneButton != null) {
doneButton.setText(doneButtonText);
}
}
}
}
private void inflateTitleBar(ViewGroup view) {
ViewStub stub = (ViewStub) view.findViewById(R.id.com_facebook_picker_title_bar_stub);
if (stub != null) {
View titleBar = stub.inflate();
final RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
layoutParams.addRule(RelativeLayout.BELOW, R.id.com_facebook_picker_title_bar);
listView.setLayoutParams(layoutParams);
if (titleBarBackground != null) {
titleBar.setBackgroundDrawable(titleBarBackground);
}
doneButton = (Button) view.findViewById(R.id.com_facebook_picker_done_button);
if (doneButton != null) {
doneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
logAppEvents(true);
appEventsLogged = true;
if (onDoneButtonClickedListener != null) {
onDoneButtonClickedListener.onDoneButtonClicked(PickerFragment.this);
}
}
});
if (getDoneButtonText() != null) {
doneButton.setText(getDoneButtonText());
}
if (doneButtonBackground != null) {
doneButton.setBackgroundDrawable(doneButtonBackground);
}
}
titleTextView = (TextView) view.findViewById(R.id.com_facebook_picker_title);
if (titleTextView != null) {
if (getTitleText() != null) {
titleTextView.setText(getTitleText());
}
}
}
}
private void onListItemClick(ListView listView, View v, int position) {
@SuppressWarnings("unchecked")
T graphObject = (T) listView.getItemAtPosition(position);
String id = adapter.getIdOfGraphObject(graphObject);
selectionStrategy.toggleSelection(id);
adapter.notifyDataSetChanged();
if (onSelectionChangedListener != null) {
onSelectionChangedListener.onSelectionChanged(PickerFragment.this);
}
}
private void loadDataSkippingRoundTripIfCached() {
clearResults();
Request request = getRequestForLoadData(getSession());
if (request != null) {
onLoadingData();
loadingStrategy.startLoading(request);
}
}
private void clearResults() {
if (adapter != null) {
boolean wasSelection = !selectionStrategy.isEmpty();
boolean wasData = !adapter.isEmpty();
loadingStrategy.clearResults();
selectionStrategy.clear();
adapter.notifyDataSetChanged();
// Tell anyone who cares the data and selection has changed, if they have.
if (wasData && onDataChangedListener != null) {
onDataChangedListener.onDataChanged(PickerFragment.this);
}
if (wasSelection && onSelectionChangedListener != null) {
onSelectionChangedListener.onSelectionChanged(PickerFragment.this);
}
}
}
void updateAdapter(SimpleGraphObjectCursor data) {
if (adapter != null) {
// As we fetch additional results and add them to the table, we do not
// want the items displayed jumping around seemingly at random, frustrating the user's
// attempts at scrolling, etc. Since results may be added anywhere in
// the table, we choose to try to keep the first visible row in a fixed
// position (from the user's perspective). We try to keep it positioned at
// the same offset from the top of the screen so adding new items seems
// smoother, as opposed to having it "snap" to a multiple of row height
// We use the second row, to give context above and below it and avoid
// cases where the first row is only barely visible, thus providing little context.
// The exception is where the very first row is visible, in which case we use that.
View view = listView.getChildAt(1);
int anchorPosition = listView.getFirstVisiblePosition();
if (anchorPosition > 0) {
anchorPosition++;
}
GraphObjectAdapter.SectionAndItem anchorItem = adapter.getSectionAndItem(anchorPosition);
final int top = (view != null &&
anchorItem.getType() != GraphObjectAdapter.SectionAndItem.Type.ACTIVITY_CIRCLE) ?
view.getTop() : 0;
// Now actually add the results.
boolean dataChanged = adapter.changeCursor(data);
if (view != null && anchorItem != null) {
// Put the item back in the same spot it was.
final int newPositionOfItem = adapter.getPosition(anchorItem.sectionKey, anchorItem.graphObject);
if (newPositionOfItem != -1) {
listView.setSelectionFromTop(newPositionOfItem, top);
}
}
if (dataChanged && onDataChangedListener != null) {
onDataChangedListener.onDataChanged(PickerFragment.this);
}
}
}
private void reprioritizeDownloads() {
int lastVisibleItem = listView.getLastVisiblePosition();
if (lastVisibleItem >= 0) {
int firstVisibleItem = listView.getFirstVisiblePosition();
adapter.prioritizeViewRange(firstVisibleItem, lastVisibleItem, PROFILE_PICTURE_PREFETCH_BUFFER);
}
}
private ListView.OnScrollListener onScrollListener = new ListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
reprioritizeDownloads();
}
};
/**
* Callback interface that will be called when a network or other error is encountered
* while retrieving graph objects.
*/
public interface OnErrorListener {
/**
* Called when a network or other error is encountered.
*
* @param error a FacebookException representing the error that was encountered.
*/
void onError(PickerFragment> fragment, FacebookException error);
}
/**
* Callback interface that will be called when the underlying data being displayed in the
* picker has been updated.
*/
public interface OnDataChangedListener {
/**
* Called when the set of data being displayed in the picker has changed.
*/
void onDataChanged(PickerFragment> fragment);
}
/**
* Callback interface that will be called when the user selects or unselects graph objects
* in the picker.
*/
public interface OnSelectionChangedListener {
/**
* Called when the user selects or unselects graph objects in the picker.
*/
void onSelectionChanged(PickerFragment> fragment);
}
/**
* Callback interface that will be called when the user clicks the Done button on the
* title bar.
*/
public interface OnDoneButtonClickedListener {
/**
* Called when the user clicks the Done button.
*/
void onDoneButtonClicked(PickerFragment> fragment);
}
/**
* Callback interface that will be called to determine if a graph object should be displayed.
*
* @param
*/
public interface GraphObjectFilter {
/**
* Called to determine if a graph object should be displayed.
*
* @param graphObject the graph object
* @return true to display the graph object, false to hide it
*/
boolean includeItem(T graphObject);
}
abstract class LoadingStrategy {
protected final static int CACHED_RESULT_REFRESH_DELAY = 2 * 1000;
protected GraphObjectPagingLoader loader;
protected GraphObjectAdapter adapter;
public void attach(GraphObjectAdapter adapter) {
loader = (GraphObjectPagingLoader) getLoaderManager().initLoader(0, null,
new LoaderManager.LoaderCallbacks>() {
@Override
public Loader> onCreateLoader(int id, Bundle args) {
return LoadingStrategy.this.onCreateLoader();
}
@Override
public void onLoadFinished(Loader> loader,
SimpleGraphObjectCursor data) {
if (loader != LoadingStrategy.this.loader) {
throw new FacebookException("Received callback for unknown loader.");
}
LoadingStrategy.this.onLoadFinished((GraphObjectPagingLoader) loader, data);
}
@Override
public void onLoaderReset(Loader> loader) {
if (loader != LoadingStrategy.this.loader) {
throw new FacebookException("Received callback for unknown loader.");
}
LoadingStrategy.this.onLoadReset((GraphObjectPagingLoader) loader);
}
});
loader.setOnErrorListener(new GraphObjectPagingLoader.OnErrorListener() {
@Override
public void onError(FacebookException error, GraphObjectPagingLoader> loader) {
hideActivityCircle();
if (onErrorListener != null) {
onErrorListener.onError(PickerFragment.this, error);
}
}
});
this.adapter = adapter;
// Tell the adapter about any data we might already have.
this.adapter.changeCursor(loader.getCursor());
this.adapter.setOnErrorListener(new GraphObjectAdapter.OnErrorListener() {
@Override
public void onError(GraphObjectAdapter> adapter, FacebookException error) {
if (onErrorListener != null) {
onErrorListener.onError(PickerFragment.this, error);
}
}
});
}
public void detach() {
adapter.setDataNeededListener(null);
adapter.setOnErrorListener(null);
loader.setOnErrorListener(null);
loader = null;
adapter = null;
}
public void clearResults() {
if (loader != null) {
loader.clearResults();
}
}
public void startLoading(Request request) {
if (loader != null) {
loader.startLoading(request, canSkipRoundTripIfCached());
onStartLoading(loader, request);
}
}
public boolean isDataPresentOrLoading() {
return !adapter.isEmpty() || loader.isLoading();
}
protected GraphObjectPagingLoader onCreateLoader() {
return new GraphObjectPagingLoader(getActivity(), graphObjectClass);
}
protected void onStartLoading(GraphObjectPagingLoader loader, Request request) {
displayActivityCircle();
}
protected void onLoadReset(GraphObjectPagingLoader loader) {
adapter.changeCursor(null);
}
protected void onLoadFinished(GraphObjectPagingLoader loader, SimpleGraphObjectCursor data) {
updateAdapter(data);
}
protected boolean canSkipRoundTripIfCached() {
return true;
}
}
abstract class SelectionStrategy {
abstract boolean isSelected(String id);
abstract void toggleSelection(String id);
abstract Collection getSelectedIds();
abstract void clear();
abstract boolean isEmpty();
abstract boolean shouldShowCheckBoxIfUnselected();
abstract void saveSelectionToBundle(Bundle outBundle, String key);
abstract void readSelectionFromBundle(Bundle inBundle, String key);
}
class SingleSelectionStrategy extends SelectionStrategy {
private String selectedId;
public Collection getSelectedIds() {
return Arrays.asList(new String[]{selectedId});
}
@Override
boolean isSelected(String id) {
return selectedId != null && id != null && selectedId.equals(id);
}
@Override
void toggleSelection(String id) {
if (selectedId != null && selectedId.equals(id)) {
selectedId = null;
} else {
selectedId = id;
}
}
@Override
void saveSelectionToBundle(Bundle outBundle, String key) {
if (!TextUtils.isEmpty(selectedId)) {
outBundle.putString(key, selectedId);
}
}
@Override
void readSelectionFromBundle(Bundle inBundle, String key) {
if (inBundle != null) {
selectedId = inBundle.getString(key);
}
}
@Override
public void clear() {
selectedId = null;
}
@Override
boolean isEmpty() {
return selectedId == null;
}
@Override
boolean shouldShowCheckBoxIfUnselected() {
return false;
}
}
class MultiSelectionStrategy extends SelectionStrategy {
private Set selectedIds = new HashSet();
public Collection getSelectedIds() {
return selectedIds;
}
@Override
boolean isSelected(String id) {
return id != null && selectedIds.contains(id);
}
@Override
void toggleSelection(String id) {
if (id != null) {
if (selectedIds.contains(id)) {
selectedIds.remove(id);
} else {
selectedIds.add(id);
}
}
}
@Override
void saveSelectionToBundle(Bundle outBundle, String key) {
if (!selectedIds.isEmpty()) {
String ids = TextUtils.join(",", selectedIds);
outBundle.putString(key, ids);
}
}
@Override
void readSelectionFromBundle(Bundle inBundle, String key) {
if (inBundle != null) {
String ids = inBundle.getString(key);
if (ids != null) {
String[] splitIds = TextUtils.split(ids, ",");
selectedIds.clear();
Collections.addAll(selectedIds, splitIds);
}
}
}
@Override
public void clear() {
selectedIds.clear();
}
@Override
boolean isEmpty() {
return selectedIds.isEmpty();
}
@Override
boolean shouldShowCheckBoxIfUnselected() {
return true;
}
}
abstract class PickerFragmentAdapter extends GraphObjectAdapter {
public PickerFragmentAdapter(Context context) {
super(context);
}
@Override
boolean isGraphObjectSelected(String graphObjectId) {
return selectionStrategy.isSelected(graphObjectId);
}
@Override
void updateCheckboxState(CheckBox checkBox, boolean graphObjectSelected) {
checkBox.setChecked(graphObjectSelected);
int visible = (graphObjectSelected || selectionStrategy
.shouldShowCheckBoxIfUnselected()) ? View.VISIBLE : View.GONE;
checkBox.setVisibility(visible);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy