/*
* Copyright (C) 2014 barter.li
*
* 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 li.barter.activities;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ClearCacheRequest;
import java.util.concurrent.atomic.AtomicInteger;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import li.barter.BarterLiApplication;
import li.barter.R;
import li.barter.analytics.GoogleAnalyticsManager;
import li.barter.chat.ChatService;
import li.barter.data.DBInterface;
import li.barter.data.DBInterface.AsyncDbQueryCallback;
import li.barter.data.TableChatMessages;
import li.barter.data.TableChats;
import li.barter.fragments.AbstractBarterLiFragment;
import li.barter.fragments.FragmentTransition;
import li.barter.http.IBlRequestContract;
import li.barter.http.IVolleyHelper;
import li.barter.http.ResponseInfo;
import li.barter.http.VolleyCallbacks;
import li.barter.http.VolleyCallbacks.IHttpCallbacks;
import li.barter.utils.AppConstants;
import li.barter.utils.AppConstants.DeviceInfo;
import li.barter.utils.AppConstants.Keys;
import li.barter.utils.AppConstants.QueryTokens;
import li.barter.utils.AppConstants.UserInfo;
import li.barter.utils.Logger;
import li.barter.utils.SharedPreferenceHelper;
import li.barter.utils.Utils;
import li.barter.widgets.TypefaceCache;
import li.barter.widgets.TypefacedSpan;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan;
import uk.co.chrisjenx.calligraphy.CalligraphyUtils;
/**
* @author Vinay S Shenoy Base class for inheriting all other Activities from
*/
public abstract class AbstractBarterLiActivity extends ActionBarActivity
implements IHttpCallbacks, AsyncDbQueryCallback,
OnClickListener {
private static final String TAG = "BaseBarterLiActivity";
private static final int ACTION_BAR_DISPLAY_MASK = ActionBar.DISPLAY_HOME_AS_UP
| ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_USE_LOGO | ActionBar
.DISPLAY_SHOW_HOME;
/**
* {@link VolleyCallbacks} for encapsulating Volley request responses
*/
protected VolleyCallbacks mVolleyCallbacks;
private AtomicInteger mRequestCounter;
private ActivityTransition mActivityTransition;
/**
* Whether a screen hit should be reported to analytics
*/
private boolean mShouldReportScreenHit;
/**
* Whether the current layout is a multipane layout or not
*/
private boolean mMultipaneLayout;
/**
* Creates a Crouton View based on the style
*
* @param context {@link Context} reference to get the {@link LayoutInflater} reference
* @param message The message to display
* @param style The style of Crouton
* @return A View to display as a Crouton
*/
private static View getCroutonViewForStyle(final Context context,
final String message, final AlertStyle style) {
int layoutResId = R.layout.crouton_info; //Default layout
switch (style) {
case ALERT: {
layoutResId = R.layout.crouton_alert;
break;
}
case ERROR: {
layoutResId = R.layout.crouton_error;
break;
}
case INFO: {
layoutResId = R.layout.crouton_info;
}
}
final View croutonText = LayoutInflater.from(context)
.inflate(layoutResId, null);
((TextView) croutonText.findViewById(R.id.text_message))
.setText(message);
return croutonText;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
mMultipaneLayout = getResources().getBoolean(R.bool.multipane);
/* Here, getClass() might show an Ambiguous method call bug. It's a bug in IntelliJ IDEA 13
* http://youtrack.jetbrains.com/issue/IDEA-72835 */
mActivityTransition = getClass()
.getAnnotation(ActivityTransition.class);
long lastScreenTime = 0l;
if (savedInstanceState == null) {
if (mActivityTransition != null) {
overridePendingTransition(mActivityTransition.createEnterAnimation(),
mActivityTransition
.createExitAnimation()
);
}
} else {
lastScreenTime = savedInstanceState.getLong(Keys.LAST_SCREEN_TIME);
}
if (Utils.shouldReportScreenHit(lastScreenTime)) {
mShouldReportScreenHit = true;
} else {
mShouldReportScreenHit = false;
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayOptions(ACTION_BAR_DISPLAY_MASK);
setActionBarTitle(getTitle().toString());
}
final RequestQueue requestQueue = ((IVolleyHelper) getApplication())
.getRequestQueue();
mVolleyCallbacks = new VolleyCallbacks(requestQueue, this);
mRequestCounter = new AtomicInteger(0);
setProgressBarIndeterminateVisibility(false);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(new CalligraphyContextWrapper(newBase));
}
/**
* Whether the current layout is a multipane layout or not
*/
public boolean isMultipane() {
return mMultipaneLayout;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(Keys.LAST_SCREEN_TIME, Utils.getCurrentEpochTime());
}
@Override
protected void onResume() {
super.onResume();
checkAndReportScreenHit();
}
/**
* Reports a screen hit
*/
public void checkAndReportScreenHit() {
if (mShouldReportScreenHit) {
final String analyticsScreenName = getAnalyticsScreenName();
if (!TextUtils.isEmpty(analyticsScreenName)) {
GoogleAnalyticsManager.getInstance()
.sendScreenHit(getAnalyticsScreenName());
}
}
}
/**
* Gets the screen name for reporting to google analytics. Send empty string, or
* <code>null</code> if you don't want the Activity tracked
*/
protected abstract String getAnalyticsScreenName();
/**
* Disconnects the chat service, clears any local data
*/
public void logout() {
if (isLoggedIn()) {
//TODO: Vinay - Add Dialog about chat messages being lost
final RequestQueue requestQueue = ((BarterLiApplication) getApplication())
.getRequestQueue();
requestQueue.add(new ClearCacheRequest(requestQueue.getCache(), new Runnable() {
@Override
public void run() {
UserInfo.INSTANCE.reset();
DBInterface.deleteAsync(QueryTokens.DELETE_CHATS, getTaskTag(), null,
TableChats.NAME, null, null, true,
AbstractBarterLiActivity.this);
DBInterface.deleteAsync(QueryTokens.DELETE_CHAT_MESSAGES, getTaskTag(), null,
TableChatMessages.NAME, null, null, true,
AbstractBarterLiActivity.this);
SharedPreferenceHelper
.removeKeys(AbstractBarterLiActivity.this, R.string.pref_auth_token,
R.string.pref_email, R.string.pref_description,
R.string.pref_location, R.string.pref_first_name,
R.string.pref_last_name, R.string.pref_user_id,
R.string.pref_profile_image, R.string.pref_share_token,
R.string.pref_referrer, R.string.pref_referrer_count);
final Intent disconnectChatIntent = new Intent(AbstractBarterLiActivity.this,
ChatService.class);
disconnectChatIntent.setAction(AppConstants.ACTION_DISCONNECT_CHAT);
startService(disconnectChatIntent);
LocalBroadcastManager.getInstance(BarterLiApplication.getStaticContext())
.sendBroadcast(
new Intent(AppConstants.ACTION_USER_INFO_UPDATED));
final Intent homeIntent = new Intent(AbstractBarterLiActivity.this,
HomeActivity.class);
homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(homeIntent);
}
}));
}
}
@Override
public void onInsertComplete(final int token, final Object cookie,
final long insertRowId) {
}
@Override
public void onDeleteComplete(final int token, final Object cookie,
final int deleteCount) {
switch (token) {
case QueryTokens.DELETE_CHAT_MESSAGES: {
Logger.v(TAG, "Deleted %d messages", deleteCount);
break;
}
case QueryTokens.DELETE_CHATS: {
Logger.v(TAG, "Deleted %d chats", deleteCount);
break;
}
case QueryTokens.DELETE_MY_BOOKS: {
Logger.v(TAG, "Deleted %d books", deleteCount);
break;
}
default:
break;
}
}
@Override
public void onQueryComplete(final int token, final Object cookie,
final Cursor cursor) {
}
@Override
public void onUpdateComplete(final int token, final Object cookie,
final int updateCount) {
}
/**
* Add a request on the network queue
*
* @param request The {@link Request} to add
* @param showErrorOnNoNetwork Whether an error toast should be displayed on no internet
* connection
* @param errorMsgResId String resource Id for error message to show if no internet
* connection, 0 for a default error message
*/
protected void addRequestToQueue(final Request<?> request,
final boolean showErrorOnNoNetwork,
final int errorMsgResId, boolean addHeader) {
if (isConnectedToInternet()) {
request.setTag(getTaskTag());
mVolleyCallbacks.queue(request, addHeader);
} else if (showErrorOnNoNetwork) {
showCrouton(errorMsgResId != 0 ? errorMsgResId
: R.string.no_network_connection, AlertStyle.ERROR);
}
}
/**
* A Tag to add to all async requests. This must be unique for all Activity types
*
* @return An Object that's the tag for this fragment
*/
protected abstract Object getTaskTag();
@Override
protected void onStop() {
super.onStop();
// Cancel all pending requests because they shouldn't be delivered
mVolleyCallbacks.cancelAll(getTaskTag());
DBInterface.cancelAll(getTaskTag());
setProgressBarIndeterminateVisibility(false);
}
public void setActionBarDisplayOptions(final int displayOptions) {
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayOptions(displayOptions, ACTION_BAR_DISPLAY_MASK);
}
}
/**
* Is the device connected to a network or not.
*
* @return <code>true</code> if connected, <code>false</code> otherwise
*/
public boolean isConnectedToInternet() {
return DeviceInfo.INSTANCE.isNetworkConnected();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
//Fetch the current primary fragment. If that will handle the Menu click,
// pass it to that one
final AbstractBarterLiFragment currentMainFragment = (AbstractBarterLiFragment)
getSupportFragmentManager()
.findFragmentById(R.id.frame_content);
boolean handled = false;
if (currentMainFragment != null) {
handled = currentMainFragment.onOptionsItemSelected(item);
}
if (!handled) {
// To provide Up navigation
if (item.getItemId() == android.R.id.home) {
doUpNavigation();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
return handled;
}
/**
* Moves up in the hierarchy using the Support meta data specified in manifest
*/
private void doUpNavigation() {
final Intent upIntent = NavUtils.getParentActivityIntent(this);
if (upIntent == null) {
NavUtils.navigateUpFromSameTask(this);
} else {
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a
// new
// task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
}
}
/**
* Sets the Action bar title, using the desired {@link Typeface} loaded from {@link
* TypefaceCache}
*
* @param title The title to set for the Action Bar
*/
public final void setActionBarTitle(final String title) {
final SpannableString s = new SpannableString(title);
s.setSpan(new TypefacedSpan(this, TypefaceCache.SLAB_REGULAR), 0, s.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// Update the action bar title with the TypefaceSpan instance
final ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(s);
}
/**
* Sets the Action bar title, using the desired {@link Typeface} loaded from {@link
* TypefaceCache}
*
* @param titleResId The title string resource Id to set for the Action Bar
*/
public final void setActionBarTitle(final int titleResId) {
setActionBarTitle(getString(titleResId));
}
/**
* Display an alert, with a string message
*
* @param message The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showCrouton(final String message, final AlertStyle style) {
//Crouton.make(activity, customView, viewGroupResId, configuration)
//Crouton.make(this, getCroutonViewForStyle(this, message, style)).show();
Crouton.make(this, getCroutonViewForStyle(this, message, style))
.setConfiguration(
new de.keyboardsurfer.android.widget.crouton.Configuration.Builder()
.setDuration(
de.keyboardsurfer.android.widget.crouton.Configuration
.DURATION_SHORT
)
.build()
).show();
}
/**
* Display an alert, with a string message
*
* @param messageResId The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showCrouton(final int messageResId, final AlertStyle style) {
showCrouton(getString(messageResId), style);
}
/**
* Display an alert, with a string message with infinite time
*
* @param message The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showInfiniteCrouton(final String message, final AlertStyle style) {
Crouton.make(this, getCroutonViewForStyle(this, message, style))
.setConfiguration(
new de.keyboardsurfer.android.widget.crouton.Configuration.Builder()
.setDuration(
de.keyboardsurfer.android.widget.crouton.Configuration
.DURATION_INFINITE
)
.build()
).show();
}
/**
* Display an alert, with a string message with infinite time
*
* @param messageResId The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showInfiniteCrouton(final int messageResId,
final AlertStyle style) {
showInfiniteCrouton(getString(messageResId), style);
}
/**
* Cancels all queued {@link Crouton}s. If there is a {@link Crouton} displayed currently, it
* will be the last one displayed.
*/
public void cancelAllCroutons() {
Crouton.cancelAllCroutons();
}
/**
* Finish the Activity, specifying whether to use custom or default animations
*
* @param defaultAnimation <code>true</code> to use Activity default animation,
* <code>false</code> to use custom Animation. In order for the custom
* Animation to be applied, however, you must add the {@link
* ActivityTransition} Annotation to the Activity declaration
*/
public void finish(final boolean defaultAnimation) {
super.finish();
if ((mActivityTransition != null) && !defaultAnimation) {
overridePendingTransition(mActivityTransition.destroyEnterAnimation(),
mActivityTransition
.destroyExitAnimation()
);
}
}
@Override
public void finish() {
finish(false);
}
/**
* Helper method to load fragments into layout
*
* @param containerResId The container resource Id in the content view into which to load the
* fragment
* @param fragment The fragment to load
* @param tag The fragment tag
* @param addToBackStack Whether the transaction should be addded to the backstack
* @param backStackTag The tag used for the backstack tag
*/
public void loadFragment(final int containerResId,
final AbstractBarterLiFragment fragment, final String tag,
final boolean addToBackStack, final String backStackTag) {
loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag, false);
}
/**
* Helper method to load fragments into layout
*
* @param containerResId The container resource Id in the content view into which to load the
* fragment
* @param fragment The fragment to load
* @param tag The fragment tag
* @param addToBackStack Whether the transaction should be addded to the backstack
* @param backStackTag The tag used for the backstack tag
* @param customAnimate Whether to provide a custom animation for the Fragment. If
* <code>true</code>, the Fragment also needs to be annotated with a
* {@linkplain li.barter.fragments.FragmentTransition} annotation which
* describes the transition to perform. If <code>false</code>, will use
* default fragment transition
*/
public void loadFragment(final int containerResId,
final AbstractBarterLiFragment fragment, final String tag,
final boolean addToBackStack, final String backStackTag,
final boolean customAnimate) {
loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag, customAnimate, false);
}
/**
* Helper method to load fragments into layout
*
* @param containerResId The container resource Id in the content view into which to load the
* fragment
* @param fragment The fragment to load
* @param tag The fragment tag
* @param addToBackStack Whether the transaction should be addded to the backstack
* @param backStackTag The tag used for the backstack tag
* @param customAnimate Whether to provide a custom animation for the Fragment. If
* <code>true</code>, the Fragment also needs to be annotated with a
* {@linkplain li.barter.fragments.FragmentTransition} annotation which
* describes the transition to perform. If <code>false</code>, will use
* default fragment transition
* @param remove Whether the fragment should be removed before adding it
*/
public void loadFragment(final int containerResId,
final AbstractBarterLiFragment fragment, final String tag,
final boolean addToBackStack, final String backStackTag,
final boolean customAnimate, final boolean remove) {
final FragmentManager fragmentManager = getSupportFragmentManager();
if (remove) {
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().remove(fragment).commit();
fragmentManager.executePendingTransactions();
}
final FragmentTransaction transaction = fragmentManager
.beginTransaction();
if (customAnimate) {
final FragmentTransition fragmentTransition = fragment.getClass()
.getAnnotation(
FragmentTransition.class);
if (fragmentTransition != null) {
transaction
.setCustomAnimations(fragmentTransition.enterAnimation(), fragmentTransition
.exitAnimation(), fragmentTransition
.popEnterAnimation(), fragmentTransition
.popExitAnimation());
}
}
transaction.replace(containerResId, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(backStackTag);
}
transaction.commit();
}
/**
* Is the user logged in
*/
protected boolean isLoggedIn() {
return !TextUtils.isEmpty(UserInfo.INSTANCE.getAuthToken());
}
@Override
public void onWindowFocusChanged(final boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Reset background to reduce overdaw
getWindow().setBackgroundDrawable(null);
}
/**
* Returns the current master fragment. In single pane layout, this is the fragment in the main
* content. In a multi-pane layout, returns the fragment in the master container, which is the
* one responsible for coordination
*
* @return <code>null</code> If no fragment is loaded,the {@link AbstractBarterLiFragment}
* implementation which is the current master fragment otherwise
*/
public AbstractBarterLiFragment getCurrentMasterFragment() {
return (AbstractBarterLiFragment) getSupportFragmentManager()
.findFragmentById(R.id.frame_content);
}
@Override
public void onPreExecute(final IBlRequestContract request) {
mRequestCounter.incrementAndGet();
setProgressBarIndeterminateVisibility(true);
}
@Override
public void onPostExecute(final IBlRequestContract request) {
if (mRequestCounter.decrementAndGet() == 0) {
setProgressBarIndeterminateVisibility(false);
}
}
@Override
public abstract void onSuccess(int requestId, IBlRequestContract request,
ResponseInfo response);
@Override
public abstract void onBadRequestError(int requestId,
IBlRequestContract request, int errorCode,
String errorMessage, Bundle errorResponseBundle);
@Override
public void onAuthError(final int requestId,
final IBlRequestContract request) {
//TODO Show Login Fragment and ask user to login again
}
@Override
public void onOtherError(final int requestId,
final IBlRequestContract request, final int errorCode) {
//TODO Show generic network error message
}
@Override
public void onClick(final DialogInterface dialog, final int which) {
final AbstractBarterLiFragment fragment = getCurrentMasterFragment();
if ((fragment != null) && fragment.isVisible()) {
if (fragment.willHandleDialog(dialog)) {
fragment.onDialogClick(dialog, which);
}
}
}
@Override
public void onBackPressed() {
/* Get the reference to the current master fragment and check if that will handle
onBackPressed. If yes, do nothing. Else, let the Activity handle it. */
final AbstractBarterLiFragment masterFragment = getCurrentMasterFragment();
boolean handled = false;
if (masterFragment != null && masterFragment.isResumed()) {
handled = masterFragment.onBackPressed();
}
if (!handled) {
super.onBackPressed();
}
}
/**
* @author Vinay S Shenoy Enum to handle the different types of Alerts that can be shown
*/
public enum AlertStyle {
ALERT,
INFO,
ERROR
}
}