/* * Copyright 2015. Appsi Mobile * * 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.appsimobile.appsii; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.util.CircularArray; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.ProgressBar; import android.widget.RelativeLayout; import com.appsimobile.appsii.dagger.AppsiInjector; import javax.inject.Inject; /** * This class is the basic implementation of the sidebar. It communicates various * state events from Appsii to the SidebarPagerAdapter and manages the ViewPager */ public class Sidebar extends RelativeLayout implements LoaderListener, View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener, AbstractSidebarPagerAdapter.FlagListener { /** The delay before the sidebar is closed after an app-widget action was clicked */ static final int DELAYED_CLOSE_DURATION = 2500; /** * The delay before the sidebar is closed after an app-widget action was clicked * that needs user feedback */ static final int DELAYED_CLOSE_DURATION_WHEN_ASKED = 8000; /** * A bundle used to manage state in by any client after an app-widget close * message is being shown */ final Bundle mCloseStateBundle = new Bundle(); private final OnPageChangeListenerImpl mOnPageChangeListener = new OnPageChangeListenerImpl(); /** * A listener that can be called when the sidebar needs to be closed */ SidebarListener mSidebarListener; /** * True when we are opening from the left side. False otherwise. * Left and right are absolute and also used in rtl locales */ boolean mIsLeft; View mSidebarBackgroundView; View mLeftShadow; View mRightShadow; OnCancelCloseListener mOnCancelCloseListener; View mCancelClosingOverlay; ProgressBar mCancelClosingProgressBar; ObjectAnimator mClosingProgressBarAnimator; View mCloseText; View mCancelClosingOverlayAlwaysCloseButton; View mCancelClosingOverlayNeverCloseButton; @Inject SidebarContext mAppsiContext; @Inject SharedPreferences mSharedPreferences; private ViewPager mAppsiViewPager; private SidebarPagerAdapter mAdapter; public Sidebar(Context context) { super(context); init(); } public Sidebar(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Sidebar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private static int indexOfId(CircularArray<HotspotPageEntry> entries, long defaultPage) { if (defaultPage == -1L) return -1; for (int i = 0; i < entries.size(); i++) { HotspotPageEntry e = entries.get(i); if (e.mPageId == defaultPage) return i; } return -1; } private void init() { AppsiInjector.inject(this); } public void onTrimMemory(int level) { if (mAdapter != null) { mAdapter.onTrimMemory(level); } } public void setOnCancelCloseListener(OnCancelCloseListener onCancelCloseListener) { mOnCancelCloseListener = onCancelCloseListener; } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { switch (key) { case "pref_paging_animation": setupPagingAnimation(sharedPreferences); break; } } private void setupPagingAnimation(SharedPreferences sharedPreferences) { String pagingAnimation = sharedPreferences.getString("pref_paging_animation", "fade"); updatePagingAnimation(pagingAnimation); } private void updatePagingAnimation(String pagingAnimation) { if (pagingAnimation == null) return; switch (pagingAnimation) { case "fade": mAppsiViewPager.setPageTransformer(true, new FadePageTransformer()); break; case "depth": mAppsiViewPager.setPageTransformer(true, new DepthPageTransformer()); break; case "zoom": mAppsiViewPager.setPageTransformer(true, new ZoomOutPageTransformer()); break; case "tablet": mAppsiViewPager.setPageTransformer(true, new TabletPageTransformer()); break; } } @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { closeSidebar(); return true; } return super.onKeyUp(keyCode, event); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mAppsiContext.setContentWidth(w); } @Override protected void onFinishInflate() { super.onFinishInflate(); // we need to find the actionbar before we initialize the view // pager adapter. All views inside the adapter will assume a // special SidebarContext that provides access to the actionbar. mAppsiViewPager = (ViewPager) findViewById(R.id.appsi_view_pager); mAppsiViewPager.addOnPageChangeListener(mOnPageChangeListener); mAdapter = new SidebarPagerAdapter(mAppsiContext); mAppsiViewPager.setAdapter(mAdapter); mAdapter.setFlagListener(this); mSidebarBackgroundView = findViewById(R.id.sidebar_back); mLeftShadow = findViewById(R.id.sidebar_left_shadow); mRightShadow = findViewById(R.id.sidebar_right_shadow); mCloseText = findViewById(R.id.always_close_text); mCancelClosingOverlayAlwaysCloseButton = findViewById(R.id.always_close); mCancelClosingOverlayNeverCloseButton = findViewById(R.id.never_close); mCancelClosingOverlayAlwaysCloseButton. setBackground(ThemingUtils.createButtonDrawable(mAppsiContext)); mCancelClosingOverlayNeverCloseButton. setBackground(ThemingUtils.createButtonDrawable(mAppsiContext)); mCancelClosingOverlay = findViewById(R.id.widget_action_overlay); mCancelClosingProgressBar = (ProgressBar) findViewById(R.id.closing_progress_bar); mCancelClosingOverlay.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { cancelClosingOverlay(); return true; } }); mClosingProgressBarAnimator = ObjectAnimator.ofInt(mCancelClosingProgressBar, "progress", 100, 0); mClosingProgressBarAnimator.setDuration(DELAYED_CLOSE_DURATION); setFocusableInTouchMode(true); if (!isInEditMode()) { findViewById(R.id.sidebar_close_area).setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { closeSidebar(); return true; } }); } mCancelClosingOverlayAlwaysCloseButton.setOnClickListener(this); mCancelClosingOverlayNeverCloseButton.setOnClickListener(this); } protected void cancelClosingOverlay() { mOnCancelCloseListener.onCloseCancelled(); mCancelClosingOverlay.animate().alpha(0); mCancelClosingOverlay.setVisibility(View.GONE); } boolean closeSidebar() { // mSearchBox.setText(""); if (mSidebarListener != null) { mSidebarListener.onCloseSidebar(); return true; } return false; } public void setSidebarListener(SidebarListener sidebarListener) { mSidebarListener = sidebarListener; } @Override public void onClick(View view) { int id = view.getId(); if (id == R.id.always_close) { mAdapter.rememberCloseAction(mCloseStateBundle, SidebarPagerAdapter.CLOSE_ACTION_AUTO_CLOSE); mSidebarListener.onCloseSidebar(); } else if (id == R.id.never_close) { mAdapter.rememberCloseAction(mCloseStateBundle, SidebarPagerAdapter.CLOSE_ACTION_KEEP_OPEN); cancelClosingOverlay(); } } public void showCloseOverlay(CloseCallback closeCallback) { mCloseStateBundle.clear(); int closeAction = mAdapter.shouldClose(mCloseStateBundle); // boolean showButtons; // int delay; switch (closeAction) { default: case SidebarPagerAdapter.CLOSE_ACTION_DONT_KNOW: case SidebarPagerAdapter.CLOSE_ACTION_AUTO_CLOSE: // showButtons = false; // delay = DELAYED_CLOSE_DURATION; closeCallback.close(); return; case SidebarPagerAdapter.CLOSE_ACTION_ASK: // showButtons = true; // delay = DELAYED_CLOSE_DURATION_WHEN_ASKED; break; case SidebarPagerAdapter.CLOSE_ACTION_KEEP_OPEN: return; } mClosingProgressBarAnimator.setDuration(DELAYED_CLOSE_DURATION_WHEN_ASKED); mCancelClosingOverlay.setVisibility(View.VISIBLE); mCancelClosingOverlay.setAlpha(0); mCancelClosingOverlay.animate().alpha(1); mCancelClosingProgressBar.setProgress(100); mClosingProgressBarAnimator.start(); // if (showButtons) { mCloseText.setVisibility(VISIBLE); mCancelClosingOverlayAlwaysCloseButton.setVisibility(VISIBLE); mCancelClosingOverlayNeverCloseButton.setVisibility(VISIBLE); // } else { // mCancelClosingOverlayAlwaysCloseButton.setVisibility(GONE); // mCancelClosingOverlayNeverCloseButton.setVisibility(GONE); // mCloseText.setVisibility(GONE); // } closeCallback.closeDelayed(DELAYED_CLOSE_DURATION_WHEN_ASKED); } @Override public void onStartLoad() { } @Override public void onEndLoad() { } public boolean getIsLeft() { return mIsLeft; } public void setIsLeft(boolean left) { mIsLeft = left; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (!isInEditMode()) { SharedPreferences prefs = mSharedPreferences; prefs.registerOnSharedPreferenceChangeListener(this); setupPagingAnimation(prefs); mAdapter.onAttachedToWindow(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mCancelClosingOverlay.setVisibility(GONE); SharedPreferences prefs = mSharedPreferences; prefs.unregisterOnSharedPreferenceChangeListener(this); mAdapter.onDetachedFromWindow(); } public void updateAdapterData(long defaultPage, CircularArray<HotspotPageEntry> entries) { mAdapter.setPages(entries); int idx = indexOfId(entries, defaultPage); if (idx != -1L) { mAppsiViewPager.setCurrentItem(idx, false); } } @Override public void onFlagsChanged(int flags) { handleNewFlags(flags); } private void handleNewFlags(int flags) { if ((flags & SidebarPagerAdapter.FLAG_NO_DECORATIONS) == AbstractSidebarPagerAdapter.FLAG_NO_DECORATIONS) { mSidebarBackgroundView.animate().alpha(.5f); mLeftShadow.animate().alpha(.5f); mRightShadow.animate().alpha(.5f); } else { mSidebarBackgroundView.animate().alpha(1); mLeftShadow.animate().alpha(1); mRightShadow.animate().alpha(1); } } public void setInFullScreenMode(boolean fullscreen) { mAppsiContext.mIsFullScreen = fullscreen; } public void setSidebarOpening(boolean sidebarOpening) { mAdapter.setSidebarOpening(sidebarOpening); } public void onSidebarClosing() { mAdapter.setSidebarClosed(); } public interface OnCancelCloseListener { void onCloseCancelled(); } public interface SidebarListener { void onCloseSidebar(); } @TargetApi(11) public static class DepthPageTransformer extends AbstractPageTransformer { private static final float MIN_SCALE = 0.75f; @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); reset(view); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // (0,1] // Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. } } } public static class ZoomOutPageTransformer extends AbstractPageTransformer { private static final float MIN_SCALE = 0.85f; private static final float MIN_ALPHA = 0.5f; @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); reset(view); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. } else if (position <= 1) { // [-1,1] // Modify the default slide transition to shrink the page as well float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageHeight * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { view.setTranslationX(horzMargin - vertMargin / 2); } else { view.setTranslationX(-horzMargin + vertMargin / 2); } // Scale the page down (between MIN_SCALE and 1) view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); // Fade the page relative to its size. view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // (1,+Infinity] // This page is way off-screen to the right. } } } public static class TabletPageTransformer extends AbstractPageTransformer { private static float MIN_SCALE = 0.85f; private static float MIN_ALPHA = 0.5f; public TabletPageTransformer() { } @Override public void transformPage(View view, float position) { reset(view); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. } else if (position <= 1) { // [-1, 0] view.setRotationY(position * -30); } else { // (1,+Infinity] // This page is way off-screen to the right. } } } public static class FadePageTransformer extends AbstractPageTransformer { @Override public void transformPage(View view, float position) { reset(view); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. } else if (position <= 0) { // [-1, 0] view.setAlpha(position + 1); } else if (position <= 1) { // [0 ,1] view.setAlpha(1 - position); } else { // (1,+Infinity] // This page is way off-screen to the right. } } } static abstract class AbstractPageTransformer implements ViewPager.PageTransformer { public void reset(View view) { view.setAlpha(1); view.setRotationX(0); view.setRotationY(0); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } } class OnPageChangeListenerImpl implements ViewPager.OnPageChangeListener { int mCurrentPage; @Override public void onPageScrolled(int i, float v, int i2) { } @Override public void onPageSelected(int i) { mCurrentPage = i; } @Override public void onPageScrollStateChanged(int i) { } } }