co.carlosjimenez.android.currencyalerts.app.MainActivityFragment.java Source code

Java tutorial

Introduction

Here is the source code for co.carlosjimenez.android.currencyalerts.app.MainActivityFragment.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Carlos Andres Jimenez <apps@carlosandresjimenez.co>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package co.carlosjimenez.android.currencyalerts.app;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import com.google.firebase.analytics.FirebaseAnalytics;

import butterknife.BindView;
import butterknife.ButterKnife;
import co.carlosjimenez.android.currencyalerts.app.data.Currency;
import co.carlosjimenez.android.currencyalerts.app.data.ForexContract;
import co.carlosjimenez.android.currencyalerts.app.sync.ForexSyncAdapter;
import co.carlosjimenez.android.currencyalerts.app.sync.LoadCurrencyTask;
import co.carlosjimenez.android.currencyalerts.app.widget.CurrencyEditText;

public class MainActivityFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    public static final String LOG_TAG = MainActivityFragment.class.getSimpleName();

    // These indices are tied to FOREX_COLUMNS.  If FOREX_COLUMNS changes, these
    // must change.
    static final int COL_RATE_ID = 0;
    static final int COL_CURRENCY_FROM_ID = 1;
    static final int COL_CURRENCY_FROM_NAME = 2;
    static final int COL_CURRENCY_FROM_SYMBOL = 3;
    static final int COL_COUNTRY_FROM_CODE = 4;
    static final int COL_COUNTRY_FROM_NAME = 5;
    static final int COL_COUNTRY_FROM_FLAG = 6;
    static final int COL_CURRENCY_TO_ID = 7;
    static final int COL_CURRENCY_TO_NAME = 8;
    static final int COL_CURRENCY_TO_SYMBOL = 9;
    static final int COL_COUNTRY_TO_CODE = 10;
    static final int COL_COUNTRY_TO_NAME = 11;
    static final int COL_COUNTRY_TO_FLAG = 12;
    static final int COL_RATE_DATE = 13;
    static final int COL_RATE_VAL = 14;

    private static final int FOREX_LOADER = 0;

    @BindView(R.id.currencyFromAmount)
    CurrencyEditText mCurrencyEditText;
    @BindView(R.id.clMain)
    CoordinatorLayout mCoordinatorLayout;
    @BindView(R.id.topPanel)
    RelativeLayout mRelativeLayout;
    @BindView(R.id.recyclerview_forex)
    RecyclerView mRecyclerView;
    @BindView(R.id.currencyFromDescription)
    TextView mCurrencyDescription;
    @BindView(R.id.toolbar)
    Toolbar mToolbar;
    @BindView(R.id.appbarLayout)
    AppBarLayout mAppBarLayout;
    @BindView(R.id.currencyFromFlag)
    ImageView mImageView;
    @BindView(R.id.recyclerview_forex_empty)
    TextView mTvEmptyView;
    @BindView(R.id.adView)
    AdView mAdView;

    private AppCompatActivity mContext;
    private FloatingActionButton mCalculateButton;
    private ForexAdapter mForexAdapter;
    private Currency mMainCurrency;
    private FirebaseAnalytics mFirebaseAnalytics;

    private final Handler mHandler = new Handler();
    private static final int AD_DELAY_MILLISECONDS = 1000;

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Extract data included in the Intent
            int message = intent.getIntExtra(ForexSyncAdapter.FOREX_DATA_STATUS, -1);

            if (message == ForexSyncAdapter.FOREX_STATUS_OK) {
                refreshForexList();
            }
        }
    };

    public MainActivityFragment() {
        setHasOptionsMenu(true);
    }

    /**
     * A callback interface that all activities containing this fragment must
     * implement. This mechanism allows activities to be notified of item
     * selections.
     */
    public interface Callback {
        /**
         * MainActivityFragment Callback to perform an action on the activity when an item
         * has been selected.
         */
        void onItemSelected(Uri rateUri, ImageView imageFrom, ForexAdapter.ForexAdapterViewHolder vh);

        void onSettingsSelected();
    }

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

    @Override
    public void onDetach() {
        super.onDetach();
        mCoordinatorLayout.removeView(mCalculateButton);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_main, container, false);
        ButterKnife.bind(this, rootView);

        mContext = (AppCompatActivity) getActivity();
        mFirebaseAnalytics = FirebaseAnalytics.getInstance(mContext);

        mContext.setSupportActionBar(mToolbar);
        mContext.getSupportActionBar().setDisplayShowHomeEnabled(true);
        mContext.getSupportActionBar().setDisplayShowTitleEnabled(true);

        mMainCurrency = Utility.getMainCurrency(mContext);

        // Set the layout manager
        mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        // The ForecastAdapter will take data from a source and
        // use it to populate the RecyclerView it's attached to.
        mForexAdapter = new ForexAdapter(mContext, new ForexAdapter.ForexAdapterOnClickHandler() {
            @Override
            public void onClick(String currencyId, String currencyName, ForexAdapter.ForexAdapterViewHolder vh) {
                String[] currencies = { mMainCurrency.getId(), currencyId };

                sendHitToAnalytics(currencyId, currencyName);

                double value = Double.parseDouble(mCurrencyEditText.getText().toString());
                ((Callback) getActivity()).onItemSelected(
                        ForexContract.RateEntry.buildCurrencyRateWithValue(currencies, value), mImageView, vh);
            }
        }, mTvEmptyView);

        // specify an adapter (see also next example)
        mRecyclerView.setAdapter(mForexAdapter);

        getCalculateButton();
        loadMainCurrencyDetails();

        mCoordinatorLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop,
                    int oldRight, int oldBottom) {
                v.removeOnLayoutChangeListener(this);
                addFloatingActionButton();
            }
        });

        new LoadCurrencyTask(mContext).execute();

        ForexSyncAdapter.initializeSyncAdapter(mContext);

        // Ad is delayed some seconds as this affects the performance
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                loadAd();
            }
        }, AD_DELAY_MILLISECONDS);

        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        getLoaderManager().initLoader(FOREX_LOADER, null, this);
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (null != mRecyclerView) {
            mRecyclerView.clearOnScrollListeners();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(mContext).registerReceiver(mMessageReceiver,
                new IntentFilter(ForexSyncAdapter.ACTION_DATA_UPDATED));

    }

    @Override
    public void onPause() {
        LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mMessageReceiver);
        super.onPause();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        if (getActivity() instanceof MainActivity) {
            // Inflate the menu; this adds items to the action bar if it is present.
            inflater.inflate(R.menu.menu_main, menu);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.

        switch (item.getItemId()) {
        case R.id.action_settings:
            ((Callback) getActivity()).onSettingsSelected();
            return true;
        case R.id.action_refresh:
            refresh();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        // This is called when a new Loader needs to be created.  This
        // fragment only uses one loader, so we don't care about checking the id.

        // To only show current and future dates, filter the query to return weather only for
        // dates after or including today.

        // Sort order:  Ascending, by date.
        Uri rateUri = ForexContract.RateEntry.buildStartCurrencyWithDate(mMainCurrency.getId(),
                Utility.getForexSyncDate(mContext));

        return new CursorLoader(getActivity(), rateUri, ForexContract.RATE_CURRENCY_COLUMNS, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        String maxRateString = "";
        String rateString = "";
        String maxRateSymbol = "";
        double maxRateValue = 0;

        updateEmptyView();

        if (data == null) {
            Log.d(LOG_TAG, "Main Forex Loader Finished: No data returned");

            return;
        }
        if (data.getCount() <= 0) {
            Log.d(LOG_TAG, "Main Forex Loader Finished: No data returned");

            data.close();
            return;
        }

        for (int i = 0; i < data.getCount(); i++) {
            data.moveToPosition(i);
            rateString = Utility.formatCurrencyRate(getActivity(), data.getString(COL_CURRENCY_TO_SYMBOL),
                    data.getDouble(COL_RATE_VAL));

            if (rateString.length() > maxRateString.length()) {
                maxRateString = rateString;
                maxRateSymbol = data.getString(COL_CURRENCY_TO_SYMBOL);
                maxRateValue = data.getDouble(COL_RATE_VAL);
            }
        }

        mForexAdapter.setMaxRateVal(maxRateSymbol, maxRateValue);
        mForexAdapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mForexAdapter.swapCursor(null);
    }

    public void refresh() {
        ForexSyncAdapter.syncImmediately(getActivity());
    }

    public void refreshForexList() {
        getLoaderManager().restartLoader(FOREX_LOADER, null, this);
    }

    /**
     * Helper method to obtain the main currency from the shared preferences and set the information
     * on the top frame.
     */
    public void loadMainCurrencyDetails() {
        mCurrencyEditText.setPrefix(mMainCurrency.getSymbol());
        mCurrencyEditText.setSelection(mCurrencyEditText.getText().length());
        mCurrencyEditText.setOnEditorActionListener(new EditText.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                boolean handled = false;
                if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE) {
                    calculateRates();
                    handled = true;
                }
                return handled;
            }
        });

        Glide.with(this).load(mMainCurrency.getCountryFlag()).error(R.drawable.globe).centerCrop().crossFade()
                .into(mImageView);
        mImageView.setContentDescription(Utility.formatCountryFlagName(mContext, mMainCurrency.getCountryName()));

        mCurrencyDescription.setText(mMainCurrency.getName());
        mImageView.setContentDescription(mMainCurrency.getName());
    }

    /**
     * Updates the empty list view with contextually relevant information that the user can
     * use to determine why they aren't seeing currency rates.
     */
    private void updateEmptyView() {
        if (mForexAdapter.getItemCount() == 0) {
            if (null != mTvEmptyView) {
                // if cursor is empty, why? do we have an invalid location
                int message = R.string.empty_forex_list;
                @ForexSyncAdapter.ForexStatus
                int status = Utility.getForexStatus(getActivity());
                switch (status) {
                case ForexSyncAdapter.FOREX_STATUS_SERVER_DOWN:
                    message = R.string.empty_forex_list_server_down;
                    break;
                case ForexSyncAdapter.FOREX_STATUS_SERVER_INVALID:
                    message = R.string.empty_forex_list_server_error;
                    break;
                case ForexSyncAdapter.FOREX_STATUS_INVALID:
                    message = R.string.empty_forex_list_invalid;
                    break;
                default:
                    if (!Utility.isNetworkAvailable(getActivity())) {
                        message = R.string.empty_forex_list_no_network;
                    }
                }
                mTvEmptyView.setText(message);
            }
        }
    }

    /**
     * Method to set the position of the FAB button according to the material design guidelines.
     */
    private void addFloatingActionButton() {
        final int fabSize = getResources().getDimensionPixelSize(R.dimen.size_fab);
        final int spacingDouble = getResources().getDimensionPixelSize(R.dimen.spacing_double);

        int bottomOfToolbar = mAppBarLayout.getBottom();

        final CoordinatorLayout.LayoutParams fabLayoutParams = new CoordinatorLayout.LayoutParams(fabSize, fabSize);
        fabLayoutParams.gravity = Gravity.END | Gravity.TOP;
        final int halfAFab = fabSize / 2;

        fabLayoutParams.setMargins(0, // left
                bottomOfToolbar - halfAFab, //top
                0, // right
                spacingDouble); // bottom
        MarginLayoutParamsCompat.setMarginEnd(fabLayoutParams, spacingDouble);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // Account for the fab's emulated shadow.
            fabLayoutParams.topMargin -= (mCalculateButton.getPaddingTop() / 2);
        }
        mCoordinatorLayout.addView(mCalculateButton, fabLayoutParams);
    }

    /**
     * Method to create a new FAB button.
     */
    private void getCalculateButton() {
        if (null == mCalculateButton) {
            mCalculateButton = new FloatingActionButton(mContext);
            mCalculateButton.setImageDrawable(mContext.getDrawable(R.drawable.ic_calculator_grey600_24dp));
            mCalculateButton.setContentDescription(mContext.getString(R.string.calculate_description));
            mCalculateButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onFabClicked();
                }
            });
        }
    }

    public void onFabClicked() {
        calculateRates();
    }

    /**
     * Method that handles the calculation of the rate when the user click on the FAB or the
     * keyboard go buttons.
     */
    public void calculateRates() {
        if (mForexAdapter == null || mForexAdapter.getItemCount() == 0) {
            showErrorMessage(getString(R.string.empty_forex_list));
            return;
        }

        if (mCurrencyEditText == null || mCurrencyEditText.getText().length() <= 0) {
            showAlertMessage();
            return;
        }

        mForexAdapter.setMainAmount(Double.parseDouble(mCurrencyEditText.getText().toString()));
        hideIme();
    }

    /**
     * Helper method to show errors on a snack bar.
     *
     * @param message message to show to the user
     */
    private void showErrorMessage(String message) {
        Snackbar.make(mCoordinatorLayout, message, Snackbar.LENGTH_LONG)
                .setAction("Refresh", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        refresh();
                    }
                }).show();
    }

    /**
     * Helper method to show errors on an alert dialog.
     */
    private void showAlertMessage() {
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mContext);

        // set dialog message
        alertDialogBuilder.setMessage(mContext.getString(R.string.dialog_calc_rates_empty_value))
                .setCancelable(false).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {

                    }
                });
        // create alert dialog
        AlertDialog alertDialog = alertDialogBuilder.create();

        // show it
        alertDialog.show();
    }

    /**
     * This method forces the soft keyboard to hide
     */
    private void hideIme() {
        InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Activity.INPUT_METHOD_SERVICE);
        //Find the currently focused view, so we can grab the correct window token from it.
        View view = mContext.getCurrentFocus();
        //If no view currently has focus, create a new one, just so we can grab a window token from it
        if (view == null) {
            view = new View(mContext);
        }
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    /**
     * Helper method to load a Google Ad
     */
    private void loadAd() {
        AdRequest adRequest = new AdRequest.Builder().build();
        mAdView.loadAd(adRequest);
    }

    /**
     * Helper method to send the hit to Google Analytics of a selected currency to be
     * displayed on the detail screen
     *
     * @param currencyId       String containing the currency id
     * @param currencyName     String containing the currency name
     */
    private void sendHitToAnalytics(String currencyId, String currencyName) {
        Bundle payload = new Bundle();
        payload.putString(FirebaseAnalytics.Param.ITEM_ID, currencyId);
        payload.putString(FirebaseAnalytics.Param.ITEM_NAME, currencyName);
        payload.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "text/html");
        mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, payload);
    }
}