io.v.syncslides.NavigateFragment.java Source code

Java tutorial

Introduction

Here is the source code for io.v.syncslides.NavigateFragment.java

Source

// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package io.v.syncslides;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
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.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import io.v.syncslides.db.DB;
import io.v.syncslides.model.DynamicList;
import io.v.syncslides.model.ListListener;
import io.v.syncslides.model.Session;
import io.v.syncslides.model.Slide;
import io.v.v23.verror.VException;

/**
 * Provides both the presenter and audience views for navigating through a presentation.
 * Instantiated by the PresentationActivity along with other views/fragments of the presentation
 * to make transitions between them seamless.
 */
public class NavigateFragment extends Fragment {
    private static final String TAG = "NavigateFragment";
    private static final String SESSION_ID_KEY = "session_id_key";

    private Session mSession;
    private int mSlideNum = 0;
    private SlideNumberListener mSlideNumberListener = new SlideNumberListener();
    private ListListener mSlideListListener = new SlideListListener();
    private ImageView mPrevThumb;
    private ImageView mNextThumb;
    private ImageView mCurrentSlide;
    private TextView mSlideNumText;
    private EditText mNotes;
    private boolean mEditing;
    private DynamicList<Slide> mSlides;

    public static NavigateFragment newInstance(String sessionId) {
        NavigateFragment fragment = new NavigateFragment();
        Bundle args = new Bundle();
        args.putString(SESSION_ID_KEY, sessionId);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // When editing notes, display a menu with "Save".
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Bundle bundle = savedInstanceState;
        if (bundle == null) {
            bundle = getArguments();
        }
        String sessionId = bundle.getString(SESSION_ID_KEY);
        try {
            mSession = DB.Singleton.get().getSession(sessionId);
        } catch (VException e) {
            handleFatalError("Failed to fetch Session", e);
        }

        final View rootView = inflater.inflate(R.layout.fragment_navigate, container, false);

        //        mFabSync = rootView.findViewById(R.id.audience_sync_fab);
        //        if (((PresentationActivity) getActivity()).getSynced() || mRole != Role.AUDIENCE) {
        //            mFabSync.setVisibility(View.INVISIBLE);
        //        } else {
        //            mFabSync.setVisibility(View.VISIBLE);
        //        }
        //
        //        mFabSync.setOnClickListener(new NavigateClickListener() {
        //            @Override
        //            public void onClick(View v) {
        //                super.onClick(v);
        //                sync();
        //                mFabSync.setVisibility(View.INVISIBLE);
        //            }
        //        });
        View.OnClickListener previousSlideListener = new NavigateClickListener() {
            @Override
            void onNavigate() {
                previousSlide();
            }
        };
        View arrowBack = rootView.findViewById(R.id.arrow_back);
        arrowBack.setOnClickListener(previousSlideListener);
        mPrevThumb = (ImageView) rootView.findViewById(R.id.prev_thumb);
        mPrevThumb.setOnClickListener(previousSlideListener);

        View.OnClickListener nextSlideListener = new NavigateClickListener() {
            @Override
            void onNavigate() {
                nextSlide();
            }
        };
        // Show either the arrowForward or the FAB but not both.
        View arrowForward = rootView.findViewById(R.id.arrow_forward);
        View fabForward = rootView.findViewById(R.id.primary_navigation_fab);
        //        if (mRole == Role.PRESENTER) {
        //            arrowForward.setVisibility(View.INVISIBLE);
        //            fabForward.setOnClickListener(nextSlideListener);
        //        } else {
        fabForward.setVisibility(View.INVISIBLE);
        arrowForward.setOnClickListener(nextSlideListener);
        //        }
        mNextThumb = (ImageView) rootView.findViewById(R.id.next_thumb);
        mNextThumb.setOnClickListener(nextSlideListener);
        //        mQuestions = (ImageView) rootView.findViewById(R.id.questions);
        //        // TODO(kash): Hide the mQuestions button if mRole == BROWSER.
        //        mQuestions.setOnClickListener(new NavigateClickListener() {
        //            @Override
        //            public void onClick(View v) {
        //                super.onClick(v);
        //                questionButton();
        //            }
        //        });
        mCurrentSlide = (ImageView) rootView.findViewById(R.id.slide_current_medium);
        mCurrentSlide.setOnClickListener(new NavigateClickListener() {
            @Override
            public void onNavigate() {
                // TODO(kash): Disallow presenter from switching to fullscreen.
                ((PresentationActivity) getActivity()).showFullscreenSlide();
            }
        });

        mSlideNumText = (TextView) rootView.findViewById(R.id.slide_num_text);
        mNotes = (EditText) rootView.findViewById(R.id.notes);
        mNotes.setOnFocusChangeListener((v, hasFocus) -> {
            if (hasFocus) {
                ((PresentationActivity) getActivity()).getSupportActionBar().show();
                mEditing = true;
                getActivity().invalidateOptionsMenu();
                // We don't want the presentation to advance while the user
                // is editing the notes.  Force the app to stay on this slide.
                try {
                    mSession.setLocalSlideNum(mSlideNum);
                } catch (VException e) {
                    handleFatalError("Could not set local slide num", e);
                }
            }
        });

        // The parent of mNotes needs to be focusable in order to clear focus
        // from mNotes when done editing.  We set the attributes in code rather
        // than in XML because it is too easy to add an extra level of layout
        // in XML and forget to add these attributes.
        ViewGroup parent = (ViewGroup) mNotes.getParent();
        parent.setFocusable(true);
        parent.setClickable(true);
        parent.setFocusableInTouchMode(true);

        //        View slideListIcon = rootView.findViewById(R.id.slide_list);
        //        slideListIcon.setOnClickListener(new NavigateClickListener() {
        //            @Override
        //            public void onClick(View v) {
        //                super.onClick(v);
        //                if (mRole == Role.AUDIENCE) {
        //                    ((PresentationActivity) getActivity()).showSlideList();
        //                } else {
        //                    getActivity().getSupportFragmentManager().popBackStack();
        //                }
        //            }
        //        });
        //        mQuestionsNum = (TextView) rootView.findViewById(R.id.questions_num);
        //        // Start off invisible for everyone.  If there are questions, this
        //        // will be set to visible in the mDB.getQuestionerList() callback.
        //        mQuestionsNum.setVisibility(View.INVISIBLE);
        //
        //        mDB = DB.Singleton.get(getActivity().getApplicationContext());
        //        mDB.getSlides(mDeckId, new DB.Callback<List<Slide>>() {
        //            @Override
        //            public void done(List<Slide> slides) {
        //                mSlides = slides;
        //                // The CurrentSlideListener could have been notified while we were waiting for
        //                // the slides to load.
        //                if (mLoadingCurrentSlide != -1) {
        //                    currentSlideChanged(mLoadingCurrentSlide);
        //                }
        //                updateView();
        //            }
        //        });
        //        if (((PresentationActivity) getActivity()).getSynced()) {
        //            sync();
        //        } else {
        //            unsync();
        //        }

        return rootView;
    }

