/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.plaidapp.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.StyleSpan;
import android.transition.TransitionManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import butterknife.BindInt;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.plaidapp.R;
import io.plaidapp.data.DataManager;
import io.plaidapp.data.PlaidItem;
import io.plaidapp.data.Source;
import io.plaidapp.data.api.designernews.PostStoryService;
import io.plaidapp.data.api.designernews.model.Story;
import io.plaidapp.data.pocket.PocketUtils;
import io.plaidapp.data.prefs.DesignerNewsPrefs;
import io.plaidapp.data.prefs.DribbblePrefs;
import io.plaidapp.data.prefs.SourceManager;
import io.plaidapp.ui.recyclerview.FilterTouchHelperCallback;
import io.plaidapp.ui.recyclerview.GridItemDividerDecoration;
import io.plaidapp.ui.recyclerview.InfiniteScrollListener;
import io.plaidapp.ui.transitions.FabTransform;
import io.plaidapp.ui.transitions.MorphTransform;
import io.plaidapp.util.AnimUtils;
import io.plaidapp.util.ViewUtils;
public class HomeActivity extends Activity {
private static final int RC_SEARCH = 0;
private static final int RC_AUTH_DRIBBBLE_FOLLOWING = 1;
private static final int RC_AUTH_DRIBBBLE_USER_LIKES = 2;
private static final int RC_AUTH_DRIBBBLE_USER_SHOTS = 3;
private static final int RC_NEW_DESIGNER_NEWS_STORY = 4;
private static final int RC_NEW_DESIGNER_NEWS_LOGIN = 5;
@BindView(R.id.drawer) DrawerLayout drawer;
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.grid) RecyclerView grid;
@BindView(R.id.fab) ImageButton fab;
@BindView(R.id.filters) RecyclerView filtersList;
@BindView(android.R.id.empty) ProgressBar loading;
@Nullable @BindView(R.id.no_connection) ImageView noConnection;
ImageButton fabPosting;
GridLayoutManager layoutManager;
@BindInt(R.integer.num_columns) int columns;
boolean connected = true;
private TextView noFiltersEmptyText;
private boolean monitoringConnectivity = false;
// data
DataManager dataManager;
FeedAdapter adapter;
FilterAdapter filtersAdapter;
private DesignerNewsPrefs designerNewsPrefs;
private DribbblePrefs dribbblePrefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
ButterKnife.bind(this);
drawer.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
setActionBar(toolbar);
if (savedInstanceState == null) {
animateToolbar();
}
setExitSharedElementCallback(FeedAdapter.createSharedElementReenterCallback(this));
dribbblePrefs = DribbblePrefs.get(this);
designerNewsPrefs = DesignerNewsPrefs.get(this);
filtersAdapter = new FilterAdapter(this, SourceManager.getSources(this),
new FilterAdapter.FilterAuthoriser() {
@Override
public void requestDribbbleAuthorisation(View sharedElement, Source forSource) {
Intent login = new Intent(HomeActivity.this, DribbbleLogin.class);
MorphTransform.addExtras(login,
ContextCompat.getColor(HomeActivity.this, R.color.background_dark),
sharedElement.getHeight() / 2);
ActivityOptions options =
ActivityOptions.makeSceneTransitionAnimation(HomeActivity.this,
sharedElement, getString(R.string.transition_dribbble_login));
startActivityForResult(login,
getAuthSourceRequestCode(forSource), options.toBundle());
}
});
dataManager = new DataManager(this, filtersAdapter) {
@Override
public void onDataLoaded(List<? extends PlaidItem> data) {
adapter.addAndResort(data);
checkEmptyState();
}
};
adapter = new FeedAdapter(this, dataManager, columns, PocketUtils.isPocketInstalled(this));
grid.setAdapter(adapter);
layoutManager = new GridLayoutManager(this, columns);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return adapter.getItemColumnSpan(position);
}
});
grid.setLayoutManager(layoutManager);
grid.addOnScrollListener(toolbarElevation);
grid.addOnScrollListener(new InfiniteScrollListener(layoutManager, dataManager) {
@Override
public void onLoadMore() {
dataManager.loadAllDataSources();
}
});
grid.setHasFixedSize(true);
grid.addItemDecoration(new GridItemDividerDecoration(adapter.getDividedViewHolderClasses(),
this, R.dimen.divider_height, R.color.divider));
grid.setItemAnimator(new HomeGridItemAnimator());
// drawer layout treats fitsSystemWindows specially so we have to handle insets ourselves
drawer.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
// inset the toolbar down by the status bar height
ViewGroup.MarginLayoutParams lpToolbar = (ViewGroup.MarginLayoutParams) toolbar
.getLayoutParams();
lpToolbar.topMargin += insets.getSystemWindowInsetTop();
lpToolbar.leftMargin += insets.getSystemWindowInsetLeft();
lpToolbar.rightMargin += insets.getSystemWindowInsetRight();
toolbar.setLayoutParams(lpToolbar);
// inset the grid top by statusbar+toolbar & the bottom by the navbar (don't clip)
grid.setPadding(
grid.getPaddingLeft() + insets.getSystemWindowInsetLeft(), // landscape
insets.getSystemWindowInsetTop()
+ ViewUtils.getActionBarSize(HomeActivity.this),
grid.getPaddingRight() + insets.getSystemWindowInsetRight(), // landscape
grid.getPaddingBottom() + insets.getSystemWindowInsetBottom());
// inset the fab for the navbar
ViewGroup.MarginLayoutParams lpFab = (ViewGroup.MarginLayoutParams) fab
.getLayoutParams();
lpFab.bottomMargin += insets.getSystemWindowInsetBottom(); // portrait
lpFab.rightMargin += insets.getSystemWindowInsetRight(); // landscape
fab.setLayoutParams(lpFab);
View postingStub = findViewById(R.id.stub_posting_progress);
ViewGroup.MarginLayoutParams lpPosting =
(ViewGroup.MarginLayoutParams) postingStub.getLayoutParams();
lpPosting.bottomMargin += insets.getSystemWindowInsetBottom(); // portrait
lpPosting.rightMargin += insets.getSystemWindowInsetRight(); // landscape
postingStub.setLayoutParams(lpPosting);
// we place a background behind the status bar to combine with it's semi-transparent
// color to get the desired appearance. Set it's height to the status bar height
View statusBarBackground = findViewById(R.id.status_bar_background);
FrameLayout.LayoutParams lpStatus = (FrameLayout.LayoutParams)
statusBarBackground.getLayoutParams();
lpStatus.height = insets.getSystemWindowInsetTop();
statusBarBackground.setLayoutParams(lpStatus);
// inset the filters list for the status bar / navbar
// need to set the padding end for landscape case
final boolean ltr = filtersList.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
filtersList.setPaddingRelative(filtersList.getPaddingStart(),
filtersList.getPaddingTop() + insets.getSystemWindowInsetTop(),
filtersList.getPaddingEnd() + (ltr ? insets.getSystemWindowInsetRight() :
0),
filtersList.getPaddingBottom() + insets.getSystemWindowInsetBottom());
// clear this listener so insets aren't re-applied
drawer.setOnApplyWindowInsetsListener(null);
return insets.consumeSystemWindowInsets();
}
});
setupTaskDescription();
filtersList.setAdapter(filtersAdapter);
filtersList.setItemAnimator(new FilterAdapter.FilterAnimator());
filtersAdapter.registerFilterChangedCallback(filtersChangedCallbacks);
dataManager.loadAllDataSources();
ItemTouchHelper.Callback callback = new FilterTouchHelperCallback(filtersAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(filtersList);
checkEmptyState();
}
@Override
protected void onResume() {
super.onResume();
dribbblePrefs.addLoginStatusListener(filtersAdapter);
checkConnectivity();
}
@Override
protected void onPause() {
dribbblePrefs.removeLoginStatusListener(filtersAdapter);
if (monitoringConnectivity) {
final ConnectivityManager connectivityManager
= (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
connectivityManager.unregisterNetworkCallback(connectivityCallback);
monitoringConnectivity = false;
}
super.onPause();
}
@Override
public void onActivityReenter(int resultCode, Intent data) {
if (data == null || resultCode != RESULT_OK
|| !data.hasExtra(DribbbleShot.RESULT_EXTRA_SHOT_ID)) return;
// When reentering, if the shared element is no longer on screen (e.g. after an
// orientation change) then scroll it into view.
final long sharedShotId = data.getLongExtra(DribbbleShot.RESULT_EXTRA_SHOT_ID, -1L);
if (sharedShotId != -1L // returning from a shot
&& adapter.getDataItemCount() > 0 // grid populated
&& grid.findViewHolderForItemId(sharedShotId) == null) { // view not attached
final int position = adapter.getItemPosition(sharedShotId);
if (position == RecyclerView.NO_POSITION) return;
// delay the transition until our shared element is on-screen i.e. has been laid out
postponeEnterTransition();
grid.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int l, int t, int r, int b,
int oL, int oT, int oR, int oB) {
grid.removeOnLayoutChangeListener(this);
startPostponedEnterTransition();
}
});
grid.scrollToPosition(position);
toolbar.setTranslationZ(-1f);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem dribbbleLogin = menu.findItem(R.id.menu_dribbble_login);
if (dribbbleLogin != null) {
dribbbleLogin.setTitle(dribbblePrefs.isLoggedIn() ?
R.string.dribbble_log_out : R.string.dribbble_login);
}
final MenuItem designerNewsLogin = menu.findItem(R.id.menu_designer_news_login);
if (designerNewsLogin != null) {
designerNewsLogin.setTitle(designerNewsPrefs.isLoggedIn() ?
R.string.designer_news_log_out : R.string.designer_news_login);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_filter:
drawer.openDrawer(GravityCompat.END);
return true;
case R.id.menu_search:
View searchMenuView = toolbar.findViewById(R.id.menu_search);
Bundle options = ActivityOptions.makeSceneTransitionAnimation(this, searchMenuView,
getString(R.string.transition_search_back)).toBundle();
startActivityForResult(new Intent(this, SearchActivity.class), RC_SEARCH, options);
return true;
case R.id.menu_dribbble_login:
if (!dribbblePrefs.isLoggedIn()) {
dribbblePrefs.login(HomeActivity.this);
} else {
dribbblePrefs.logout();
// TODO something better than a toast!!
Toast.makeText(getApplicationContext(), R.string.dribbble_logged_out, Toast
.LENGTH_SHORT).show();
}
return true;
case R.id.menu_designer_news_login:
if (!designerNewsPrefs.isLoggedIn()) {
startActivity(new Intent(this, DesignerNewsLogin.class));
} else {
designerNewsPrefs.logout(HomeActivity.this);
// TODO something better than a toast!!
Toast.makeText(getApplicationContext(), R.string.designer_news_logged_out,
Toast.LENGTH_SHORT).show();
}
return true;
case R.id.menu_about:
startActivity(new Intent(HomeActivity.this, AboutActivity.class),
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.END)) {
drawer.closeDrawer(GravityCompat.END);
} else {
super.onBackPressed();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RC_SEARCH:
// reset the search icon which we hid
View searchMenuView = toolbar.findViewById(R.id.menu_search);
if (searchMenuView != null) {
searchMenuView.setAlpha(1f);
}
if (resultCode == SearchActivity.RESULT_CODE_SAVE) {
String query = data.getStringExtra(SearchActivity.EXTRA_QUERY);
if (TextUtils.isEmpty(query)) return;
Source dribbbleSearch = null;
Source designerNewsSearch = null;
boolean newSource = false;
if (data.getBooleanExtra(SearchActivity.EXTRA_SAVE_DRIBBBLE, false)) {
dribbbleSearch = new Source.DribbbleSearchSource(query, true);
newSource = filtersAdapter.addFilter(dribbbleSearch);
}
if (data.getBooleanExtra(SearchActivity.EXTRA_SAVE_DESIGNER_NEWS, false)) {
designerNewsSearch = new Source.DesignerNewsSearchSource(query, true);
newSource |= filtersAdapter.addFilter(designerNewsSearch);
}
if (newSource) {
highlightNewSources(dribbbleSearch, designerNewsSearch);
}
}
break;
case RC_NEW_DESIGNER_NEWS_STORY:
switch (resultCode) {
case PostNewDesignerNewsStory.RESULT_DRAG_DISMISSED:
// need to reshow the FAB as there's no shared element transition
showFab();
unregisterPostStoryResultListener();
break;
case PostNewDesignerNewsStory.RESULT_POSTING:
showPostingProgress();
break;
default:
unregisterPostStoryResultListener();
break;
}
break;
case RC_NEW_DESIGNER_NEWS_LOGIN:
if (resultCode == RESULT_OK) {
showFab();
}
break;
case RC_AUTH_DRIBBBLE_FOLLOWING:
if (resultCode == RESULT_OK) {
filtersAdapter.enableFilterByKey(SourceManager.SOURCE_DRIBBBLE_FOLLOWING, this);
}
break;
case RC_AUTH_DRIBBBLE_USER_LIKES:
if (resultCode == RESULT_OK) {
filtersAdapter.enableFilterByKey(
SourceManager.SOURCE_DRIBBBLE_USER_LIKES, this);
}
break;
case RC_AUTH_DRIBBBLE_USER_SHOTS:
if (resultCode == RESULT_OK) {
filtersAdapter.enableFilterByKey(
SourceManager.SOURCE_DRIBBBLE_USER_SHOTS, this);
}
break;
}
}
@Override
protected void onDestroy() {
dataManager.cancelLoading();
super.onDestroy();
}
// listener for notifying adapter when data sources are deactivated
private FilterAdapter.FiltersChangedCallbacks filtersChangedCallbacks =
new FilterAdapter.FiltersChangedCallbacks() {
@Override
public void onFiltersChanged(Source changedFilter) {
if (!changedFilter.active) {
adapter.removeDataSource(changedFilter.key);
}
checkEmptyState();
}
@Override
public void onFilterRemoved(Source removed) {
adapter.removeDataSource(removed.key);
checkEmptyState();
}
};
private RecyclerView.OnScrollListener toolbarElevation = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// we want the grid to scroll over the top of the toolbar but for the toolbar items
// to be clickable when visible. To achieve this we play games with elevation. The
// toolbar is laid out in front of the grid but when we scroll, we lower it's elevation
// to allow the content to pass in front (and reset when scrolled to top of the grid)
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& layoutManager.findFirstVisibleItemPosition() == 0
&& layoutManager.findViewByPosition(0).getTop() == grid.getPaddingTop()
&& toolbar.getTranslationZ() != 0) {
// at top, reset elevation
toolbar.setTranslationZ(0f);
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING
&& toolbar.getTranslationZ() != -1f) {
// grid scrolled, lower toolbar to allow content to pass in front
toolbar.setTranslationZ(-1f);
}
}
};
@OnClick(R.id.fab)
protected void fabClick() {
if (designerNewsPrefs.isLoggedIn()) {
Intent intent = new Intent(this, PostNewDesignerNewsStory.class);
FabTransform.addExtras(intent,
ContextCompat.getColor(this, R.color.accent), R.drawable.ic_add_dark);
intent.putExtra(PostStoryService.EXTRA_BROADCAST_RESULT, true);
registerPostStoryResultListener();
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, fab,
getString(R.string.transition_new_designer_news_post));
startActivityForResult(intent, RC_NEW_DESIGNER_NEWS_STORY, options.toBundle());
} else {
Intent intent = new Intent(this, DesignerNewsLogin.class);
FabTransform.addExtras(intent,
ContextCompat.getColor(this, R.color.accent), R.drawable.ic_add_dark);
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, fab,
getString(R.string.transition_designer_news_login));
startActivityForResult(intent, RC_NEW_DESIGNER_NEWS_LOGIN, options.toBundle());
}
}
BroadcastReceiver postStoryResultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensurePostingProgressInflated();
switch (intent.getAction()) {
case PostStoryService.BROADCAST_ACTION_SUCCESS:
// success animation
AnimatedVectorDrawable complete =
(AnimatedVectorDrawable) getDrawable(R.drawable.avd_upload_complete);
if (complete != null) {
fabPosting.setImageDrawable(complete);
complete.start();
fabPosting.postDelayed(new Runnable() {
@Override
public void run() {
fabPosting.setVisibility(View.GONE);
}
}, 2100); // length of R.drawable.avd_upload_complete
}
// actually add the story to the grid
Story newStory = intent.getParcelableExtra(PostStoryService.EXTRA_NEW_STORY);
adapter.addAndResort(Collections.singletonList(newStory));
break;
case PostStoryService.BROADCAST_ACTION_FAILURE:
// failure animation
AnimatedVectorDrawable failed =
(AnimatedVectorDrawable) getDrawable(R.drawable.avd_upload_error);
if (failed != null) {
fabPosting.setImageDrawable(failed);
failed.start();
}
// remove the upload progress 'fab' and reshow the regular one
fabPosting.animate()
.alpha(0f)
.rotation(90f)
.setStartDelay(2000L) // leave error on screen briefly
.setDuration(300L)
.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(HomeActivity
.this))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fabPosting.setVisibility(View.GONE);
fabPosting.setAlpha(1f);
fabPosting.setRotation(0f);
}
});
break;
}
unregisterPostStoryResultListener();
}
};
void registerPostStoryResultListener() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PostStoryService.BROADCAST_ACTION_SUCCESS);
intentFilter.addAction(PostStoryService.BROADCAST_ACTION_FAILURE);
LocalBroadcastManager.getInstance(this).
registerReceiver(postStoryResultReceiver, intentFilter);
}
void unregisterPostStoryResultListener() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(postStoryResultReceiver);
}
void revealPostingProgress() {
Animator reveal = ViewAnimationUtils.createCircularReveal(fabPosting,
(int) fabPosting.getPivotX(),
(int) fabPosting.getPivotY(),
0f,
fabPosting.getWidth() / 2)
.setDuration(600L);
reveal.setInterpolator(AnimUtils.getFastOutLinearInInterpolator(this));
reveal.start();
AnimatedVectorDrawable uploading =
(AnimatedVectorDrawable) getDrawable(R.drawable.avd_uploading);
if (uploading != null) {
fabPosting.setImageDrawable(uploading);
uploading.start();
}
}
void ensurePostingProgressInflated() {
if (fabPosting != null) return;
fabPosting = (ImageButton) ((ViewStub) findViewById(R.id.stub_posting_progress)).inflate();
}
void checkEmptyState() {
if (adapter.getDataItemCount() == 0) {
// if grid is empty check whether we're loading or if no filters are selected
if (filtersAdapter.getEnabledSourcesCount() > 0) {
if (connected) {
loading.setVisibility(View.VISIBLE);
setNoFiltersEmptyTextVisibility(View.GONE);
}
} else {
loading.setVisibility(View.GONE);
setNoFiltersEmptyTextVisibility(View.VISIBLE);
}
toolbar.setTranslationZ(0f);
} else {
loading.setVisibility(View.GONE);
setNoFiltersEmptyTextVisibility(View.GONE);
}
}
int getAuthSourceRequestCode(Source filter) {
switch (filter.key) {
case SourceManager.SOURCE_DRIBBBLE_FOLLOWING:
return RC_AUTH_DRIBBBLE_FOLLOWING;
case SourceManager.SOURCE_DRIBBBLE_USER_LIKES:
return RC_AUTH_DRIBBBLE_USER_LIKES;
case SourceManager.SOURCE_DRIBBBLE_USER_SHOTS:
return RC_AUTH_DRIBBBLE_USER_SHOTS;
}
throw new InvalidParameterException();
}
private void showPostingProgress() {
ensurePostingProgressInflated();
fabPosting.setVisibility(View.VISIBLE);
// if stub has just been inflated then it will not have been laid out yet
if (fabPosting.isLaidOut()) {
revealPostingProgress();
} else {
fabPosting.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int l, int t, int r, int b,
int oldL, int oldT, int oldR, int oldB) {
fabPosting.removeOnLayoutChangeListener(this);
revealPostingProgress();
}
});
}
}
private void setNoFiltersEmptyTextVisibility(int visibility) {
if (visibility == View.VISIBLE) {
if (noFiltersEmptyText == null) {
// create the no filters empty text
ViewStub stub = (ViewStub) findViewById(R.id.stub_no_filters);
noFiltersEmptyText = (TextView) stub.inflate();
String emptyText = getString(R.string.no_filters_selected);
int filterPlaceholderStart = emptyText.indexOf('\u08B4');
int altMethodStart = filterPlaceholderStart + 3;
SpannableStringBuilder ssb = new SpannableStringBuilder(emptyText);
// show an image of the filter icon
ssb.setSpan(new ImageSpan(this, R.drawable.ic_filter_small,
ImageSpan.ALIGN_BASELINE),
filterPlaceholderStart,
filterPlaceholderStart + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// make the alt method (swipe from right) less prominent and italic
ssb.setSpan(new ForegroundColorSpan(
ContextCompat.getColor(this, R.color.text_secondary_light)),
altMethodStart,
emptyText.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new StyleSpan(Typeface.ITALIC),
altMethodStart,
emptyText.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
noFiltersEmptyText.setText(ssb);
noFiltersEmptyText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawer.openDrawer(GravityCompat.END);
}
});
}
noFiltersEmptyText.setVisibility(visibility);
} else if (noFiltersEmptyText != null) {
noFiltersEmptyText.setVisibility(visibility);
}
}
private void setupTaskDescription() {
Bitmap overviewIcon =
BitmapFactory.decodeResource(getResources(), getApplicationInfo().icon);
setTaskDescription(new ActivityManager.TaskDescription(getString(R.string.app_name),
overviewIcon,
ContextCompat.getColor(this, R.color.primary)));
overviewIcon.recycle();
}
private void animateToolbar() {
// this is gross but toolbar doesn't expose it's children to animate them :(
View t = toolbar.getChildAt(0);
if (t != null && t instanceof TextView) {
TextView title = (TextView) t;
// fade in and space out the title. Animating the letterSpacing performs horribly so
// fake it by setting the desired letterSpacing then animating the scaleX ¯\_(ツ)_/¯
title.setAlpha(0f);
title.setScaleX(0.8f);
title.animate()
.alpha(1f)
.scaleX(1f)
.setStartDelay(300)
.setDuration(900)
.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(this));
}
}
private void showFab() {
fab.setAlpha(0f);
fab.setScaleX(0f);
fab.setScaleY(0f);
fab.setTranslationY(fab.getHeight() / 2);
fab.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.translationY(0f)
.setDuration(300L)
.setInterpolator(AnimUtils.getLinearOutSlowInInterpolator(this))
.start();
}
/**
* Highlight the new source(s) by:
* 1. opening the drawer
* 2. scrolling new source(s) into view
* 3. flashing new source(s) background
* 4. closing the drawer (if user hasn't interacted with it)
*/
private void highlightNewSources(final Source... sources) {
final Runnable closeDrawerRunnable = new Runnable() {
@Override
public void run() {
drawer.closeDrawer(GravityCompat.END);
}
};
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
// if the user interacts with the filters while it's open then don't auto-close
private final View.OnTouchListener filtersTouch = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
drawer.removeCallbacks(closeDrawerRunnable);
return false;
}
};
@Override
public void onDrawerOpened(View drawerView) {
// scroll to the new item(s) and highlight them
List<Integer> filterPositions = new ArrayList<>(sources.length);
for (Source source : sources) {
if (source != null) {
filterPositions.add(filtersAdapter.getFilterPosition(source));
}
}
int scrollTo = Collections.max(filterPositions);
filtersList.smoothScrollToPosition(scrollTo);
for (int position : filterPositions) {
filtersAdapter.highlightFilter(position);
}
filtersList.setOnTouchListener(filtersTouch);
}
@Override
public void onDrawerClosed(View drawerView) {
// reset
filtersList.setOnTouchListener(null);
drawer.removeDrawerListener(this);
}
@Override
public void onDrawerStateChanged(int newState) {
// if the user interacts with the drawer manually then don't auto-close
if (newState == DrawerLayout.STATE_DRAGGING) {
drawer.removeCallbacks(closeDrawerRunnable);
}
}
});
drawer.openDrawer(GravityCompat.END);
drawer.postDelayed(closeDrawerRunnable, 2000L);
}
private void checkConnectivity() {
final ConnectivityManager connectivityManager
= (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
connected = activeNetworkInfo != null && activeNetworkInfo.isConnected();
if (!connected) {
loading.setVisibility(View.GONE);
if (noConnection == null) {
final ViewStub stub = (ViewStub) findViewById(R.id.stub_no_connection);
noConnection = (ImageView) stub.inflate();
}
final AnimatedVectorDrawable avd =
(AnimatedVectorDrawable) getDrawable(R.drawable.avd_no_connection);
if (noConnection != null && avd != null) {
noConnection.setImageDrawable(avd);
avd.start();
}
connectivityManager.registerNetworkCallback(
new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build(),
connectivityCallback);
monitoringConnectivity = true;
}
}
private ConnectivityManager.NetworkCallback connectivityCallback
= new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
connected = true;
if (adapter.getDataItemCount() != 0) return;
runOnUiThread(new Runnable() {
@Override
public void run() {
TransitionManager.beginDelayedTransition(drawer);
noConnection.setVisibility(View.GONE);
loading.setVisibility(View.VISIBLE);
dataManager.loadAllDataSources();
}
});
}
@Override
public void onLost(Network network) {
connected = false;
}
};
}