1/*
2 * Copyright 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package eu.siacs.conversations.ui.widget;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.view.LayoutInflater;
22import android.view.View;
23import android.view.ViewGroup;
24import android.widget.ListView;
25
26import androidx.fragment.app.ListFragment;
27import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
28
29import eu.siacs.conversations.R;
30import eu.siacs.conversations.ui.util.StyledAttributes;
31
32/**
33 * Subclass of {@link androidx.fragment.app.ListFragment} which provides automatic support for
34 * providing the 'swipe-to-refresh' UX gesture by wrapping the the content view in a
35 * {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout}.
36 */
37public class SwipeRefreshListFragment extends ListFragment {
38
39 private boolean enabled = false;
40 private boolean refreshing = false;
41
42 private SwipeRefreshLayout.OnRefreshListener onRefreshListener;
43
44 private SwipeRefreshLayout mSwipeRefreshLayout;
45
46 @Override
47 public View onCreateView(LayoutInflater inflater, ViewGroup container,
48 Bundle savedInstanceState) {
49
50 // Create the list fragment's content view by calling the super method
51 final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
52
53 // Now create a SwipeRefreshLayout to wrap the fragment's content view
54 mSwipeRefreshLayout = new ListFragmentSwipeRefreshLayout(container.getContext());
55 mSwipeRefreshLayout.setEnabled(enabled);
56 mSwipeRefreshLayout.setRefreshing(refreshing);
57
58 final Context context = getActivity();
59 if (context != null) {
60 mSwipeRefreshLayout.setColorSchemeColors(StyledAttributes.getColor(context, R.attr.colorAccent));
61 }
62
63 if (onRefreshListener != null) {
64 mSwipeRefreshLayout.setOnRefreshListener(onRefreshListener);
65 }
66
67 // Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills
68 // the SwipeRefreshLayout
69 mSwipeRefreshLayout.addView(listFragmentView,
70 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
71
72 // Make sure that the SwipeRefreshLayout will fill the fragment
73 mSwipeRefreshLayout.setLayoutParams(
74 new ViewGroup.LayoutParams(
75 ViewGroup.LayoutParams.MATCH_PARENT,
76 ViewGroup.LayoutParams.MATCH_PARENT));
77
78 // Now return the SwipeRefreshLayout as this fragment's content view
79 return mSwipeRefreshLayout;
80 }
81
82 /**
83 * Set the {@link androidx.core.widget.SwipeRefreshLayout.OnRefreshListener} to listen for
84 * initiated refreshes.
85 *
86 * @see androidx.core.widget.SwipeRefreshLayout#setOnRefreshListener(androidx.core.widget.SwipeRefreshLayout.OnRefreshListener)
87 */
88 public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) {
89 onRefreshListener = listener;
90 enabled = true;
91 if (mSwipeRefreshLayout != null) {
92 mSwipeRefreshLayout.setEnabled(true);
93 mSwipeRefreshLayout.setOnRefreshListener(listener);
94 }
95 }
96
97 /**
98 * Set whether the {@link androidx.core.widget.SwipeRefreshLayout} should be displaying
99 * that it is refreshing or not.
100 *
101 * @see androidx.core.widget.SwipeRefreshLayout#setRefreshing(boolean)
102 */
103 public void setRefreshing(boolean refreshing) {
104 this.refreshing = refreshing;
105 if (mSwipeRefreshLayout != null) {
106 mSwipeRefreshLayout.setRefreshing(refreshing);
107 }
108 }
109
110
111 /**
112 * Sub-class of {@link androidx.core.widget.SwipeRefreshLayout} for use in this
113 * {@link androidx.core.app.ListFragment}. The reason that this is needed is because
114 * {@link androidx.core.widget.SwipeRefreshLayout} only supports a single child, which it
115 * expects to be the one which triggers refreshes. In our case the layout's child is the content
116 * view returned from
117 * {@link androidx.core.app.ListFragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)}
118 * which is a {@link android.view.ViewGroup}.
119 *
120 * <p>To enable 'swipe-to-refresh' support via the {@link android.widget.ListView} we need to
121 * override the default behavior and properly signal when a gesture is possible. This is done by
122 * overriding {@link #canChildScrollUp()}.
123 */
124 private class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout {
125
126 public ListFragmentSwipeRefreshLayout(Context context) {
127 super(context);
128 }
129
130 /**
131 * As mentioned above, we need to override this method to properly signal when a
132 * 'swipe-to-refresh' is possible.
133 *
134 * @return true if the {@link android.widget.ListView} is visible and can scroll up.
135 */
136 @Override
137 public boolean canChildScrollUp() {
138 final ListView listView = getListView();
139 if (listView.getVisibility() == View.VISIBLE) {
140 return listView.canScrollVertically(-1);
141 } else {
142 return false;
143 }
144 }
145
146 }
147
148}