/*
* 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.fragments;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Typeface;
import android.location.Criteria;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import com.android.volley.Request;
import com.android.volley.Request.Method;
import com.android.volley.RequestQueue;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.atomic.AtomicInteger;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import li.barter.R;
import li.barter.activities.AbstractBarterLiActivity;
import li.barter.activities.AbstractBarterLiActivity.AlertStyle;
import li.barter.analytics.GoogleAnalyticsManager;
import li.barter.data.DBInterface;
import li.barter.fragments.dialogs.AddUserInfoDialogFragment;
import li.barter.http.BlMultiPartRequest;
import li.barter.http.HttpConstants;
import li.barter.http.HttpConstants.ApiEndpoints;
import li.barter.http.HttpConstants.RequestId;
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.FragmentTags;
import li.barter.utils.AppConstants.Keys;
import li.barter.utils.AppConstants.UserInfo;
import li.barter.utils.Utils;
import li.barter.widgets.TypefaceCache;
/**
* Base fragment class to encapsulate common functionality. Call the init() method in the
* onCreateView() of your fragments
*
* @author Vinay S Shenoy
*/
public abstract class AbstractBarterLiFragment extends Fragment implements
IHttpCallbacks {
private static final String TAG = "AbstractBarterLiFragment";
/**
* Flag that indicates that this fragment is attached to an Activity
*/
private boolean mIsAttached;
/**
* Stores the id for the container view
*/
protected int mContainerViewId;
/**
* {@link VolleyCallbacks} for encapsulating the Volley response flow
*/
protected VolleyCallbacks mVolleyCallbacks;
private AtomicInteger mRequestCounter;
/**
* Whether a screen hit should be reported to analytics
*/
private boolean mShouldReportScreenHit;
public boolean mRefreshBooks = false;
/**
* {@link AddUserInfoDialogFragment} for
*/
private AddUserInfoDialogFragment mAddUserInfoDialogFragment;
@Override
public void onAttach(final Activity activity) {
super.onAttach(activity);
mIsAttached = true;
final RequestQueue requestQueue = ((IVolleyHelper) activity
.getApplication()).getRequestQueue();
mVolleyCallbacks = new VolleyCallbacks(requestQueue, this);
mRequestCounter = new AtomicInteger(0);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(Keys.LAST_SCREEN_TIME, Utils.getCurrentEpochTime());
}
/**
* Call this method in the onCreateView() of any subclasses
*
* @param container The container passed into onCreateView()
* @param savedInstanceState The Instance state bundle passed into the onCreateView() method
*/
protected void init(final ViewGroup container,
final Bundle savedInstanceState) {
mContainerViewId = container.getId();
long lastScreenTime = 0l;
if (savedInstanceState != null) {
mAddUserInfoDialogFragment = (AddUserInfoDialogFragment) getFragmentManager()
.findFragmentByTag(FragmentTags.DIALOG_ADD_NAME);
lastScreenTime = savedInstanceState.getLong(Keys.LAST_SCREEN_TIME);
}
if (Utils.shouldReportScreenHit(lastScreenTime)) {
mShouldReportScreenHit = true;
} else {
mShouldReportScreenHit = false;
}
}
@Override
public void onResume() {
super.onResume();
checkAndReportScreenHit();
}
/**
* Reports a screen hit
*/
private void checkAndReportScreenHit() {
if (mShouldReportScreenHit) {
final String analyticsScreenName = getAnalyticsScreenName();
if (!TextUtils.isEmpty(analyticsScreenName)) {
GoogleAnalyticsManager.getInstance()
.sendScreenHit(analyticsScreenName);
}
}
}
/**
* Gets the screen name for reporting to google analytics. Send empty string, or
* <code>null</code> if you don't want the Fragment tracked
*/
protected abstract String getAnalyticsScreenName();
/**
* 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) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity())
.loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag);
}
}
/**
* 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) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity())
.loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag, customAnimate);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mContainerViewId = 0;
}
@Override
public void onDetach() {
super.onDetach();
mIsAttached = false;
mVolleyCallbacks = null;
mRequestCounter = null;
}
/**
* Is the device connected to a network or not.
*
* @return <code>true</code> if connected, <code>false</code> otherwise
*/
public boolean isConnectedToInternet() {
return ((AbstractBarterLiActivity) getActivity())
.isConnectedToInternet();
}
public void setActionBarDisplayOptions(final int displayOptions) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity())
.setActionBarDisplayOptions(displayOptions);
}
}
@Override
public void onStop() {
super.onStop();
Crouton.clearCroutonsForActivity(getActivity());
mVolleyCallbacks.cancelAll(getTaskTag());
DBInterface.cancelAll(getTaskTag());
getActivity().setProgressBarIndeterminateVisibility(false);
}
/**
* 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 (mIsAttached) {
request.setTag(getTaskTag());
if (isConnectedToInternet()) {
mRequestCounter.incrementAndGet();
getActivity().setProgressBarIndeterminateVisibility(true);
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 tasks. This must be unique for all Fragments types
*
* @return An Object that's the tag for this fragment
*/
protected abstract Object getTaskTag();
/**
* 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) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity())
.showCrouton(message, style);
}
}
/**
* 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) {
if (mIsAttached) {
showCrouton(getString(messageResId), style);
}
}
/**
* Display an alert, with a string message infinitely
*
* @param messageResId The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showInfiniteCrouton(final int messageResId,
final AlertStyle style) {
if (mIsAttached) {
showInfiniteCrouton(getString(messageResId), style);
}
}
public void cancelAllCroutons() {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity()).cancelAllCroutons();
}
}
/**
* Display an alert, with a string message infinitely
*
* @param message The message to display
* @param style The {@link AlertStyle} of message to display
*/
public void showInfiniteCrouton(final String message, final AlertStyle style) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity())
.showInfiniteCrouton(message, style);
}
}
/**
* Whether this Fragment is currently attached to an Activity
*
* @return <code>true</code> if attached, <code>false</code> otherwise
*/
public boolean isAttached() {
return mIsAttached;
}
/**
* 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) {
if (mIsAttached) {
((AbstractBarterLiActivity) getActivity()).setActionBarTitle(title);
}
}
/**
* 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));
}
/**
* Is the user logged in
*/
protected boolean isLoggedIn() {
return !TextUtils.isEmpty(UserInfo.INSTANCE.getAuthToken());
}
/**
* Does the user have a first name
*/
protected boolean hasFirstName() {
return !TextUtils.isEmpty(UserInfo.INSTANCE.getFirstName());
}
/**
* Show the dialog for the user to add his name, in case it's not already added
*/
protected void showAddFirstNameDialog() {
mAddUserInfoDialogFragment = new AddUserInfoDialogFragment();
mAddUserInfoDialogFragment
.show(AlertDialog.THEME_HOLO_LIGHT, 0, R.string.update_info, R.string.submit,
R.string.cancel, 0, getFragmentManager(), true, FragmentTags.DIALOG_ADD_NAME);
}
/**
* Handles the behaviour for onBackPressed().
*
* @return <code>true</code> If the fragment will handle onBackPressed
*/
public boolean onBackPressed() {
return false;
}
/**
* Whether the fragment is loaded into a multipane layout
*/
public boolean isMultipane() {
if (isAttached()) {
return ((AbstractBarterLiActivity) getActivity()).isMultipane();
}
//Incorrect call if it reaches here
return false;
}
@Override
public void onPreExecute(final IBlRequestContract request) {
mRequestCounter.incrementAndGet();
if (mIsAttached) {
getActivity().setProgressBarIndeterminateVisibility(true);
}
}
@Override
public void onPostExecute(final IBlRequestContract request) {
assert (mRequestCounter != null);
if (mIsAttached && (mRequestCounter.decrementAndGet() == 0)) {
getActivity().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
}
/**
* Whether this fragment will handle the particular dialog click or not
*
* @param dialog The dialog that was interacted with
* @return <code>true</code> If the fragment will handle it, <code>false</code> otherwise
*/
public boolean willHandleDialog(final DialogInterface dialog) {
if ((mAddUserInfoDialogFragment != null)
&& mAddUserInfoDialogFragment.getDialog()
.equals(dialog)) {
return true;
}
return false;
}
/**
* Handle the click for the dialog. The fragment will receive this call, only if {@link
* #willHandleDialog(DialogInterface)} returns <code>true</code>
*
* @param dialog The dialog that was interacted with
* @param which The button that was clicked
*/
public void onDialogClick(final DialogInterface dialog, final int which) {
if ((mAddUserInfoDialogFragment != null)
&& mAddUserInfoDialogFragment.getDialog()
.equals(dialog)) {
if (which == DialogInterface.BUTTON_POSITIVE) {
final String firstName = mAddUserInfoDialogFragment
.getFirstName();
final String lastName = mAddUserInfoDialogFragment
.getLastName();
if (!TextUtils.isEmpty(firstName)) {
updateUserInfo(firstName, lastName);
}
}
}
}
/**
* Updates the user info with just the first name and last name
*
* @param firstName The user's first name
* @param lastName The user's last name
*/
public void updateUserInfo(final String firstName, final String lastName) {
final String url = HttpConstants.getApiBaseUrl()
+ ApiEndpoints.UPDATE_USER_INFO;
final JSONObject mUserProfileObject = new JSONObject();
final JSONObject mUserProfileMasterObject = new JSONObject();
try {
mUserProfileObject.put(HttpConstants.FIRST_NAME, firstName);
mUserProfileObject.put(HttpConstants.LAST_NAME, lastName);
mUserProfileMasterObject
.put(HttpConstants.USER, mUserProfileObject);
final BlMultiPartRequest updateUserProfileRequest = new BlMultiPartRequest(Method.PUT,
url, null,
mVolleyCallbacks);
updateUserProfileRequest
.addMultipartParam(HttpConstants.USER, "application/json",
mUserProfileMasterObject
.toString()
);
updateUserProfileRequest.setRequestId(RequestId.SAVE_USER_PROFILE);
addRequestToQueue(updateUserProfileRequest, true, 0, true);
} catch (final JSONException e) {
e.printStackTrace();
}
}
/**
* Hides the keyboard
*
* @param view the view from which keyboard was open
*/
public void hideKeyBoard(View view) {
InputMethodManager imm = (InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
public boolean isLocationServiceEnabled() {
LocationManager lm = (LocationManager) getActivity()
.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = lm.getBestProvider(criteria, true);
return ((provider != null) && !LocationManager.PASSIVE_PROVIDER
.equals(provider));
}
}