com.f16gaming.pathofexilestatistics.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.f16gaming.pathofexilestatistics.MainActivity.java

Source

/*
 * Copyright (c) 2013 by Adam Hellberg.
 *
 * 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 com.f16gaming.pathofexilestatistics;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import net.simonvt.numberpicker.NumberPicker;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.json.JSONException;

import java.util.ArrayList;
import java.util.Collections;

/**
 * Created with IntelliJ IDEA.
 * User: Gamer
 * Date: 2013-02-28
 * Time: 02:27
 * To change this template use File | Settings | File Templates.
 */
public class MainActivity extends Activity {
    private final String statsUrl = "http://api.pathofexile.com/leagues/%s?ladder=1&ladderOffset=%d&ladderLimit=%d";
    private final String normalLeague = "Standard";
    private final String hardcoreLeague = "Hardcore";
    private final int limit = 200;
    private final int max = 15000;
    private final int refreshWarningLimit = 4000; // Show warning if user tries to refresh more than this amount of entries

    private ProgressDialog progressDialog;
    private AlertDialog refreshWarningDialog;
    private AlertDialog rankGoDialog;
    private NumberPicker numberPicker;

    private ListView statsView;
    private View listFooter;

    private int offset = 0; // Num entries to load = limit + limit * offset
    private boolean showHardcore = false;
    private int refreshOffset = 0; // Current refresh offset
    private int refreshTopOffset = 0; // Cached top offset for refresh

    private ArrayList<PoeEntry> poeEntries;
    private EntryAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        LayoutInflater inflater = getLayoutInflater();

        poeEntries = new ArrayList<PoeEntry>();

        statsView = (ListView) findViewById(R.id.statsView);

        listFooter = inflater.inflate(R.layout.list_footer, null);

        statsView.addFooterView(listFooter);