    @Override
    public void onStart() {
        super.onStart();
        ((PresentationActivity) getActivity()).setUiImmersive(true);
        mSlides = mSession.getSlides();
        mSlides.addListener(mSlideListListener);
        mSession.addSlideNumberListener(mSlideNumberListener);
    }

    @Override
    public void onStop() {
        super.onStop();
        ((PresentationActivity) getActivity()).setUiImmersive(false);
        mSession.removeSlideNumberListener(mSlideNumberListener);
        mSlides.removeListener(mSlideListListener);
        mSlides = null;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(SESSION_ID_KEY, mSession.getId());
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        if (mEditing) {
            inflater.inflate(R.menu.edit_notes, menu);
        }
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_save:
            saveNotes();
            return true;
        }
        return false;
    }

    /**
     * Advances to the next slide, if there is one, and updates the UI.
     */
    private void nextSlide() {
        if (mSlideNum == -1) {
            // Wait until the state has loaded before letting the user move around.
            return;
        }
        if (mSlideNum < mSession.getSlides().getItemCount() - 1) {
            try {
                mSession.setLocalSlideNum(mSlideNum + 1);
            } catch (VException e) {
                handleError("Failed to advance", e);
            }
        }
    }

    /**
     * Goes back to the previous slide, if there is one, and updates the UI.
     */
    private void previousSlide() {
        if (mSlideNum == -1) {
            // Wait until the state has loaded before letting the user move around.
            return;
        }
        if (mSlideNum > 0) {
            try {
                mSession.setLocalSlideNum(mSlideNum - 1);
            } catch (VException e) {
                handleError("Failed to go back", e);
            }
        }
    }

    private void updateView() {
        if (mSlideNum < 0 || mSlideNum >= mSlides.getItemCount()) {
            // Still loading.
            return;
        }
        if (mSlideNum > 0) {
            setThumbBitmap(mPrevThumb, mSlides.get(mSlideNum - 1).getThumb());
        } else {
            setThumbNull(mPrevThumb);
        }
        // TODO(kash): Switch to full size image.
        mCurrentSlide.setImageBitmap(mSlides.get(mSlideNum).getThumb());
        if (mSlideNum == mSlides.getItemCount() - 1) {
            setThumbNull(mNextThumb);
        } else {
            setThumbBitmap(mNextThumb, mSlides.get(mSlideNum + 1).getThumb());
        }
        if (!mSlides.get(mSlideNum).getNotes().equals("")) {
            mNotes.setText(mSlides.get(mSlideNum).getNotes());
        } else {
            mNotes.getText().clear();
        }
        mSlideNumText.setText(String.valueOf(mSlideNum + 1) + " of " + String.valueOf(mSlides.getItemCount()));
    }

    private void setThumbBitmap(ImageView thumb, Bitmap bitmap) {
        thumb.setImageBitmap(bitmap);
        // In landscape, the height is dependent on the image size.  However, if the
        // image was null, the height is hardcoded to 9/16 of the width in setThumbNull.
        // This resets it to the actual image size.
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            ViewGroup.LayoutParams thumbParams = thumb.getLayoutParams();
            thumbParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
    }

    private void setThumbNull(ImageView thumb) {
        thumb.setImageDrawable(null);
        // In landscape, the height is dependent on the image size.  Because we don't have an
        // image, assume all of the images are 16:9.  The width is fixed, so we can calculate
        // the expected height.
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            ViewGroup grandparent = (ViewGroup) thumb.getParent().getParent();
            ViewGroup.LayoutParams thumbParams = thumb.getLayoutParams();
            thumbParams.height = (int) ((9 / 16.0) * grandparent.getMeasuredWidth());
        }
    }

    /**
     * If the user is editing the text field and the text has changed, save the
     * notes in Syncbase.  That will trigger a notification that the slide has
     * changed and the UI will refresh.
     */
    public void saveNotes() {
        final String notes = mNotes.getText().toString();
        if (mEditing && (!notes.equals(mSlides.get(mSlideNum).getNotes()))) {
            try {
                mSession.setNotes(mSlideNum, notes);
            } catch (VException e) {
                handleError("Could not save notes", e);
            }
        }
        mNotes.clearFocus();
        mEditing = false;
        InputMethodManager inputManager = (InputMethodManager) getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (getActivity().getCurrentFocus() != null) {
            inputManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(),
                    InputMethodManager.HIDE_NOT_ALWAYS);
        }
        ((PresentationActivity) getActivity()).setUiImmersive(true);
    }

    private void handleError(String msg, Throwable throwable) {
        Log.e(TAG, msg + ": " + Log.getStackTraceString(throwable));
        Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
    }

    private void handleFatalError(String msg, Throwable throwable) {
        handleError(msg, throwable);
        getActivity().finish();
    }

    /**
     * Updates the view whenever the list of slides changes.
     */
    private class SlideListListener implements ListListener {
        @Override
        public void notifyDataSetChanged() {
            updateView();
        }

        @Override
        public void notifyItemChanged(int position) {
            updateView();
        }

        @Override
        public void notifyItemInserted(int position) {
            updateView();
        }

        @Override
        public void notifyItemRemoved(int position) {
            updateView();
        }

        @Override
        public void onError(Exception e) {
            handleFatalError("Error watching slide list", e);
        }
    }

    private class SlideNumberListener implements Session.SlideNumberListener {
        @Override
        public void onChange(int slideNum) {
            mSlideNum = slideNum;
            updateView();
        }

        @Override
        public void onError(Exception e) {
            handleFatalError("Error listening to slide number changes", e);
        }
    }

    /**
     * If the user is editing notes and then clicks anywhere else on the screen,
     * we want that action to save the notes first.  Using this class forces
     * that behavior.
     */
    private abstract class NavigateClickListener implements View.OnClickListener {
        @Override
        public final void onClick(View v) {
            saveNotes();
            onNavigate();
        }

        /**
         * Called when there is a click.
         */
        abstract void onNavigate();
    }
}