Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Commit bcff398

Browse files
committed
Show SnackBar for connectivity changes.
1 parent 1540dca commit bcff398

File tree

5 files changed

+256
-0
lines changed

5 files changed

+256
-0
lines changed

app/src/main/java/com/zulip/android/activities/ZulipActivity.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
import android.os.CountDownTimer;
2929
import android.os.Environment;
3030
import android.os.Handler;
31+
import android.os.Looper;
3132
import android.provider.MediaStore;
3233
import android.support.design.widget.AppBarLayout;
3334
import android.support.design.widget.CoordinatorLayout;
3435
import android.support.design.widget.FloatingActionButton;
36+
import android.support.design.widget.Snackbar;
3537
import android.support.v4.app.ActionBarDrawerToggle;
3638
import android.support.v4.app.ActivityCompat;
3739
import android.support.v4.app.FragmentManager;
@@ -157,6 +159,7 @@ public class ZulipActivity extends BaseActivity implements
157159
private boolean logged_in = false;
158160
private boolean backPressedOnce = false;
159161
private boolean inSearch = false;
162+
private String networkStatus = Constants.STATUS_CONNECTED;
160163
private ZulipActivity that = this; // self-ref
161164
private DrawerLayout drawerLayout;
162165
private ActionBarDrawerToggle drawerToggle;
@@ -1972,6 +1975,17 @@ public boolean onCreateOptionsMenu(Menu menu) {
19721975
return false;
19731976
}
19741977

1978+
@Override
1979+
public boolean onPrepareOptionsMenu(Menu menu) {
1980+
if (networkStatus.equals(Constants.STATUS_CONNECTED)) {
1981+
menu.findItem(R.id.refresh).setEnabled(true);
1982+
} else {
1983+
//Disabling the refresh button
1984+
menu.findItem(R.id.refresh).setEnabled(false);
1985+
}
1986+
return true;
1987+
}
1988+
19751989
private boolean prepareSearchView(Menu menu) {
19761990
if (this.logged_in && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
19771991
// Get the SearchView and set the searchable configuration
@@ -2355,4 +2369,35 @@ public MessageListFragment getCurrentMessageList() {
23552369
return narrowedList;
23562370
}
23572371
}
2372+
/**
2373+
* This function shows the snackbar stating the connectivity status of the device and also changes the behaviour of the
2374+
* fab.
2375+
*/
2376+
public void showConnectivitySnackBar(final String networkState) {
2377+
final CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);
2378+
final Handler handler = new Handler(Looper.getMainLooper()) {
2379+
@Override
2380+
public void handleMessage(android.os.Message msg) {
2381+
if (networkState.equals(Constants.STATUS_CONNECTING)) {
2382+
networkStatus = Constants.STATUS_CONNECTING;
2383+
Snackbar.make(coordinatorLayout, R.string.connecting, Snackbar.LENGTH_INDEFINITE).show();
2384+
2385+
} else if (networkState.equals(Constants.STATUS_CONNECTED)) {
2386+
//Starts a network request only when there is an active network connection
2387+
startRequests();
2388+
networkStatus = Constants.STATUS_CONNECTED;
2389+
Snackbar.make(coordinatorLayout, R.string.connection_established, Snackbar.LENGTH_SHORT).show();
2390+
} else {
2391+
displayChatBox(false);
2392+
displayFAB(true);
2393+
networkStatus = Constants.STATUS_NOT_CONNECTED;
2394+
Snackbar.make(coordinatorLayout, R.string.no_connection, Snackbar.LENGTH_INDEFINITE).show();
2395+
}
2396+
Log.d("NetworkStatus", networkState);
2397+
super.handleMessage(msg);
2398+
}
2399+
};
2400+
2401+
handler.sendEmptyMessage(0);
2402+
}
23582403
}

app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.zulip.android.networking.response.events.StreamWrapper;
2929
import com.zulip.android.networking.response.events.SubscriptionWrapper;
3030
import com.zulip.android.networking.response.events.UpdateMessageWrapper;
31+
import com.zulip.android.util.Constants;
3132
import com.zulip.android.util.MutedTopics;
3233
import com.zulip.android.util.TypeSwapper;
3334
import com.zulip.android.util.ZLog;
@@ -105,6 +106,10 @@ private void backoff(Exception e) {
105106
long backoff = (long) (Math.exp(failures / 2.0) * 1000);
106107
Log.e(ASYNC_GET_EVENTS, "Failure " + failures + ", sleeping for "
107108
+ backoff);
109+
//MAX_CONNECTION_FAILURE_COUNT failures represent loss in network connectivity
110+
if (failures == Constants.MAX_CONNECTION_FAILURE_COUNT) {
111+
mActivity.showConnectivitySnackBar(Constants.STATUS_NOT_CONNECTED);
112+
}
108113
SystemClock.sleep(backoff);
109114
}
110115

