diff --git a/ParseLoginSampleBasic/build.gradle b/ParseLoginSampleBasic/build.gradle
index 42f48eb..7aa1c05 100644
--- a/ParseLoginSampleBasic/build.gradle
+++ b/ParseLoginSampleBasic/build.gradle
@@ -7,6 +7,7 @@ dependencies {
compile rootProject.ext.androidSupport
compile files(rootProject.ext.parsePath)
compile files(rootProject.ext.parseFacebookUtilsPath)
+ compile files(rootProject.ext.parseTwitterUtilsPath)
}
android {
diff --git a/ParseLoginSampleCodeCustomization/build.gradle b/ParseLoginSampleCodeCustomization/build.gradle
index 42f48eb..7aa1c05 100644
--- a/ParseLoginSampleCodeCustomization/build.gradle
+++ b/ParseLoginSampleCodeCustomization/build.gradle
@@ -7,6 +7,7 @@ dependencies {
compile rootProject.ext.androidSupport
compile files(rootProject.ext.parsePath)
compile files(rootProject.ext.parseFacebookUtilsPath)
+ compile files(rootProject.ext.parseTwitterUtilsPath)
}
android {
diff --git a/ParseLoginSampleLayoutOverride/build.gradle b/ParseLoginSampleLayoutOverride/build.gradle
index 42f48eb..7aa1c05 100644
--- a/ParseLoginSampleLayoutOverride/build.gradle
+++ b/ParseLoginSampleLayoutOverride/build.gradle
@@ -7,6 +7,7 @@ dependencies {
compile rootProject.ext.androidSupport
compile files(rootProject.ext.parsePath)
compile files(rootProject.ext.parseFacebookUtilsPath)
+ compile files(rootProject.ext.parseTwitterUtilsPath)
}
android {
diff --git a/ParseLoginSampleWithDispatchActivity/build.gradle b/ParseLoginSampleWithDispatchActivity/build.gradle
index 42f48eb..7aa1c05 100644
--- a/ParseLoginSampleWithDispatchActivity/build.gradle
+++ b/ParseLoginSampleWithDispatchActivity/build.gradle
@@ -7,6 +7,7 @@ dependencies {
compile rootProject.ext.androidSupport
compile files(rootProject.ext.parsePath)
compile files(rootProject.ext.parseFacebookUtilsPath)
+ compile files(rootProject.ext.parseTwitterUtilsPath)
}
android {
diff --git a/ParseLoginUI/build.gradle b/ParseLoginUI/build.gradle
index 00108d2..f6c81e2 100644
--- a/ParseLoginUI/build.gradle
+++ b/ParseLoginUI/build.gradle
@@ -13,8 +13,9 @@ dependencies {
//
// Since the dependency below is "provided" instead of "compile", your project's build.gradle
// does not have to refer to the same Parse SDK instance that's in the ParseLoginUI/libs folder.
- provided files("$rootProject.projectDir/ParseLoginUI/libs/Parse-1.9.4.jar")
- provided files("$rootProject.projectDir/ParseLoginUI/libs/ParseFacebookUtilsV4-1.9.4.jar")
+ provided files("$rootProject.projectDir/ParseLoginUI/libs/Parse-1.10.0.jar")
+ provided files("$rootProject.projectDir/ParseLoginUI/libs/ParseFacebookUtilsV4-1.10.0.jar")
+ provided files("$rootProject.projectDir/ParseLoginUI/libs/ParseTwitterUtils-1.10.0.jar")
}
android {
diff --git a/ParseLoginUI/libs/Parse-1.10.0.jar b/ParseLoginUI/libs/Parse-1.10.0.jar
new file mode 100644
index 0000000..08b0d7b
Binary files /dev/null and b/ParseLoginUI/libs/Parse-1.10.0.jar differ
diff --git a/ParseLoginUI/libs/Parse-1.9.4.jar b/ParseLoginUI/libs/Parse-1.9.4.jar
deleted file mode 100644
index 8d1bc70..0000000
Binary files a/ParseLoginUI/libs/Parse-1.9.4.jar and /dev/null differ
diff --git a/ParseLoginUI/libs/ParseFacebookUtilsV4-1.9.4.jar b/ParseLoginUI/libs/ParseFacebookUtilsV4-1.10.0.jar
similarity index 86%
rename from ParseLoginUI/libs/ParseFacebookUtilsV4-1.9.4.jar
rename to ParseLoginUI/libs/ParseFacebookUtilsV4-1.10.0.jar
index 1e336b5..3e6eadb 100644
Binary files a/ParseLoginUI/libs/ParseFacebookUtilsV4-1.9.4.jar and b/ParseLoginUI/libs/ParseFacebookUtilsV4-1.10.0.jar differ
diff --git a/ParseLoginUI/libs/ParseTwitterUtils-1.10.0.jar b/ParseLoginUI/libs/ParseTwitterUtils-1.10.0.jar
new file mode 100644
index 0000000..b3e929c
Binary files /dev/null and b/ParseLoginUI/libs/ParseTwitterUtils-1.10.0.jar differ
diff --git a/ParseLoginUI/src/com/parse/ParseImageView.java b/ParseLoginUI/src/com/parse/ParseImageView.java
new file mode 100644
index 0000000..d8a4823
--- /dev/null
+++ b/ParseLoginUI/src/com/parse/ParseImageView.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2014, Parse, LLC. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Parse.
+ *
+ * As with any software that integrates with the Parse platform, your use of
+ * this software is subject to the Parse Terms of Service
+ * [https://www.parse.com/about/terms]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+package com.parse;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/**
+ * A specialized {@code ImageView} that downloads and displays remote images stored on Parse's
+ * servers.
+ *
+ * Given a {@code ParseFile} storing an image, a {@code ParseImageView} works seamlessly to fetch
+ * the file data and display it in the background. See below for an example:
+ *
+ *
+ * ParseImageView imageView = (ParseImageView) findViewById(android.R.id.icon);
+ * // The placeholder will be used before and during the fetch, to be replaced by the fetched image
+ * // data.
+ * imageView.setPlaceholder(getResources().getDrawable(R.drawable.placeholder));
+ * imageView.setParseFile(file);
+ * imageView.loadInBackground(new GetDataCallback() {
+ * @Override
+ * public void done(byte[] data, ParseException e) {
+ * Log.i("ParseImageView",
+ * "Fetched! Data length: " + data.length + ", or exception: " + e.getMessage());
+ * }
+ * });
+ *
+ */
+public class ParseImageView extends ImageView {
+ private ParseFile file;
+ private Drawable placeholder;
+ private boolean isLoaded = false;
+
+ /**
+ * Simple constructor to use when creating a {@code ParseImageView} from code.
+ *
+ * @param context
+ * Context for this View
+ */
+ public ParseImageView(Context context) {
+ super(context);
+ }
+
+ /**
+ * Constructor that is called when inflating a {@code ParseImageView} from XML.
+ *
+ * @param context
+ * Context for this View
+ * @param attributeSet
+ * AttributeSet defined for this View in XML
+ */
+ public ParseImageView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style.
+ *
+ * @param context
+ * Context for this View
+ * @param attributeSet
+ * AttributeSet defined for this View in XML
+ * @param defStyle
+ * Class-specific base style.
+ */
+ public ParseImageView(Context context, AttributeSet attributeSet, int defStyle) {
+ super(context, attributeSet, defStyle);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ // AdapterViews tend to try and avoid calling this, instead preferring to recycle the Views +
+ // subviews. This is, however, called when the AdapterView itself is detached, or the Activity
+ // is destroyed.
+ if (this.file != null) {
+ this.file.cancel();
+ }
+ }
+
+ @Override
+ public void setImageBitmap(Bitmap bitmap) {
+ super.setImageBitmap(bitmap);
+ this.isLoaded = true;
+ }
+
+ /**
+ * Sets the placeholder to be used while waiting for an image to be loaded.
+ *
+ * @param placeholder
+ * A {@code Drawable} to be displayed while the remote image data is being fetched. This
+ * value can be null, and this {@code ImageView} will simply be blank while data is
+ * fetched.
+ */
+ public void setPlaceholder(Drawable placeholder) {
+ this.placeholder = placeholder;
+ if (!this.isLoaded) {
+ this.setImageDrawable(this.placeholder);
+ }
+ }
+
+ /**
+ * Sets the remote file on Parse's server that stores the image.
+ *
+ * @param file
+ * The remote file on Parse's server.
+ */
+ public void setParseFile(ParseFile file) {
+ if (this.file != null) {
+ this.file.cancel();
+ }
+ this.isLoaded = false;
+ this.file = file;
+ this.setImageDrawable(this.placeholder);
+ }
+
+ /**
+ * Kick off downloading of remote image. When the download is finished, the image data will be
+ * displayed.
+ *
+ * @return A Task that is resolved when the image data is fetched and this View displays the image.
+ */
+ public Task loadInBackground() {
+ if (file == null) {
+ return Task.forResult(null);
+ }
+
+ final ParseFile loadingFile = file;
+ return file.getDataInBackground().onSuccessTask(new Continuation>() {
+ @Override
+ public Task then(Task task) throws Exception {
+ byte[] data = task.getResult();
+ if (file != loadingFile) {
+ // This prevents the very slim chance of the file's download finishing and the callback
+ // triggering just before this ImageView is reused for another ParseObject.
+ return Task.cancelled();
+ }
+ if (data != null) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ if (bitmap != null) {
+ setImageBitmap(bitmap);
+ }
+ }
+ return task;
+ }
+ }, Task.UI_THREAD_EXECUTOR);
+ }
+
+ /**
+ * Kick off downloading of remote image. When the download is finished, the image data will be
+ * displayed and the {@code completionCallback} will be triggered.
+ *
+ * @param completionCallback
+ * A custom {@code GetDataCallback} to be called after the image data is fetched and this
+ * {@code ImageView} displays the image.
+ */
+ public void loadInBackground(final GetDataCallback completionCallback) {
+ ParseTaskUtils.callbackOnMainThreadAsync(loadInBackground(), completionCallback, true);
+ }
+}
\ No newline at end of file
diff --git a/ParseLoginUI/src/com/parse/ParseQueryAdapter.java b/ParseLoginUI/src/com/parse/ParseQueryAdapter.java
new file mode 100644
index 0000000..82f57ab
--- /dev/null
+++ b/ParseLoginUI/src/com/parse/ParseQueryAdapter.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (c) 2014, Parse, LLC. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Parse.
+ *
+ * As with any software that integrates with the Parse platform, your use of
+ * this software is subject to the Parse Terms of Service
+ * [https://www.parse.com/about/terms]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+package com.parse;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.parse.ParseQuery.CachePolicy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.WeakHashMap;
+
+import bolts.Capture;
+
+/**
+ * A {@code ParseQueryAdapter} handles the fetching of objects by page, and displaying objects as
+ * views in a {@link android.widget.ListView}.
+ *
+ * This class is highly configurable, but also intended to be easy to get started with. See below
+ * for an example of using a {@code ParseQueryAdapter} inside an {@link android.app.Activity}'s
+ * {@code onCreate}:
+ *
+ * final ParseQueryAdapter adapter = new ParseQueryAdapter(this, "TestObject");
+ * adapter.setTextKey("name");
+ *
+ * ListView listView = (ListView) findViewById(R.id.listview);
+ * listView.setAdapter(adapter);
+ *
+ *
+ * Below, an example showing off the level of configuration available with this class:
+ *
+ * // Instantiate a QueryFactory to define the ParseQuery to be used for fetching items in this
+ * // Adapter.
+ * ParseQueryAdapter.QueryFactory<ParseObject> factory =
+ * new ParseQueryAdapter.QueryFactory<ParseObject>() {
+ * public ParseQuery create() {
+ * ParseQuery query = new ParseQuery("Customer");
+ * query.whereEqualTo("activated", true);
+ * query.orderByDescending("moneySpent");
+ * return query;
+ * }
+ * };
+ *
+ * // Pass the factory into the ParseQueryAdapter's constructor.
+ * ParseQueryAdapter<ParseObject> adapter = new ParseQueryAdapter<ParseObject>(this, factory);
+ * adapter.setTextKey("name");
+ *
+ * // Perhaps set a callback to be fired upon successful loading of a new set of ParseObjects.
+ * adapter.addOnQueryLoadListener(new OnQueryLoadListener<ParseObject>() {
+ * public void onLoading() {
+ * // Trigger any "loading" UI
+ * }
+ *
+ * public void onLoaded(List<ParseObject> objects, ParseException e) {
+ * // Execute any post-loading logic, hide "loading" UI
+ * }
+ * });
+ *
+ * // Attach it to your ListView, as in the example above
+ * ListView listView = (ListView) findViewById(R.id.listview);
+ * listView.setAdapter(adapter);
+ *
+ */
+public class ParseQueryAdapter extends BaseAdapter {
+
+ /**
+ * Implement to construct your own custom {@link ParseQuery} for fetching objects.
+ */
+ public static interface QueryFactory {
+ public ParseQuery create();
+ }
+
+ /**
+ * Implement with logic that is called before and after objects are fetched from Parse by the
+ * adapter.
+ */
+ public static interface OnQueryLoadListener {
+ public void onLoading();
+
+ public void onLoaded(List objects, Exception e);
+ }
+
+ // The key to use to display on the cell text label.
+ private String textKey;
+
+ // The key to use to fetch an image for display in the cell's image view.
+ private String imageKey;
+
+ // The number of objects to show per page (default: 25)
+ private int objectsPerPage = 25;
+
+ // Whether the table should use the built-in pagination feature (default:
+ // true)
+ private boolean paginationEnabled = true;
+
+ // A Drawable placeholder, to be set on ParseImageViews while images are loading. Can be null.
+ private Drawable placeholder;
+
+ // A WeakHashMap, holding references to ParseImageViews that have been configured by this PQA.
+ // Accessed and iterated over if setPlaceholder(Drawable) is called after some set of
+ // ParseImageViews have already been instantiated and configured.
+ private WeakHashMap imageViewSet = new WeakHashMap<>();
+
+ // A WeakHashMap, keeping track of the DataSetObservers on this class
+ private WeakHashMap dataSetObservers = new WeakHashMap<>();
+
+ // Whether the adapter should trigger loadObjects() on registerDataSetObserver(); Defaults to
+ // true.
+ private boolean autoload = true;
+
+ private Context context;
+
+ private List objects = new ArrayList<>();
+
+ // Used to keep track of the pages of objects when using CACHE_THEN_NETWORK. When using this,
+ // the data will be flattened and put into the objects list.
+ private List> objectPages = new ArrayList<>();
+
+ private int currentPage = 0;
+
+ private Integer itemResourceId;
+
+ private boolean hasNextPage = true;
+
+ private QueryFactory queryFactory;
+
+ private List> onQueryLoadListeners =
+ new ArrayList<>();
+
+ private static final int VIEW_TYPE_ITEM = 0;
+ private static final int VIEW_TYPE_NEXT_PAGE = 1;
+
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Given a {@link ParseObject} subclass, this adapter will
+ * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param clazz
+ * The {@link ParseObject} subclass type to fetch and display.
+ */
+ public ParseQueryAdapter(Context context, Class extends ParseObject> clazz) {
+ this(context, ParseObject.getClassName(clazz));
+ }
+
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Given a {@link ParseObject} subclass, this adapter will
+ * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param className
+ * The name of the Parse class of {@link ParseObject}s to display.
+ */
+ public ParseQueryAdapter(Context context, final String className) {
+ this(context, new QueryFactory() {
+ @Override
+ public ParseQuery create() {
+ ParseQuery query = ParseQuery.getQuery(className);
+ query.orderByDescending("createdAt");
+
+ return query;
+ }
+ });
+
+ if (className == null) {
+ throw new RuntimeException("You need to specify a className for the ParseQueryAdapter");
+ }
+ }
+
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Given a {@link ParseObject} subclass, this adapter will
+ * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param clazz
+ * The {@link ParseObject} subclass type to fetch and display.
+ * @param itemViewResource
+ * A resource id that represents the layout for an item in the AdapterView.
+ */
+ public ParseQueryAdapter(Context context, Class extends ParseObject> clazz,
+ int itemViewResource) {
+ this(context, ParseObject.getClassName(clazz), itemViewResource);
+ }
+
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Given a {@link ParseObject} subclass, this adapter will
+ * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param className
+ * The name of the Parse class of {@link ParseObject}s to display.
+ * @param itemViewResource
+ * A resource id that represents the layout for an item in the AdapterView.
+ */
+ public ParseQueryAdapter(Context context, final String className, int itemViewResource) {
+ this(context, new QueryFactory() {
+ @Override
+ public ParseQuery create() {
+ ParseQuery query = ParseQuery.getQuery(className);
+ query.orderByDescending("createdAt");
+
+ return query;
+ }
+ }, itemViewResource);
+
+ if (className == null) {
+ throw new RuntimeException("You need to specify a className for the ParseQueryAdapter");
+ }
+ }
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Allows the caller to define further constraints on the
+ * {@link ParseQuery} to be used when fetching items from Parse.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param queryFactory
+ * A {@link QueryFactory} to build a {@link ParseQuery} for fetching objects.
+ */
+ public ParseQueryAdapter(Context context, QueryFactory queryFactory) {
+ this(context, queryFactory, null);
+ }
+
+ /**
+ * Constructs a {@code ParseQueryAdapter}. Allows the caller to define further constraints on the
+ * {@link ParseQuery} to be used when fetching items from Parse.
+ *
+ * @param context
+ * The activity utilizing this adapter.
+ * @param queryFactory
+ * A {@link QueryFactory} to build a {@link ParseQuery} for fetching objects.
+ * @param itemViewResource
+ * A resource id that represents the layout for an item in the AdapterView.
+ */
+ public ParseQueryAdapter(Context context, QueryFactory queryFactory, int itemViewResource) {
+ this(context, queryFactory, Integer.valueOf(itemViewResource));
+ }
+
+ private ParseQueryAdapter(Context context, QueryFactory queryFactory, Integer itemViewResource) {
+ super();
+ this.context = context;
+ this.queryFactory = queryFactory;
+ this.itemResourceId = itemViewResource;
+ }
+
+ /**
+ * Return the context provided by the {@code Activity} utilizing this {@code ParseQueryAdapter}.
+ *
+ * @return The activity utilizing this adapter.
+ */
+ public Context getContext() {
+ return this.context;
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public T getItem(int index) {
+ if (index == this.getPaginationCellRow()) {
+ return null;
+ }
+ return this.objects.get(index);
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == this.getPaginationCellRow()) {
+ return VIEW_TYPE_NEXT_PAGE;
+ }
+ return VIEW_TYPE_ITEM;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ super.registerDataSetObserver(observer);
+ this.dataSetObservers.put(observer, null);
+ if (this.autoload) {
+ this.loadObjects();
+ }
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ super.unregisterDataSetObserver(observer);
+ this.dataSetObservers.remove(observer);
+ }
+
+ /**
+ * Remove all elements from the list.
+ */
+ public void clear() {
+ this.objectPages.clear();
+ syncObjectsWithPages();
+ this.notifyDataSetChanged();
+ this.currentPage = 0;
+ }
+
+ /**
+ * Clears the table and loads the first page of objects asynchronously. This method is called
+ * automatically when this {@code Adapter} is attached to an {@code AdapterView}.
+ *
+ * {@code loadObjects()} should only need to be called if {@link #setAutoload(boolean)} is set to
+ * {@code false}.
+ */
+ public void loadObjects() {
+ this.loadObjects(0, true);
+ }
+
+ private void loadObjects(final int page, final boolean shouldClear) {
+ final ParseQuery query = this.queryFactory.create();
+
+ if (this.objectsPerPage > 0 && this.paginationEnabled) {
+ this.setPageOnQuery(page, query);
+ }
+
+ this.notifyOnLoadingListeners();
+
+ // Create a new page
+ if (page >= objectPages.size()) {
+ objectPages.add(page, new ArrayList());
+ }
+
+ // In the case of CACHE_THEN_NETWORK, two callbacks will be called. Using this flag to keep track,
+ final Capture firstCallBack = new Capture<>(true);
+
+ query.findInBackground(new FindCallback() {
+ @SuppressLint("ShowToast")
+ @Override
+ public void done(List foundObjects, ParseException e) {
+ if ((!Parse.isLocalDatastoreEnabled() &&
+ query.getCachePolicy() == CachePolicy.CACHE_ONLY)
+ && (e != null) && e.getCode() == ParseException.CACHE_MISS) {
+ // no-op on cache miss
+ return;
+ }
+
+ if ((e != null)
+ && ((e.getCode() == ParseException.CONNECTION_FAILED) || (e.getCode() != ParseException.CACHE_MISS))) {
+ hasNextPage = true;
+ } else if (foundObjects != null) {
+ if (shouldClear && firstCallBack.get()) {
+ objectPages.clear();
+ objectPages.add(new ArrayList());
+ currentPage = page;
+ firstCallBack.set(false);
+ }
+
+ // Only advance the page, this prevents second call back from CACHE_THEN_NETWORK to
+ // reset the page.
+ if (page >= currentPage) {
+ currentPage = page;
+
+ // since we set limit == objectsPerPage + 1
+ hasNextPage = (foundObjects.size() > objectsPerPage);
+ }
+
+ if (paginationEnabled && foundObjects.size() > objectsPerPage) {
+ // Remove the last object, fetched in order to tell us whether there was a "next page"
+ foundObjects.remove(objectsPerPage);
+ }
+
+ List currentPage = objectPages.get(page);
+ currentPage.clear();
+ currentPage.addAll(foundObjects);
+
+ syncObjectsWithPages();
+
+ // executes on the UI thread
+ notifyDataSetChanged();
+ }
+
+ notifyOnLoadedListeners(foundObjects, e);
+ }
+ });
+ }
+
+ /**
+ * This is a helper function to sync the objects with objectPages. This is only used with the
+ * CACHE_THEN_NETWORK option.
+ */
+ private void syncObjectsWithPages() {
+ objects.clear();
+ for (List pageOfObjects : objectPages) {
+ objects.addAll(pageOfObjects);
+ }
+ }
+
+ /**
+ * Loads the next page of objects, appends to table, and notifies the UI that the model has
+ * changed.
+ */
+ public void loadNextPage() {
+ this.loadObjects(currentPage + 1, false);
+ }
+
+ /**
+ * Overrides {@link Adapter#getCount()} method to return the number of cells to
+ * display. If pagination is turned on, this count will include an extra +1 count for the
+ * pagination cell row.
+ *
+ * @return The number of cells to be displayed by the {@link android.widget.ListView}.
+ */
+ @Override
+ public int getCount() {
+ int count = this.objects.size();
+
+ if (this.shouldShowPaginationCell()) {
+ count++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Override this method to customize each cell given a {@link ParseObject}.
+ *
+ * If a view is not provided, a default view will be created based upon
+ * {@code android.R.layout.activity_list_item}.
+ *
+ * This method expects a {@code TextView} with id {@code android.R.id.text1} in your object views.
+ * If {@link #setImageKey(String)} was used, this method also expects an {@code ImageView} with id
+ * {@code android.R.id.icon}.
+ *
+ * This method displays the text value specified by the text key (set via
+ * {@link #setTextKey(String)}) and an image (described by a {@link ParseFile}, under the key set
+ * via {@link #setImageKey(String)}) if applicable. If the text key is not set, the value for
+ * {@link ParseObject#getObjectId()} will be displayed instead.
+ *
+ * @param object
+ * The {@link ParseObject} associated with this item.
+ * @param v
+ * The {@code View} associated with this row. This view, if non-null, is being recycled
+ * and intended to be used for displaying this item.
+ * @param parent
+ * The parent that this view will eventually be attached to
+ * @return The customized view displaying the {@link ParseObject}'s information.
+ */
+ public View getItemView(T object, View v, ViewGroup parent) {
+ if (v == null) {
+ v = this.getDefaultView(this.context);
+ }
+
+ TextView textView;
+ try {
+ textView = (TextView) v.findViewById(android.R.id.text1);
+ } catch (ClassCastException ex) {
+ throw new IllegalStateException(
+ "Your object views must have a TextView whose id attribute is 'android.R.id.text1'", ex);
+ }
+
+ if (textView != null) {
+ if (this.textKey == null) {
+ textView.setText(object.getObjectId());
+ } else if (object.get(this.textKey) != null) {
+ textView.setText(object.get(this.textKey).toString());
+ } else {
+ textView.setText(null);
+ }
+ }
+
+ if (this.imageKey != null) {
+ ParseImageView imageView;
+ try {
+ imageView = (ParseImageView) v.findViewById(android.R.id.icon);
+ } catch (ClassCastException ex) {
+ throw new IllegalStateException(
+ "Your object views must have a ParseImageView whose id attribute is 'android.R.id.icon'",
+ ex);
+ }
+ if (imageView == null) {
+ throw new IllegalStateException(
+ "Your object views must have a ParseImageView whose id attribute is 'android.R.id.icon' if an imageKey is specified");
+ }
+ if (!this.imageViewSet.containsKey(imageView)) {
+ this.imageViewSet.put(imageView, null);
+ }
+ imageView.setPlaceholder(this.placeholder);
+ imageView.setParseFile((ParseFile) object.get(this.imageKey));
+ imageView.loadInBackground();
+ }
+
+ return v;
+ }
+
+ /**
+ * Override this method to customize the "Load Next Page" cell, visible when pagination is turned
+ * on and there may be more results to display.
+ *
+ * This method expects a {@code TextView} with id {@code android.R.id.text1}.
+ *
+ * @param v
+ * The view object associated with this row + type (a "Next Page" view, instead of an
+ * "Item" view).
+ * @param parent
+ * The parent that this view will eventually be attached to
+ * @return The view object that allows the user to paginate.
+ */
+ public View getNextPageView(View v, ViewGroup parent) {
+ if (v == null) {
+ v = this.getDefaultView(this.context);
+ }
+ TextView textView = (TextView) v.findViewById(android.R.id.text1);
+ textView.setText("Load more...");
+ return v;
+ }
+
+ /**
+ * The base class, {@code Adapter}, defines a {@code getView} method intended to display data at
+ * the specified position in the data set. We override it here in order to toggle between
+ * {@link #getNextPageView(View, ViewGroup)} and
+ * {@link #getItemView(ParseObject, View, ViewGroup)} depending on the value of
+ * {@link #getItemViewType(int)}.
+ */
+ @Override
+ public final View getView(int position, View convertView, ViewGroup parent) {
+ if (this.getItemViewType(position) == VIEW_TYPE_NEXT_PAGE) {
+ View nextPageView = this.getNextPageView(convertView, parent);
+ nextPageView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ loadNextPage();
+ }
+ });
+ return nextPageView;
+ }
+ return this.getItemView(this.getItem(position), convertView, parent);
+ }
+
+ /**
+ * Override this method to manually paginate the provided {@code ParseQuery}. By default, this
+ * method will set the {@code limit} value to {@link #getObjectsPerPage()} and the {@code skip}
+ * value to {@link #getObjectsPerPage()} * {@code page}.
+ *
+ * Overriding this method will not be necessary, in most cases.
+ *
+ * @param page
+ * the page number of results to fetch from Parse.
+ * @param query
+ * the {@link ParseQuery} used to fetch items from Parse. This query will be mutated and
+ * used in its mutated form.
+ */
+ protected void setPageOnQuery(int page, ParseQuery query) {
+ query.setLimit(this.objectsPerPage + 1);
+ query.setSkip(page * this.objectsPerPage);
+ }
+
+ public void setTextKey(String textKey) {
+ this.textKey = textKey;
+ }
+
+ public void setImageKey(String imageKey) {
+ this.imageKey = imageKey;
+ }
+
+ public void setObjectsPerPage(int objectsPerPage) {
+ this.objectsPerPage = objectsPerPage;
+ }
+
+ public int getObjectsPerPage() {
+ return this.objectsPerPage;
+ }
+
+ /**
+ * Enable or disable pagination of results. Defaults to true.
+ *
+ * @param paginationEnabled
+ * Defaults to true.
+ */
+ public void setPaginationEnabled(boolean paginationEnabled) {
+ this.paginationEnabled = paginationEnabled;
+ }
+
+ /**
+ * Sets a placeholder image to be used when fetching data for each item in the {@code AdapterView}
+ * . Will not be used if {@link #setImageKey(String)} was not used to define which images to
+ * display.
+ *
+ * @param placeholder
+ * A {@code Drawable} to be displayed while the remote image data is being fetched. This
+ * value can be null, and {@code ImageView}s in this AdapterView will simply be blank
+ * while data is being fetched.
+ */
+ public void setPlaceholder(Drawable placeholder) {
+ if (this.placeholder == placeholder) {
+ return;
+ }
+ this.placeholder = placeholder;
+ Iterator iter = this.imageViewSet.keySet().iterator();
+ ParseImageView imageView;
+ while (iter.hasNext()) {
+ imageView = iter.next();
+ if (imageView != null) {
+ imageView.setPlaceholder(this.placeholder);
+ }
+ }
+ }
+
+ /**
+ * Enable or disable the automatic loading of results upon attachment to an {@code AdapterView}.
+ * Defaults to true.
+ *
+ * @param autoload
+ * Defaults to true.
+ */
+ public void setAutoload(boolean autoload) {
+ if (this.autoload == autoload) {
+ // An extra precaution to prevent an overzealous setAutoload(true) after assignment to an
+ // AdapterView from triggering an unnecessary additional loadObjects().
+ return;
+ }
+ this.autoload = autoload;
+ if (this.autoload && !this.dataSetObservers.isEmpty() && this.objects.isEmpty()) {
+ this.loadObjects();
+ }
+ }
+
+ public void addOnQueryLoadListener(OnQueryLoadListener listener) {
+ this.onQueryLoadListeners.add(listener);
+ }
+
+ public void removeOnQueryLoadListener(OnQueryLoadListener listener) {
+ this.onQueryLoadListeners.remove(listener);
+ }
+
+ private View getDefaultView(Context context) {
+ if (this.itemResourceId != null) {
+ return View.inflate(context, this.itemResourceId, null);
+ }
+ LinearLayout view = new LinearLayout(context);
+ view.setPadding(8, 4, 8, 4);
+
+ ParseImageView imageView = new ParseImageView(context);
+ imageView.setId(android.R.id.icon);
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(50, 50));
+ view.addView(imageView);
+
+ TextView textView = new TextView(context);
+ textView.setId(android.R.id.text1);
+ textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ textView.setPadding(8, 0, 0, 0);
+ view.addView(textView);
+
+ return view;
+ }
+
+ private int getPaginationCellRow() {
+ return this.objects.size();
+ }
+
+ private boolean shouldShowPaginationCell() {
+ return this.paginationEnabled && this.objects.size() > 0 && this.hasNextPage;
+ }
+
+ private void notifyOnLoadingListeners() {
+ for (OnQueryLoadListener listener : this.onQueryLoadListeners) {
+ listener.onLoading();
+ }
+ }
+
+ private void notifyOnLoadedListeners(List objects, Exception e) {
+ for (OnQueryLoadListener listener : this.onQueryLoadListeners) {
+ listener.onLoaded(objects, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e6ff26b..3ef1a06 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,6 +24,7 @@ ext {
facebookSDK = 'com.facebook.android:facebook-android-sdk:4.0.1'
androidSupport = 'com.android.support:support-v4:22.0.0'
bolts = 'com.parse.bolts:bolts-android:1.2.0'
- parsePath = "$rootProject.projectDir/ParseLoginUI/libs/Parse-1.9.4.jar"
- parseFacebookUtilsPath = "$rootProject.projectDir/ParseLoginUI/libs/ParseFacebookUtilsV4-1.9.4.jar"
+ parsePath = "$rootProject.projectDir/ParseLoginUI/libs/Parse-1.10.0.jar"
+ parseFacebookUtilsPath = "$rootProject.projectDir/ParseLoginUI/libs/ParseFacebookUtilsV4-1.10.0.jar"
+ parseTwitterUtilsPath = "$rootProject.projectDir/ParseLoginUI/libs/ParseTwitterUtils-1.10.0.jar"
}