Java tutorial
/* * 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.fragment; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBar; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.ContextMenu; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.Spinner; import com.ayuget.redface.R; import com.ayuget.redface.account.UserManager; import com.ayuget.redface.data.DataService; import com.ayuget.redface.data.api.model.Category; import com.ayuget.redface.data.api.model.Subcategory; import com.ayuget.redface.data.api.model.Topic; import com.ayuget.redface.data.api.model.TopicFilter; import com.ayuget.redface.data.rx.EndlessObserver; import com.ayuget.redface.settings.RedfaceSettings; import com.ayuget.redface.ui.UIConstants; import com.ayuget.redface.ui.adapter.SubcategoriesAdapter; import com.ayuget.redface.ui.adapter.TopicsAdapter; import com.ayuget.redface.ui.event.TopicContextItemSelectedEvent; import com.ayuget.redface.ui.misc.DataPresenter; import com.ayuget.redface.ui.misc.DividerItemDecoration; import com.ayuget.redface.ui.misc.EndlessScrollListener; import com.ayuget.redface.ui.misc.SnackbarHelper; import com.ayuget.redface.ui.misc.UiUtils; import com.ayuget.redface.ui.view.ContextMenuRecyclerView; import com.hannesdorfmann.fragmentargs.annotation.Arg; import com.squareup.phrase.Phrase; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import butterknife.InjectView; public class TopicListFragment extends ToggleToolbarFragment implements TopicsAdapter.OnTopicClickedListener, TopicsAdapter.OnTopicLongClickListener { private static final String LOG_TAG = TopicListFragment.class.getSimpleName(); private static final String ARG_TOPIC_LIST = "topic_list"; private static final String ARG_LAST_LOADED_PAGE = "last_loaded_page"; /** * Interface definition for a callback to be invoked when a topic in this fragment has * been clicked. */ public interface OnTopicClickedListener { void onTopicClicked(Topic topic); } @Arg Category category; @Arg(required = false) Subcategory subcategory; @Arg(required = false) TopicFilter topicFilter; /** * List of topics displayed in the fragment */ protected ArrayList<Topic> displayedTopics; protected TopicsAdapter topicsAdapter; private SubcategoriesAdapter subcategoriesAdapter; @InjectView(R.id.topics_list) ContextMenuRecyclerView topicsRecyclerView; @InjectView(R.id.topic_list_swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout; @InjectView(R.id.empty_content_image) ImageView emptyTopicsImage; @Inject UserManager userManager; protected LinearLayoutManager layoutManager; protected int lastLoadedPage = 0; protected DataPresenter dataPresenter; /** * Listener invoked when a topic is clicked */ private List<OnTopicClickedListener> onTopicClickedListeners; @Inject DataService dataService; @Inject RedfaceSettings settings; public TopicListFragment() { onTopicClickedListeners = new ArrayList<>(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (topicFilter == null) { topicFilter = TopicFilter.NONE; } initializeAdapters(); setHasOptionsMenu(true); } protected void initializeAdapters() { subcategoriesAdapter = new SubcategoriesAdapter(getActivity(), topicFilter); subcategoriesAdapter.replaceWith(category); topicsAdapter = new TopicsAdapter( new ContextThemeWrapper(getActivity(), themeManager.getActiveThemeStyle()), themeManager, settings.isCompactModeEnabled()); topicsAdapter.setOnTopicClickedListener(this); topicsAdapter.setOnTopicLongClickListener(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflateRootView(R.layout.fragment_topic_list, inflater, container); Drawable listDivider = getActivity().getResources().getDrawable(themeManager.getListDividerDrawable()); topicsRecyclerView.setHasFixedSize(true); topicsRecyclerView.addItemDecoration(new DividerItemDecoration(listDivider, false, false)); registerForContextMenu(topicsRecyclerView); layoutManager = new LinearLayoutManager(getActivity()); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); topicsRecyclerView.setLayoutManager(layoutManager); topicsRecyclerView.setAdapter(topicsAdapter); // Implement swipe to refresh swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { Log.d(LOG_TAG, String.format("Refreshing topic list for category %s (refresh)", category)); loadTopics(); } }); swipeRefreshLayout.setColorSchemeResources(R.color.theme_primary, R.color.theme_primary_dark); dataPresenter = DataPresenter.from(rootView).withDataView(R.id.topic_list_swipe_refresh_layout) .withEmptyView(R.id.empty_content_layout, R.id.empty_reload_button) .withErrorView(R.id.error_layout, R.id.error_reload_button).withLoadingView(R.id.loading_indicator) .build(); dataPresenter.setOnRefreshRequestedListener(new DataPresenter.OnRefreshRequestedListener() { @Override public void onRefresh() { dataPresenter.showLoadingView(); loadTopics(); } }); UiUtils.setDrawableColor(emptyTopicsImage.getDrawable(), getResources().getColor(R.color.empty_view_image_color)); return rootView; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Restore the list of topics when the fragment is recreated by the framework if (savedInstanceState != null) { displayedTopics = savedInstanceState.getParcelableArrayList(ARG_TOPIC_LIST); if (displayedTopics != null) { Log.i(LOG_TAG, "Restored " + String.valueOf(displayedTopics.size()) + " topics to fragment"); topicsAdapter.replaceWith(displayedTopics); showTopics(); } lastLoadedPage = savedInstanceState.getInt(ARG_LAST_LOADED_PAGE, 0); } } @Override public void onResume() { super.onResume(); if (displayedTopics == null) { displayedTopics = new ArrayList<>(); loadTopics(); } else { showTopics(); } } @Override public void onToolbarInitialized(Toolbar toolbar) { initializeToolbarTitle(toolbar); // Deal with endless scrolling topicsRecyclerView.addOnScrollListener(new EndlessScrollListener(layoutManager) { @Override public void onLoadMore(int page, int totalItemsCount) { // When a topic filter is selected, all topics are displayed on one single page, // and trying to load more will not result in a 404 error, but it will instead load // the same topics over and over if (topicFilter == TopicFilter.NONE) { int realPage = page + 1; if (realPage > lastLoadedPage) { loadPage(realPage); } } } }); int progressBarStartMargin = getResources() .getDimensionPixelSize(R.dimen.swipe_refresh_progress_bar_start_margin); int progressBarEndMargin = getResources() .getDimensionPixelSize(R.dimen.swipe_refresh_progress_bar_end_margin); swipeRefreshLayout.setProgressViewOffset(false, progressBarStartMargin, progressBarEndMargin); } protected void initializeToolbarTitle(Toolbar toolbar) { View spinnerContainer = LayoutInflater.from(getActivity()).inflate(R.layout.actionbar_spinner, toolbar, false); ActionBar.LayoutParams lp = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); toolbar.addView(spinnerContainer, 0, lp); Spinner spinner = (Spinner) spinnerContainer.findViewById(R.id.actionbar_spinner); spinner.setAdapter(subcategoriesAdapter); // "Hack to avoid onItemSelected to be fired off on spinner instantiation spinner.setSelection(0, false); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> spinner, View view, int position, long itemId) { if (subcategoriesAdapter.getItemViewType(position) == SubcategoriesAdapter.TYPE_CATEGORY) { subcategory = null; // Reset subcategory filtering } else { subcategory = (Subcategory) subcategoriesAdapter.getItem(position); } loadTopics(); } @Override public void onNothingSelected(AdapterView<?> adapterView) { } }); } @Override public void onCreateOptionsMenu(Toolbar toolbar) { toolbar.inflateMenu(R.menu.menu_topics); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList(ARG_TOPIC_LIST, displayedTopics); outState.putInt(ARG_LAST_LOADED_PAGE, lastLoadedPage); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); boolean changedTopicFilter = false; switch (id) { case R.id.action_topics_filter_all: topicFilter = TopicFilter.NONE; changedTopicFilter = true; break; case R.id.action_topics_filter_favorites: topicFilter = TopicFilter.FAVORITE; changedTopicFilter = true; break; case R.id.action_topics_filter_participated: topicFilter = TopicFilter.PARTICIPATED; changedTopicFilter = true; break; case R.id.action_topics_filter_read: topicFilter = TopicFilter.READ; changedTopicFilter = true; break; case R.id.action_refresh_topic_list: dataPresenter.showLoadingView(); loadTopics(); break; } if (changedTopicFilter) { dataPresenter.showLoadingView(); loadTopics(); if (subcategoriesAdapter != null) { subcategoriesAdapter.setActiveTopicFilter(topicFilter); } } return super.onOptionsItemSelected(item); } /** * Loads topics for a given category, replacing current topics. Only loads a single topic page, * user has to swipe at the bottom of the list to load the next pages */ public void loadTopics() { Log.d(LOG_TAG, String.format( "Loading first page for category '%s' (subcategory: '%s') and replacing current topics (with filter='%s')", category.getName(), subcategory, topicFilter == null ? "null" : topicFilter.toString())); // Load categories for active user subscribe(dataService.loadTopics(userManager.getActiveUser(), category, subcategory, 1, topicFilter, new EndlessObserver<List<Topic>>() { @Override public void onNext(List<Topic> loadedTopics) { Log.d(LOG_TAG, String.format("Loading request completed, %d topics loaded", loadedTopics.size())); displayedTopics.clear(); displayedTopics.addAll(loadedTopics); topicsAdapter.replaceWith(loadedTopics); swipeRefreshLayout.setRefreshing(false); lastLoadedPage = 1; layoutManager.scrollToPosition(0); showTopics(); } @Override public void onError(Throwable throwable) { Log.e(LOG_TAG, String.format("Error loading first page for category '%s', subcategory '%s'", category.getName(), subcategory), throwable); swipeRefreshLayout.setRefreshing(false); if (displayedTopics.size() == 0) { dataPresenter.showErrorView(); } else { SnackbarHelper.make(TopicListFragment.this, R.string.error_loading_topics).show(); } } })); } /** * Loads a given topics page for the current category, subcategory and topic filter * @param page page to load (1..n) */ protected void loadPage(final int page) { Log.d(LOG_TAG, String.format("Loading page '%d' for category '%s', subcategory '%s'", page, category, subcategory)); if (category == null) { Log.e(LOG_TAG, "Category is null cannot load page"); return; } subscribe(dataService.loadTopics(userManager.getActiveUser(), category, subcategory, page, topicFilter, new EndlessObserver<List<Topic>>() { @Override public void onNext(List<Topic> loadedTopics) { Log.d(LOG_TAG, String.format("Loading request completed, %d topics loaded", loadedTopics.size())); displayedTopics.addAll(loadedTopics); topicsAdapter.extendWith(loadedTopics); swipeRefreshLayout.setRefreshing(false); lastLoadedPage = page; showTopics(); } @Override public void onError(Throwable throwable) { Log.e(LOG_TAG, String.format("Error loading page '%d' for category '%s', subcategory '%s'", page, category.getName(), subcategory), throwable); swipeRefreshLayout.setRefreshing(false); // Do not display error view because topics are displayed (we are "just" loading additional content) SnackbarHelper.make(TopicListFragment.this, Phrase .from(getActivity(), R.string.error_loading_topics_page).put("page", page).format()) .show(); } })); } public void addOnTopicClickedListener(OnTopicClickedListener onTopicClickedListener) { if (!onTopicClickedListeners.contains(onTopicClickedListener)) { onTopicClickedListeners.add(onTopicClickedListener); } } @Override public void onTopicClicked(Topic topic) { // Dispatch event to all subscribers for (OnTopicClickedListener listener : onTopicClickedListeners) { listener.onTopicClicked(topic); } } protected void showTopics() { if (displayedTopics.size() > 0) { dataPresenter.showDataView(); } else { dataPresenter.showEmptyView(); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } /** * Initialize context menu for long clicks on topics */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { menu.add(0, UIConstants.TOPIC_ACTION_GO_TO_LAST_READ_PAGE, 0, getResources().getString(R.string.action_go_to_last_read_page)); menu.add(0, UIConstants.TOPIC_ACTION_GO_TO_FIRST_PAGE, 1, getResources().getString(R.string.action_go_to_first_page)); menu.add(0, UIConstants.TOPIC_ACTION_GO_TO_SPECIFIC_PAGE, 2, getResources().getString(R.string.action_go_to_specific_page)); menu.add(0, UIConstants.TOPIC_ACTION_GO_TO_LAST_PAGE, 3, getResources().getString(R.string.action_go_to_last_page)); menu.add(0, UIConstants.TOPIC_ACTION_REPLY_TO_TOPIC, 4, getResources().getString(R.string.action_reply_to_topic)); menu.add(0, UIConstants.TOPIC_ACTION_COPY_LINK, 5, getResources().getString(R.string.action_copy_link)); menu.add(0, UIConstants.TOPIC_ACTION_SHARE, 6, getResources().getString(R.string.action_share)); } @Override public void onTopicLongClick(int position) { topicsRecyclerView.showContextMenuForPosition(position); } @Override public boolean onContextItemSelected(MenuItem item) { ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView.RecyclerContextMenuInfo) item .getMenuInfo(); bus.post(new TopicContextItemSelectedEvent(topicsAdapter.getItem(info.getPosition()), item.getItemId())); return true; } }