@@ -282,6 +287,9 @@ public void run() {
282287
if (body.getEvents().size() > 0) {
283288
this.processEvents(body);
284289
app.setLastEventId(body.getEvents().get(body.getEvents().size() - 1).getId());
290+
//Shows connected status on receiving data
291+
if (failures > 0)
292+
mActivity.showConnectivitySnackBar(Constants.STATUS_CONNECTED);
285293
failures = 0;
286294
}
287295

app/src/main/java/com/zulip/android/util/Constants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ public class Constants {
1414
public final static int DEFAULT_MAXIMUM_CONTENT_EDIT_LIMIT = 600;
1515
public final static boolean DEFAULT_EDITING_ALLOWED = true;
1616
public final static String SERVER_URL = "SERVER_URL";
17+
//Connection States
18+
public static final String STATUS_CONNECTED = "connected";
19+
public static final String STATUS_CONNECTING = "connecting";
20+
public static final String STATUS_NOT_CONNECTED = "no_connection";
21+
public static final int MAX_CONNECTION_FAILURE_COUNT=2;
1722

1823
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package com.zulip.android.util;
2+
3+
import android.animation.Animator;
4+
import android.annotation.SuppressLint;
5+
import android.content.Context;
6+
import android.support.design.widget.AppBarLayout;
7+
import android.support.design.widget.CoordinatorLayout;
8+
import android.support.design.widget.FloatingActionButton;
9+
import android.support.design.widget.Snackbar;
10+
import android.support.v4.view.ViewCompat;
11+
import android.support.v4.view.animation.FastOutSlowInInterpolator;
12+
import android.support.v7.widget.LinearLayoutManager;
13+
import android.util.AttributeSet;
14+
import android.util.TypedValue;
15+
import android.view.View;
16+
import android.view.ViewPropertyAnimator;
17+
import android.view.animation.Interpolator;
18+
import android.widget.LinearLayout;
19+
20+
import com.zulip.android.R;
21+
import com.zulip.android.activities.RecyclerMessageAdapter;
22+
23+
import java.util.List;
24+
25+
/**
26+
* This hides the {@link android.support.design.widget.FloatingActionButton} when the
27+
* recyclerView is scrolled, used in here {@link com.zulip.android.R.layout#main} as a behaviour.
28+
* This also shrinks the {@link android.support.design.widget.FloatingActionButton} when the snackbar comes
29+
* and goes in and out of view.
30+
*/
31+
32+
33+
public class RemoveFabOnScroll extends CoordinatorLayout.Behavior<FloatingActionButton> {
34+
35+
private static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
36+
private static float toolbarHeight;
37+
private int changeInYDir;
38+
private boolean mIsShowing;
39+
private boolean isViewHidden;
40+
private View chatBox;
41+
private LinearLayoutManager linearLayoutManager;
42+
private RecyclerMessageAdapter adapter;
43+
44+
public RemoveFabOnScroll(Context context, AttributeSet attrs) {
45+
super(context, attrs);
46+
TypedValue tv = new TypedValue();
47+
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
48+
toolbarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics());
49+
}
50+
51+
public RemoveFabOnScroll(LinearLayoutManager linearLayoutManager, RecyclerMessageAdapter adapter) {
52+
this.linearLayoutManager = linearLayoutManager;
53+
this.adapter = adapter;
54+
}
55+
56+
@Override
57+
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
58+
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
59+
}
60+
61+
@SuppressLint("NewApi")
62+
@Override
63+
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dx, int dy, int[] consumed) throws NullPointerException {
64+
//count index starts from 1 where as position starts from 0, thus difference 1
65+
//we have 2 loading layouts one at top and another at bottom of the messages which should be ignored
66+
//resulting in a overall difference of 3
67+
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() < adapter.getItemCount() - 3) {
68+
if (dy > 0 && changeInYDir < 0 || dy < 0 && changeInYDir > 0) {
69+
child.animate().cancel();
70+
changeInYDir = 0;
71+
}
72+
73+
changeInYDir += dy;
74+
if ((changeInYDir > toolbarHeight && child.getVisibility() == View.VISIBLE) && (!isViewHidden || isTopSnackBar(child)))
75+
hideView(child);
76+
else if (changeInYDir < 0 && (child.getVisibility() == View.GONE && !mIsShowing) || isTopSnackBar(child)) {
77+
if (chatBox == null)
78+
chatBox = coordinatorLayout.findViewById(R.id.messageBoxContainer);
79+
if (chatBox.getVisibility() == View.VISIBLE) {
80+
return;
81+
}
82+
showView(child);
83+
}
84+
}
85+
}
86+
87+
private boolean isTopSnackBar(View child) {
88+
return (child.getId() != R.id.appBarLayout && !(child instanceof FloatingActionButton));
89+
}
90+
91+
@SuppressLint("NewApi")
92+
private void hideView(final View view) {
93+
isViewHidden = true;
94+
int y = view.getHeight();
95+
;
96+
if (view instanceof AppBarLayout) {
97+
y = -1 * view.getHeight();
98+
} else if (view instanceof LinearLayout) {
99+
y = 0;
100+
}
101+
ViewPropertyAnimator animator = view.animate()
102+
.translationY(y)
103+
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
104+
.setDuration(200);
105+
106+
animator.setListener(new Animator.AnimatorListener() {
107+
@Override
108+
public void onAnimationStart(Animator animator) {
109+
}
110+
111+
@Override
112+
public void onAnimationEnd(Animator animator) {
113+
isViewHidden = false;
114+
view.setVisibility(View.GONE);
115+
}
116+
117+
@Override
118+
public void onAnimationCancel(Animator animator) {
119+
isViewHidden = false;
120+
if (!mIsShowing)
121+
showView(view);
122+
}
123+
124+
@Override
125+
public void onAnimationRepeat(Animator animator) {
126+
}
127+
});
128+
animator.start();
129+
}
130+
131+
@SuppressLint("NewApi")
132+
public void showView(final View view) {
133+
mIsShowing = true;
134+
ViewPropertyAnimator animator = view.animate()
135+
.translationY((view.getId() == R.id.appBarLayout || view instanceof FloatingActionButton) ? 0 : toolbarHeight)
136+
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
137+
.setDuration(200);
138+
139+
animator.setListener(new Animator.AnimatorListener() {
140+
@Override
141+
public void onAnimationStart(Animator animator) {
142+
view.setVisibility(View.VISIBLE);
143+
}
144+
145+
@Override
146+
public void onAnimationEnd(Animator animator) {
147+
mIsShowing = false;
148+
}
149+
150+
@Override
151+
public void onAnimationCancel(Animator animator) {
152+
mIsShowing = false;
153+
if (!isViewHidden)
154+
hideView(view);
155+
}
156+
157+
@Override
158+
public void onAnimationRepeat(Animator animator) {
159+
}
160+
});
161+
animator.start();
162+
}
163+
164+
@Override
165+
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
166+
return dependency instanceof Snackbar.SnackbarLayout;
167+
}
168+
169+
@Override
170+
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
171+
float translationY = getFabTranslationYForSnackbar(parent, child);
172+
float percentComplete = -translationY / dependency.getHeight();
173+
float scaleFactor = 1 - percentComplete;
174+
175+
child.setScaleX(scaleFactor);
176+
child.setScaleY(scaleFactor);
177+
return false;
178+
}
179+
180+
private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
181+
FloatingActionButton fab) {
182+
float minOffset = 0;
183+
final List<View> dependencies = parent.getDependencies(fab);
184+
for (int i = 0, z = dependencies.size(); i < z; i++) {
185+
final View view = dependencies.get(i);
186+
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
187+
minOffset = Math.min(minOffset,
188+
ViewCompat.getTranslationY(view) - view.getHeight());
189+
}
190+
}
191+
192+
return minOffset;
193+
}
194+
195+
}

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,7 @@
156156
<string name="copy_message_fetch">Copying message to clipboard</string>
157157
<string name="copy_failed">Copying to clipboard failed</string>
158158
<string name="try_again_error_msg">Something went wrong, try again</string>
159+
<string name="connection_established">Connection Established</string>
160+
<string name="no_connection">Waiting for Connection</string>
161+
<string name="connecting">Connecting ...</string>
159162
</resources>

0 commit comments

Comments
 (0)