package com.dozuki.ifixit.ui.guide.view;
import android.accounts.Account;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SyncStatusObserver;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
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.dozuki.Site;
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.create.OfflineGuideListItem;
import com.dozuki.ifixit.util.Utils;
import com.dozuki.ifixit.util.api.Api;
import com.dozuki.ifixit.util.api.ApiContentProvider;
import com.dozuki.ifixit.util.api.ApiDatabase;
import com.dozuki.ifixit.util.api.ApiEvent;
import com.dozuki.ifixit.util.api.ApiSyncAdapter;
import com.dozuki.ifixit.util.api.GuideMediaProgress;
import com.squareup.otto.Subscribe;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class OfflineGuidesActivity extends BaseMenuDrawerActivity implements
LoaderManager.LoaderCallbacks<List<GuideMediaProgress>> {
private static class OfflineGuideLoader extends AsyncTaskLoader<List<GuideMediaProgress>> {
private Context mContext;
private Site mSite;
private User mUser;
private List<GuideMediaProgress> mGuides;
public OfflineGuideLoader(Context context, Site site, User user) {
super(context);
mContext = context;
mSite = site;
mUser = user;
}
@Override
public List<GuideMediaProgress> loadInBackground() {
mGuides = ApiDatabase.get(mContext).getOfflineGuides(mSite, mUser);
return mGuides;
}
@Override
public void onStartLoading() {
if (mGuides != null) {
deliverResult(mGuides);
}
if (takeContentChanged() || mGuides == null) {
forceLoad();
}
}
}
private class OfflineGuideListAdapter extends BaseAdapter {
protected boolean mDisplayLiveImages;
public OfflineGuideListAdapter(boolean displayLiveImages) {
mDisplayLiveImages = displayLiveImages;
}
@Override
public int getCount() {
return mGuides.size();
}
@Override
public Object getItem(int position) {
return mGuides.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
OfflineGuideListItem itemView;
GuideMediaProgress currItem = (GuideMediaProgress)getItem(position);
if (convertView != null) {
itemView = (OfflineGuideListItem)convertView;
} else {
itemView = new OfflineGuideListItem(OfflineGuidesActivity.this);
}
itemView.setRowData(currItem, mDisplayLiveImages, mIsSyncing);
return itemView;
}
}
private static final String TAG = "OfflineGuidesActivity";
private static final String REAUTHENTICATE = "REAUTHENTICATE";
protected SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
@Override
public void onStatusChanged(int which) {
runOnUiThread(new Runnable() {
@Override
public void run() {
refreshSyncStatus(/* force */ false);
}
});
}
};
protected Object mSyncObserverHandle;
protected OfflineGuideListAdapter mAdapter;
protected List<GuideMediaProgress> mGuides = Collections.emptyList();
protected ListView mListView;
protected RelativeLayout mSyncBox;
protected TextView mSyncCommand;
protected TextView mSyncStatusText;
protected Button mCancelButton;
protected ProgressBar mSyncProgressBar;
protected boolean mIsSyncing;
protected BroadcastReceiver mNewGuideReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
getSupportLoaderManager().getLoader(R.id.offline_guide_loaderid).onContentChanged();
}
};
protected BroadcastReceiver mGuideProgressReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
updateGuideProgress(
extras.getInt(ApiSyncAdapter.GUIDEID),
extras.getInt(ApiSyncAdapter.GUIDE_MEDIA_DOWNLOADED),
extras.getInt(ApiSyncAdapter.GUIDE_MEDIA_TOTAL)
);
updateTotalProgress(
extras.getInt(ApiSyncAdapter.MEDIA_DOWNLOADED),
extras.getInt(ApiSyncAdapter.MEDIA_TOTAL)
);
}
};
private final int SYNC_TIME_REFRESH_INTERVAL = 30000;
private Handler mSyncStatusUpdateHandler;
private Runnable mSyncStatusUpdateRunnable;
public static Intent view(Context context) {
return new Intent(context, OfflineGuidesActivity.class);
}
public static Intent reauthenticate(Context context) {
Intent intent = view(context);
intent.putExtra(REAUTHENTICATE, true);
return intent;
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
App.sendScreenView("user/offline_guides");
final App app = App.get();
final boolean hasInternet = app.isConnected();
setTitle(R.string.favorites);
setContentView(R.layout.offline_guides);
mAdapter = new OfflineGuideListAdapter(hasInternet);
mListView = (ListView)findViewById(R.id.offline_guides_listview);
mSyncBox = (RelativeLayout)findViewById(R.id.sync_box);
mSyncCommand = (TextView)findViewById(R.id.sync_status);
mSyncStatusText = (TextView)findViewById(R.id.last_sync_time);
mCancelButton = (Button)findViewById(R.id.sync_cancel_button);
mSyncProgressBar = (ProgressBar)findViewById(R.id.sync_progress_bar);
mListView.setAdapter(mAdapter);
mListView.setEmptyView(findViewById(R.id.no_offline_guides_text));
mSyncBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Don't request a sync if there isn't any internet.
if (!mIsSyncing && App.get().isConnected()) {
App.sendEvent("ui_action", "button_press", "offline_guides_sync_now", null);
app.requestSync(false);
} else {
// Cancel is handled by the cancel button.
}
}
});
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
App.sendEvent("ui_action", "button_press", "offline_guides_cancel_sync", null);
app.cancelSync();
}
});
// Only check if this is the first onCreate. Otherwise the user will be logged out on
// every orientation change.
if (savedState == null && getIntent().getBooleanExtra(REAUTHENTICATE, false)) {
App.sendEvent("ui_action", "button_press", "offline_guides_reauthenticate", null);
// The sync service indicates that the user is logged out so lets make sure
// that we think that the user is so login can happen as normal.
app.shallowLogout(false);
}
if (!openLoginDialogIfLoggedOut()) {
// Initialize the loader if the user is logged in. Otherwise this will
// happen when the user logs in.
initLoader();
// Initiate a sync the first time this Activity is opened if the user is logged
// in. Otherwise it will happen automatically upon login.
if (savedState == null && hasInternet && app.getSyncAutomatically()) {
app.requestSync(false);
}
}
refreshSyncStatus(/* force */ true);
startSyncStatusUpdate();
}
@Override
public void onDestroy() {
super.onDestroy();
stopSyncStatusUpdate();
}
@Override
public void onRestart() {
super.onRestart();
Loader loader = getSupportLoaderManager().getLoader(R.id.offline_guide_loaderid);
if (loader != null) {
loader.onContentChanged();
}
}
/**
* Sets up the repeating event to update the last sync time.
*/
private void startSyncStatusUpdate() {
mSyncStatusUpdateHandler = new Handler();
mSyncStatusUpdateRunnable = new Runnable() {
@Override
public void run() {
updateSyncStatus();
mSyncStatusUpdateHandler.postDelayed(mSyncStatusUpdateRunnable,
SYNC_TIME_REFRESH_INTERVAL);
}
};
mSyncStatusUpdateRunnable.run();
}
private void stopSyncStatusUpdate() {
mSyncStatusUpdateHandler.removeCallbacks(mSyncStatusUpdateRunnable);
}
private void updateSyncStatus() {
long lastSyncStatus = App.get().getLastSyncTime();
boolean isConnected = App.get().isConnected();
if (mIsSyncing || lastSyncStatus == App.NEVER_SYNCED_VALUE) {
mSyncStatusText.setVisibility(View.INVISIBLE);
} else {
mSyncStatusText.setVisibility(View.VISIBLE);
mSyncStatusText.setText(getString(R.string.last_synced,
Utils.getRelativeTime(this, lastSyncStatus)));
}
mSyncCommand.setText(isConnected ?
(mIsSyncing ? R.string.sync_status_syncing : R.string.sync_now) :
R.string.no_connection);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(mSyncCommand.getLayoutParams());
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
if (mIsSyncing || !isConnected) {
lp.addRule(RelativeLayout.CENTER_VERTICAL);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
lp.removeRule(RelativeLayout.CENTER_VERTICAL);
} else {
lp.addRule(RelativeLayout.CENTER_VERTICAL, 0);
}
}
mSyncCommand.setLayoutParams(lp);
int backgroundColor = mIsSyncing || !isConnected ? R.color.disabled_grey_bg : R.color.emphasis;
mSyncBox.setBackgroundColor(getResources().getColor(backgroundColor));
}
@Override
public void onLogin(LoginEvent.Login loginEvent) {
super.onLogin(loginEvent);
initLoader();
App.get().requestSync(false);
}
private void initLoader() {
showLoading(R.id.loading_container);
getSupportLoaderManager().initLoader(R.id.offline_guide_loaderid, null, this);
}
@Override
public void onResume() {
super.onResume();
registerReceiver(mNewGuideReceiver, new IntentFilter(
ApiSyncAdapter.NEW_OFFLINE_GUIDE_ACTION));
registerReceiver(mGuideProgressReceiver, new IntentFilter(
ApiSyncAdapter.GUIDE_PROGRESS_ACTION));
mSyncObserverHandle = ContentResolver.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE | ContentResolver.SYNC_OBSERVER_TYPE_PENDING,
mSyncStatusObserver);
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(mNewGuideReceiver);
unregisterReceiver(mGuideProgressReceiver);
if (mSyncObserverHandle != null) {
ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.offline_guides, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem syncAutomatically = menu.findItem(R.id.sync_automatically);
syncAutomatically.setChecked(App.get().getSyncAutomatically());
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.sync_automatically:
// The checked state is updated after this is called so we must negate it.
boolean enableSync = !item.isChecked();
App.sendEvent("ui_action", "button_press",
"offline_guides_auto_sync_" + (enableSync ? "on" : "off"), null);
App.get().setSyncAutomatically(enableSync);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
protected void updateTotalProgress(int progress, int total) {
// Don't update progress if we aren't syncing. This caused the progress bar to
// reappear after cancelling the sync even though the status has changed already.
if (!mIsSyncing) {
return;
}
mSyncProgressBar.setVisibility(View.VISIBLE);
mSyncProgressBar.setIndeterminate(false);
mSyncProgressBar.setMax(total);
mSyncProgressBar.setProgress(progress);
}
protected void updateGuideProgress(int guideid, int progress, int total) {
for (GuideMediaProgress guide : mGuides) {
if (guide.mGuideInfo.mGuideid == guideid) {
guide.mTotalMedia = total;
guide.mMediaProgress = progress;
mAdapter.notifyDataSetChanged();
return;
}
}
}
protected void refreshSyncStatus(boolean forceUpdate) {
final String authority = ApiContentProvider.getAuthority();
final Account account = App.get().getUserAccount();
boolean isSyncing = false;
if (account != null) {
isSyncing = ContentResolver.isSyncActive(account, authority) ||
ContentResolver.isSyncPending(account, authority);
}
// This gets called a lot so we only update the UI if there is a change.
if (forceUpdate || isSyncing != mIsSyncing) {
mIsSyncing = isSyncing;
if (mIsSyncing) {
// Display as indeterminate. This will be overridden in updateTotalProgress.
mSyncProgressBar.setIndeterminate(true);
mSyncProgressBar.setVisibility(View.VISIBLE);
} else {
// Hide it if we're not syncing.
mSyncProgressBar.setVisibility(View.INVISIBLE);
}
updateSyncStatus();
mCancelButton.setVisibility(mIsSyncing ? View.VISIBLE : View.GONE);
}
}
@Subscribe
public void onGuideFavorited(ApiEvent.FavoriteGuide event) {
if (!event.hasError()) {
App.sendEvent("ui_action", "button_press", "offline_guides_unfavorite", null);
// Note: We assume that this is an unfavorite because that's the only
// thing you can do in this view currently.
int guideid = Integer.parseInt(event.mApiCall.getQuery());
for (Iterator<GuideMediaProgress> itr = mGuides.iterator(); itr.hasNext();) {
GuideMediaProgress guide = itr.next();
if (guide.mGuideInfo.mGuideid == guideid) {
// Remove the guide from the list and request a sync to actually remove it.
itr.remove();
App.get().requestSync(/* force */ true);
mAdapter.notifyDataSetChanged();
return;
}
}
} else {
Api.getErrorDialog(this, event).show();
}
}
@Override
public boolean finishActivityIfLoggedOut() {
return true;
}
@Override
public Loader<List<GuideMediaProgress>> onCreateLoader(int i, Bundle bundle) {
App app = App.get();
return new OfflineGuideLoader(this, app.getSite(), app.getUser());
}
@Override
public void onLoadFinished(Loader<List<GuideMediaProgress>> loader,
List<GuideMediaProgress> guides) {
hideLoading();
mGuides = guides;
mAdapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader<List<GuideMediaProgress>> listLoader) {
}
}