com.avapira.bobroreader.BoardFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.avapira.bobroreader.BoardFragment.java

Source

/*
 * Bobroreader is open source software, created, maintained, and shared under
 * the MIT license by Avadend Piroserpen Arts. The project includes components
 * from other open source projects which remain under their existing licenses,
 * detailed in their respective source files.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015. Avadend Piroserpen Arts Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */

package com.avapira.bobroreader;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.LayoutRes;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.*;
import com.avapira.bobroreader.hanabira.Hanabira;
import com.avapira.bobroreader.hanabira.entity.HanabiraPost;
import com.avapira.bobroreader.hanabira.entity.HanabiraThread;
import com.avapira.bobroreader.util.Consumer;

import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class BoardFragment extends Fragment {

    private static final String TAG = BoardFragment.class.getSimpleName();
    private static final String ARG_KEY = "arg_board_key";
    private static final String ARG_PAGE = "arg_board_page";
    private static final String ARG_RECYCLER_LAYOUT = "arg_board_recycler_layout";
    private ProgressBar progressBar;
    private RecyclerView recycler;
    private RecyclerView.LayoutManager recyclerLayoutManager;
    private HidingScrollListener scrollListener;
    private int recentListSize = 3;
    private String boardKey;
    private int page;
    private Castor supervisor;
    private BoardAdapter boardAdapter;

    public BoardFragment() {
        // Required empty public constructor
    }

    private class BoardAdapter extends RecyclerView.Adapter<BoardAdapter.ThreadWithPreviewViewHolder> {

        public static final int VIEW_TYPE_PREV_PAGE = 1;
        public static final int VIEW_TYPE_THREAD = 2;
        public static final int VIEW_TYPE_NEXT_PAGE = 3;
        final List<Integer> threadIds;
        private final int ELLIPSIZE_MAX_LINES = 15;
        private final List<Boolean> requestFillRecentPosts;
        private int whereIsRecentPostsShownPosition = -239;

        public BoardAdapter(List<Integer> threadInternalIds) {
            threadIds = threadInternalIds;
            requestFillRecentPosts = new ArrayList<>(threadInternalIds.size());
            for (int i = 0; i < threadInternalIds.size(); i++) {
                requestFillRecentPosts.add(true);
            }
            Hanabira.getStem().asyncParse(threadIds, recentListSize);
        }

        public class ThreadWithPreviewViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

            final TextView threadTitle;
            final PostHolder postHolder;
            final HorizontalScrollView filesScroller;
            final LinearLayout previewList;
            final TextView replies;
            final Button optionsBtn;
            final ImageButton expandBtn;
            final Button recentBtn;
            final Button openBtn;

            public PostHolder[] recents;

            public ThreadWithPreviewViewHolder(final View itemView) {
                super(itemView);
                threadTitle = (TextView) itemView.findViewById(R.id.text_thread_title);

                postHolder = new PostHolder(itemView);
                replies = (TextView) itemView.findViewById(R.id.text_post_content_replies);
                filesScroller = (HorizontalScrollView) itemView.findViewById(R.id.post_files_scroller);
                previewList = (LinearLayout) itemView.findViewById(R.id.layout_thread_expandable_posts_preview);
                optionsBtn = (Button) itemView.findViewById(R.id.thread_controls_options);
                expandBtn = (ImageButton) itemView.findViewById(R.id.thread_controls_expand);
                recentBtn = (Button) itemView.findViewById(R.id.thread_controls_recent);
                openBtn = (Button) itemView.findViewById(R.id.thread_controls_open);

                if (previewList != null) {
                    for (int i = 0; i < recentListSize; i++) {
                        LayoutInflater.from(getContext()).inflate(R.layout.layout_post, previewList);
                    }
                    recents = new PostHolder[previewList.getChildCount()];
                    for (int i = 0; i < recents.length; i++) {
                        recents[i] = new PostHolder(previewList.getChildAt(i));
                    }
                }

                if (optionsBtn != null) {
                    optionsBtn.setOnClickListener(this);
                    // hope either not null
                    expandBtn.setOnClickListener(this);
                    recentBtn.setOnClickListener(this);
                    openBtn.setOnClickListener(this);
                }
            }

            public class NotRecycleAdapter extends AnimatorListenerAdapter {

                @Override
                public void onAnimationStart(Animator animation) {
                    setIsRecyclable(false);
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    setIsRecyclable(true);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    setIsRecyclable(true);
                }
            }

            public void onClick(View v) {
                switch (v.getId()) {
                case R.id.thread_controls_options:
                    return;
                case R.id.thread_controls_expand:
                    onExpandClick();
                    break;
                case R.id.thread_controls_open:
                    onOpenClick();
                    break;
                case R.id.thread_controls_recent:
                    onRecentClick();
                    break;
                }
            }

            private void onRecentClick() {
                toggle(ThreadWithPreviewViewHolder.this);
            }

            private void onOpenClick() {
                supervisor.onThreadSelected(threadIds.get(getAdapterPosition() - 1));
            }

            public void onExpandClick() {
                final int now = postHolder.message.getMaxLines();
                final int will = now == Integer.MAX_VALUE ? ELLIPSIZE_MAX_LINES : Integer.MAX_VALUE;
                final boolean growth = will > now;
                if (postHolder.message.getLineCount() <= ELLIPSIZE_MAX_LINES) {
                    expandBtn.animate().rotation(360f).setDuration(500).setInterpolator(new BounceInterpolator())
                            .withEndAction(new Runnable() {
                                @Override
                                public void run() {
                                    expandBtn.setRotation(0);
                                }
                            }).start();
                    return;
                }
                scrollListener.expandTriggered();
                postHolder.message.setMaxLines(will); // set to measure
                final Animator animator = animateViewHeight(postHolder.message);
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (postHolder.message.getLineCount() != will) {
                            postHolder.message.setMaxLines(will);
                        }
                        postHolder.message.measure(View.MeasureSpec.makeMeasureSpec(500, View.MeasureSpec.AT_MOST),
                                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                        System.out.println(postHolder.message.getMeasuredHeight());
                    }

                    @Override
                    public void onAnimationStart(Animator animation) {
                        expandBtn.animate().rotation(growth ? 180f : 360f).withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                expandBtn.setRotation(growth ? 180f : 0f);
                            }
                        }).setInterpolator(new FastOutSlowInInterpolator()).start();
                        postHolder.message.setMaxLines(Integer.MAX_VALUE);
                    }
                });
                animator.start();
            }

            public void openHolder(final boolean animate) {
                int threadIndex = getLayoutPosition() - 1;
                if (requestFillRecentPosts.get(threadIndex)) {
                    requestFillRecentPosts.set(threadIndex, false);
                    List<Integer> recentsList = Hanabira.getStem()
                            .findThreadById(threadIds.get(getLayoutPosition() - 1)).getLastN(recentListSize);
                    int i = 0;
                    for (; i < recentsList.size(); i++) {
                        recents[i].fillWithData(recentsList.get(i));
                    }
                }

                if (animate) {
                    previewList.setVisibility(View.VISIBLE);
                    final Animator animator = animateViewHeight(itemView);
                    animator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                            final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(previewList, View.ALPHA, 1);
                            alphaAnimator.addListener(new NotRecycleAdapter());
                            alphaAnimator.setInterpolator(new DecelerateInterpolator());
                            alphaAnimator.start();
                        }
                    });
                    animator.start();
                } else {
                    previewList.setVisibility(View.VISIBLE);
                    previewList.setAlpha(1);
                }
            }

            public void closeHolder(final boolean animate) {
                if (animate) {
                    previewList.setVisibility(View.GONE);
                    final Animator animator = animateViewHeight(itemView);
                    previewList.setVisibility(View.VISIBLE);
                    animator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                            final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(previewList, View.ALPHA, 0);
                            alphaAnimator.addListener(new NotRecycleAdapter());
                            alphaAnimator.setInterpolator(new DecelerateInterpolator());
                            alphaAnimator.start();
                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            previewList.setVisibility(View.GONE);
                            previewList.setAlpha(0);
                        }

                        @Override
                        public void onAnimationCancel(Animator animation) {
                            previewList.setVisibility(View.GONE);
                            previewList.setAlpha(0);
                        }
                    });
                    animator.start();
                } else {
                    previewList.setVisibility(View.GONE);
                    previewList.setAlpha(0);
                }
            }

            public Animator animateViewHeight(final View v) {
                View parent = (View) v.getParent();
                if (parent == null) {
                    throw new IllegalStateException("Cannot animate the layout of a view that has no parent");
                }

                int start = v.getMeasuredHeight();
                System.out.println("Start: " + start);
                v.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                int end = v.getMeasuredHeight();
                System.out.println("End: " + end);
                final ValueAnimator animator = ValueAnimator.ofInt(start, end);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        final ViewGroup.LayoutParams lp = v.getLayoutParams();
                        lp.height = (int) animation.getAnimatedValue();
                        v.setLayoutParams(lp);
                    }

                });

                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        final ViewGroup.LayoutParams params = v.getLayoutParams();
                        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
                        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                        v.setLayoutParams(params);
                    }
                });

                animator.setInterpolator(new FastOutSlowInInterpolator());
                animator.addListener(new NotRecycleAdapter());

                return animator;
            }
        }

        @LayoutRes
        public int getLayoutIdForViewType(int viewType) {
            switch (viewType) {
            case VIEW_TYPE_PREV_PAGE:
                return R.layout.board_header_view;
            case VIEW_TYPE_THREAD:
                return R.layout.layout_thread_with_preview;
            case VIEW_TYPE_NEXT_PAGE:
                return R.layout.board_footer_view;
            default:
                throw new IllegalArgumentException("Wrong view type received");
            }
        }

        @Override
        public ThreadWithPreviewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            double start = System.nanoTime();
            View postcard = LayoutInflater.from(getContext()).inflate(getLayoutIdForViewType(viewType), parent,
                    false);
            if (viewType == VIEW_TYPE_PREV_PAGE && page == 0) {
                postcard.findViewById(R.id.board_header_text).setVisibility(View.GONE);
            }
            if (viewType == VIEW_TYPE_NEXT_PAGE
                    && page == Hanabira.getStem().findBoardByKey(boardKey).getPagesCount() - 1) {
                postcard.findViewById(R.id.frame_footer_container).setVisibility(View.GONE);
            }
            ThreadWithPreviewViewHolder tpvh = new ThreadWithPreviewViewHolder(postcard);
            Log.i("onCreateViewHolder", "Time: " + Double.toString(((double) System.nanoTime() - start) / 10e5));
            return tpvh;
        }

        @Override
        public void onBindViewHolder(final ThreadWithPreviewViewHolder holder, int position) {
            long start = System.nanoTime();
            boolean borderItem = true;
            switch (getItemViewType(position)) {
            case VIEW_TYPE_NEXT_PAGE:
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        switchPage(page + 1);
                    }
                });
                break;
            case VIEW_TYPE_PREV_PAGE:
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        switchPage(page - 1);
                    }
                });
                break;
            default:
                borderItem = false;
            }
            if (borderItem) {
                return;
            }

            int threadIndex = position - 1;
            int threadId = threadIds.get(threadIndex);

            HanabiraThread thread = Hanabira.getStem().findThreadById(threadId);
            HanabiraPost op = Hanabira.getStem().findPostByDisplayId(boardKey, thread.getDisplayId());

            holder.threadTitle.setText(thread.getTitle());
            holder.postHolder.fillWithData(op);
            holder.postHolder.message.setMaxLines(ELLIPSIZE_MAX_LINES);// reset the
            holder.expandBtn.setRotation(0f); // expanding

            if (op.getFiles() == null || op.getFiles().size() == 0) {
                holder.filesScroller.setVisibility(View.GONE);
            }
            holder.recentBtn.setEnabled(thread.getPostsCount() > 1);
            requestFillRecentPosts.set(threadIndex, true);

            prepare(holder, position);

            //            filesScroller.setLayoutManager(
            //                    new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
            //            filesScroller.setAdapter(new FilesAdapter(/*todo*/));
            double ms = ((double) (System.nanoTime() - start)) / 10e5;
            if (ms > 16) {
                Log.w("onBindViewHolder", "Time: " + ms);
            } else {
                Log.i("onBindViewHolder", "Time: " + ms);
            }
        }

        @Override
        public int getItemCount() {
            return threadIds.size() + 2; // prevPage+[threadIds]+nextPage
        }

        @Override
        public int getItemViewType(int position) {
            if (position > 0) {
                if (position < threadIds.size() + 1) {
                    return VIEW_TYPE_THREAD;
                } else {
                    return VIEW_TYPE_NEXT_PAGE;
                }
            } else {
                return VIEW_TYPE_PREV_PAGE;
            }
        }

        public void prepare(BoardAdapter.ThreadWithPreviewViewHolder holder, int position) {
            if (position == whereIsRecentPostsShownPosition) {
                holder.openHolder(false);
            } else {
                holder.closeHolder(false);
            }
        }

        public boolean toggle(BoardAdapter.ThreadWithPreviewViewHolder holder) {
            scrollListener.expandTriggered();
            if (whereIsRecentPostsShownPosition == holder.getLayoutPosition()) {
                whereIsRecentPostsShownPosition = -239;
                holder.closeHolder(true);
                return false;
            } else {
                int previous = whereIsRecentPostsShownPosition;
                whereIsRecentPostsShownPosition = holder.getLayoutPosition();
                holder.openHolder(true);

                final BoardAdapter.ThreadWithPreviewViewHolder oldHolder = (BoardAdapter.ThreadWithPreviewViewHolder) ((RecyclerView) holder.itemView
                        .getParent()).findViewHolderForLayoutPosition(previous);
                if (oldHolder != null) {
                    oldHolder.closeHolder(true);
                }
                return true;
            }
        }

    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment NotificationFragment.
     */
    public static BoardFragment newInstance(String boardKey) {
        Log.d(TAG, "new instance");
        BoardFragment fragment = new BoardFragment();
        Bundle b = new Bundle();
        b.putString(ARG_KEY, boardKey);
        b.putInt(ARG_PAGE, 1);
        fragment.setArguments(b);
        fragment.setRetainInstance(true);
        return fragment;
    }

    @Override
    public void onViewStateRestored(Bundle savedInstanceState) {
        Log.d(TAG, "restore");
        super.onViewStateRestored(savedInstanceState);
        if (savedInstanceState != null) {
            boardKey = savedInstanceState.getString("board");
            page = savedInstanceState.getInt("page");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "create");
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            boardKey = boardKey == null ? getArguments().getString(ARG_KEY) : boardKey;
        }
    }

    private void switchPage(final int newPage) {
        Log.d(TAG, "switch page from " + page + " to " + newPage);
        supervisor.retitleOnLoading();
        progressBar.setVisibility(View.VISIBLE);
        recycler.setVisibility(View.INVISIBLE);
        page = newPage;
        Hanabira.getFlower().getBoardPage(boardKey, page, new Consumer<List<Integer>>() {
            @Override
            public void accept(List<Integer> hanabiraThreads) {
                boardAdapter = new BoardAdapter(hanabiraThreads);
                hookUpThreads();
            }
        });
    }

    /**
     * @deprecated Use {@link #onAttach(Context)} instead.
     */
    @Deprecated
    public void onAttach(Activity activity) {
        Log.d(TAG, "attach");
        super.onAttach(activity);
        try {
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
            recentListSize = Integer.parseInt(prefs.getString("pref_board_recent_list_size", "3"));
            supervisor = (Castor) activity;
        } catch (ClassCastException e) {
            e.printStackTrace();
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        Log.d(TAG, "detach");
        super.onDetach();
        supervisor = null;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(TAG, "SIS");
        super.onSaveInstanceState(outState);
        outState.putString(ARG_KEY, boardKey);
        outState.putInt(ARG_PAGE, page);
        outState.putParcelable(ARG_RECYCLER_LAYOUT, recyclerLayoutManager.onSaveInstanceState());
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(TAG, "activity created (open bundle)");
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
            boardKey = savedInstanceState.getString(ARG_KEY, null);
            page = savedInstanceState.getInt(ARG_PAGE, 0);
            recycler.getLayoutManager()
                    .onRestoreInstanceState(savedInstanceState.getParcelable(ARG_RECYCLER_LAYOUT));
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_scroller, container, false);
    }

    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
        Log.d(TAG, "view created (find and switch)");

        progressBar = (ProgressBar) view.findViewById(R.id.pb);
        recycler = (RecyclerView) view.findViewById(R.id.thread_recycler);
        recyclerLayoutManager = new LinearLayoutManager(getContext());
        recycler.setLayoutManager(recyclerLayoutManager);
        if (boardAdapter == null) {
            // open board page
            scrollListener = new HidingScrollListener(getContext());
            switchPage(page);
        } else {
            // popping from fragments stack
            hookUpThreads();
            // should be run immediately because it's already main thread
        }

        scrollListener.resetContainer((FrameLayout) getActivity().findViewById(R.id.frame_toolbar_container));
        recycler.addOnScrollListener(scrollListener);
    }

    private void hookUpThreads() {
        Log.d(TAG, "hook up page " + page);
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                recycler.setAdapter(boardAdapter);
                progressBar.setVisibility(View.GONE);
                recycler.setVisibility(View.VISIBLE);
                supervisor.retitleOnBoardLoad(boardKey, page);
            }
        });
    }

    @Override
    public void onDestroyView() {
        Log.d(TAG, "destroy view");
        super.onDestroyView();
    }

    @Override
    public void onStart() {
        Log.d(TAG, "start");
        super.onStart();
    }

    @Override
    public void onResume() {
        Log.d(TAG, "resume");
        super.onResume();
    }

    @Override
    public void onPause() {
        Log.d(TAG, "pause");
        super.onPause();
    }

    @Override
    public void onStop() {
        Log.d(TAG, "stop");
        super.onStop();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "destroy");
        super.onDestroy();
    }
}