diff --git a/app/src/main/java/i2p/bote/android/EmailListFragment.java b/app/src/main/java/i2p/bote/android/EmailListFragment.java index 03283ad..76442d8 100644 --- a/app/src/main/java/i2p/bote/android/EmailListFragment.java +++ b/app/src/main/java/i2p/bote/android/EmailListFragment.java @@ -9,7 +9,6 @@ import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; -import android.support.v4.view.ViewCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBarActivity; import android.support.v7.view.ActionMode; @@ -20,6 +19,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ListView; import android.widget.TextView; @@ -39,6 +39,7 @@ import i2p.bote.android.util.BetterAsyncTaskLoader; import i2p.bote.android.util.BoteHelper; import i2p.bote.android.util.MoveToDialogFragment; import i2p.bote.android.util.MultiSelectionUtil; +import i2p.bote.android.util.MultiSwipeRefreshLayout; import i2p.bote.email.Email; import i2p.bote.fileencryption.PasswordException; import i2p.bote.folder.EmailFolder; @@ -54,7 +55,8 @@ public class EmailListFragment extends AuthenticatedListFragment implements OnEmailSelectedListener mCallback; - private SwipeRefreshLayout mSwipeRefreshLayout; + private MultiSwipeRefreshLayout mSwipeRefreshLayout; + private TextView mEmptyText; private TextView mNumIncompleteEmails; private EmailListAdapter mAdapter; @@ -102,83 +104,35 @@ public class EmailListFragment extends AuthenticatedListFragment implements mFolder = BoteHelper.getMailFolder(folderName); if (BoteHelper.isInbox(mFolder)) { - // Now create a SwipeRefreshLayout to wrap the fragment's content view - mSwipeRefreshLayout = new ListFragmentSwipeRefreshLayout(container.getContext()); + // Inflate the MultiSwipeRefreshLayout + mSwipeRefreshLayout = (MultiSwipeRefreshLayout) inflater.inflate( + R.layout.fragment_list_emails, container, false); + FrameLayout listContainer = (FrameLayout) mSwipeRefreshLayout.findViewById(R.id.list_container); + listContainer.addView(listFragmentView); - // Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills - // the SwipeRefreshLayout - mSwipeRefreshLayout.addView(listFragmentView, - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + // Set up the empty view + View emptyView = mSwipeRefreshLayout.findViewById(android.R.id.empty); + ListView listView = (ListView) mSwipeRefreshLayout.findViewById(android.R.id.list); + listView.setEmptyView(emptyView); + mEmptyText = (TextView) mSwipeRefreshLayout.findViewById(R.id.empty_text); - // Make sure that the SwipeRefreshLayout will fill the fragment - mSwipeRefreshLayout.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - // Set up the SwipeRefreshLayout + // Set up the MultiSwipeRefreshLayout + mSwipeRefreshLayout.setSwipeableChildren(android.R.id.list, android.R.id.empty); mSwipeRefreshLayout.setOnRefreshListener(this); mSwipeRefreshLayout.setRefreshing(I2PBote.getInstance().isCheckingForMail()); - // Now return the SwipeRefreshLayout as this fragment's content view + // Now return the MultiSwipeRefreshLayout as this fragment's content view return mSwipeRefreshLayout; } else return listFragmentView; } - /** - * Sub-class of {@link android.support.v4.widget.SwipeRefreshLayout} for use in this - * {@link android.support.v4.app.ListFragment}. The reason that this is needed is because - * {@link android.support.v4.widget.SwipeRefreshLayout} only supports a single child, which it - * expects to be the one which triggers refreshes. In our case the layout's child is the content - * view returned from - * {@link android.support.v4.app.ListFragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)} - * which is a {@link android.view.ViewGroup}. - * - *
To enable 'swipe-to-refresh' support via the {@link android.widget.ListView} we need to - * override the default behavior and properly signal when a gesture is possible. This is done by - * overriding {@link #canChildScrollUp()}. - */ - private class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout { - - public ListFragmentSwipeRefreshLayout(Context context) { - super(context); - } - - /** - * As mentioned above, we need to override this method to properly signal when a - * 'swipe-to-refresh' is possible. - * - * @return true if the {@link android.widget.ListView} is visible and can scroll up. - */ - @Override - public boolean canChildScrollUp() { - final ListView listView = getListView(); - if (listView.getVisibility() == View.VISIBLE) { - return canListViewScrollUp(listView); - } else { - return false; - } - } - - } - - /** - * Utility method to check whether a {@link ListView} can scroll up from it's current position. - * Handles platform version differences, providing backwards compatible functionality where - * needed. - */ - private static boolean canListViewScrollUp(ListView listView) { - if (android.os.Build.VERSION.SDK_INT >= 14) { - // For ICS and above we can call canScrollVertically() to determine this - return ViewCompat.canScrollVertically(listView, -1); - } else { - // Pre-ICS we need to manually check the first visible item and the child view's top - // value - return listView.getChildCount() > 0 && - (listView.getFirstVisiblePosition() > 0 - || listView.getChildAt(0).getTop() < listView.getPaddingTop()); - } + @Override + public void setEmptyText(CharSequence text) { + if (mEmptyText == null) + super.setEmptyText(text); + else + mEmptyText.setText(text); } @Override diff --git a/app/src/main/java/i2p/bote/android/util/MultiSwipeRefreshLayout.java b/app/src/main/java/i2p/bote/android/util/MultiSwipeRefreshLayout.java new file mode 100644 index 0000000..66a8ba6 --- /dev/null +++ b/app/src/main/java/i2p/bote/android/util/MultiSwipeRefreshLayout.java @@ -0,0 +1,87 @@ +package i2p.bote.android.util; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.AttributeSet; +import android.view.View; +import android.widget.AbsListView; + +/** + * A descendant of {@link android.support.v4.widget.SwipeRefreshLayout} which supports multiple + * child views triggering a refresh gesture. You set the views which can trigger the gesture via + * {@link #setSwipeableChildren(int...)}, providing it the child ids. + */ +public class MultiSwipeRefreshLayout extends SwipeRefreshLayout { + + private View[] mSwipeableChildren; + + public MultiSwipeRefreshLayout(Context context) { + super(context); + } + + public MultiSwipeRefreshLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Set the children which can trigger a refresh by swiping down when they are visible. These + * views need to be a descendant of this view. + */ + public void setSwipeableChildren(final int... ids) { + assert ids != null; + + // Iterate through the ids and find the Views + mSwipeableChildren = new View[ids.length]; + for (int i = 0; i < ids.length; i++) { + mSwipeableChildren[i] = findViewById(ids[i]); + } + } + + /** + * This method controls when the swipe-to-refresh gesture is triggered. By returning false here + * we are signifying that the view is in a state where a refresh gesture can start. + * + *
As {@link android.support.v4.widget.SwipeRefreshLayout} only supports one direct child by
+ * default, we need to manually iterate through our swipeable children to see if any are in a
+ * state to trigger the gesture. If so we return false to start the gesture.
+ */
+ @Override
+ public boolean canChildScrollUp() {
+ if (mSwipeableChildren != null && mSwipeableChildren.length > 0) {
+ // Iterate through the scrollable children and check if any of them can not scroll up
+ for (View view : mSwipeableChildren) {
+ if (view != null && view.isShown() && !canViewScrollUp(view)) {
+ // If the view is shown, and can not scroll upwards, return false and start the
+ // gesture.
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Utility method to check whether a {@link View} can scroll up from it's current position.
+ * Handles platform version differences, providing backwards compatible functionality where
+ * needed.
+ */
+ private static boolean canViewScrollUp(View view) {
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ // For ICS and above we can call canScrollVertically() to determine this
+ return ViewCompat.canScrollVertically(view, -1);
+ } else {
+ if (view instanceof AbsListView) {
+ // Pre-ICS we need to manually check the first visible item and the child view's top
+ // value
+ final AbsListView listView = (AbsListView) view;
+ return listView.getChildCount() > 0 &&
+ (listView.getFirstVisiblePosition() > 0
+ || listView.getChildAt(0).getTop() < listView.getPaddingTop());
+ } else {
+ // For all other view types we just check the getScrollY() value
+ return view.getScrollY() > 0;
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/fragment_list_emails.xml b/app/src/main/res/layout/fragment_list_emails.xml
new file mode 100644
index 0000000..6275ea1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_list_emails.xml
@@ -0,0 +1,30 @@
+
+