net.niyonkuru.koodroid.ui.PageFragment.java Source code

Java tutorial

Introduction

Here is the source code for net.niyonkuru.koodroid.ui.PageFragment.java

Source

/*
 * Copyright 2012 Mike Niyonkuru
 *
 * 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 net.niyonkuru.koodroid.ui;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v15.widget.PopupMenu;
import android.support.v15.widget.PopupMenu.OnDismissListener;
import android.support.v15.widget.PopupMenu.OnMenuItemClickListener;
import android.support.v4.app.LoaderManager;
import android.support.v4.view.ViewPager;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.FrameLayout;
import android.widget.Toast;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

import net.niyonkuru.koodroid.App;
import net.niyonkuru.koodroid.R;
import net.niyonkuru.koodroid.provider.SettingsContract.Settings;
import net.niyonkuru.koodroid.service.SyncService;
import net.niyonkuru.koodroid.util.AnimUtils;
import net.niyonkuru.koodroid.util.DetachableResultReceiver;

import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

import static net.niyonkuru.koodroid.BuildConfig.DEBUG;

/**
 * Contains functionality shared amongst the 3 main pages: {@link AccountFragment}, {@link OverviewFragment} and
 * {@link UsageFragment}.
 */
public abstract class PageFragment extends SherlockFragment implements DetachableResultReceiver.Receiver,
        View.OnClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<Cursor> {

    private static final String TAG = PageFragment.class.getSimpleName();

    private static final int ACCOUNT_PAGE = 0;
    private static final int OVERVIEW_PAGE = 1;
    private static final int USAGE_PAGE = 2;

    private static final String EXTRA_POSITION = "position";
    private static final String EXTRA_EMAIL = "email";
    private static final String EXTRA_SUBSCRIBER = "subscriber";

    protected static final Handler sHandler = new Handler();

    protected Context mContext;

    protected Settings mSettings;

    private BaseCallBack mCallBack;

    private DecimalFormat mCurrencyFormat;

    private NumberFormat mNumberFormat;

    private DetachableResultReceiver mReceiver;

    private Menu mOptionsMenu;

    /**
     * used to provide page specific synchronization requests
     */
    protected abstract void sync();

    protected abstract void updateTimestamps();

    protected interface BaseCallBack {
        public void onLoginRequired();
    }

    /**
     * Create an appropriate page fragment instance based on position given.
     *
     * @param position   value that usually comes in from {@link ViewPager}
     * @param email      used to query appropriate data
     * @param subscriber used to query appropriate data
     */
    public static PageFragment newInstance(int position, String email, String subscriber) {
        final PageFragment f;

        switch (position) {
        case ACCOUNT_PAGE:
            f = new AccountFragment();
            break;
        case OVERVIEW_PAGE:
            f = new OverviewFragment();
            break;
        case USAGE_PAGE:
            f = new UsageFragment();
            break;
        default:
            return null;
        }

        Bundle args = new Bundle();
        args.putInt(EXTRA_POSITION, position);
        args.putString(EXTRA_EMAIL, email);
        args.putString(EXTRA_SUBSCRIBER, subscriber);

        f.setArguments(args);

        return f;
    }

    private int getPosition() {
        return getArguments().getInt(EXTRA_POSITION);
    }

    protected String getEmail() {
        return getArguments().getString(EXTRA_EMAIL);
    }

    protected String getSubscriber() {
        return getArguments().getString(EXTRA_SUBSCRIBER);
    }

    protected NumberFormat getNumberFormat() {
        return (NumberFormat) mNumberFormat.clone();
    }

    protected String formatMoney(Object amount) {
        if (amount == null)
            return null;

        /*
           * TODO: protect against NPE, better yet, move this to the currency utility class
           */
        if (amount instanceof String) {
            try {
                amount = mNumberFormat.parse((String) amount).doubleValue();
            } catch (ParseException e) {
                amount = 0.0;
            }
        }

        return mCurrencyFormat.format(amount);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        mContext = activity.getApplicationContext();

        mSettings = App.getSettings();

        Locale locale = new Locale(getString(R.string.locale), "CA");

        mNumberFormat = DecimalFormat.getInstance(locale);
        mCurrencyFormat = CurrencyUtils.getCurrencyFormat(locale);

        try {
            mCallBack = (BaseCallBack) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement BaseCallBack type");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        setRetainInstance(true);

        mReceiver = new DetachableResultReceiver(sHandler);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (DEBUG) {
            Log.d(TAG, " showing position: " + getPosition() + " email: " + getEmail() + " subscriber: "
                    + getSubscriber());
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        mReceiver.setReceiver(this);

        if (canSync())
            sync();
        showLoading(isSyncing());

        final ContentResolver cr = mContext.getContentResolver();
        final Uri pageChangeUri = Uri.withAppendedPath(Settings.CONTENT_URI, Settings.PAGE);
        cr.registerContentObserver(pageChangeUri, false, mPageChangeObserver);

        updateTimestamps();
    }

    @Override
    public void onPause() {
        super.onPause();

        mReceiver.clearReceiver();

        if (getActivity().isFinishing())
            cancelSync();

        final ContentResolver cr = mContext.getContentResolver();
        cr.unregisterContentObserver(mPageChangeObserver);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        mOptionsMenu = menu;

        inflater.inflate(R.menu.refresh_menu_item, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();

        if (itemId == R.id.menu_refresh) {
            if (isSyncing()) {
                cancelSync();
            } else {
                sync(true);
            }
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Toggles the ActionBar's refresh action between progress spinner and refresh icon according to a specified loading
     * state.
     */
    public void showLoading(boolean loading) {
        if (mOptionsMenu == null)
            return;

        final MenuItem refreshItem = mOptionsMenu.findItem(R.id.menu_refresh);
        if (refreshItem != null) {
            if (loading) {
                refreshItem.setActionView(R.layout.actionbar_indeterminate_progress);
                refreshItem.getActionView().setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        cancelSync();
                    }
                });
            } else {
                refreshItem.setActionView(null);
            }
        }
    }

    @Override
    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_FINISHED:
            break;
        case SyncService.STATUS_ERROR:
            final String errorText = getString(R.string.toast_sync_error, resultData.getString(Intent.EXTRA_TEXT));
            Toast.makeText(mContext, errorText, Toast.LENGTH_LONG).show();
            break;
        case SyncService.STATUS_LOGIN_REQUIRED:
            mCallBack.onLoginRequired();
            break;
        }

        showLoading(isSyncing());
    }

    /**
     * Process sync requests and pass them to the {@link SyncService}
     */
    protected void sync(int request) {
        final Intent i = new Intent(mContext, SyncService.class);
        final String subscriber = getSubscriber();

        i.putExtra(SyncService.EXTRA_STATUS_RECEIVER, mReceiver);
        i.putExtra(SyncService.EXTRA_EMAIL, getEmail());
        i.putExtra(SyncService.EXTRA_SUBSCRIBER, subscriber);

        if (!subscriber.equals(mSettings.subscriber())) {
            /* switch the user first before doing the request */

            i.putExtra(SyncService.EXTRA_REQUEST, SyncService.NAVIGATION_SERVICE);
            mContext.startService(i);
        }

        i.putExtra(SyncService.EXTRA_REQUEST, request);

        mContext.startService(i);
    }

    /**
     * Sync immediately or delay 5 seconds
     */
    protected void sync(boolean immediate) {
        if (immediate) {
            new Thread(mSyncTask).run();

            Toast.makeText(mContext, R.string.toast_sync_start, Toast.LENGTH_LONG).show();
        } else {
            /* delay the synchronization by 5 seconds */

            sHandler.postDelayed(mSyncTask, DateUtils.SECOND_IN_MILLIS * 5);
        }
    }

    private void cancelSync() {
        if (DEBUG)
            Log.d(TAG, "cancel sync");

        sHandler.removeCallbacks(mSyncTask);

        mContext.stopService(new Intent(mContext, SyncService.class));

        showLoading(isSyncing());
    }

    private static boolean isSyncing() {
        return SyncService.sSyncing;
    }

    private boolean canSync() {
        /* current page points to this fragment and auto sync is allowed */
        return mSettings.page() == getPosition() && mSettings.autoSyncAllowed();
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();

        switch (id) {
        case R.id.plan_title:
        case R.id.bill_title:
        case R.id.tab_title:
        case R.id.billing_cycle_title:
        case R.id.airtime_title:
        case R.id.data_title:
            final View options = ((FrameLayout) v).getChildAt(2);
            AnimUtils.fade(options);

            v.setFocusableInTouchMode(true);
            v.requestFocusFromTouch();
            v.setOnFocusChangeListener(new OnFocusChangeListener() {
                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    if (!hasFocus) {
                        AnimUtils.fadeOut(options);
                        v.setFocusableInTouchMode(false);
                    }

                    updateTimestamps();
                }
            });
        }

        /* give the user feedback as to how fresh the data is */
        updateTimestamps();
    }

    protected void toggleService(String service, boolean value) {
        ContentValues cv = new ContentValues(1);
        cv.put(service, value);
        mContext.getContentResolver().insert(Settings.buildSubscriberUri(getSubscriber()), cv);
    }

    private final ContentObserver mPageChangeObserver = new ContentObserver(sHandler) {
        @Override
        public void onChange(boolean selfChange) {
            if (canSync()) {
                cancelSync();
                sync(false);
            }

            updateTimestamps();
        }
    };

    private final Runnable mSyncTask = new Runnable() {
        @Override
        public void run() {
            sync();
            updateTimestamps();
        }
    };

    public static class CurrencyUtils {
        private CurrencyUtils() {
        }

        public static DecimalFormat getCurrencyFormat(Locale locale) {
            final DecimalFormat currency = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);

            currency.setNegativePrefix("-" + currency.getNegativePrefix().replace("(", ""));
            currency.setNegativeSuffix(currency.getNegativeSuffix().replace(")", ""));

            return currency;
        }
    }

    /**
     * Display a PopupMenu anchored to a menu option.
     */
    class PopupMenuBuilder {
        private final PopupMenu mPopup;

        public PopupMenuBuilder(Context ctx, final View anchor) {
            mPopup = new PopupMenu(ctx, anchor);

            mPopup.setOnDismissListener(new OnDismissListener() {
                @Override
                public void onDismiss(PopupMenu menu) {
                    /* turn off options mode */
                    AnimUtils.fadeOut((View) anchor.getParent());
                }
            });
        }

        public PopupMenuBuilder addMenuResource(int resource) {
            mPopup.getMenuInflater().inflate(resource, mPopup.getMenu());
            return this;
        }

        public void addMenuItemResource(int resource, int titleRes) {
            mPopup.getMenu().add(Menu.NONE, resource, 2, titleRes);
        }

        public void show(OnMenuItemClickListener listener) {
            mPopup.setOnMenuItemClickListener(listener);

            mPopup.show();
        }
    }
}