From a57f22efa9091a953288ba5f648415c3a9e94106 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Fri, 13 Nov 2015 01:30:15 -0800 Subject: [PATCH 1/5] ParseQueryPagerAdapter for ViewPager First commit of a ParseQueryAdapter for use with ViewPager. Uses FragmentStatePagerAdapter v13 under the hood, but works basically like ParseQueryAdapter. --- ParseUI-Widget/build.gradle | 4 +- ...seQueryPagerAdapterOfflineEnabledTest.java | 23 + .../com/parse/ParseQueryPagerAdapterTest.java | 618 +++++++++++++++ .../com/parse/ParseQueryPagerAdapter.java | 723 ++++++++++++++++++ 4 files changed, 1367 insertions(+), 1 deletion(-) create mode 100644 ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterOfflineEnabledTest.java create mode 100644 ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterTest.java create mode 100644 ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java diff --git a/ParseUI-Widget/build.gradle b/ParseUI-Widget/build.gradle index 5142939..e9568ff 100644 --- a/ParseUI-Widget/build.gradle +++ b/ParseUI-Widget/build.gradle @@ -32,6 +32,8 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'org.skyscreamer:jsonassert:1.2.3' + + compile 'com.android.support:support-v13:23.1.0' } android { @@ -39,7 +41,7 @@ android { buildToolsVersion "21.1.1" defaultConfig { - minSdkVersion 9 + minSdkVersion 13 targetSdkVersion 21 versionName project.version versionCode 1 diff --git a/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterOfflineEnabledTest.java b/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterOfflineEnabledTest.java new file mode 100644 index 0000000..e7f8a79 --- /dev/null +++ b/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterOfflineEnabledTest.java @@ -0,0 +1,23 @@ +package com.parse; + +/** + * Modified by Pablo Baxter (Github: soaboz) + */ +public class ParseQueryPagerAdapterOfflineEnabledTest extends ParseQueryPagerAdapterTest { + @Override + public void setUp() throws Exception { + super.setUp(); + Parse.enableLocalDatastore(null); + } + + @Override + public void tearDown() throws Exception { + Parse.disableLocalDatastore(); + super.tearDown(); + } + + @Override + public void testLoadObjectsWithCacheThenNetworkQueryAndPagination() throws Exception { + // Do nothing, there is no cache policy when LDS is enabled. + } +} diff --git a/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterTest.java b/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterTest.java new file mode 100644 index 0000000..988557f --- /dev/null +++ b/ParseUI-Widget/src/androidTest/java/com/parse/ParseQueryPagerAdapterTest.java @@ -0,0 +1,618 @@ +package com.parse; + +import android.app.Fragment; +import android.database.DataSetObserver; +import android.view.View; +import android.widget.TextView; + +import com.parse.ParseQuery.CachePolicy; +import com.parse.ParseQueryPagerAdapter.OnQueryLoadListener; +import com.parse.ParseQueryPagerAdapter.QueryFactory; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import bolts.Capture; +import bolts.Task; + +import com.parse.widget.test.R; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Modified by Pablo Baxter (Github: soaboz) + */ +public class ParseQueryPagerAdapterTest extends BaseActivityInstrumentationTestCase2{ + + @ParseClassName("Thing") + public static class Thing extends ParseObject{ + public Thing(){ + } + } + + public ParseQueryPagerAdapterTest(){ + super(TestActivity.class); + } + + private int TOTAL_THINGS = 10; + private List savedThings = new ArrayList(); + + @Override + public void setUp() throws Exception { + super.setUp(); + + // Register a mock cachedQueryController, the controller maintain a cache list and return + // results based on query state's CachePolicy + ParseQueryController queryController = mock(ParseQueryController.class); + Answer>> queryAnswer = new Answer>>() { + private List cachedThings = new ArrayList<>(); + + @Override + public Task> answer(InvocationOnMock invocation) throws Throwable { + ParseQuery.State state = (ParseQuery.State) invocation.getArguments()[0]; + int start = state.skip(); + // The default value of limit in ParseQuery is -1. + int end = state.limit() > 0 ? + Math.min(state.skip() + state.limit(), TOTAL_THINGS) : TOTAL_THINGS; + List things; + if (state.cachePolicy() == CachePolicy.CACHE_ONLY) { + try { + things = new ArrayList<>(cachedThings.subList(start, end)); + } catch (IndexOutOfBoundsException e) { + // Cache miss, throw exception + return Task.forError( + new ParseException(ParseException.CACHE_MISS, "results not cached")); + } + } else { + things = new ArrayList<>(savedThings.subList(start, end)); + // Update cache + for (int i = start; i < end; i++) { + if (i < cachedThings.size()) { + cachedThings.set(i, savedThings.get(i)); + } else { + cachedThings.add(i, savedThings.get(i)); + } + } + } + return Task.forResult(things); + } + }; + when(queryController.findAsync(any(ParseQuery.State.class), any(ParseUser.class), any(Task.class))) + .thenAnswer(queryAnswer); + ParseCorePlugins.getInstance().registerQueryController(queryController); + + // Register a mock currentUserController to make getSessionToken work + ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); + when(currentUserController.getAsync()).thenReturn(Task.forResult(mock(ParseUser.class))); + when(currentUserController.getCurrentSessionTokenAsync()) + .thenReturn(Task.forResult(null)); + ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); + + ParseObject.registerSubclass(Thing.class); + // Make test data set + for (int i = 0; i < TOTAL_THINGS; i++) { + ParseObject thing = ParseObject.create("Thing"); + thing.put("aValue", i * 10); + thing.put("name", "Thing " + i); + thing.setObjectId(String.valueOf(i)); + savedThings.add(thing); + } + } + + @Override + public void tearDown() throws Exception { + savedThings = null; + ParseCorePlugins.getInstance().reset(); + ParseObject.unregisterSubclass("Thing"); + super.tearDown(); + } + + public void testLoadObjects() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + assertNull(e); + assertEquals(TOTAL_THINGS, objects.size()); + done.release(); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithGenericParseObjects() throws Exception { + final ParseQueryPagerAdapter adapter = + new ParseQueryPagerAdapter<>(activity, Thing.class); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + assertNull(e); + assertEquals(TOTAL_THINGS, objects.size()); + done.release(); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testGetItemViewWithTextKey() { + ParseQueryPagerAdapter adapter = + new ParseQueryPagerAdapter<>(activity, Thing.class); + adapter.setTextKey("name"); + + Fragment fragment = adapter.getFragment(savedThings.get(0)); + View view = fragment.onCreateView(null, null, null); + TextView textView = (TextView) view.findViewById(android.R.id.text1); + + assertEquals("Thing 0", textView.getText()); + } + + public void testGetItemViewWithCustomLayout() { + ParseQueryPagerAdapter adapter = + new ParseQueryPagerAdapter<>(activity, Thing.class, R.layout.view_item); + adapter.setTextKey("name"); + + Fragment fragment = adapter.getFragment(savedThings.get(0)); + //Direcly call onCreateView for fragment, to mock regular implementation. + //LayoutInflator, ViewGroup, and Bundle are not used for defaultView + View view = fragment.onCreateView(null, null, null); + TextView textView = (TextView) view.findViewById(android.R.id.text1); + assertEquals("Thing 0", textView.getText()); + + // We should have inflated our own layout for the items. + assertNotNull(view.findViewById(android.R.id.message)); + } + + public void testGetItemViewWithNoTextKey() throws ParseException { + ParseQueryPagerAdapter adapter = + new ParseQueryPagerAdapter<>(activity, Thing.class); + + Fragment fragment = adapter.getFragment(savedThings.get(0)); + View view = fragment.onCreateView(null, null, null); + TextView textView = (TextView) view.findViewById(android.R.id.text1); + + // Since we do not set the textKey, we should display objectId + assertEquals(savedThings.get(0).getObjectId(), textView.getText()); + } + + public void testLoadObjectsWithLimitsObjectsPerPage() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final int pageSize = 4; + adapter.setObjectsPerPage(pageSize); + final Capture timesThrough = new Capture<>(0); + final Semaphore done = new Semaphore(0); + final OnQueryLoadListener listener = new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + if (e != null) { + return; + } + + switch (timesThrough.get()) { + case 0: + // first time through, should have one page of results + "Load more" + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + adapter.loadNextPage(); + break; + case 1: + // second time through, should have two pages of results + "Load more" + assertEquals(pageSize, objects.size()); + assertEquals(2 * pageSize + 1, adapter.getCount()); + adapter.loadNextPage(); + break; + case 2: + // last time through, no "Load more" necessary. + assertEquals(TOTAL_THINGS - 2 * pageSize, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + } + timesThrough.set(timesThrough.get() + 1); + } + }; + adapter.addOnQueryLoadListener(listener); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithLimitsObjectsPerPageAndNoRemainder() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final int pageSize = 5; + adapter.setObjectsPerPage(pageSize); + final Capture timesThrough = new Capture<>(0); + final Semaphore done = new Semaphore(0); + final OnQueryLoadListener listener = new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + if (e != null) { + return; + } + + switch (timesThrough.get()) { + case 0: + // first time through, should have one page of results + "Load more" fragment + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + adapter.loadNextPage(); + break; + case 1: + // second time through, should have two pages' worth of results. It should realize that an + // additional "Load more" link isn't necessary, since this second page covers all of the + // results. + assertEquals(TOTAL_THINGS - pageSize, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + } + timesThrough.set(timesThrough.get() + 1); + } + }; + adapter.addOnQueryLoadListener(listener); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithPaginationNextPageView() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final int pageSize = 5; + adapter.setObjectsPerPage(pageSize); + final Capture timesThrough = new Capture<>(0); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + if (e != null) { + return; + } + + switch (timesThrough.get()) { + case 0: + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + + // Get Next Page fragment by passing in pageSize as the index + Fragment fragment = adapter.getItem(pageSize); + View view = fragment.onCreateView(null, null, null); + TextView textView = (TextView) view.findViewById(android.R.id.text1); + assertEquals("Load more...", textView.getText()); + // Fragment's root view should have OnClickListener attached. In API level 15+, we could call + // view.hasOnClickListeners() instead. + assertTrue(view.performClick()); + break; + case 1: + // Triggered by the performClick() call + done.release(); + } + timesThrough.set(timesThrough.get() + 1); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithNoPagination() throws Exception { + final int additional = 16; + for (int i = 0; i < additional; i++) { + ParseObject thing = ParseObject.create(Thing.class); + thing.put("name", "Additional Thing " + i); + savedThings.add(thing); + } + TOTAL_THINGS += additional; + + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + adapter.setPaginationEnabled(false); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + assertNull(e); + assertEquals(TOTAL_THINGS, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testClear() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final Semaphore done = new Semaphore(0); + final Capture counter = new Capture<>(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + if (e != null) { + return; + } + switch (counter.get()) { + case 0: + assertEquals(TOTAL_THINGS, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + adapter.clear(); + assertEquals(0, adapter.getCount()); + adapter.loadObjects(); + break; + default: + assertEquals(TOTAL_THINGS, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + } + counter.set(counter.get() + 1); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithCacheThenNetworkQueryAndPagination() throws Exception { + QueryFactory factory = new QueryFactory() { + @Override + public ParseQuery create() { + ParseQuery query = new ParseQuery(Thing.class); + query.setCachePolicy(CachePolicy.CACHE_THEN_NETWORK); + return query; + } + }; + + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, factory); + final int pageSize = 5; + adapter.setObjectsPerPage(pageSize); + adapter.setPaginationEnabled(true); + final Capture timesThrough = new Capture<>(0); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + if (e != null) { + return; + } + + switch (timesThrough.get()) { + case 0: + // Network callback for first page + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + adapter.loadNextPage(); + break; + case 1: + // Network callback for second page + assertEquals(TOTAL_THINGS - pageSize, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + adapter.loadObjects(); + break; + case 2: + // Cache callback for first page + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + break; + case 3: + // Network callback for first page + assertEquals(pageSize, objects.size()); + assertEquals(pageSize + 1, adapter.getCount()); + adapter.loadNextPage(); + break; + case 4: + // Cache callback for second page + assertEquals(TOTAL_THINGS - pageSize, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + break; + case 5: + // Network callback for second page + assertEquals(TOTAL_THINGS - pageSize, objects.size()); + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + break; + } + timesThrough.set(timesThrough.get() + 1); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithOnLoadingAndOnLoadedCallback() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + adapter.setObjectsPerPage(5); + final Capture flag = new Capture<>(false); + final Semaphore done = new Semaphore(0); + + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + assertFalse(flag.get()); + flag.set(true); + assertEquals(0, adapter.getCount()); + } + + @Override + public void onLoaded(List objects, Exception e) { + assertTrue(flag.get()); + assertEquals(5, objects.size()); + done.release(); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadNextPageBeforeLoadObjects() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + @Override + public void onLoaded(List objects, Exception e) { + assertNull(e); + assertEquals(TOTAL_THINGS, objects.size()); + done.release(); + } + }); + + adapter.loadNextPage(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testIncomingQueryResultAfterClearing() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final int pageSize = 4; + adapter.setObjectsPerPage(pageSize); + final Semaphore done = new Semaphore(0); + final Capture timesThrough = new Capture<>(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() {} + + @Override + public void onLoaded(List objects, Exception e) { + switch (timesThrough.get()) { + case 0: + adapter.loadNextPage(); + adapter.clear(); + case 1: + done.release(); + } + timesThrough.set(timesThrough.get() + 1); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithOverrideSetPageOnQuery() throws Exception { + final int arbitraryLimit = 3; + final ParseQueryPagerAdapter adapter = + new ParseQueryPagerAdapter(activity, Thing.class) { + @Override + public void setPageOnQuery(int page, ParseQuery query) { + // Make sure that this method is being used + respected. + query.setLimit(arbitraryLimit); + } + }; + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + } + + ; + + @Override + public void onLoaded(List objects, Exception e) { + assertEquals(arbitraryLimit, objects.size()); + done.release(); + } + }); + + adapter.loadObjects(); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } + + public void testLoadObjectsWithtAutoload() throws Exception { + final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter<>(activity, Thing.class); + final Capture flag = new Capture<>(false); + // Make sure that the Adapter doesn't start trying to load objects until AFTER we set this flag + // to true (= triggered by calling setAutoload, NOT registerDataSetObserver, if autoload is + // false). + adapter.setAutoload(false); + final Semaphore done = new Semaphore(0); + adapter.addOnQueryLoadListener(new OnQueryLoadListener() { + @Override + public void onLoading() { + assertEquals(0, adapter.getCount()); + assertTrue(flag.get()); + } + + @Override + public void onLoaded(List objects, Exception e) { + assertEquals(TOTAL_THINGS, adapter.getCount()); + done.release(); + } + }); + DataSetObserver observer = new DataSetObserver() { }; + adapter.registerDataSetObserver(observer); + flag.set(true); + adapter.setAutoload(true); + + // Make sure we assert in callback is executed + assertTrue(done.tryAcquire(10, TimeUnit.SECONDS)); + } +} diff --git a/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java new file mode 100644 index 0000000..0b3a607 --- /dev/null +++ b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java @@ -0,0 +1,723 @@ +package com.parse; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v13.app.FragmentStatePagerAdapter; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView;; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +import bolts.Capture; + +/** + * A {@code ParseQueryPagerAdapter} handles the fetching of objects by page, and displaying objects as + * fragments in a {@link android.app.Fragment}. + *

+ * This class is highly configurable, but also intended to be easy to get started with. See below + * for an example of using a {@code ParseQueryPagerAdapter} inside an {@link android.app.Activity}'s + * {@code onCreate}: + *

+ * final ParseQueryPagerAdapter adapter = new ParseQueryPagerAdapter(this, "TestObject");
+ * adapter.setTextKey("name");
+ *
+ * ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
+ * viewPager.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.
+ * ParseQueryPagerAdapter.QueryFactory<ParseObject> factory =
+ *     new ParseQueryPagerAdapter.QueryFactory<ParseObject>() {
+ *       public ParseQuery create() {
+ *         ParseQuery query = new ParseQuery("Customer");
+ *         query.whereEqualTo("activated", true);
+ *         query.orderByDescending("moneySpent");
+ *         return query;
+ *       }
+ *     };
+ *
+ * // Pass the factory into the ParseQueryPagerAdapter's constructor.
+ * ParseQueryPagerAdapter<ParseObject> adapter = new ParseQueryPagerAdapter<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 ViewPager, as in the example above
+ * ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
+ * viewpager.setAdapter(adapter);
+ * 
+ * + * Modification by Pablo Baxter (Github: soaboz) + * + */ +public class ParseQueryPagerAdapter extends FragmentStatePagerAdapter { + + /** + * 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 fragment text label. + private String textKey; + + // The key to use to fetch an image for display in the viewpager's image view. + private String imageKey; + + // The number of objects to retrieve per page (default: 25) + private int objectsPerPage = 25; + + // Whether the viewpager 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; + + //The activity that will provide the necessary FragmentManager + private Activity activity; + + private List objects = new ArrayList<>(); + + private Set runningQueries = + Collections.newSetFromMap(new ConcurrentHashMap()); + + + // 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; + + //Holds the 'next page' fragment, in order to refresh it after next page has loaded. + private Fragment nextPageFragment; + + private QueryFactory queryFactory; + + private List> onQueryLoadListeners = + new ArrayList<>(); + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Given a {@link ParseObject} subclass, this adapter will + * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time. + * + * @param activity + * The activity utilizing this adapter. + * @param clazz + * The {@link ParseObject} subclass type to fetch and display. + */ + public ParseQueryPagerAdapter(Activity activity, Class clazz) { + this(activity, ParseObject.getClassName(clazz)); + } + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Given a {@link ParseObject} subclass, this adapter will + * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time. + * + * @param activity + * The activity utilizing this adapter. + * @param className + * The name of the Parse class of {@link ParseObject}s to display. + */ + public ParseQueryPagerAdapter(Activity activity, final String className) { + this(activity, 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 ParseQueryPagerAdapter"); + } + } + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Given a {@link ParseObject} subclass, this adapter will + * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time. + * + * @param activity + * 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 ParseQueryPagerAdapter(Activity activity, Class clazz, + int itemViewResource) { + this(activity, ParseObject.getClassName(clazz), itemViewResource); + } + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Given a {@link ParseObject} subclass, this adapter will + * fetch and display all {@link ParseObject}s of the specified class, ordered by creation time. + * + * @param activity + * 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 ParseQueryPagerAdapter(Activity activity, final String className, int itemViewResource) { + this(activity, 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 ParseQueryPagerAdapter"); + } + } + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Allows the caller to define further constraints on the + * {@link ParseQuery} to be used when fetching items from Parse. + * + * @param activity + * The activity utilizing this adapter. + * @param queryFactory + * A {@link QueryFactory} to build a {@link ParseQuery} for fetching objects. + */ + public ParseQueryPagerAdapter(Activity activity, QueryFactory queryFactory) { + this(activity, queryFactory, null); + } + + /** + * Constructs a {@code ParseQueryPagerAdapter}. Allows the caller to define further constraints on the + * {@link ParseQuery} to be used when fetching items from Parse. + * + * @param activity + * 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 ParseQueryPagerAdapter(Activity activity, QueryFactory queryFactory, int itemViewResource) { + this(activity, queryFactory, Integer.valueOf(itemViewResource)); + } + + private ParseQueryPagerAdapter(Activity activity, QueryFactory queryFactory, Integer itemViewResource) { + super(activity.getFragmentManager()); + this.activity = activity; + this.queryFactory = queryFactory; + this.itemResourceId = itemViewResource; + } + + /** + * Return the context provided by the {@code Activity} utilizing this {@code ParseQueryPagerAdapter}. + * + * @return The activity utilizing this adapter. + */ + public Activity getActivity() { + return this.activity; + } + + /** {@inheritDoc} **/ + @Override + public Fragment getItem(int index) { + if (index == this.getPaginationFragment()) { + nextPageFragment = getNextPageFragment(); + return nextPageFragment; + } + return getFragment(this.objects.get(index)); + } + + /** {@inheritDoc} **/ + @Override + public int getItemPosition(Object object){ + Fragment fragment = object instanceof Fragment ? (Fragment)object : null; + if(fragment == nextPageFragment){ + nextPageFragment = null; + return POSITION_NONE; + } + return POSITION_UNCHANGED; + } + + /** + * Overrides {@link android.support.v4.view.PagerAdapter#getCount()} method to return the number of fragments to + * display. If pagination is turned on, this count will include an extra +1 count for the + * pagination fragment. + * + * @return The number of fragments to be displayed by the {@link android.support.v4.view.ViewPager}. + */ + @Override + public int getCount() { + int count = this.objects.size(); + + if (this.shouldShowPaginationFragment()) { + count++; + } + + return count; + } + + @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(); + cancelAllQueries(); + syncObjectsWithPages(); + this.notifyDataSetChanged(); + this.currentPage = 0; + } + + private void cancelAllQueries() { + for (ParseQuery q : runningQueries) { + q.cancel(); + } + runningQueries.clear(); + } + + /** + * Clears the viewpager and loads the first page of objects asynchronously. This method is called + * automatically when this {@code PagerAdapter} is attached to a {@code ViewPager}. + *

+ * {@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 of the callbacks. + final Capture firstCallBack = new Capture<>(true); + + runningQueries.add(query); + + // TODO convert to Tasks and CancellationTokens + // (depends on https://github.com/ParsePlatform/Parse-SDK-Android/issues/6) + query.findInBackground(new FindCallback() { + @Override + public void done(List foundObjects, ParseException e) { + if (!runningQueries.contains(query)) { + return; + } + // In the case of CACHE_THEN_NETWORK, two callbacks will be called. We can only remove the + // query after the second callback. + if (Parse.isLocalDatastoreEnabled() || + (query.getCachePolicy() != ParseQuery.CachePolicy.CACHE_THEN_NETWORK) || + (query.getCachePolicy() == ParseQuery.CachePolicy.CACHE_THEN_NETWORK && !firstCallBack.get())) { + runningQueries.remove(query); + } + + if ((!Parse.isLocalDatastoreEnabled() && + query.getCachePolicy() == ParseQuery.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()) { + runningQueries.remove(query); + cancelAllQueries(); + runningQueries.add(query); // allow 2nd callback + 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 viewpager, and notifies the UI that the model has + * changed. + */ + public void loadNextPage() { + if (objects.size() == 0 && runningQueries.size() == 0) { + loadObjects(0, false); + } + else { + loadObjects(currentPage + 1, false); + } + } + + /** + * Override this method to customize each fragment given a {@link ParseObject}. + *

+ * If a fragment is not provided, a default fragment will be created + *

+ * 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. + * @return The customized fragment displaying the {@link ParseObject}'s information. + */ + public Fragment getFragment(final T object){ + + return new Fragment(){ + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = getDefaultView(activity); + TextView textView; + try { + textView = (TextView) view.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 (textKey == null) { + textView.setText(object.getObjectId()); + } else if (object.get(textKey) != null) { + textView.setText(object.get(textKey).toString()); + } else { + textView.setText(null); + } + } + + if (imageKey != null) { + ParseImageView imageView; + try { + imageView = (ParseImageView) view.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 (!imageViewSet.containsKey(imageView)) { + imageViewSet.put(imageView, null); + } + imageView.setPlaceholder(placeholder); + imageView.setParseFile((ParseFile) object.get(imageKey)); + imageView.loadInBackground(); + } + return view; + } + }; + } + + /** + * Override this method to customize the "Load Next Page" fragment, 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}. + * + * @return The fragment that allows the user to paginate. + */ + public Fragment getNextPageFragment() { + return new Fragment(){ + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + LinearLayout view = new LinearLayout(activity); + LinearLayout.LayoutParams params = new LinearLayout. + LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + params.gravity = Gravity.CENTER; + view.setLayoutParams(params); + + TextView textView = new TextView(activity); + textView.setId(android.R.id.text1); + textView.setText("Load more..."); + view.addView(textView); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + loadNextPage(); + } + }); + return view; + } + }; + } + + /** + * 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 ViewPager} + * . 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 ViewPager}. + * 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.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams mainParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + view.setLayoutParams(mainParams); + + ParseImageView imageView = new ParseImageView(context); + imageView.setId(android.R.id.icon); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(500, 500); + params.gravity = Gravity.CENTER_HORIZONTAL; + imageView.setLayoutParams(params); + view.addView(imageView); + + TextView textView = new TextView(context); + textView.setId(android.R.id.text1); + LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params1.gravity = Gravity.CENTER_HORIZONTAL; + textView.setLayoutParams(params1); + textView.setPadding(8, 0, 0, 0); + view.addView(textView); + + return view; + } + + private int getPaginationFragment() { + return this.objects.size(); + } + + private boolean shouldShowPaginationFragment() { + 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); + } + } +} From 2bab89301a537d315ac476d708d6933093f19fa9 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Fri, 13 Nov 2015 21:20:26 -0800 Subject: [PATCH 2/5] Updated UI for vanilla adapter. --- ParseUI-Widget/build.gradle | 3 +- .../com/parse/ParseQueryPagerAdapter.java | 51 ++++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ParseUI-Widget/build.gradle b/ParseUI-Widget/build.gradle index e9568ff..1742e77 100644 --- a/ParseUI-Widget/build.gradle +++ b/ParseUI-Widget/build.gradle @@ -27,13 +27,12 @@ group = 'com.parse' dependencies { compile 'com.parse:parse-android:1.10.3' + compile 'com.android.support:support-v13:21.0.3' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'org.skyscreamer:jsonassert:1.2.3' - - compile 'com.android.support:support-v13:23.1.0' } android { diff --git a/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java index 0b3a607..dfa1768 100644 --- a/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java +++ b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java @@ -13,6 +13,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView;; import java.util.ArrayList; @@ -554,17 +555,10 @@ public Fragment getNextPageFragment() { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - LinearLayout view = new LinearLayout(activity); - LinearLayout.LayoutParams params = new LinearLayout. - LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - params.gravity = Gravity.CENTER; - view.setLayoutParams(params); - - TextView textView = new TextView(activity); - textView.setId(android.R.id.text1); + View view = getDefaultView(activity); + view.findViewById(android.R.id.icon).setVisibility(View.GONE); + TextView textView = (TextView)view.findViewById(android.R.id.text1); textView.setText("Load more..."); - view.addView(textView); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -676,27 +670,34 @@ private View getDefaultView(Context context) { if (this.itemResourceId != null) { return View.inflate(context, this.itemResourceId, null); } - LinearLayout view = new LinearLayout(context); - view.setOrientation(LinearLayout.VERTICAL); - LinearLayout.LayoutParams mainParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - view.setLayoutParams(mainParams); + RelativeLayout view = new RelativeLayout(context); + RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + view.setLayoutParams(relativeParams); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + RelativeLayout.LayoutParams linearParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + linearParams.addRule(RelativeLayout.CENTER_IN_PARENT); + linearLayout.setLayoutParams(linearParams); + view.addView(linearLayout); + + TextView textView = new TextView(context); + textView.setId(android.R.id.text1); + LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params1.gravity = Gravity.CENTER_HORIZONTAL; + textView.setLayoutParams(params1); + textView.setGravity(Gravity.CENTER); + linearLayout.addView(textView); ParseImageView imageView = new ParseImageView(context); imageView.setId(android.R.id.icon); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(500, 500); params.gravity = Gravity.CENTER_HORIZONTAL; imageView.setLayoutParams(params); - view.addView(imageView); - - TextView textView = new TextView(context); - textView.setId(android.R.id.text1); - LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - params1.gravity = Gravity.CENTER_HORIZONTAL; - textView.setLayoutParams(params1); - textView.setPadding(8, 0, 0, 0); - view.addView(textView); + linearLayout.addView(imageView); return view; } From d1599c00eeeb3183ae37223b3e99b930658495c1 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Sat, 14 Nov 2015 00:15:36 -0800 Subject: [PATCH 3/5] Update gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 4357929..2f7e685 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,13 @@ gen-external-apklibs # Gradle .gradle build +ParseUI-Widget/._src +ParseUI-Widget/src/._androidTest +ParseUI-Widget/src/._main +ParseUI-Widget/src/androidTest/._AndroidManifest.xml +ParseUI-Widget/src/androidTest/._java +ParseUI-Widget/src/androidTest/._res +ParseUI-Widget/src/androidTest/res/._drawable +ParseUI-Widget/src/androidTest/res/._layout +ParseUI-Widget/src/main/._AndroidManifest.xml +ParseUI-Widget/src/main/._java From 8f0093d335d03acca54ca5c3f667a729a7f97083 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Sat, 14 Nov 2015 10:50:51 -0800 Subject: [PATCH 4/5] Updated header signature --- .../src/main/java/com/parse/ParseQueryPagerAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java index dfa1768..f32bcac 100644 --- a/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java +++ b/ParseUI-Widget/src/main/java/com/parse/ParseQueryPagerAdapter.java @@ -75,7 +75,7 @@ * viewpager.setAdapter(adapter); * * - * Modification by Pablo Baxter (Github: soaboz) + * Modification by Pablo Baxter (Github: pablobaxter) * */ public class ParseQueryPagerAdapter extends FragmentStatePagerAdapter { From 4d82790c7500a3ce689362410572a6e9ba413598 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Tue, 8 Dec 2015 11:41:42 -0800 Subject: [PATCH 5/5] Update .gitignore Removed unnecessary files from project --- .gitignore | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.gitignore b/.gitignore index 2f7e685..4357929 100644 --- a/.gitignore +++ b/.gitignore @@ -43,13 +43,3 @@ gen-external-apklibs # Gradle .gradle build -ParseUI-Widget/._src -ParseUI-Widget/src/._androidTest -ParseUI-Widget/src/._main -ParseUI-Widget/src/androidTest/._AndroidManifest.xml -ParseUI-Widget/src/androidTest/._java -ParseUI-Widget/src/androidTest/._res -ParseUI-Widget/src/androidTest/res/._drawable -ParseUI-Widget/src/androidTest/res/._layout -ParseUI-Widget/src/main/._AndroidManifest.xml -ParseUI-Widget/src/main/._java