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

Commit 2a98b47

Browse files
committed
Show SnackBar on changing Network State.
1 parent 6e72c07 commit 2a98b47

File tree

5 files changed

+249
-3
lines changed

5 files changed

+249
-3
lines changed

Diff for: app/src/main/java/com/zulip/android/activities/ZulipActivity.java

+38-3
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
import android.os.CountDownTimer;
3535
import android.os.Environment;
3636
import android.os.Handler;
37+
import android.os.Looper;
3738
import android.provider.MediaStore;
3839
import android.support.design.widget.AppBarLayout;
3940
import android.support.design.widget.CoordinatorLayout;
4041
import android.support.design.widget.FloatingActionButton;
42+
import android.support.design.widget.Snackbar;
4143
import android.support.v4.app.ActionBarDrawerToggle;
4244
import android.support.v4.app.ActivityCompat;
4345
import android.support.v4.app.FragmentManager;
@@ -112,6 +114,7 @@
112114
import com.zulip.android.util.Constants;
113115
import com.zulip.android.util.FilePathHelper;
114116
import com.zulip.android.util.MutedTopics;
117+
import com.zulip.android.util.RemoveFabOnScroll;
115118
import com.zulip.android.util.RemoveViewsOnScroll;
116119
import com.zulip.android.util.SwipeRemoveLinearLayout;
117120
import com.zulip.android.util.UrlHelper;
@@ -162,6 +165,7 @@ public class ZulipActivity extends BaseActivity implements
162165
private boolean logged_in = false;
163166
private boolean backPressedOnce = false;
164167
private boolean inSearch = false;
168+
private String networkStatus = Constants.STATUS_CONNECTING;
165169
private ZulipActivity that = this; // self-ref
166170
private DrawerLayout drawerLayout;
167171
private ActionBarDrawerToggle drawerToggle;
@@ -229,8 +233,8 @@ public void removeChatBox(boolean animToRight) {
229233
AnimationHelper.hideViewX(chatBox, animToRight);
230234
//show fab button
231235
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
232-
RemoveViewsOnScroll removeViewsOnScroll = (RemoveViewsOnScroll) layoutParams.getBehavior();
233-
removeViewsOnScroll.showView(fab);
236+
RemoveFabOnScroll removeFabOnScroll = (RemoveFabOnScroll) layoutParams.getBehavior();
237+
removeFabOnScroll.showView(fab);
234238
}
235239

236240
public HashMap<String, Bitmap> getGravatars() {
@@ -1751,7 +1755,7 @@ public void setLayoutBehaviour(LinearLayoutManager linearLayoutManager, Recycler
17511755
appBarLayout.requestLayout();
17521756

17531757
layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
1754-
layoutParams.setBehavior(new RemoveViewsOnScroll(linearLayoutManager, adapter));
1758+
layoutParams.setBehavior(new RemoveFabOnScroll(linearLayoutManager, adapter));
17551759
fab.setLayoutParams(layoutParams);
17561760

17571761
topSnackBar.setMessagesLayoutManager(linearLayoutManager);
@@ -2424,4 +2428,35 @@ public MessageListFragment getCurrentMessageList() {
24242428
public enum Flag {
24252429
RESET_DATABASE,
24262430
}
2431+
/**
2432+
* This function shows the snackbar stating the connectivity status of the device and also changes the behaviour of the
2433+
* fab.
2434+
*/
2435+
public void showConnectivitySnackBar(final String networkState) {
2436+
final CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);
2437+
final Handler handler = new Handler(Looper.getMainLooper()) {
2438+
@Override
2439+
public void handleMessage(android.os.Message msg) {
2440+
if (networkState.equals(Constants.STATUS_CONNECTING)) {
2441+
networkStatus = Constants.STATUS_CONNECTING;
2442+
Snackbar.make(coordinatorLayout, R.string.connecting, Snackbar.LENGTH_INDEFINITE).show();
2443+
2444+
} else if (networkState.equals(Constants.STATUS_CONNECTED)) {
2445+
//Starts a network request only when there is an active network connection
2446+
startRequests();
2447+
networkStatus = Constants.STATUS_CONNECTED;
2448+
Snackbar.make(coordinatorLayout, R.string.connection_established, Snackbar.LENGTH_SHORT).show();
2449+
} else {
2450+
displayChatBox(false);
2451+
displayFAB(true);
2452+
networkStatus = Constants.STATUS_NOT_CONNECTED;
2453+
Snackbar.make(coordinatorLayout, R.string.no_connection, Snackbar.LENGTH_INDEFINITE).show();
2454+
}
2455+
Log.d("NetworkStatus", networkState);
2456+
super.handleMessage(msg);
2457+
}
2458+
};
2459+
2460+
handler.sendEmptyMessage(0);
2461+
}
24272462
}

Diff for: app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.zulip.android.networking.response.events.StarWrapper;
3131
import com.zulip.android.networking.response.events.SubscriptionWrapper;
3232
import com.zulip.android.networking.response.events.UpdateMessageWrapper;
33+
import com.zulip.android.util.Constants;
3334
import com.zulip.android.util.MutedTopics;
3435
import com.zulip.android.util.TypeSwapper;
3536
import com.zulip.android.util.ZLog;
@@ -107,6 +108,10 @@ private void backoff(Exception e) {
107108
long backoff = (long) (Math.exp(failures / 2.0) * 1000);
108109
Log.e(ASYNC_GET_EVENTS, "Failure " + failures + ", sleeping for "
109110
+ backoff);
111+
//MAX_CONNECTION_FAILURE_COUNT failures represent loss in network connectivity
112+
if (failures == Constants.MAX_CONNECTION_FAILURE_COUNT) {
113+
mActivity.showConnectivitySnackBar(Constants.STATUS_NOT_CONNECTED);
114+
}
110115
SystemClock.sleep(backoff);
111116
}
112117

@@ -288,6 +293,9 @@ public void run() {
288293
if (body.getEvents().size() > 0) {
289294
this.processEvents(body);
290295
app.setLastEventId(body.getEvents().get(body.getEvents().size() - 1).getId());
296+
//Shows connected status on receiving data
297+
if (failures > 0)
298+
mActivity.showConnectivitySnackBar(Constants.STATUS_CONNECTED);
291299
failures = 0;
292300
}
293301

Diff for: app/src/main/java/com/zulip/android/util/Constants.java

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public class Constants {
2424
public static final int ALL_PEOPLE_ID = -1;
2525
// row number which is used to differentiate the '@-mentions' in people drawer
2626
public static int MENTIONS = -2;
27+
//Connection States
28+
public static final String STATUS_CONNECTED = "connected";
29+
public static final String STATUS_CONNECTING = "connecting";
30+
public static final String STATUS_NOT_CONNECTED = "no_connection";
31+
public static final int MAX_CONNECTION_FAILURE_COUNT = 2;
2732
}
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+
}

Diff for: app/src/main/res/values/strings.xml

+3
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,7 @@
167167
<string name="people_drawer_recent_pm_label">Recent Private Messages</string>
168168
<string name="people_drawer_others_label">New Private Message</string>
169169
<string name="mentions">\@-mentions</string>
170+
<string name="connection_established">Connection Established</string>
171+
<string name="no_connection">Waiting for Connection</string>
172+
<string name="connecting">Connecting ...</string>
170173
</resources>

0 commit comments

Comments
 (0)