package tellh.com.gitclub.presentation.widget; import android.animation.ArgbEvaluator; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import com.github.paolorotolo.appintro.AppIntroViewPager; import com.github.paolorotolo.appintro.ISlideBackgroundColorHolder; import com.github.paolorotolo.appintro.ISlidePolicy; import com.github.paolorotolo.appintro.ISlideSelectionListener; import com.github.paolorotolo.appintro.IndicatorController; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import tellh.com.gitclub.R; public abstract class AppIntroBase extends AppCompatActivity implements AppIntroViewPager.OnNextPageRequestedListener { public final static int DEFAULT_COLOR = 1; private static final String TAG = "AppIntroBase"; private static final int DEFAULT_SCROLL_DURATION_FACTOR = 1; private static final int PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1; private static final String INSTANCE_DATA_IMMERSIVE_MODE_ENABLED = "com.github.paolorotolo.appintro_immersive_mode_enabled"; private static final String INSTANCE_DATA_IMMERSIVE_MODE_STICKY = "com.github.paolorotolo.appintro_immersive_mode_sticky"; private static final String INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED = "com.github.paolorotolo.appintro_color_transitions_enabled"; protected PagerAdapter mPagerAdapter; protected AppIntroViewPager pager; protected Vibrator mVibrator; protected IndicatorController mController; private GestureDetectorCompat gestureDetector; protected final List<Fragment> fragments = new Vector<>(); protected int slidesNumber; protected int vibrateIntensity = 20; protected int selectedIndicatorColor = DEFAULT_COLOR; protected int unselectedIndicatorColor = DEFAULT_COLOR; protected View loginButton; protected View skipButton; protected int savedCurrentItem; private final ArgbEvaluator argbEvaluator = new ArgbEvaluator(); protected boolean isVibrateOn = false; protected boolean baseProgressButtonEnabled = true; protected boolean progressButtonEnabled = true; private boolean isGoBackLockEnabled = false; private boolean isImmersiveModeEnabled = false; private boolean isImmersiveModeSticky = false; private boolean areColorTransitionsEnabled = false; protected boolean skipButtonEnabled = true; private int currentlySelectedItem = -1; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(getLayoutId()); gestureDetector = new GestureDetectorCompat(this, new WindowGestureListener()); loginButton = findViewById(R.id.btn_login); skipButton = findViewById(R.id.skip); mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE); mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), fragments); pager = (AppIntroViewPager) findViewById(R.id.view_pager); pager.setOffscreenPageLimit(4); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(@NonNull View v) { if (isVibrateOn) { mVibrator.vibrate(vibrateIntensity); } Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem()); boolean isSlideChangingAllowed = handleBeforeSlideChanged(); if (isSlideChangingAllowed) { handleSlideChanged(currentFragment, null); onLoginPressed(currentFragment); } else { handleIllegalSlideChangeAttempt(); } } }); skipButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(@NonNull View v) { if (isVibrateOn) { mVibrator.vibrate(vibrateIntensity); } onSkipPressed(mPagerAdapter.getItem(pager.getCurrentItem())); } }); pager.setAdapter(this.mPagerAdapter); pager.addOnPageChangeListener(new PagerOnPageChangeListener()); pager.setOnNextPageRequestedListener(this); setScrollDurationFactor(DEFAULT_SCROLL_DURATION_FACTOR); } @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Call deprecated init method only if no fragments have been added trough onCreate() or onStart() if (fragments.size() == 0) { init(null); } // required for triggering onPageSelected and onSlideChanged for first page pager.setCurrentItem(savedCurrentItem); pager.post(new Runnable() { @Override public void run() { handleSlideChanged(null, mPagerAdapter.getItem(pager.getCurrentItem())); } }); slidesNumber = fragments.size(); initController(); } @Override public void onBackPressed() { // Do nothing if go back lock is enabled or slide has custom policy. if (!isGoBackLockEnabled) { super.onBackPressed(); } } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus && isImmersiveModeEnabled) { setImmersiveMode(true, isImmersiveModeSticky); } } @Override public boolean dispatchTouchEvent(MotionEvent event) { if (isImmersiveModeEnabled) { gestureDetector.onTouchEvent(event); } return super.dispatchTouchEvent(event); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("baseProgressButtonEnabled", baseProgressButtonEnabled); outState.putBoolean("progressButtonEnabled", progressButtonEnabled); outState.putBoolean("nextEnabled", pager.isPagingEnabled()); outState.putBoolean("nextPagingEnabled", pager.isNextPagingEnabled()); outState.putBoolean("skipButtonEnabled", skipButtonEnabled); outState.putInt("lockPage", pager.getLockPage()); outState.putInt("currentItem", pager.getCurrentItem()); outState.putBoolean(INSTANCE_DATA_IMMERSIVE_MODE_ENABLED, isImmersiveModeEnabled); outState.putBoolean(INSTANCE_DATA_IMMERSIVE_MODE_STICKY, isImmersiveModeSticky); outState.putBoolean(INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED, areColorTransitionsEnabled); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); this.baseProgressButtonEnabled = savedInstanceState.getBoolean("baseProgressButtonEnabled"); this.progressButtonEnabled = savedInstanceState.getBoolean("progressButtonEnabled"); this.skipButtonEnabled = savedInstanceState.getBoolean("skipButtonEnabled"); this.savedCurrentItem = savedInstanceState.getInt("currentItem"); pager.setPagingEnabled(savedInstanceState.getBoolean("nextEnabled")); pager.setNextPagingEnabled(savedInstanceState.getBoolean("nextPagingEnabled")); pager.setLockPage(savedInstanceState.getInt("lockPage")); isImmersiveModeEnabled = savedInstanceState.getBoolean(INSTANCE_DATA_IMMERSIVE_MODE_ENABLED); isImmersiveModeSticky = savedInstanceState.getBoolean(INSTANCE_DATA_IMMERSIVE_MODE_STICKY); areColorTransitionsEnabled = savedInstanceState.getBoolean(INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED); } @Override public boolean onCanRequestNextPage() { return handleBeforeSlideChanged(); } @Override public void onIllegallyRequestedNextPage() { handleIllegalSlideChangeAttempt(); } private void initController() { if (mController == null) mController = new DefaultIndicatorController(); FrameLayout indicatorContainer = (FrameLayout) findViewById(R.id.indicator_container); indicatorContainer.addView(mController.newInstance(this)); mController.initialize(slidesNumber); if (selectedIndicatorColor != DEFAULT_COLOR) mController.setSelectedIndicatorColor(selectedIndicatorColor); if (unselectedIndicatorColor != DEFAULT_COLOR) mController.setUnselectedIndicatorColor(unselectedIndicatorColor); mController.selectPosition(currentlySelectedItem); } private void handleIllegalSlideChangeAttempt() { Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem()); if (currentFragment != null && currentFragment instanceof ISlidePolicy) { ISlidePolicy slide = (ISlidePolicy) currentFragment; if (!slide.isPolicyRespected()) { slide.onUserIllegallyRequestedNextPage(); } } } /** * Called before a slide change happens. By returning false, one can disallow the slide change. * * @return true, if the slide change should be allowed, else false */ private boolean handleBeforeSlideChanged() { Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem()); // Check if the current fragment implements ISlidePolicy, else a change is always allowed if (currentFragment instanceof ISlidePolicy) { ISlidePolicy slide = (ISlidePolicy) currentFragment; // Check if policy is fulfilled if (!slide.isPolicyRespected()) { return false; } } return true; } private void handleSlideChanged(Fragment oldFragment, Fragment newFragment) { // Check if oldFragment implements ISlideSelectionListener if (oldFragment != null && oldFragment instanceof ISlideSelectionListener) { ((ISlideSelectionListener) oldFragment).onSlideDeselected(); } // Check if newFragment implements ISlideSelectionListener if (newFragment != null && newFragment instanceof ISlideSelectionListener) { ((ISlideSelectionListener) newFragment).onSlideSelected(); } onSlideChanged(oldFragment, newFragment); } /** * Gets the layout id of the layout used by the current activity * * @return Layout to use */ protected abstract int getLayoutId(); /** * Called after a new slide has been selected * * @param position Position of the new selected slide */ protected void onPageSelected(int position) { // ; } public boolean isSkipButtonEnabled() { return skipButtonEnabled; } /** * Called when the user clicked the skip button * * @param currentFragment Instance of the currently displayed fragment */ public void onSkipPressed(Fragment currentFragment) { } protected void setScrollDurationFactor(int factor) { pager.setScrollDurationFactor(factor); } /** * Helper method for displaying a view * * @param button View which visibility should be changed * @param show Whether the view should be visible or not */ protected void setButtonState(View button, boolean show) { if (show) { button.setVisibility(View.VISIBLE); } else { button.setVisibility(View.INVISIBLE); } } /** * Returns the used ViewPager instance * * @return Instance of the used ViewPager */ public AppIntroViewPager getPager() { return pager; } /** * Returns all current slides. * * @return List of the current slides */ @NonNull public List<Fragment> getSlides() { return mPagerAdapter.getFragments(); } /** * Adds a new slide * * @param fragment Instance of Fragment which should be added as slide */ public void addSlide(@NonNull Fragment fragment) { fragments.add(fragment); mPagerAdapter.notifyDataSetChanged(); } public boolean isProgressButtonEnabled() { return progressButtonEnabled; } public void setOffScreenPageLimit(int limit) { pager.setOffscreenPageLimit(limit); } /** * @param savedInstanceState Null * @deprecated It is strongly recommended to use {@link #onCreate(Bundle)} instead. Be sure calling super.onCreate() in your method. * Please note that this method WILL NOT be called when the activity gets recreated i.e. the fragment instances get restored. * The method will only be called when there are no fragments registered to the intro at all. */ public void init(@Nullable Bundle savedInstanceState) { } /** * Called when the user clicked the done button * * @param currentFragment Instance of the currently displayed fragment */ public void onLoginPressed(Fragment currentFragment) { } /** * Called when the selected fragment changed. This will be called automatically if the into starts or is finished via the done button. * * @param oldFragment Instance of the fragment which was displayed before. This might be null if the the intro has just started. * @param newFragment Instance of the fragment which is displayed now. This might be null if the intro has finished */ public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) { } @Override public boolean onKeyDown(int code, KeyEvent kvent) { if (code == KeyEvent.KEYCODE_ENTER || code == KeyEvent.KEYCODE_BUTTON_A || code == KeyEvent.KEYCODE_DPAD_CENTER) { ViewPager vp = (ViewPager) this.findViewById(R.id.view_pager); if (vp.getCurrentItem() == vp.getAdapter().getCount() - 1) { onLoginPressed(fragments.get(vp.getCurrentItem())); } else { vp.setCurrentItem(vp.getCurrentItem() + 1); } return false; } return super.onKeyDown(code, kvent); } /** * Allows the user to set the nav bar color of their app intro * * @param Color string form of color in 3 or 6 digit hex form (#ffffff) */ public void setNavBarColor(String Color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setNavigationBarColor(android.graphics.Color.parseColor(Color)); } } /** * Allows the user to set the nav bar color of their app intro * * @param color int form of color. pass your color resource to here (R.color.your_color) */ public void setNavBarColor(@ColorRes int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setNavigationBarColor(ContextCompat.getColor(this, color)); } } /** * Allows for setting statusbar visibility (true by default) * * @param isVisible put true to show status bar, and false to hide it */ public void showStatusBar(boolean isVisible) { if (!isVisible) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } /** * Specifies whether to enable the immersive mode. * Note that immersive mode is only supported on Kitkat and newer. * * @param isEnabled Whether the immersive mode should be enabled or not. * @param isSticky Whether to use the sticky immersive mode or not */ public void setImmersiveMode(boolean isEnabled, boolean isSticky) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (!isEnabled && isImmersiveModeEnabled) { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); isImmersiveModeEnabled = false; } else if (isEnabled) { int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; if (isSticky) { flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; isImmersiveModeSticky = true; } else { flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; isImmersiveModeSticky = false; } getWindow().getDecorView().setSystemUiVisibility(flags); isImmersiveModeEnabled = true; } } } public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case PERMISSIONS_REQUEST_ALL_PERMISSIONS: pager.setCurrentItem(pager.getCurrentItem() + 1); break; default: } } private final class PagerOnPageChangeListener implements ViewPager.OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (areColorTransitionsEnabled) { if (position < mPagerAdapter.getCount() - 1) { if (mPagerAdapter.getItem(position) instanceof ISlideBackgroundColorHolder && mPagerAdapter.getItem(position + 1) instanceof ISlideBackgroundColorHolder) { Fragment currentSlide = mPagerAdapter.getItem(position); Fragment nextSlide = mPagerAdapter.getItem(position + 1); ISlideBackgroundColorHolder currentSlideCasted = (ISlideBackgroundColorHolder) currentSlide; ISlideBackgroundColorHolder nextSlideCasted = (ISlideBackgroundColorHolder) nextSlide; // Check if both fragments are attached to an activity, otherwise getDefaultBackgroundColor may fail. if (currentSlide.isAdded() && nextSlide.isAdded()) { int newColor = (int) argbEvaluator.evaluate(positionOffset, currentSlideCasted.getDefaultBackgroundColor(), nextSlideCasted.getDefaultBackgroundColor()); currentSlideCasted.setBackgroundColor(newColor); nextSlideCasted.setBackgroundColor(newColor); } } else { throw new IllegalStateException("Color transitions are only available if all slides implement ISlideBackgroundColorHolder."); } } } } @Override public void onPageSelected(int position) { if (slidesNumber > 1) mController.selectPosition(position); AppIntroBase.this.onPageSelected(position); if (slidesNumber > 0) { if (currentlySelectedItem == -1) { handleSlideChanged(null, mPagerAdapter.getItem(position)); } else { handleSlideChanged(mPagerAdapter.getItem(currentlySelectedItem), mPagerAdapter.getItem(pager.getCurrentItem())); } } currentlySelectedItem = position; } @Override public void onPageScrollStateChanged(int state) { } } private final class WindowGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { if (isImmersiveModeEnabled && !isImmersiveModeSticky) { setImmersiveMode(true, isImmersiveModeSticky); } return false; } } class PagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragments; private Map<Integer, Fragment> retainedFragments; public PagerAdapter(FragmentManager fm, @NonNull List<Fragment> fragments) { super(fm); this.fragments = fragments; this.retainedFragments = new HashMap<>(); } @Override public Fragment getItem(int position) { // Check if the fragment at this position has been retained by the PagerAdapter if (retainedFragments.containsKey(position)) { return retainedFragments.get(position); } return fragments.get(position); } @Override public int getCount() { return this.fragments.size(); } @NonNull public List<Fragment> getFragments() { return fragments; } @NonNull public Collection<Fragment> getRetainedFragments() { return retainedFragments.values(); } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment) super.instantiateItem(container, position); retainedFragments.put(position, fragment); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (retainedFragments.containsKey(position)) { retainedFragments.remove(position); } super.destroyItem(container, position, object); } } }