        statsView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                onStatsViewClick(position);
            }
        });

        AlertDialog.Builder refreshWarningBuilder = new AlertDialog.Builder(this);
        refreshWarningBuilder.setTitle(R.string.refresh_warning).setMessage(R.string.refresh_warning_message)
                .setCancelable(true)
                .setNegativeButton(R.string.refresh_warning_reset, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        resetList();
                    }
                }).setPositiveButton(R.string.refresh_warning_confirm, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        refreshList(true);
                    }
                });

        refreshWarningDialog = refreshWarningBuilder.create();

        AlertDialog.Builder rankGoBuilder = new AlertDialog.Builder(this);
        View view = inflater.inflate(R.layout.rank_go_dialog, null);

        numberPicker = (net.simonvt.numberpicker.NumberPicker) view.findViewById(R.id.number_picker);
        numberPicker.setMinValue(1);
        numberPicker.setMaxValue(max);

        rankGoBuilder.setTitle(R.string.rank_go_dialog_title).setView(view).setCancelable(true)
                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        // Do nothing
                    }
                }).setPositiveButton(R.string.go, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        int rawOffset = numberPicker.getValue() - 1;
                        if (rawOffset >= limit * offset && rawOffset <= limit + limit * offset)
                            showToast(String.format(getString(R.string.toast_rank_showing), rawOffset + 1));
                        else
                            updateList(showHardcore, false, rawOffset);
                    }
                });

        rankGoDialog = rankGoBuilder.create();

        resetList();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem toggleItem = menu.findItem(R.id.menu_toggle);
        toggleItem.setTitle(showHardcore ? R.string.menu_normal : R.string.menu_hardcore);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_refresh:
            refreshList();
            return true;
        case R.id.menu_search:
            rankGoDialog.show();
            return true;
        case R.id.menu_reset:
            resetList();
            return true;
        case R.id.menu_toggle:
            toggleList();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Gets the list offset relative to list start entry.
     * @return The zero-based offset relative to the start entry on the list.
     */
    private int getRelativeOffset() {
        return (int) Math.floor((double) poeEntries.size() / (double) limit) - 1;
    }

    /**
     * Gets the offset of the top list entry.
     * @return The zero-based offset value for the top entry on the list.
     */
    private int getTopOffset() {
        return offset - getRelativeOffset();
    }

    private void showToast(CharSequence text) {
        Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
    }

    private void showProgress(CharSequence text) {
        showProgress("", text);
    }

    private void showProgress(CharSequence title, CharSequence text) {
        if (progressDialog != null) {
            progressDialog.setTitle(title);
            progressDialog.setMessage(text);
            return;
        }

        progressDialog = ProgressDialog.show(this, title, text, true, false);
    }

    private void showProgress(CharSequence title, String format, Object... args) {
        showProgress(title, String.format(format, args));
    }

    private void hideProgress() {
        if (progressDialog == null)
            return;
        progressDialog.dismiss();
        progressDialog = null;
    }

    private void resetList() {
        offset = -1;
        updateList(showHardcore);
    }

    private void toggleList() {
        updateList(!showHardcore);
    }

    private void refreshList() {
        if (poeEntries.size() > refreshWarningLimit) {
            String message = getResources().getString(R.string.refresh_warning_message);
            refreshWarningDialog.setMessage(String.format(message, poeEntries.size()));
            refreshWarningDialog.show();
        } else
            refreshList(true);
    }

    private void refreshList(boolean confirmed) {
        if (!confirmed)
            return;

        // Get the offset we need to start loading from when refreshing
        // (Current offset subtracted with the number of offsets previously loaded)
        refreshOffset = getTopOffset();
        refreshTopOffset = getTopOffset();
        updateList(showHardcore, true);
    }

    private void updateList(boolean hardcore) {
        updateList(hardcore, false);
    }

    private void updateList(boolean hardcore, boolean refresh) {
        updateList(hardcore, refresh, 0);
    }

    private void updateList(boolean hardcore, boolean refresh, int rawOffset) {
        if (hardcore != showHardcore) // Switching mode
            offset = 0;
        else if (!refresh && rawOffset == 0) // Not refreshing or jumping, load more entries
            offset++;
        else if (rawOffset > 0) // Calculate actual offset
            offset = (int) Math.floor((double) rawOffset / (double) limit);

        if (refresh)
            showProgress(getString(R.string.refreshing), getString(R.string.refreshing_page), refreshOffset + 1,
                    offset + 1);
        else
            showProgress(getString(R.string.retrieving_data));

        // Get the league we want to load from
        String league = hardcore ? hardcoreLeague : normalLeague;

        // Construct proper URL
        // Refreshes start at 0 then build up to offset var, hence the separate refreshOffset var
        String url = refresh ? String.format(statsUrl, league, limit * refreshOffset, limit)
                : String.format(statsUrl, league, limit * offset, limit);

        // Create the task object to handle our request
        new RetrieveStatsTask(new RetrieveStatsListener() {
            @Override
            public void handleResponse(StatsResponse response, boolean hardcore, boolean refresh, boolean jump) {
                updateList(response, hardcore, refresh, jump);
            }
        }, hardcore, refresh, rawOffset > 0).execute(url); // And execute it!
    }

    private void updateList(StatsResponse response, boolean hardcore, boolean refresh, boolean jump) {
        if (response == null) {
            // Something went wrong and we didn't even get a response body
            showToast(getString(R.string.retrieve_data_fail));
            hideProgress();
            return;
        }

        try {
            StatusLine status = response.getStatus();
            // Should probably make this handle status codes listed on PoE API
            if (status.getStatusCode() == HttpStatus.SC_OK) {
                String responseString = response.getResponseString();
                PoeEntry[] newEntries = PoeEntry.getEntriesFromJSONString(responseString);

                // This was a mode switch, refresh or jump; clear the previous entries
                // We also clear it if the offset is 0 (first execution or list reset)
                if (offset == 0 || hardcore != showHardcore || (refresh && refreshOffset == refreshTopOffset)
                        || jump)
                    poeEntries.clear();

                // Add the newly loaded entries
                Collections.addAll(poeEntries, newEntries);

                // Continue loading entries if this is a refresh and it's not done yet
                if (refresh && refreshOffset < offset) {
                    refreshOffset++;
                    updateList(hardcore, true);
                    return;
                }

                // If adapter var is null, this is the first updateList execution
                if (adapter == null) {
                    adapter = new EntryAdapter(this, poeEntries, this, getResources());
                    statsView.setAdapter(adapter);
                } else { // Otherwise just notify it that data has changed
                    adapter.notifyDataSetChanged();
                }

                // Show a toast to the user telling them the data is updated
                showToast(hardcore ? getString(R.string.hardcore_updated) : getString(R.string.normal_updated));

                // Remove the "Load more entries" at bottom of list if we reached the maximum
                if (hardcore == showHardcore && limit + limit * offset >= max)
                    statsView.removeFooterView(listFooter);
                else if (hardcore != showHardcore) {
                    // Otherwise add it back if it's not there
                    if (statsView.getFooterViewsCount() == 0)
                        statsView.addFooterView(listFooter);
                    statsView.setSelection(0);
                }

                // Update the showHardcore var to reflect new value
                showHardcore = hardcore;

                // Update title to new mode
                setTitle(showHardcore ? R.string.hardcore : R.string.normal);

                // Invalidate the options menu to update the text properly
                // (This is only needed and supported on SDK level >= 11, since older versions
                // always update the menu every time it's shown
                if (Build.VERSION.SDK_INT >= 11)
                    invalidateOptionsMenu();
            } else {
                showToast(getString(R.string.retrieve_data_fail));
            }
        } catch (JSONException e) { // Invalid JSON returned
            showToast(getString(R.string.json_parse_error));
        } catch (ClassCastException e) { // Usually indicative of a JSON parsing error
            showToast(getString(R.string.retrieve_data_fail));
        }

        hideProgress();
    }

    private void onStatsViewClick(int index) {
        if (poeEntries == null || index < 0)
            return;

        if (index >= poeEntries.size() && index == statsView.getCount() - 1) {
            updateList(showHardcore);
        } else {
            PoeEntry entry = poeEntries.get(index);
            AlertDialog dialog = entry.getInfoDialog(this, getResources());
            dialog.show();
        }
    }
}