org.bohrmeista.chan.ui.activity.BoardActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.bohrmeista.chan.ui.activity.BoardActivity.java

Source

/*
 * Clover - 4chan browser https://github.com/Floens/Clover/
 * Copyright (C) 2014  Floens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.bohrmeista.chan.ui.activity;

import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;
import android.widget.TextView;

import org.bohrmeista.chan.ChanApplication;
import org.bohrmeista.chan.R;
import org.bohrmeista.chan.chan.ChanUrls;
import org.bohrmeista.chan.core.ChanPreferences;
import org.bohrmeista.chan.core.loader.Loader;
import org.bohrmeista.chan.core.manager.BoardManager;
import org.bohrmeista.chan.core.manager.ThreadManager;
import org.bohrmeista.chan.core.model.Board;
import org.bohrmeista.chan.core.model.Loadable;
import org.bohrmeista.chan.core.model.Pin;
import org.bohrmeista.chan.core.model.Post;
import org.bohrmeista.chan.ui.fragment.ThreadFragment;
import org.bohrmeista.chan.utils.Logger;
import org.bohrmeista.chan.utils.Utils;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BoardActivity extends BaseActivity
        implements AdapterView.OnItemSelectedListener, BoardManager.BoardChangeListener {
    private static final String TAG = "BoardActivity";
    private static final Pattern THREAD_URL_PATTERN = Pattern.compile("(\\d+)\\.html");

    private Loadable boardLoadable;
    private Loadable threadLoadable;
    private ThreadFragment boardFragment;
    private ThreadFragment threadFragment;

    private boolean ignoreNextOnItemSelected = false;
    private Spinner boardSpinner;
    private BoardSpinnerAdapter spinnerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ChanApplication.getBoardManager().addListener(this);

        boardLoadable = new Loadable();
        threadLoadable = new Loadable();

        boardFragment = ThreadFragment.newInstance(this);
        setBoardFragmentViewMode();

        threadFragment = ThreadFragment.newInstance(this);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.left_pane, boardFragment);
        ft.replace(R.id.right_pane, threadFragment);
        ft.commitAllowingStateLoss();

        final ActionBar actionBar = getActionBar();

        boardSpinner = new Spinner(actionBar.getThemedContext());
        spinnerAdapter = new BoardSpinnerAdapter(this, boardSpinner);
        boardSpinner.setAdapter(spinnerAdapter);
        boardSpinner.setOnItemSelectedListener(this);

        actionBar.setCustomView(boardSpinner);
        actionBar.setDisplayShowCustomEnabled(true);

        updatePaneState();

        Intent startIntent = getIntent();
        Uri startUri = startIntent.getData();

        if (savedInstanceState != null) {
            threadLoadable.readFromBundle(this, "thread", savedInstanceState);
            startLoadingThread(threadLoadable);

            // Reset page etc.
            Loadable tmp = new Loadable();
            tmp.readFromBundle(this, "board", savedInstanceState);
            startLoadingBoard(new Loadable(tmp.board));
        } else {
            if (startUri != null) {
                handleIntentURI(startUri);
            }

            if (boardLoadable.mode == Loadable.Mode.INVALID) {
                List<Board> savedValues = ChanApplication.getBoardManager().getSavedBoards();
                if (savedValues.size() > 0) {
                    startLoadingBoard(new Loadable(savedValues.get(0).value));
                }
            }
        }

        if (startIntent.getExtras() != null) {
            handleExtraBundle(startIntent.getExtras());
        }

        ignoreNextOnItemSelected = true;
    }

    @Override
    protected void onNewIntent(final Intent intent) {
        super.onNewIntent(intent);

        if (intent.getExtras() != null) {
            handleExtraBundle(intent.getExtras());
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        boardLoadable.writeToBundle(this, "board", outState);
        threadLoadable.writeToBundle(this, "thread", outState);
    }

    @Override
    protected void onStart() {
        super.onStart();

        ChanApplication.getInstance().activityEnteredForeground();
    }

    @Override
    protected void onStop() {
        super.onStop();

        ChanApplication.getInstance().activityEnteredBackground();
    }

    @Override
    protected void onPause() {
        super.onPause();

        ChanApplication.getWatchManager().updateDatabase();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        ChanApplication.getBoardManager().removeListener(this);
    }

    @Override
    protected void initDrawer() {
        pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer, R.drawable.ic_drawer, R.string.drawer_open,
                R.string.drawer_close) {
        };

        super.initDrawer();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        pinDrawerListener.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        pinDrawerListener.onConfigurationChanged(newConfig);

        updatePaneState();
    }

    @Override
    public void onBackPressed() {
        if (threadPane.isOpen()) {
            super.onBackPressed();
        } else {
            threadPane.openPane();
        }
    }

    @Override
    public void openPin(Pin pin) {
        startLoadingThread(pin.loadable);

        pinDrawer.closeDrawer(pinDrawerView);
    }

    @Override
    public void onOPClicked(Post post) {
        Loadable l = new Loadable(post.board, post.no);
        l.generateTitle(post);
        startLoadingThread(l);
    }

    @Override
    public void onOpenThread(Loadable thread) {
        startLoadingThread(thread);
    }

    @Override
    public void onOpenBoard(Loadable board) {
        startLoadingBoard(board);
        threadPane.openPane();
    }

    @Override
    public void onThreadLoaded(Loadable loadable, List<Post> posts) {
        updateActionBarState();
        pinnedAdapter.notifyDataSetChanged();
    }

    @Override
    public void updatePin(Pin pin) {
        super.updatePin(pin);
        updateActionBarState();
    }

    @Override
    public void onNothingSelected(final AdapterView<?> parent) {
    }

    @Override
    public void onPanelClosed(View view) {
        updateActionBarState();
    }

    @Override
    public void onPanelOpened(View view) {
        updateActionBarState();
    }

    @Override
    public void onBoardsChanged() {
        spinnerAdapter.setBoards();
        spinnerAdapter.notifyDataSetChanged();
    }

    private void handleExtraBundle(Bundle extras) {
        int pinId = extras.getInt("pin_id", -2);
        if (pinId != -2) {
            if (pinId == -1) {
                pinDrawer.openDrawer(pinDrawerView);
            } else {
                Pin pin = ChanApplication.getWatchManager().findPinById(pinId);
                if (pin != null) {
                    startLoadingThread(pin.loadable);
                }
            }
        }
    }

    private void updatePaneState() {
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int width = metrics.widthPixels;

        FrameLayout left = (FrameLayout) findViewById(R.id.left_pane);
        FrameLayout right = (FrameLayout) findViewById(R.id.right_pane);

        LayoutParams leftParams = left.getLayoutParams();
        LayoutParams rightParams = right.getLayoutParams();

        boolean wasSlidable = threadPane.isSlideable();
        boolean isSlidable;

        // Content view dp's:
        // Nexus 4 is 384 x 640 dp
        // Nexus 7 is 600 x 960 dp
        // Nexus 10 is 800 x 1280 dp

        if (ChanPreferences.getForcePhoneLayout()) {
            leftParams.width = width - Utils.dp(30);
            rightParams.width = width;
            isSlidable = true;
        } else {
            if (width < Utils.dp(400)) {
                leftParams.width = width - Utils.dp(30);
                rightParams.width = width;
                isSlidable = true;
            } else if (width < Utils.dp(800)) {
                leftParams.width = width - Utils.dp(60);
                rightParams.width = width;
                isSlidable = true;
            } else if (width < Utils.dp(1000)) {
                leftParams.width = Utils.dp(300);
                rightParams.width = width - Utils.dp(300);
                isSlidable = false;
            } else {
                leftParams.width = Utils.dp(400);
                rightParams.width = width - Utils.dp(400);
                isSlidable = false;
            }
        }

        left.setLayoutParams(leftParams);
        right.setLayoutParams(rightParams);

        threadPane.requestLayout();
        left.requestLayout();
        right.requestLayout();

        LayoutParams drawerParams = pinDrawerView.getLayoutParams();

        if (width < Utils.dp(340)) {
            drawerParams.width = Utils.dp(280);
        } else {
            drawerParams.width = Utils.dp(320);
        }

        pinDrawerView.setLayoutParams(drawerParams);

        updateActionBarState();

        if (isSlidable != wasSlidable) {
            // Terrible hack to sync state for some devices when it changes slidable mode
            threadPane.postDelayed(new Runnable() {
                @Override
                public void run() {
                    updateActionBarState();
                }
            }, 1000);
        }
    }

    private void updateActionBarState() {
        // Force the actionbar state after the ThreadPane layout,
        // otherwise the ThreadPane incorrectly reports that it's not slidable.
        threadPane.post(new Runnable() {
            @Override
            public void run() {
                updateActionBarStateCallback();
            }
        });
    }

    private void updateActionBarStateCallback() {
        final ActionBar actionBar = getActionBar();

        if (threadPane.isSlideable()) {
            if (threadPane.isOpen()) {
                actionBar.setDisplayShowCustomEnabled(true);
                if (ChanApplication.getBoardManager().getBoardExists(boardLoadable.board)) {
                    spinnerAdapter.setCustomBoard(null);
                } else {
                    spinnerAdapter.setCustomBoard(boardLoadable.board);
                }

                spinnerAdapter.setBoard(boardLoadable.board);

                actionBar.setTitle("");
                pinDrawerListener.setDrawerIndicatorEnabled(true);

                if (boardLoadable.isBoardMode()) {
                    setShareUrl(ChanUrls.getBoardUrlDesktop(boardLoadable.board));
                } else if (boardLoadable.isCatalogMode()) {
                    setShareUrl(ChanUrls.getCatalogUrlDesktop(boardLoadable.board));
                }
            } else {
                actionBar.setDisplayShowCustomEnabled(false);
                actionBar.setTitle(threadLoadable.title);
                pinDrawerListener.setDrawerIndicatorEnabled(false);

                if (threadLoadable.isThreadMode())
                    setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no));
            }
        } else {
            actionBar.setDisplayShowCustomEnabled(true);
            pinDrawerListener.setDrawerIndicatorEnabled(true);
            actionBar.setTitle(threadLoadable.title);

            if (threadLoadable.isThreadMode()) {
                setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no));
            }
        }

        actionBar.setHomeButtonEnabled(true);
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setDisplayShowTitleEnabled(true);

        invalidateOptionsMenu();
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        boolean open = threadPane.isOpen();
        boolean slidable = threadPane.isSlideable();

        setMenuItemEnabled(menu.findItem(R.id.action_reload_board), slidable && open);
        setMenuItemEnabled(menu.findItem(R.id.action_reload_thread), slidable && !open);
        setMenuItemEnabled(menu.findItem(R.id.action_reload_tablet), !slidable);

        setMenuItemEnabled(menu.findItem(R.id.action_pin), !slidable || !open);

        setMenuItemEnabled(menu.findItem(R.id.action_reply), slidable);
        setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !slidable);

        setMenuItemEnabled(menu.findItem(R.id.action_board_view_mode), !slidable || open);

        if (ChanPreferences.getBoardViewMode().equals("list")) {
            menu.findItem(R.id.action_board_view_mode_list).setChecked(true);
        } else if (ChanPreferences.getBoardViewMode().equals("grid")) {
            menu.findItem(R.id.action_board_view_mode_grid).setChecked(true);
        }

        setMenuItemEnabled(menu.findItem(R.id.action_search), slidable);
        setMenuItemEnabled(menu.findItem(R.id.action_search_tablet), !slidable);

        return super.onPrepareOptionsMenu(menu);
    }

    private void setMenuItemEnabled(MenuItem item, boolean enabled) {
        if (item != null) {
            item.setVisible(enabled);
            item.setEnabled(enabled);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (pinDrawerListener.onOptionsItemSelected(item)) {
            return true;
        }

        switch (item.getItemId()) {
        case R.id.action_reload_board:
        case R.id.action_reload_tablet_board:
            boardFragment.reload();
            return true;
        case R.id.action_reload_thread:
        case R.id.action_reload_tablet_thread:
            threadFragment.reload();
            return true;
        case R.id.action_reply:
            if (threadPane.isOpen()) {
                boardFragment.openReply();
            } else {
                threadFragment.openReply();
            }
            return true;
        case R.id.action_reply_board:
            boardFragment.openReply();

            return true;
        case R.id.action_reply_thread:
            threadFragment.openReply();

            return true;
        case R.id.action_pin:
            if (threadFragment.hasLoader()) {
                Loader loader = threadFragment.getLoader();
                if (loader.getCachedPosts().size() > 0) {
                    ChanApplication.getWatchManager().addPin(loader.getLoadable(), loader.getCachedPosts().get(0));
                    pinDrawer.openDrawer(pinDrawerView);
                }
            }

            return true;
        case R.id.action_open_browser:
            openInBrowser();

            return true;
        case R.id.action_board_view_mode_grid:
            if (!ChanPreferences.getBoardViewMode().equals("grid")) {
                ChanPreferences.setBoardViewMode("grid");
                setBoardFragmentViewMode();
                startLoadingBoard(boardLoadable);
            }
            return true;
        case R.id.action_board_view_mode_list:
            if (!ChanPreferences.getBoardViewMode().equals("list")) {
                ChanPreferences.setBoardViewMode("list");
                setBoardFragmentViewMode();
                startLoadingBoard(boardLoadable);
            }
            return true;
        case R.id.action_search:
            if (threadPane.isOpen()) {
                boardFragment.startFiltering();
            } else {
                threadFragment.startFiltering();
            }
            return true;
        case R.id.action_search_board:
            boardFragment.startFiltering();
            return true;
        case R.id.action_search_thread:
            threadFragment.startFiltering();
            return true;
        case android.R.id.home:
            threadPane.openPane();

            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
        if (ignoreNextOnItemSelected) {
            Logger.d(TAG, "Ignoring onItemSelected");
            ignoreNextOnItemSelected = false;
            return;
        }

        spinnerAdapter.onItemSelected(position);
    }

    private void startLoadingBoard(Loadable loadable) {
        if (loadable.mode == Loadable.Mode.INVALID)
            return;

        boardLoadable = loadable;

        if (ChanPreferences.getBoardMode().equals("catalog")) {
            boardLoadable.mode = Loadable.Mode.CATALOG;
        } else if (ChanPreferences.getBoardMode().equals("pages")) {
            boardLoadable.mode = Loadable.Mode.BOARD;
        }

        // Force catalog mode when using grid
        if (boardFragment.getViewMode() == ThreadManager.ViewMode.GRID) {
            boardLoadable.mode = Loadable.Mode.CATALOG;
        }

        boardFragment.bindLoadable(boardLoadable);
        boardFragment.requestData();

        updateActionBarState();
    }

    private void startLoadingThread(Loadable loadable) {
        if (loadable.mode == Loadable.Mode.INVALID)
            return;

        Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable);
        if (pin != null) {
            // Use the loadable from the pin.
            // This way we can store the listview position in the pin loadable,
            // and not in a separate loadable instance.
            loadable = pin.loadable;
        }

        threadLoadable = loadable;

        threadFragment.bindLoadable(loadable);
        threadFragment.requestData();

        threadPane.closePane();

        updateActionBarState();
    }

    /**
     * Handle opening from an external url.
     *
     * @param startUri
     */
    private void handleIntentURI(Uri startUri) {
        Logger.d(TAG, "Opening " + startUri.getPath());

        List<String> parts = startUri.getPathSegments();

        if (parts.size() == 1) {
            // Board mode
            String rawBoard = parts.get(0);
            startLoadingBoard(new Loadable(rawBoard));
        } else if (parts.size() >= 3) {
            // Thread mode
            String rawBoard = parts.get(0);
            int no = -1;

            try {
                Matcher matcher = THREAD_URL_PATTERN.matcher(parts.get(2));
                if (matcher.find())
                    no = Integer.parseInt(matcher.group(1));
            } catch (NumberFormatException e) {
            }

            int post = -1;
            String fragment = startUri.getFragment();
            if (fragment != null) {
                int index = fragment.indexOf("p");
                if (index >= 0) {
                    try {
                        post = Integer.parseInt(fragment.substring(index + 1));
                    } catch (NumberFormatException e) {
                    }
                }
            }

            if (no >= 0) {
                startLoadingThread(new Loadable(rawBoard, no));
                if (post >= 0) {
                    threadFragment.highlightPost(post);
                }
            } else {
                handleIntentURIFallback(startUri.toString());
            }
        } else {
            showUrlOpenPicker(startUri.toString());
        }
    }

    private void handleIntentURIFallback(final String url) {
        new AlertDialog.Builder(this).setTitle(R.string.open_unknown_title).setMessage(R.string.open_unknown)
                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        showUrlOpenPicker(url);
                    }
                }).setCancelable(false).create().show();
    }

    private void setBoardFragmentViewMode() {
        if (ChanPreferences.getBoardViewMode().equals("list")) {
            boardFragment.setViewMode(ThreadManager.ViewMode.LIST);
        } else if (ChanPreferences.getBoardViewMode().equals("grid")) {
            boardFragment.setViewMode(ThreadManager.ViewMode.GRID);
        }
    }

    private class BoardSpinnerAdapter extends BaseAdapter {
        private static final int VIEW_TYPE_ITEM = 0;
        private static final int VIEW_TYPE_ADD = 1;

        private Context context;
        private Spinner spinner;
        private List<Board> boards;
        private int lastSelectedPosition = 0;
        private String customBoard;

        public BoardSpinnerAdapter(Context context, Spinner spinner) {
            this.context = context;
            this.spinner = spinner;
            setBoards();
        }

        public void setBoards() {
            boards = ChanApplication.getBoardManager().getSavedBoards();
        }

        public void setBoard(String boardValue) {
            for (int i = 0; i < boards.size(); i++) {
                if (boards.get(i).value.equals(boardValue)) {
                    spinner.setSelection(i);
                    return;
                }
            }
            if (customBoard != null)
                spinner.setSelection(boards.size() + 1);
        }

        public void onItemSelected(int position) {
            if (position >= 0 && position < boards.size()) {
                Loadable board = new Loadable(boards.get(position).value);

                // onItemSelected is called after the view initializes,
                // ignore if it's the same board
                if (boardLoadable.equals(board))
                    return;

                startLoadingBoard(board);

                lastSelectedPosition = position;
            } else if (position == boards.size()) {
                startActivity(new Intent(context, BoardEditor.class));
                spinner.setSelection(lastSelectedPosition);
            }
        }

        @Override
        public int getCount() {
            return boards.size() + (customBoard == null ? 1 : 2);
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            return position == boards.size() ? VIEW_TYPE_ADD : VIEW_TYPE_ITEM;
        }

        @Override
        public long getItemId(final int position) {
            return position;
        }

        @Override
        public String getItem(final int position) {
            switch (getItemViewType(position)) {
            case VIEW_TYPE_ITEM:
                if (position < boards.size())
                    return boards.get(position).key;
                return customBoard != null ? customBoard : "";
            case VIEW_TYPE_ADD:
                return context.getString(R.string.board_select_add);
            default:
                return "";
            }
        }

        @Override
        public View getView(final int position, View convertView, final ViewGroup parent) {
            switch (getItemViewType(position)) {
            case VIEW_TYPE_ITEM: {
                if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ITEM) {
                    convertView = LayoutInflater.from(context).inflate(R.layout.board_select_spinner, null);
                    convertView.setTag(VIEW_TYPE_ITEM);
                }

                TextView textView = (TextView) convertView;
                textView.setText(getItem(position));
                return textView;
            }
            case VIEW_TYPE_ADD: {
                if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ADD) {
                    convertView = LayoutInflater.from(context).inflate(R.layout.board_select_add, null);
                    convertView.setTag(VIEW_TYPE_ADD);
                }

                TextView textView = (TextView) convertView;
                textView.setText(getItem(position));
                return textView;
            }
            }

            return null;
        }

        public void setCustomBoard(String board) {
            this.customBoard = board;
        }
    }
}