package com.dozuki.ifixit.ui.guide.view;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.dozuki.ifixit.App;
import com.dozuki.ifixit.R;
import com.dozuki.ifixit.model.Comment;
import com.dozuki.ifixit.model.guide.Guide;
import com.dozuki.ifixit.model.user.LoginEvent;
import com.dozuki.ifixit.model.user.User;
import com.dozuki.ifixit.ui.BaseMenuDrawerActivity;
import com.dozuki.ifixit.ui.guide.CommentsActivity;
import com.dozuki.ifixit.ui.guide.create.GuideIntroActivity;
import com.dozuki.ifixit.ui.guide.create.StepEditActivity;
import com.dozuki.ifixit.ui.guide.create.StepsActivity;
import com.dozuki.ifixit.util.CheatSheet;
import com.dozuki.ifixit.util.api.Api;
import com.dozuki.ifixit.util.api.ApiCall;
import com.dozuki.ifixit.util.api.ApiDatabase;
import com.dozuki.ifixit.util.api.ApiEvent;
import com.squareup.otto.Subscribe;
import com.viewpagerindicator.TitlePageIndicator;
import java.util.ArrayList;
public class GuideViewActivity extends BaseMenuDrawerActivity implements
ViewPager.OnPageChangeListener {
private static final int DEFAULT_INBOUND_STEPID = -1;
private static final String TAG = "GuideViewActivity";
private static final String FAVORITING = "FAVORITING";
private static final String IS_OFFLINE_GUIDE = "IS_OFFLINE_GUIDE";
public static final String CURRENT_PAGE = "CURRENT_PAGE";
public static final String SAVED_GUIDE = "SAVED_GUIDE";
public static final String GUIDEID = "GUIDEID";
public static final String TOPIC_NAME_KEY = "TOPIC_NAME_KEY";
public static final String FROM_EDIT = "FROM_EDIT_KEY";
public static final String INBOUND_STEP_ID = "INBOUND_STEP_ID";
public static final String COMMENTS_TAG = "COMMENTS_TAG";
private static final int COMMENT_REQUEST = 0;
private int mGuideid;
private Guide mGuide;
private int mCurrentPage = -1;
private int mStepOffset = 1;
private ViewPager mPager;
private TitlePageIndicator mIndicator;
private int mInboundStepId = DEFAULT_INBOUND_STEPID;
private GuideViewAdapter mAdapter;
private boolean mFavoriting = false;
private boolean mIsOfflineGuide;
private Toast mToast;
/////////////////////////////////////////////////////
// LIFECYCLE
/////////////////////////////////////////////////////
public static Intent viewGuideid(Context context, int guideid) {
Intent intent = new Intent(context, GuideViewActivity.class);
intent.putExtra(GUIDEID, guideid);
return intent;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.guide_main);
mPager = (ViewPager) findViewById(R.id.guide_pager);
mIndicator = (TitlePageIndicator) findViewById(R.id.guide_step_title_indicator);
if (savedInstanceState != null) {
mGuideid = savedInstanceState.getInt(GUIDEID);
mFavoriting = savedInstanceState.getBoolean(FAVORITING);
mIsOfflineGuide = savedInstanceState.getBoolean(IS_OFFLINE_GUIDE);
if (savedInstanceState.containsKey(SAVED_GUIDE)) {
mGuide = (Guide) savedInstanceState.getSerializable(SAVED_GUIDE);
}
if (mGuide != null) {
mCurrentPage = savedInstanceState.getInt(CURRENT_PAGE);
setGuide(mGuide, mCurrentPage);
mIndicator.setCurrentItem(mCurrentPage);
mPager.setCurrentItem(mCurrentPage);
}
} else {
extractExtras(getIntent().getExtras());
}
if (mGuide != null) {
setGuide(mGuide, mCurrentPage);
} else {
fetchGuideFromApi(mGuideid);
}
}
private void extractExtras(Bundle extras) {
if (extras != null) {
if (extras.containsKey(GUIDEID)) {
mGuideid = extras.getInt(GUIDEID);
}
if (extras.containsKey(GuideViewActivity.SAVED_GUIDE)) {
mGuide = (Guide) extras.getSerializable(GuideViewActivity.SAVED_GUIDE);
}
mInboundStepId = extras.getInt(INBOUND_STEP_ID, DEFAULT_INBOUND_STEPID);
mCurrentPage = extras.getInt(GuideViewActivity.CURRENT_PAGE, 0);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// Reset everything to default values since we're getting a new intent - forces the view to refresh.
mGuide = null;
mCurrentPage = -1;
mInboundStepId = -1;
extractExtras(intent.getExtras());
fetchGuideFromApi(mGuideid);
}
@Override
public void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putInt(GUIDEID, mGuideid);
state.putSerializable(SAVED_GUIDE, mGuide);
state.putInt(CURRENT_PAGE, mCurrentPage);
state.putBoolean(FAVORITING, mFavoriting);
state.putBoolean(IS_OFFLINE_GUIDE, mIsOfflineGuide);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.guide_view_menu, menu);
MenuItem item = menu.findItem(R.id.comments);
item.getActionView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mGuide != null) {
ArrayList<Comment> comments;
int stepIndex = getStepIndex(), contextid;
String title, context;
// If we're in one of the introduction pages, show guide comments.
if (GuideViewActivity.this.notOnStep(stepIndex)) {
comments = mGuide.getComments();
title = getString(R.string.guide_comments);
context = "guide";
contextid = mGuide.getGuideid();
} else {
comments = mGuide.getStep(stepIndex).getComments();
contextid = mGuide.getStep(stepIndex).getStepid();
context = "step";
title = getString(R.string.step_number_comments, stepIndex + 1);
}
startActivityForResult(CommentsActivity.viewGuideComments(getApplicationContext(), comments, title,
context, contextid, mGuide.getGuideid()), COMMENT_REQUEST);
}
}
});
CheatSheet.setup(item.getActionView(), R.string.view_comments);
return super.onCreateOptionsMenu(menu);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == COMMENT_REQUEST) {
Bundle extras = data.getExtras();
if (resultCode == RESULT_OK && extras != null) {
ArrayList<Comment> comments = (ArrayList<Comment>)extras.getSerializable(COMMENTS_TAG);
int stepIndex = getStepIndex();
if (notOnStep(stepIndex)) {
mGuide.setComments(comments);
} else {
mGuide.getStep(stepIndex).setComments(comments);
}
updateCommentCounts();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem comments = menu.findItem(R.id.comments);
MenuItem favoriteGuide = menu.findItem(R.id.favorite_guide);
MenuItem reloadGuide = menu.findItem(R.id.reload_guide);
MenuItem editGuide = menu.findItem(R.id.edit_guide);
TextView countView = ((TextView)comments.getActionView().findViewById(R.id.comment_count));
if (mGuide != null) {
int stepIndex = getStepIndex();
int commentCount = 0;
if (notOnStep(stepIndex)) {
commentCount = mGuide.getCommentCount();
} else if (mGuide.getNumSteps() < stepIndex) {
commentCount = mGuide.getStep(stepIndex).getCommentCount();
}
if (countView != null) {
countView.setText(commentCount + "");
}
}
boolean favorited = mGuide != null && mGuide.isFavorited();
favoriteGuide.setIcon(favorited ? R.drawable.ic_action_favorite_filled :
R.drawable.ic_action_favorite_empty);
favoriteGuide.setEnabled(!mFavoriting && mGuide != null);
favoriteGuide.setTitle(favorited ? R.string.unfavorite_guide : R.string.favorite_guide);
reloadGuide.setEnabled(mGuide != null);
editGuide.setEnabled(mGuide != null);
if (mIsOfflineGuide) {
reloadGuide.setVisible(false);
editGuide.setVisible(false);
favoriteGuide.setVisible(false);
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.edit_guide:
if (mGuide != null) {
App.sendEvent("menu_action", "button_press", "edit_guide", (long)mGuide.getGuideid());
// If the user is on the introduction, take them to edit the introduction fields.
if (mCurrentPage < mAdapter.getStepOffset() ||
(mCurrentPage - mAdapter.getStepOffset()) >= mGuide.getNumSteps()) {
Intent intent = new Intent(this, GuideIntroActivity.class);
intent.putExtra(StepsActivity.GUIDE_KEY, mGuide);
intent.putExtra(GuideIntroActivity.STATE_KEY, true);
startActivity(intent);
} else {
Intent intent = new Intent(this, StepEditActivity.class);
int stepNum = 0;
// Take into account the introduction, parts and tools page.
if (mCurrentPage >= mAdapter.getStepOffset()) {
stepNum = mCurrentPage - mAdapter.getStepOffset();
// Account for array indexed starting at 1
intent.putExtra(StepEditActivity.GUIDE_STEP_NUM_KEY, stepNum + 1);
}
int stepGuideid = mGuide.getStep(stepNum).getGuideid();
// If the step is part of a prerequisite guide, store the parents
// guideid so that we can get back from editing this prerequisite.
if (stepGuideid != mGuide.getGuideid()) {
intent.putExtra(StepEditActivity.PARENT_GUIDE_ID_KEY, mGuide.getGuideid());
}
// We have to pass along the steps guideid to account for prerequisite guides.
intent.putExtra(StepEditActivity.GUIDE_ID_KEY, stepGuideid);
intent.putExtra(StepEditActivity.GUIDE_PUBLIC_KEY, mGuide.isPublic());
intent.putExtra(StepEditActivity.GUIDE_STEP_ID, mGuide.getStep(stepNum).getStepid());
startActivity(intent);
}
}
return true;
case R.id.reload_guide:
// Set guide to null to force a refresh of the guide object.
mGuide = null;
supportInvalidateOptionsMenu();
fetchGuideFromApi(mGuideid);
return true;
case R.id.comments:
ArrayList<Comment> comments;
int stepIndex = getStepIndex(), contextid;
String title, context;
// If we're in one of the introduction pages, show guide comments.
if (notOnStep(stepIndex)) {
comments = mGuide.getComments();
title = getString(R.string.guide_comments);
context = "guide";
contextid = mGuide.getGuideid();
} else {
comments = mGuide.getStep(stepIndex).getComments();
title = getString(R.string.step_number_comments, stepIndex + 1);
context = "step";
contextid = mGuide.getStep(stepIndex).getStepid();
}
startActivity(CommentsActivity.viewComments(this, comments, title, context,
contextid));
case R.id.favorite_guide:
// Current favorite state.
boolean favorited = mGuide == null ? false : mGuide.isFavorited();
mFavoriting = true;
Api.call(this, ApiCall.favoriteGuide(mGuideid, !favorited));
supportInvalidateOptionsMenu();
if (App.get().isUserLoggedIn()) {
// Only Toast if the user is logged in. Otherwise it happens
// in the login success event handler.
toast(favorited ? R.string.unfavoriting :
R.string.favoriting, Toast.LENGTH_LONG);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private int getStepIndex() {
return (mCurrentPage - mAdapter.getStepOffset());
}
/////////////////////////////////////////////////////
// NOTIFICATION LISTENERS
/////////////////////////////////////////////////////
@Subscribe
public void onGuide(ApiEvent.ViewGuide event) {
if (App.get().isUserLoggedIn() &&
(event.mStoredResponse || event.hasError())) {
// Attempt to use an offline guide if it isn't a live response.
fetchOfflineGuide(mGuideid, event);
} else {
displayApiEvent(event);
}
}
private void displayApiEvent(ApiEvent.ViewGuide event) {
if (!event.hasError()) {
if (mGuide == null) {
Guide guide = event.getResult();
mCurrentPage = calculateInitialPage(guide);
setGuide(guide, mCurrentPage);
}
} else {
Api.getErrorDialog(this, event).show();
}
}
private int calculateInitialPage(Guide guide) {
if (mInboundStepId != DEFAULT_INBOUND_STEPID) {
for (int i = 0; i < guide.getSteps().size(); i++) {
if (mInboundStepId == guide.getStep(i).getStepid()) {
int stepOffset = 1;
if (guide.getNumTools() != 0) stepOffset++;
if (guide.getNumParts() != 0) stepOffset++;
// Account for the introduction, parts and tools pages
return i + stepOffset;
}
}
}
// Default to the first page.
return 0;
}
@Subscribe
public void onFavorite(ApiEvent.FavoriteGuide event) {
mFavoriting = false;
if (!event.hasError()) {
boolean favorited = event.getResult();
App.sendEvent("ui_action", "button_press",
"guide_view_" + (favorited ? "" : "un") + "favorited", null);
if (mGuide != null) {
mGuide.setFavorited(favorited);
}
toast(favorited ? R.string.favorited : R.string.unfavorited,
Toast.LENGTH_SHORT);
// Force a sync to make it show up in the offline guides list immediately.
App.get().requestSync(/* force */ true);
} else {
Api.getErrorDialog(this, event).show();
}
supportInvalidateOptionsMenu();
}
public void onLogin(LoginEvent.Login event) {
super.onLogin(event);
if (mFavoriting) {
toast(mGuide.isFavorited() ? R.string.unfavoriting :
R.string.favoriting, Toast.LENGTH_LONG);
}
}
public void onCancelLogin(LoginEvent.Cancel event) {
// Always reset this because there is no way that the user can be
// favoriting a guide right now.
mFavoriting = false;
supportInvalidateOptionsMenu();
}
/////////////////////////////////////////////////////
// HELPERS
/////////////////////////////////////////////////////
private void setGuide(Guide guide, int currentPage) {
hideLoading();
if (guide == null) {
Log.wtf("GuideViewActivity", "Guide is not set. This should be impossible");
return;
}
mGuide = guide;
App.sendScreenView("/guide/view/" + mGuide.getGuideid());
String guideTitle = mGuide.getTitle();
setTitle(guideTitle);
mAdapter = new GuideViewAdapter(getSupportFragmentManager(), mGuide,
mIsOfflineGuide);
mPager.setAdapter(mAdapter);
mPager.setVisibility(View.VISIBLE);
mPager.setCurrentItem(currentPage);
mIndicator.setViewPager(mPager);
// listen for page changes so we can track the current index
mIndicator.setOnPageChangeListener(this);
mIndicator.setCurrentItem(currentPage);
// Enable menu items and update comment count.
supportInvalidateOptionsMenu();
}
private void fetchGuideFromApi(int guideid) {
showLoading(R.id.loading_container);
Api.call(this, ApiCall.guide(guideid));
}
/**
* Retrieves the guide from the database and displays the result. The user must
* be logged in because offline guides are stored per user. The provided ApiEvent
* is used for display if the guide is not found. This will result in either a
* cached version of the API response or a guide not found dialog.
*/
private void fetchOfflineGuide(final int guideid, final ApiEvent.ViewGuide event) {
final App app = App.get();
final User user = app.getUser();
// Can't get offline guide if the user isn't logged in.
if (user == null) {
throw new IllegalStateException("Can't fetch offline guide for logged out user.");
}
new AsyncTask<String, Void, Guide>() {
@Override
protected Guide doInBackground(String... params) {
return ApiDatabase.get(app).getOfflineGuide(app.getSite(), user, guideid);
}
@Override
protected void onPostExecute(Guide guide) {
if (guide != null) {
App.sendEvent("ui_action", "button_press", "offline_guide_view", null);
mIsOfflineGuide = true;
mCurrentPage = calculateInitialPage(guide);
setGuide(guide, mCurrentPage);
} else {
App.sendEvent("ui_action", "button_press", "offline_guide_not_found", null);
displayApiEvent(event);
}
}
}.execute();
}
/**
* Displays a toast with the given values and clears any existing Toasts
* if they exist.
*/
private void toast(int string, int duration) {
if (mToast == null) {
mToast = Toast.makeText(this, string, duration);
}
mToast.setText(string);
mToast.setDuration(duration);
mToast.show();
}
public void onPageScrollStateChanged(int arg0) { }
public void onPageScrolled(int arg0, float arg1, int arg2) { }
public void onPageSelected(int currentPage) {
mCurrentPage = currentPage;
updateCommentCounts();
App.sendScreenView(mAdapter.getFragmentScreenLabel(currentPage));
}
@Override
public void showLoading(int id) {
View container = findViewById(id);
if (container != null) {
container.setVisibility(View.VISIBLE);
}
super.showLoading(id);
}
@Override
public void hideLoading() {
super.hideLoading();
View container = findViewById(R.id.loading_container);
if (container != null) {
container.setVisibility(View.GONE);
}
}
/**
* Invalidates the menu to update the comment count.
*/
private void updateCommentCounts() {
supportInvalidateOptionsMenu();
}
private boolean notOnStep(int stepIndex) {
return stepIndex < 0 || stepIndex >= mGuide.getNumSteps();
}
}