package pro.dbro.glance.fragments; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.Fragment; import android.text.Spannable; import android.text.SpannableString; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.ProgressBar; import android.widget.TextView; import com.facebook.rebound.SimpleSpringListener; import com.facebook.rebound.Spring; import com.facebook.rebound.SpringSystem; import com.github.amlcurran.showcaseview.OnShowcaseEventListener; import com.github.amlcurran.showcaseview.ShowcaseView; import com.github.amlcurran.showcaseview.targets.PointTarget; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import java.lang.ref.WeakReference; import pro.dbro.glance.AppSpritzer; import pro.dbro.glance.GlanceApplication; import pro.dbro.glance.GlancePrefsManager; import pro.dbro.glance.R; import pro.dbro.glance.events.ChapterSelectRequested; import pro.dbro.glance.events.HttpUrlParsedEvent; import pro.dbro.glance.events.NextChapterEvent; import pro.dbro.glance.formats.SpritzerMedia; import pro.dbro.glance.lib.SpritzerTextView; import pro.dbro.glance.lib.events.SpritzFinishedEvent; public class SpritzFragment extends Fragment { private static final String TAG = "SpritzFragment"; // SpritzFragmentHandler Message codes protected static final int MSG_SPRITZ_TEXT = 1; protected static final int MSG_HIDE_CHAPTER_LABEL = 2; private static AppSpritzer mSpritzer; private TextView mAuthorView; private TextView mTitleView; private TextView mChapterView; private TextView mSpritzHistoryView; private ProgressBar mProgress; private SpritzerTextView mSpritzView; private Bus mBus; private SpritzFragmentHandler mHandler; private boolean mShowingTips; public static SpritzFragment newInstance() { SpritzFragment fragment = new SpritzFragment(); return fragment; } public SpritzFragment() { // Required empty public constructor } public void feedMediaUriToSpritzer(Uri mediaUri) { if (mSpritzer == null) { mSpritzer = new AppSpritzer(mBus, mSpritzView, mediaUri); mSpritzView.setSpritzer(mSpritzer); } else { mSpritzer.setMediaUri(mediaUri); } // Why is this commented out? if (AppSpritzer.isHttpUri(mediaUri)) { mSpritzer.setTextAndStart(getString(R.string.loading), false); showIndeterminateProgress(true); } } public void showIndeterminateProgress(boolean show) { mProgress.setIndeterminate(show); } /** * Update the UI related to Book Title, Author, * and current progress. Everything but the {@link pro.dbro.glance.lib.SpritzerTextView} */ public void updateMetaUi() { if (!mSpritzer.isMediaSelected()) { return; } SpritzerMedia book = mSpritzer.getMedia(); mAuthorView.setText(book.getAuthor()); mTitleView.setText(book.getTitle()); int curChapter = mSpritzer.getCurrentChapter(); String chapterText = mSpritzer.getMedia().getChapterTitle(curChapter); int startSpan = chapterText.length(); chapterText = String.format("%s %s m left", chapterText, (mSpritzer.getMinutesRemainingInQueue() == 0) ? "<1" : String.valueOf(mSpritzer.getMinutesRemainingInQueue())); int endSpan = chapterText.length(); Spannable spanRange = new SpannableString(chapterText); TextAppearanceSpan tas = new TextAppearanceSpan(mChapterView.getContext(), R.style.MinutesToGo); spanRange.setSpan(tas, startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mChapterView.setText(spanRange); final int progressScale = 10; int progress; // If the spritzer is showing a special message // don't factor current word queue completeness // into progress. if (mSpritzer.isSpritzingSpecialMessage()) { progress = curChapter; } else { progress = curChapter * progressScale + ((int) (progressScale * (mSpritzer.getQueueCompleteness()))); } mProgress.setMax((mSpritzer.getMaxChapter() + 1) * progressScale); mProgress.setProgress(progress); } /** * Hide or Show the UI related to Book Title, Author, * and current progress * * @param show */ public void showMetaUi(boolean show) { if (show) { if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { mAuthorView.setVisibility(View.VISIBLE); } mTitleView.setVisibility(View.VISIBLE); mChapterView.setVisibility(View.VISIBLE); mProgress.setVisibility(View.VISIBLE); } else { mAuthorView.setVisibility(View.INVISIBLE); mTitleView.setVisibility(View.INVISIBLE); mChapterView.setVisibility(View.INVISIBLE); mProgress.setVisibility(View.INVISIBLE); //mSpritzHistoryView.setText(""); } } public void hideActionBar(boolean dim) { if (getActivity().getActionBar() == null) return; if (dim) { getActivity().getActionBar().hide(); } else { getActivity().getActionBar().show(); } } /** * Temporarily fade in the Chapter label. * Used when user crosses a chapter boundary. */ private void peekChapter() { mChapterView.setVisibility(View.VISIBLE); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_CHAPTER_LABEL), 2000); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_spritz, container, false); mAuthorView = ((TextView) root.findViewById(R.id.author)); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { mAuthorView.setVisibility(View.GONE); } mTitleView = ((TextView) root.findViewById(R.id.url)); mChapterView = ((TextView) root.findViewById(R.id.chapter)); mChapterView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mSpritzer.getMaxChapter() > 1) { mBus.post(new ChapterSelectRequested()); } } }); mSpritzHistoryView = (TextView) root.findViewById(R.id.spritzHistory); mProgress = ((ProgressBar) root.findViewById(R.id.progress)); mSpritzView = (SpritzerTextView) root.findViewById(R.id.spritzText); //mSpritzView.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "UbuntuMono-R.ttf")); //mSpritzHistoryView.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "UbuntuMono-R.ttf")); setupViews(mSpritzView, mSpritzHistoryView); return root; } private void showTips() { mShowingTips = true; int[] viewLocation = new int[2]; mSpritzView.getLocationOnScreen(viewLocation); PointTarget target = new PointTarget(viewLocation[0] + mSpritzView.getWidth() / 3, viewLocation[1] + mSpritzView.getHeight() / 2); final long SHOWCASE_SINGLESHOT_ID = 3141519; new ShowcaseView.Builder(getActivity()) .setShowcaseEventListener(new OnShowcaseEventListener() { @Override public void onShowcaseViewHide(ShowcaseView showcaseView) { showMetaUi(true); } @Override public void onShowcaseViewDidHide(ShowcaseView showcaseView) { } @Override public void onShowcaseViewShow(ShowcaseView showcaseView) { showcaseView.postDelayed(new Runnable() { @Override public void run() { Log.i(TAG, "hiding meta uti"); showMetaUi(false); } }, 500); } }) .singleShot(SHOWCASE_SINGLESHOT_ID) .setTarget(target) .setContentTitle("Welcome to Glance") .setContentText("Touch the glance view to pause or resume. If you miss something, pull down to view a brief history") .hideOnTouchOutside() .build(); } private void pauseSpritzer() { mSpritzer.pause(); updateMetaUi(); showMetaUi(true); hideActionBar(false); } private void startSpritzer() { mSpritzer.start(true); showMetaUi(false); hideActionBar(true); } static float initHeight; /** * Adjust the target View's height in proportion to * drag events. On drag release, snap the view back into * it's original place. */ private void setupViews(final View touchTarget, final View transformTarget) { touchTarget.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (initHeight == 0) initHeight = transformTarget.getHeight(); } }); touchTarget.setOnTouchListener(new View.OnTouchListener() { private ViewGroup.LayoutParams params; private float peakHeight; private float lastTouchY; private float firstTouchY; private final float fullOpacityHeight = 300; /** The distance between ACTION_DOWN and ACTION_UP, above which should * be interpreted as a drag, below which a click. */ private final float movementForDragThreshold = 20; /** The time between ACTION_DOWN and ACTION_MOVE, above which should * be interpreted as a drag, and the spritzer paused */ private final int timeForPauseThreshold = 50; private boolean mSetText = false; private boolean mAnimatingBack = false; @Override public boolean onTouch(View v, MotionEvent event) { if (params == null) params = transformTarget.getLayoutParams(); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mSpritzer.isPlaying()) mSpritzer.pause(); else mSpritzer.start(true); // Log.i("TOUCH", "Down"); int coords[] = new int[2]; transformTarget.getLocationOnScreen(coords); lastTouchY = firstTouchY = event.getRawY(); } if (event.getAction() == MotionEvent.ACTION_MOVE) { if (mSpritzer.isPlaying() && (event.getEventTime() - event.getDownTime() > timeForPauseThreshold)) mSpritzer.pause(); if (!mSetText) { mSpritzHistoryView.setText(mSpritzer.getHistoryString(400)); mSetText = true; } float newHeight = event.getRawY() - lastTouchY + transformTarget.getHeight(); // Log.i("MOVE", "touch-y: " + event.getRawY() + " lastTouch: " + lastTouchY + " height: " + transformTarget.getHeight()); if (newHeight > initHeight) { // Log.i("TOUCH", "setting height " + params.height); params.height = (int) newHeight; if (newHeight >= fullOpacityHeight) { transformTarget.setAlpha(1f); // Log.i("TOUCH", "alpha 1"); } else { transformTarget.setAlpha((newHeight / fullOpacityHeight) * .8f); // Log.i("TOUCH", "alpha " + newHeight / fullOpacityHeight); } transformTarget.requestLayout(); } lastTouchY = event.getRawY(); } if (event.getAction() == MotionEvent.ACTION_UP && !mAnimatingBack) { if (event.getRawY() - firstTouchY < movementForDragThreshold) { // This is a click, not a drag // show/hide meta ui on release if (!mSpritzer.isPlaying()) pauseSpritzer(); else startSpritzer(); return false; } peakHeight = event.getRawY() - lastTouchY + transformTarget.getHeight(); mAnimatingBack = true; // Log.i("TOUCH", "animating back up " + initHeight + " " + transformTarget.getHeight()); invokeSpring(transformTarget); } return true; } private void invokeSpring(final View targetView) { mAnimatingBack = true; // Create a system to run the physics loop for a set of springs. SpringSystem springSystem = SpringSystem.create(); // Add a spring to the system. Spring spring = springSystem.createSpring(); // Add a listener to observe the motion of the spring. spring.addListener(new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { // You can observe the updates in the spring // state by asking its current value in onSpringUpdate. float value = (float) spring.getCurrentValue(); float scale = 1f - (value); //Log.i("SPRING", String.valueOf(value)); // 0 - initHeight // 1 - peakHeight if (scale < 0.05) { //Log.i("SPRING", "finished"); mSpritzHistoryView.setText(""); mSetText = false; params.height = (int) initHeight; transformTarget.setAlpha(0); mAnimatingBack = false; startSpritzer(); } else if (mAnimatingBack) { params.height = (int) ((scale * (peakHeight - initHeight)) + initHeight); if (transformTarget.getHeight() >= fullOpacityHeight * 2) { transformTarget.setAlpha(1f); } else { //fullOpacityHeight*2 = full //fullOpacityHeight = empty transformTarget.setAlpha(Math.max(0, fullOpacityHeight - transformTarget.getHeight() * 1.1f)); //Log.i("TOUCH", "alpha " + touchTarget.getHeight() / fullOpacityHeight); } } transformTarget.requestLayout(); } }); // Set the spring in motion; moving from 0 to 1 spring.setEndValue(1); } }); } @Override public void onStart() { super.onStart(); } @Override public void onResume() { super.onResume(); GlanceApplication app = (GlanceApplication) getActivity().getApplication(); mBus = app.getBus(); mBus.register(this); mHandler = new SpritzFragmentHandler(this); if (mSpritzer == null) { mSpritzer = new AppSpritzer(mBus, mSpritzView); mSpritzView.setSpritzer(mSpritzer); if (mSpritzer.getMedia() == null) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SPRITZ_TEXT, getString(R.string.select_epub)), 1500); } else { // AppSpritzer loaded the last book being read updateMetaUi(); showMetaUi(true); } } else { mSpritzer.setEventBus(mBus); mSpritzView.setSpritzer(mSpritzer); if (!mSpritzer.isPlaying()) { updateMetaUi(); showMetaUi(true); } else { // If the spritzer is currently playing, be sure to hide the ActionBar // Might the Android linter be a bit aggressive with these null checks? if (getActivity() != null && getActivity().getActionBar() != null) { hideActionBar(true); } } } mSpritzView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (!sShownTips) { showTips(); sShownTips = true; } } }); } private static boolean sShownTips = false; @Override public void onStop() { super.onStop(); if (mSpritzer != null) { mSpritzer.saveState(); } } @Override public void onDestroy() { super.onDestroy(); if (mBus != null) { mBus.unregister(this); } } /** * Called when the Spritzer finishes a section. * Called on a background thread */ @Subscribe public void onSpritzFinished(SpritzFinishedEvent event) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { updateMetaUi(); showMetaUi(true); hideActionBar(false); } }); } @Subscribe public void onNextChapter(NextChapterEvent event) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { try { updateMetaUi(); peekChapter(); } catch (Exception e) { e.printStackTrace(); } } }); } @Subscribe public void onHttpUrlParsed(HttpUrlParsedEvent event) { showIndeterminateProgress(false); //mSpritzer.pause(); updateMetaUi(); if (!mShowingTips) showMetaUi(true); } public AppSpritzer getSpritzer() { return mSpritzer; } /** * A Handler bound to the UI thread. Used to conveniently * handle actions that should occur after some delay. */ protected class SpritzFragmentHandler extends Handler { private WeakReference<SpritzFragment> mWeakSpritzFragment; public SpritzFragmentHandler(SpritzFragment fragment) { mWeakSpritzFragment = new WeakReference<SpritzFragment>(fragment); } @Override public void handleMessage(Message msg) { int what = msg.what; Object obj = msg.obj; SpritzFragment spritzer = mWeakSpritzFragment.get(); if (spritzer == null) { return; } switch (what) { case MSG_HIDE_CHAPTER_LABEL: if (getActivity() != null) { if (mSpritzer != null && mSpritzer.isPlaying()) { spritzer.mChapterView.setVisibility(View.INVISIBLE); } } break; case MSG_SPRITZ_TEXT: if (mSpritzer != null) { mSpritzer.setTextAndStart((String) obj, false); } break; } } } }