/* * Copyright 2011 Azwan Adli Abdullah * * 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 com.gh4a; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.graphics.drawable.DrawerArrowDrawable; import android.support.v7.view.SupportMenuInflater; import android.support.v7.widget.Toolbar; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.TextView; import com.gh4a.activities.Github4AndroidActivity; import com.gh4a.activities.SearchActivity; import com.gh4a.activities.home.HomeActivity; import com.gh4a.fragment.SettingsFragment; import com.gh4a.loader.LoaderCallbacks; import com.gh4a.utils.UiUtils; import com.gh4a.widget.SwipeRefreshLayout; import com.gh4a.widget.ToggleableAppBarLayoutBehavior; import java.util.ArrayList; import java.util.List; import fr.castorflex.android.smoothprogressbar.SmoothProgressBar; public abstract class BaseActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, ActivityCompat.OnRequestPermissionsResultCallback, LoaderCallbacks.ParentCallback, NavigationView.OnNavigationItemSelectedListener { private ViewGroup mContentContainer; private TextView mEmptyView; private boolean mContentShown; private boolean mContentEmpty; private boolean mErrorShown; private AppBarLayout mHeader; private ToggleableAppBarLayoutBehavior mHeaderBehavior; private SmoothProgressBar mProgress; private SwipeRefreshLayout mSwipeLayout; private DrawerLayout mDrawerLayout; private CoordinatorLayout mCoordinatorLayout; private Toolbar mToolbar; private NavigationView mRightDrawer; private View mLeftDrawerHeader; private View mRightDrawerHeader; private SupportMenuInflater mMenuInflater; private ActivityCompat.OnRequestPermissionsResultCallback mPendingPermissionCb; private final List<ColorDrawable> mHeaderDrawables = new ArrayList<>(); private final List<ColorDrawable> mStatusBarDrawables = new ArrayList<>(); private final int[] mProgressColors = new int[2]; private Animator mHeaderTransition; private final Handler mHandler = new Handler(); private final Runnable mUpdateTaskDescriptionRunnable = new Runnable() { @TargetApi(21) @Override public void run() { setTaskDescription(new ActivityManager.TaskDescription(null, null, mProgressColors[0])); } }; @Override protected void onCreate(Bundle savedInstanceState) { setTheme(Gh4Application.THEME); onInitExtras(getIntent().getExtras()); super.onCreate(savedInstanceState); super.setContentView(R.layout.base_activity); setupSwipeToRefresh(); setupNavigationDrawer(); setupHeaderDrawable(); } @Override public BaseActivity getBaseActivity() { return this; } public void handleAuthFailureDuringLoad() { Gh4Application.get().logout(); Snackbar.make(mCoordinatorLayout, R.string.load_auth_failure_notice, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.login, new View.OnClickListener() { @Override public void onClick(View v) { goToToplevelActivity(); } }) .show(); } public void handleLoadFailure(Exception e) { setErrorViewVisibility(true); } protected int getLeftNavigationDrawerMenuResource() { return 0; } @IdRes protected int getInitialLeftDrawerSelection(Menu menu) { return 0; } protected int[] getRightNavigationDrawerMenuResources() { return null; } @IdRes protected int getInitialRightDrawerSelection() { return 0; } protected boolean closeDrawers() { boolean result = false; if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) { mDrawerLayout.closeDrawer(Gravity.LEFT); result = true; } if (mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) { mDrawerLayout.closeDrawer(Gravity.RIGHT); result = true; } return result; } protected void toggleRightSideDrawer() { if (mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) { mDrawerLayout.closeDrawer(Gravity.RIGHT); } else { mDrawerLayout.openDrawer(Gravity.RIGHT); } } protected void configureLeftDrawerHeader(View header) { } protected boolean canSwipeToRefresh() { return true; } protected void setChildScrollDelegate(SwipeRefreshLayout.ChildScrollDelegate delegate) { mSwipeLayout.setChildScrollDelegate(delegate); } protected void setEmptyText(int resId) { setEmptyText(getString(resId)); } protected void setEmptyText(CharSequence text) { ensureContent(); mEmptyView.setText(text); } protected void setContentShown(boolean shown) { mContentShown = shown; updateSwipeToRefreshState(); updateViewVisibility(true); } protected void setContentEmpty(boolean isEmpty) { mContentEmpty = isEmpty; updateViewVisibility(false); } protected Intent navigateUp() { return null; } public CoordinatorLayout getRootLayout() { ensureContent(); return mCoordinatorLayout; } protected void onInitExtras(Bundle extras) { } protected void setHeaderColor(int color, int statusBarColor) { cancelHeaderTransition(); for (ColorDrawable d : mHeaderDrawables) { d.setColor(color); } for (ColorDrawable d : mStatusBarDrawables) { d.setColor(statusBarColor); } mProgressColors[0] = color; mProgressColors[1] = statusBarColor; mProgress.invalidate(); scheduleTaskDescriptionUpdate(); } protected void transitionHeaderToColor(int colorAttrId, int statusBarColorAttrId) { final AnimatorSet animation = new AnimatorSet(); List<Animator> animators = new ArrayList<>(); int color = UiUtils.resolveColor(this, colorAttrId); int statusBarColor = UiUtils.resolveColor(this, statusBarColorAttrId); for (ColorDrawable d : mHeaderDrawables) { animators.add(createColorTransition(d, color)); } for (ColorDrawable d : mStatusBarDrawables) { animators.add(createColorTransition(d, statusBarColor)); } final ValueAnimator progressAnimator1 = ValueAnimator.ofInt(mProgressColors[0], color); progressAnimator1.setEvaluator(new ArgbEvaluator()); animators.add(progressAnimator1); final ValueAnimator progressAnimator2 = ValueAnimator.ofInt(mProgressColors[1], statusBarColor); progressAnimator2.setEvaluator(new ArgbEvaluator()); animators.add(progressAnimator2); progressAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mProgressColors[0] = (int) progressAnimator1.getAnimatedValue(); mProgressColors[1] = (int) progressAnimator2.getAnimatedValue(); mProgress.invalidate(); } }); cancelHeaderTransition(); animation.playTogether(animators); animation.setDuration(200); animation.start(); mHeaderTransition = animation; scheduleTaskDescriptionUpdate(); } private void cancelHeaderTransition() { if (mHeaderTransition != null && mHeaderTransition.isRunning()) { mHeaderTransition.cancel(); } mHeaderTransition = null; } private void scheduleTaskDescriptionUpdate() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mHandler.removeCallbacks(mUpdateTaskDescriptionRunnable); mHandler.postDelayed(mUpdateTaskDescriptionRunnable, 500); } } protected void updateRightNavigationDrawer() { int[] drawerMenuResIds = getRightNavigationDrawerMenuResources(); if (drawerMenuResIds != null) { mRightDrawer.getMenu().clear(); for (int id : drawerMenuResIds) { mRightDrawer.inflateMenu(id); } mRightDrawer.setNavigationItemSelectedListener(this); int initialRightDrawerSelection = getInitialRightDrawerSelection(); if (initialRightDrawerSelection != 0) { mRightDrawer.setCheckedItem(initialRightDrawerSelection); } onPrepareRightNavigationDrawerMenu(mRightDrawer.getMenu()); mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT); } else { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT); } } protected void onPrepareRightNavigationDrawerMenu(Menu menu) { } protected void goToToplevelActivity() { Intent intent = getToplevelActivityIntent(); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } protected Intent getToplevelActivityIntent() { Gh4Application app = Gh4Application.get(); if (app.isAuthorized()) { return new Intent(this, HomeActivity.class); } else { return new Intent(this, Github4AndroidActivity.class); } } protected SharedPreferences getPrefs() { return getSharedPreferences(SettingsFragment.PREF_NAME, MODE_PRIVATE); } public void addAppBarOffsetListener(AppBarLayout.OnOffsetChangedListener l) { mHeader.addOnOffsetChangedListener(l); } public void removeAppBarOffsetListener(AppBarLayout.OnOffsetChangedListener l) { mHeader.removeOnOffsetChangedListener(l); } protected void addHeaderView(View view, boolean scrollable) { mHeader.addView(view, 1, new AppBarLayout.LayoutParams( AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT)); setAppBarChildScrollable(view, scrollable); } protected void setToolbarScrollable(boolean scrollable) { setAppBarChildScrollable(mToolbar, scrollable); mHeaderBehavior.setEnabled(scrollable); } private void setAppBarChildScrollable(View view, boolean scrollable) { AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams) view.getLayoutParams(); if (scrollable) { lp.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL); } else { lp.setScrollFlags(0); } view.setLayoutParams(lp); } @Override protected void onStart() { super.onStart(); ensureContent(); if (mContentContainer.getChildCount() == 0) { throw new IllegalStateException("Content view must be initialized before"); } } @NonNull @Override public MenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SupportMenuInflater(UiUtils.makeHeaderThemedContext(this)); } return mMenuInflater; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { Intent intent = navigateUp(); if (intent != null) { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } else { finish(); } return true; } return super.onOptionsItemSelected(item); } @Override public void setContentView(int layoutResId) { ensureContent(); LayoutInflater layoutInflater = LayoutInflater.from(this); View contentView = layoutInflater.inflate(layoutResId, mContentContainer, false); setContentView(contentView); } @Override public void setContentView(View view) { ensureContent(); if (view == null) { throw new IllegalArgumentException("Content view can't be null"); } mContentContainer.removeAllViews(); mContentContainer.addView(view); } @Override public boolean onSearchRequested() { startActivity(SearchActivity.makeIntent(this)); return true; } @Override public void onRefresh() { supportInvalidateOptionsMenu(); mSwipeLayout.setRefreshing(false); } @Override public boolean onNavigationItemSelected(MenuItem item) { mDrawerLayout.closeDrawers(); return false; } public void setRightDrawerLockedClosed(boolean locked) { mDrawerLayout.setDrawerLockMode( locked ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT); } public void requestPermission(final String permission, ActivityCompat.OnRequestPermissionsResultCallback cb, int rationaleTextResId) { if (mPendingPermissionCb != null) { throw new IllegalStateException(); } int grantResult = ActivityCompat.checkSelfPermission(this, permission); if (grantResult == PackageManager.PERMISSION_GRANTED) { cb.onRequestPermissionsResult(0, new String[] { permission }, new int[] { grantResult }); } else { mPendingPermissionCb = cb; if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) { Snackbar.make(getRootLayout(), rationaleTextResId, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.ok, new View.OnClickListener() { @Override public void onClick(View v) { ActivityCompat.requestPermissions(BaseActivity.this, new String[] { permission }, 0); } }) .show(); } else { ActivityCompat.requestPermissions(this, new String[] { permission }, 0); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (mPendingPermissionCb != null) { mPendingPermissionCb.onRequestPermissionsResult(0, permissions, grantResults); mPendingPermissionCb = null; } } @Override public void onBackPressed() { if (!closeDrawers()) { super.onBackPressed(); } } protected boolean forceLoaderReload(int... ids) { LoaderManager lm = getSupportLoaderManager(); boolean reloadedAny = false; for (int id : ids) { Loader loader = lm.getLoader(id); if (loader != null) { loader.onContentChanged(); reloadedAny = true; } } return reloadedAny; } protected void setErrorViewVisibility(boolean visible) { View content = findViewById(R.id.content); View error = findViewById(R.id.error); content.setVisibility(visible ? View.GONE : View.VISIBLE); mErrorShown = visible; updateSwipeToRefreshState(); if (error == null) { if (!visible) { // It's not inflated yet and we don't want it // to be visible, so there's nothing to do return; } ViewStub errorStub = (ViewStub) findViewById(R.id.error_stub); error = errorStub.inflate(); error.findViewById(R.id.retry_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setErrorViewVisibility(false); onRefresh(); } }); } error.setVisibility(visible ? View.VISIBLE : View.GONE); } protected void onDrawerOpened(boolean right) { } protected void onDrawerClosed(boolean right) { } private void updateSwipeToRefreshState() { ensureContent(); mSwipeLayout.setEnabled(mContentShown && !mErrorShown && canSwipeToRefresh()); } private void setupHeaderDrawable() { ensureContent(); int primaryColor = UiUtils.resolveColor(this, R.attr.colorPrimary); assignBackground(mLeftDrawerHeader, primaryColor); assignBackground(mRightDrawerHeader, primaryColor); assignBackground(mHeader, primaryColor); int primaryDarkColor = UiUtils.resolveColor(this, R.attr.colorPrimaryDark); ColorDrawable d = new ColorDrawable(primaryDarkColor); mDrawerLayout.setStatusBarBackground(d); mStatusBarDrawables.add(d); } private ObjectAnimator createColorTransition(ColorDrawable drawable, int color) { final ObjectAnimator animation = ObjectAnimator.ofInt(drawable, "color", drawable.getColor(), color); animation.setEvaluator(new ArgbEvaluator()); return animation; } @SuppressWarnings("deprecation") private void assignBackground(View view, int color) { if (view == null) { return; } ColorDrawable background = new ColorDrawable(color); view.setBackground(background); mHeaderDrawables.add(background); } private void setupSwipeToRefresh() { mSwipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container); if (canSwipeToRefresh()) { mSwipeLayout.setOnRefreshListener(this); mSwipeLayout.setColorSchemeColors( UiUtils.resolveColor(this, R.attr.colorPrimary), 0, UiUtils.resolveColor(this, R.attr.colorPrimaryDark), 0 ); } updateSwipeToRefreshState(); } private void setupNavigationDrawer() { NavigationView leftDrawer = (NavigationView) findViewById(R.id.left_drawer); applyHighlightColor(leftDrawer); mRightDrawer = (NavigationView) findViewById(R.id.right_drawer); applyHighlightColor(mRightDrawer); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_container); mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(View drawerView, float slideOffset) { } @Override public void onDrawerOpened(View drawerView) { BaseActivity.this.onDrawerOpened(drawerView == mRightDrawer); } @Override public void onDrawerClosed(View drawerView) { BaseActivity.this.onDrawerClosed(drawerView == mRightDrawer); } @Override public void onDrawerStateChanged(int newState) { } }); Toolbar toolBar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolBar); int drawerMenuResId = getLeftNavigationDrawerMenuResource(); if (drawerMenuResId != 0) { leftDrawer.inflateMenu(drawerMenuResId); leftDrawer.setNavigationItemSelectedListener(this); int initialLeftDrawerSelection = getInitialLeftDrawerSelection(leftDrawer.getMenu()); if (initialLeftDrawerSelection != 0) { leftDrawer.setCheckedItem(initialLeftDrawerSelection); } ActionBar supportActionBar = getSupportActionBar(); if (supportActionBar != null) { supportActionBar.setDisplayHomeAsUpEnabled(true); } toolBar.setNavigationIcon(new DrawerArrowDrawable(toolBar.getContext())); toolBar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { int drawerLockMode = mDrawerLayout.getDrawerLockMode(GravityCompat.START); if (drawerLockMode != DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { mDrawerLayout.openDrawer(GravityCompat.START); } } }); mLeftDrawerHeader = leftDrawer.inflateHeaderView(R.layout.drawer_header_left); configureLeftDrawerHeader(mLeftDrawerHeader); } else { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT); } mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow_left, Gravity.LEFT); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow_right, Gravity.RIGHT); mDrawerLayout.setScrimColor(ContextCompat.getColor(this, R.color.drawer_scrim)); mRightDrawerHeader = mRightDrawer.inflateHeaderView(R.layout.drawer_header_right); updateRightNavigationDrawer(); } private void applyHighlightColor(NavigationView view) { ColorStateList iconTint = createDefaultNavigationColorStateList(android.R.attr.textColorSecondary); if (iconTint != null) { view.setItemIconTintList(iconTint); } ColorStateList textColor = createDefaultNavigationColorStateList(android.R.attr.textColorPrimary); if (textColor != null) { view.setItemTextColor(textColor); } } // similar to what NavigationView does by default, // but uses accent color instead of primary color private ColorStateList createDefaultNavigationColorStateList(int baseColorThemeAttr) { TypedValue value = new TypedValue(); if (!getTheme().resolveAttribute(baseColorThemeAttr, value, true)) { return null; } ColorStateList baseColor = ContextCompat.getColorStateList(this, value.resourceId); if (!getTheme().resolveAttribute(android.support.design.R.attr.colorAccent, value, true)) { return null; } int colorAccent = value.data; int defaultColor = baseColor.getDefaultColor(); final int[] disabledStateSet = { -android.R.attr.state_enabled }; final int[] checkedStateSet = { android.R.attr.state_checked }; final int[][] states = { disabledStateSet, checkedStateSet, { 0 } }; final int[] colors = { baseColor.getColorForState(disabledStateSet, defaultColor), colorAccent, defaultColor }; return new ColorStateList(states, colors); } private void updateViewVisibility(boolean animate) { ensureContent(); updateViewVisibility(mProgress, animate, !mContentShown); updateViewVisibility(mEmptyView, animate, mContentEmpty && mContentShown); updateViewVisibility(mContentContainer, animate, !mContentEmpty && mContentShown); } private void updateViewVisibility(View view, boolean animate, boolean show) { int visibility = show ? View.VISIBLE : View.GONE; if (view.getVisibility() == visibility) { return; } if (animate) { Animation anim = AnimationUtils.loadAnimation(view.getContext(), show ? android.R.anim.fade_in : android.R.anim.fade_out); view.startAnimation(anim); } else { view.clearAnimation(); } view.setVisibility(visibility); } private void ensureContent() { if (mContentContainer != null && mProgress != null) { return; } mProgress = (SmoothProgressBar) findViewById(R.id.progress); mProgressColors[0] = UiUtils.resolveColor(this, R.attr.colorPrimary); mProgressColors[1] = UiUtils.resolveColor(this, R.attr.colorPrimaryDark); mProgress.setSmoothProgressDrawableColors(mProgressColors); mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout); mContentContainer = (ViewGroup) findViewById(R.id.content_container); mEmptyView = (TextView) findViewById(android.R.id.empty); mHeader = (AppBarLayout) findViewById(R.id.header); mToolbar = (Toolbar) findViewById(R.id.toolbar); mHeaderBehavior = new ToggleableAppBarLayoutBehavior(); CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) mHeader.getLayoutParams(); lp.setBehavior(mHeaderBehavior); mSwipeLayout.setAppBarLayout(mHeader); mContentShown = true; } }