com.ayuget.redface.ui.activity.TopicsActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.ayuget.redface.ui.activity.TopicsActivity.java

Source

/*
 * Copyright 2015 Ayuget
 *
 * 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.ayuget.redface.ui.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.ayuget.redface.R;
import com.ayuget.redface.data.api.MDLink;
import com.ayuget.redface.data.api.hfr.HFRUrlParser;
import com.ayuget.redface.data.api.model.Category;
import com.ayuget.redface.data.api.model.Topic;
import com.ayuget.redface.data.api.model.TopicStatus;
import com.ayuget.redface.data.api.model.User;
import com.ayuget.redface.data.rx.EndlessObserver;
import com.ayuget.redface.data.rx.SubscriptionHandler;
import com.ayuget.redface.data.state.CategoriesStore;
import com.ayuget.redface.ui.UIConstants;
import com.ayuget.redface.ui.event.EditPostEvent;
import com.ayuget.redface.ui.event.GoToTopicEvent;
import com.ayuget.redface.ui.event.InternalLinkClickedEvent;
import com.ayuget.redface.ui.event.PostActionEvent;
import com.ayuget.redface.ui.event.QuotePostEvent;
import com.ayuget.redface.ui.event.TopicContextItemSelectedEvent;
import com.ayuget.redface.ui.fragment.DefaultFragment;
import com.ayuget.redface.ui.fragment.DetailsDefaultFragment;
import com.ayuget.redface.ui.fragment.MetaPageFragmentBuilder;
import com.ayuget.redface.ui.fragment.TopicFragment;
import com.ayuget.redface.ui.fragment.TopicFragmentBuilder;
import com.ayuget.redface.ui.fragment.TopicListFragment;
import com.ayuget.redface.ui.fragment.TopicListFragmentBuilder;
import com.ayuget.redface.ui.misc.SnackbarHelper;
import com.ayuget.redface.ui.misc.PagePosition;
import com.ayuget.redface.ui.misc.UiUtils;
import com.rengwuxian.materialedittext.MaterialEditText;
import com.squareup.otto.Subscribe;

import javax.inject.Inject;

import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

public class TopicsActivity extends MultiPaneActivity implements TopicListFragment.OnTopicClickedListener {
    private static final String LOG_TAG = TopicsActivity.class.getSimpleName();

    private static final String DEFAULT_FRAGMENT_TAG = "default_fragment";

    private static final String DETAILS_DEFAULT_FRAGMENT_TAG = "details_default_fragment";

    private static final String TOPICS_FRAGMENT_TAG = "topics_fragment";

    private static final String TOPIC_FRAGMENT_TAG = "topic_fragment";

    private static final String ARG_TOPIC = "topic";

    private static final String ARG_CURRENT_CATEGORY = "currentCategory";

    private MaterialEditText goToPageEditText;

    private SubscriptionHandler<Integer, Topic> topicDetailsSearchHandler = new SubscriptionHandler<>();

    private SubscriptionHandler<Topic, String> quoteHandler = new SubscriptionHandler<>();

    @Inject
    CategoriesStore categoriesStore;

    @Inject
    HFRUrlParser urlParser;

    boolean restoredInstanceState = false;

    private Category currentCategory;

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

        setContentView(R.layout.activity_topics, savedInstanceState);

        if (getIntent().getData() != null) {
            restoredInstanceState = true;

            String url = getIntent().getData().toString();

            urlParser.parseUrl(url).ifTopicLink(new MDLink.IfIsTopicLink() {
                @Override
                public void call(final Category category, final int topicId, final int topicPage,
                        final PagePosition pagePosition) {
                    Log.d(LOG_TAG, String.format("Parsed link for category='%s', topic='%d', page='%d'",
                            category.getName(), topicId, topicPage));
                    onGoToTopicEvent(new GoToTopicEvent(category, topicId, topicPage, pagePosition));
                }
            });
        } else if (savedInstanceState == null) {
            Bundle extras = getIntent().getExtras();

            if (extras != null) {
                Category savedCategory = extras.getParcelable(UIConstants.ARG_SELECTED_CATEGORY);
                if (savedCategory != null) {
                    if (savedCategory.getId() == categoriesStore.getMetaCategory().getId()) {
                        onMyTopicsClicked();
                    } else {
                        onCategoryClicked(savedCategory);
                    }
                }
            }
        }
    }

    @Override
    protected void onSetupUiState() {
        Log.d(LOG_TAG, "Setting up initial state for TopicsActivity");

        DefaultFragment defaultFragment = DefaultFragment.newInstance();

        if (isTwoPaneMode()) {
            DetailsDefaultFragment detailsDefaultFragment = DetailsDefaultFragment.newInstance();

            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, defaultFragment, DEFAULT_FRAGMENT_TAG)
                    .replace(R.id.details_container, detailsDefaultFragment, DETAILS_DEFAULT_FRAGMENT_TAG).commit();
        } else {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, defaultFragment, DEFAULT_FRAGMENT_TAG).commit();
        }
    }

    @Override
    protected void onRestoreUiState(Bundle savedInstanceState) {
        Log.d(LOG_TAG, "Restoring UI state for TopicsActivity");

        // This will prevent categories loading eventfrom the navigation drawer to
        // mess-up with the UI (unnecessary reload) when the activity is re-created from
        // a previous state (like rotating the phone...)
        restoredInstanceState = true;
        currentCategory = savedInstanceState.getParcelable(ARG_CURRENT_CATEGORY);

        TopicListFragment topicListFragment = (TopicListFragment) getSupportFragmentManager()
                .findFragmentByTag(TOPICS_FRAGMENT_TAG);
        TopicFragment topicFragment = (TopicFragment) getSupportFragmentManager()
                .findFragmentByTag(TOPIC_FRAGMENT_TAG);

        if (topicListFragment != null) {
            // Register the callbacks again
            topicListFragment.addOnTopicClickedListener(this);
        }

        // Restore topic list fragment to the correct pane if we come from portrait mode
        if (isTwoPaneMode() && (topicListFragment != null && !topicListFragment.isVisible())) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, topicListFragment, TOPICS_FRAGMENT_TAG).commit();
        }
    }

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

        if (currentCategory != null) {
            outState.putParcelable(ARG_CURRENT_CATEGORY, currentCategory);
        }
    }

    /**
     * Callback invoked when categories have been loaded from cache or network.
     */
    @Override
    public void onCategoriesLoaded() {
        Log.d(LOG_TAG, "Categories have been loaded");

        if (currentCategory == null) {
            Log.d(LOG_TAG, "Loading default category");
            loadDefaultCategory();
        } else {
            Log.d(LOG_TAG, "Ignoring categories loaded event, state has been restored");
        }
    }

    /**
     * Loads default category
     */
    public void loadDefaultCategory() {
        int defaultCatId = getSettings().getDefaultCategoryId();

        // If current user is not logged in and if default category setting is on "My Topics", let's
        // redirect the user the another accessible cat. "My topics" is necessary empty when user
        // is not logged in, so having an empty landing screen is not the best user experience here
        if (!userManager.isActiveUserLoggedIn() && defaultCatId == CategoriesStore.META_CATEGORY_ID) {
            defaultCatId = getSettings().getNotLoggedInDefaultCategoryId();
        }

        Category defaultCategory = categoriesStore.getCategoryById(defaultCatId);
        if (defaultCategory == null) {
            Log.w(LOG_TAG, String.format("Category '%d' not found in cache", defaultCatId));
        } else if (defaultCategory.getId() == CategoriesStore.META_CATEGORY_ID) {
            onMyTopicsClicked();
        } else {
            onCategoryClicked(defaultCategory);
        }
    }

    @Override
    public void onCategoryClicked(Category category) {
        currentCategory = category;

        Log.d(LOG_TAG, String.format("Loading category '%s', with topicFilter='%s'", category.getName(),
                getSettings().getDefaultTopicFilter().toString()));

        TopicListFragment topicListFragment = new TopicListFragmentBuilder(category)
                .topicFilter(getSettings().getDefaultTopicFilter()).build();
        topicListFragment.addOnTopicClickedListener(this);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.container, topicListFragment, TOPICS_FRAGMENT_TAG);
        transaction.commit();
    }

    @Override
    public void onMyTopicsClicked() {
        currentCategory = categoriesStore.getMetaCategory();

        Log.d(LOG_TAG, String.format("Loading meta category, with topicFilter='%s'",
                getSettings().getDefaultTopicFilter().toString()));
        TopicListFragment topicListFragment = new MetaPageFragmentBuilder(currentCategory)
                .topicFilter(getSettings().getDefaultTopicFilter()).build();
        topicListFragment.addOnTopicClickedListener(this);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.container, topicListFragment, TOPICS_FRAGMENT_TAG);
        transaction.commit();
    }

    @Override
    public void onTopicClicked(Topic topic) {
        int pageToLoad;
        PagePosition pagePosition;

        if (topic.getStatus() == TopicStatus.FAVORITE_NEW_CONTENT
                || topic.getStatus() == TopicStatus.READ_NEW_CONTENT
                || topic.getStatus() == TopicStatus.FLAGGED_NEW_CONTENT) {
            pageToLoad = topic.getLastReadPostPage();
            pagePosition = new PagePosition(topic.getLastReadPostId());
        } else {
            pageToLoad = 1;
            pagePosition = new PagePosition(PagePosition.TOP);
        }

        loadTopic(topic, pageToLoad, pagePosition);
    }

    /**
     * Loads a topic in the appropriate panel for a given page and position
     */
    protected void loadTopic(Topic topic, int page, PagePosition pagePosition) {
        Log.d(LOG_TAG, String.format("Loading topic '%s' (page %d)", topic.getSubject(), page));
        TopicFragment topicFragment = new TopicFragmentBuilder(page, topic).currentPagePosition(pagePosition)
                .build();

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

        int topicFragmentContainer = isTwoPaneMode() ? R.id.details_container : R.id.container;

        if (!isTwoPaneMode()) {
            Log.d(LOG_TAG, "Setting slide animation for topicFragment");
            transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left,
                    R.anim.exit_to_right);
        }

        transaction.replace(topicFragmentContainer, topicFragment, TOPIC_FRAGMENT_TAG);
        transaction.addToBackStack(TOPIC_FRAGMENT_TAG);
        transaction.commit();
    }

    protected void loadAnonymousTopic(Topic topic, int page, PagePosition pagePosition) {
        TopicFragment anonymousTopicFragment = new TopicFragmentBuilder(page, topic)
                .currentPagePosition(pagePosition).build();
        int topicFragmentContainer = isTwoPaneMode() ? R.id.details_container : R.id.container;

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(topicFragmentContainer, anonymousTopicFragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    @Override
    protected void onUserSwitched(User newUser) {
        if (currentCategory != null) {
            onCategoryClicked(currentCategory);
        }
    }

    @Override
    public void onBackPressed() {
        Log.d(LOG_TAG, "On Back Pressed");
        boolean consumedEvent = false;

        TopicFragment topicFragment = (TopicFragment) getSupportFragmentManager()
                .findFragmentByTag(TOPIC_FRAGMENT_TAG);
        if (topicFragment != null) {
            consumedEvent = topicFragment.onBackPressed();
        }

        if (!consumedEvent) {
            super.onBackPressed();
        }
    }

    /**
     * Called when an item of the topic contextual menu has been clicked. This menu is accessible via long-press
     * on a topic and gives additional actions to the user.
     */
    @Subscribe
    public void onTopicContextItemSelected(TopicContextItemSelectedEvent event) {
        Log.d(LOG_TAG, String.format("Received topic contextItem event : %d for topic %s", event.getItemId(),
                event.getTopic().getSubject()));

        switch (event.getItemId()) {
        case UIConstants.TOPIC_ACTION_GO_TO_FIRST_PAGE:
            loadTopic(event.getTopic(), 1, new PagePosition(PagePosition.TOP));
            break;
        case UIConstants.TOPIC_ACTION_GO_TO_LAST_READ_PAGE:
            loadTopic(event.getTopic(), event.getTopic().getLastReadPostPage(),
                    new PagePosition(event.getTopic().getLastReadPostId()));
            break;
        case UIConstants.TOPIC_ACTION_GO_TO_SPECIFIC_PAGE:
            showGoToPageDialog(event.getTopic());
            break;
        case UIConstants.TOPIC_ACTION_REPLY_TO_TOPIC: {
            Intent intent = new Intent(this, ReplyActivity.class);
            intent.putExtra(ARG_TOPIC, event.getTopic());
            startActivity(intent);
            break;
        }
        case UIConstants.TOPIC_ACTION_GO_TO_LAST_PAGE:
            loadTopic(event.getTopic(), event.getTopic().getPagesCount(), new PagePosition(PagePosition.TOP));
            break;
        case UIConstants.TOPIC_ACTION_COPY_LINK:
            UiUtils.copyToClipboard(this, mdEndpoints.topic(event.getTopic()));
            break;
        case UIConstants.TOPIC_ACTION_SHARE: {
            UiUtils.shareText(this, mdEndpoints.topic(event.getTopic()));
            break;
        }
        }
    }

    /**
     * This event is fired if an internal link to a different post that the one displayed has been
     * clicked.
     */
    @Subscribe
    public void onGoToTopicEvent(final GoToTopicEvent event) {
        subscribe(topicDetailsSearchHandler.load(event.getTopicId(),
                mdService.getTopic(userManager.getActiveUser(), event.getCategory(), event.getTopicId()),
                new EndlessObserver<Topic>() {
                    @Override
                    public void onNext(Topic topic) {
                        if (topic != null) {
                            topic.setCategory(event.getCategory());
                            loadAnonymousTopic(topic, event.getTopicPage(), event.getPagePosition());
                        }
                    }
                }));
    }

    /**
     * Callback used to handle internal URLs. This callback does not handle the link itself, but updates the page position of the
     * appropriate topic page in order to restore correct position on back press if a new topic is loaded (meaning a new topicFragment).
     * Kinda hacky, but seems to work...
     */
    @Subscribe
    public void onInternalLinkClicked(InternalLinkClickedEvent event) {
        TopicFragment topicFragment = (TopicFragment) getSupportFragmentManager()
                .findFragmentByTag(TOPIC_FRAGMENT_TAG);
        if (topicFragment != null && event.getTopic() == topicFragment.getTopic()
                && event.getPage() == topicFragment.getCurrentPage()) {
            topicFragment.setCurrentPagePosition(event.getPagePosition());
        }
    }

    @Subscribe
    public void onQuotePost(final QuotePostEvent event) {
        subscribe(quoteHandler.load(event.getTopic(),
                mdService.getQuote(userManager.getActiveUser(), event.getTopic(), event.getPostId()),
                new EndlessObserver<String>() {
                    @Override
                    public void onNext(String quoteBBCode) {
                        startReplyActivity(event.getTopic(), quoteBBCode);
                    }
                }));
    }

    @Subscribe
    public void onEditPost(final EditPostEvent event) {
        subscribe(quoteHandler.load(event.getTopic(),
                mdService.getPostContent(userManager.getActiveUser(), event.getTopic(), event.getPostId()),
                new EndlessObserver<String>() {
                    @Override
                    public void onNext(String messageBBCode) {
                        startEditActivity(event.getTopic(), event.getPostId(), messageBBCode);
                    }
                }));
    }

    @Subscribe
    public void onPostActionEvent(final PostActionEvent event) {
        switch (event.getPostAction()) {
        case FAVORITE:
            Log.d(LOG_TAG, "About to mark post as favorite");
            markPostAsFavorite(event.getTopic(), event.getPostId());
            break;
        case DELETE:
            Log.d(LOG_TAG, "About to delete post");
            new MaterialDialog.Builder(this).content(R.string.post_delete_confirmation)
                    .positiveText(R.string.post_delete_yes).negativeText(R.string.post_delete_no)
                    .callback(new MaterialDialog.ButtonCallback() {
                        @Override
                        public void onPositive(MaterialDialog dialog) {
                            deletePost(event.getTopic(), event.getPostId());
                        }
                    }).show();
            break;
        default:
            Log.e(LOG_TAG, "Action not handled");
            break;
        }
    }

    /**
     * Marks a given post as favorite
     */
    public void markPostAsFavorite(Topic topic, int postId) {
        Toast.makeText(TopicsActivity.this, R.string.marking_as_favorite_in_progress, Toast.LENGTH_SHORT).show();

        subscribe(mdService.markPostAsFavorite(userManager.getActiveUser(), topic, postId)
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new EndlessObserver<Boolean>() {
                    @Override
                    public void onNext(Boolean success) {
                        if (success) {
                            Toast.makeText(TopicsActivity.this, R.string.mark_as_favorite_success,
                                    Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(TopicsActivity.this, R.string.mark_as_favorite_failed,
                                    Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onError(Throwable throwable) {
                        Log.e(LOG_TAG, "Unexpected error while marking a post as favorite", throwable);
                        Toast.makeText(TopicsActivity.this, R.string.mark_as_favorite_failed, Toast.LENGTH_SHORT)
                                .show();
                    }
                }));
    }

    /**
     * Shows the "Go to page" dialog where the user can enter the page he wants to consult.
     * @param topic topic concerned by the action
     */
    public void showGoToPageDialog(final Topic topic) {
        MaterialDialog dialog = new MaterialDialog.Builder(this).customView(R.layout.dialog_go_to_page, true)
                .positiveText(R.string.dialog_go_to_page_positive_text).negativeText(android.R.string.cancel)
                .theme(themeManager.getMaterialDialogTheme()).callback(new MaterialDialog.ButtonCallback() {
                    @Override
                    public void onPositive(MaterialDialog dialog) {
                        try {
                            int pageNumber = Integer.valueOf(goToPageEditText.getText().toString());
                            loadTopic(topic, pageNumber, new PagePosition(PagePosition.TOP));
                        } catch (NumberFormatException e) {
                            Log.e(LOG_TAG, String.format("Invalid page number entered : %s",
                                    goToPageEditText.getText().toString()), e);
                            SnackbarHelper.makeError(TopicsActivity.this, R.string.invalid_page_number).show();
                        }
                    }

                    @Override
                    public void onNegative(MaterialDialog dialog) {
                    }
                }).build();

        final View positiveAction = dialog.getActionButton(DialogAction.POSITIVE);
        goToPageEditText = (MaterialEditText) dialog.getCustomView().findViewById(R.id.page_number);

        goToPageEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (s.toString().trim().length() > 0) {
                    try {
                        int pageNumber = Integer.valueOf(s.toString());
                        positiveAction.setEnabled(pageNumber >= 1 && pageNumber <= topic.getPagesCount());
                    } catch (NumberFormatException e) {
                        positiveAction.setEnabled(false);
                    }
                } else {
                    positiveAction.setEnabled(false);
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        dialog.show();
        positiveAction.setEnabled(false);
    }
}