X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fuk%2Fme%2Fnjae%2Fsunshine%2FForecastFragment.java;h=8b67a06f53b7dd5bcb0c5ea0fed9803c81f5018e;hb=HEAD;hp=7a3eedf944fed00c24ee96665fd4ff043ad28a2a;hpb=776977ebe865bb31a2e270d0621a0354394d7fe3;p=Sunshine.git diff --git a/app/src/main/java/uk/me/njae/sunshine/ForecastFragment.java b/app/src/main/java/uk/me/njae/sunshine/ForecastFragment.java index 7a3eedf..8b67a06 100644 --- a/app/src/main/java/uk/me/njae/sunshine/ForecastFragment.java +++ b/app/src/main/java/uk/me/njae/sunshine/ForecastFragment.java @@ -1,41 +1,73 @@ package uk.me.njae.sunshine; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.app.Fragment; -import android.util.Log; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.SimpleCursorAdapter; 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.widget.ArrayAdapter; +import android.widget.AdapterView; import android.widget.ListView; +import android.widget.TextView; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.Date; -import java.util.List; + +import uk.me.njae.sunshine.data.WeatherContract; +import uk.me.njae.sunshine.data.WeatherContract.LocationEntry; +import uk.me.njae.sunshine.data.WeatherContract.WeatherEntry; + +import static android.util.Log.e; /** * A placeholder fragment containing a simple view. */ -public class ForecastFragment extends Fragment { - - private ArrayAdapter mForecastAdapter; +public class ForecastFragment extends Fragment implements LoaderManager.LoaderCallbacks { + + private final String LOG_TAG = ForecastFragment.class.getSimpleName(); + + private SimpleCursorAdapter mForecastAdapter; + + private static final int FORECAST_LOADER = 0; + private String mLocation; + + // For the forecast view we're showing only a small subset of the stored data. + // Specify the columns we need. + private static final String[] FORECAST_COLUMNS = { + // In this case the id needs to be fully qualified with a table name, since + // the content provider joins the location & weather tables in the background + // (both have an _id column) + // On the one hand, that's annoying. On the other, you can search the weather table + // using the location set by the user, which is only in the Location table. + // So the convenience is worth it. + WeatherEntry.TABLE_NAME + "." + WeatherEntry._ID, + WeatherEntry.COLUMN_DATETEXT, + WeatherEntry.COLUMN_SHORT_DESC, + WeatherEntry.COLUMN_MAX_TEMP, + WeatherEntry.COLUMN_MIN_TEMP, + LocationEntry.COLUMN_LOCATION_SETTING + }; + + // These indices are tied to FORECAST_COLUMNS. If FORECAST_COLUMNS changes, these + // must change. + public static final int COL_WEATHER_ID = 0; + public static final int COL_WEATHER_DATE = 1; + public static final int COL_WEATHER_DESC = 2; + public static final int COL_WEATHER_MAX_TEMP = 3; + public static final int COL_WEATHER_MIN_TEMP = 4; + public static final int COL_LOCATION_SETTING = 5; public ForecastFragment() { } @@ -59,234 +91,152 @@ public class ForecastFragment extends Fragment { // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_refresh) { - FetchWeatherTask weatherTask = new FetchWeatherTask(); - weatherTask.execute("2642465"); + updateWeather(); return true; } + if (id == R.id.action_show_location) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + String location = preferences.getString(getString(R.string.pref_location_key), + getString(R.string.pref_location_default)); + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri geoLocation; + try { + geoLocation = Uri.parse("geo:0,0?q=" + URLEncoder.encode(location, "UTF-8")); + intent.setData(geoLocation); + } catch (UnsupportedEncodingException e) { + e(LOG_TAG, "Error ", e); + } + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(intent); + } + + } return super.onOptionsItemSelected(item); } + @Override + public void onActivityCreated(Bundle savedInstanceState) { + getLoaderManager().initLoader(FORECAST_LOADER, null, this); + super.onActivityCreated(savedInstanceState); + } + + private void updateWeather() { + String location = Utility.getPreferredLocation(getActivity()); + new FetchWeatherTask(getActivity()).execute(location); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - String[] forecastArray = { - "Today - Sunny - 10/10", - "Tomorrow - Cloudy - 11/11", - "Tuesday - Snow - 12/12", - "Wednesday - Rain - 13/13", - "Thursday - Hail - 14/14", - "Friday - Scorchio - 15/15", - "Saturday - Fog - 16/16" - }; - - List weekForecast = new ArrayList( - Arrays.asList(forecastArray)); - mForecastAdapter = new ArrayAdapter( + // The SimpleCursorAdapter will take data from the database through the + // Loader and use it to populate the ListView it's attached to. + mForecastAdapter = new SimpleCursorAdapter( getActivity(), R.layout.list_item_forecast, - R.id.list_item_forecast_textview, - weekForecast + null, + // the column names to use to fill the textviews + new String[]{WeatherContract.WeatherEntry.COLUMN_DATETEXT, + WeatherContract.WeatherEntry.COLUMN_SHORT_DESC, + WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, + WeatherContract.WeatherEntry.COLUMN_MIN_TEMP + }, + // the textviews to fill with the data pulled from the columns above + new int[]{R.id.list_item_date_textview, + R.id.list_item_forecast_textview, + R.id.list_item_high_textview, + R.id.list_item_low_textview + }, + 0 ); - View rootView = inflater.inflate(R.layout.fragment_main, container, false); - - ListView list_view = (ListView) rootView.findViewById(R.id.listview_forecast); - list_view.setAdapter(mForecastAdapter); - - return rootView; - } - - public class FetchWeatherTask extends AsyncTask { - - private final String LOG_TAG = FetchWeatherTask.class.getSimpleName(); - - @Override - protected void onPostExecute(String[] result) { - if (result != null) { - mForecastAdapter.clear(); -// for (String dayForecastStr: result) { -// mForecastAdapter.add(dayForecastStr); -// } - mForecastAdapter.addAll(result); - } - } - - @Override - protected String[] doInBackground(String... params) { - if (params.length == 0) { - return null; + mForecastAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { + @Override + public boolean setViewValue(View view, Cursor cursor, int columnIndex) { + boolean isMetric = Utility.isMetric(getActivity()); + switch (columnIndex) { + case COL_WEATHER_MAX_TEMP: + case COL_WEATHER_MIN_TEMP: { + // we have to do some formatting and possibly a conversion + ((TextView) view).setText(Utility.formatTemperature( + cursor.getDouble(columnIndex), isMetric)); + return true; + } + case COL_WEATHER_DATE: { + String dateString = cursor.getString(columnIndex); + TextView dateView = (TextView) view; + dateView.setText(Utility.formatDate(dateString)); + return true; + } + } + return false; } + }); - // These two need to be declared outside the try/catch - // so that they can be closed in the finally block. - HttpURLConnection urlConnection = null; - BufferedReader reader = null; + View rootView = inflater.inflate(R.layout.fragment_main, container, false); - // Will contain the raw JSON response as a string. - String forecastJsonStr; + ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast); + listView.setAdapter(mForecastAdapter); - String format = "json"; - String units = "metric"; - int numDays = 7; + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - try { - // Construct the URL for the OpenWeatherMap query - // Possible parameters are avaiable at OWM's forecast API page, at - // http://openweathermap.org/API#forecast - final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?"; - final String QUERY_PARAM = "id"; - final String FORMAT_PARAM = "mode"; - final String UNITS_PARAM = "units"; - final String DAYS_PARAM = "cnt"; - - Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon() - .appendQueryParameter(QUERY_PARAM, params[0]) - .appendQueryParameter(FORMAT_PARAM, format) - .appendQueryParameter(UNITS_PARAM, units) - .appendQueryParameter(DAYS_PARAM, Integer.toString(numDays)) - .build(); - - URL url = new URL(builtUri.toString()); -/**/ Log.v(LOG_TAG, "Built URI " + builtUri.toString()); - - // Create the request to OpenWeatherMap, and open the connection - urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setRequestMethod("GET"); - urlConnection.connect(); - - // Read the input stream into a String - InputStream inputStream = urlConnection.getInputStream(); - StringBuffer buffer = new StringBuffer(); - if (inputStream == null) { - // Nothing to do. - return null; - } - reader = new BufferedReader(new InputStreamReader(inputStream)); - - String line; - while ((line = reader.readLine()) != null) { - // Since it's JSON, adding a newline isn't necessary (it won't affect parsing) - // But it does make debugging a *lot* easier if you print out the completed - // buffer for debugging. - buffer.append(line + "\n"); + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long l) { + Cursor cursor = mForecastAdapter.getCursor(); + if (cursor != null && cursor.moveToPosition(position)) { + Intent intent = new Intent(getActivity(), DetailActivity.class) + .putExtra(DetailActivity.DATE_KEY, cursor.getString(COL_WEATHER_DATE)); + startActivity(intent); } + } + }); - if (buffer.length() == 0) { - // Stream was empty. No point in parsing. - return null; - } - forecastJsonStr = buffer.toString(); -// Log.v(LOG_TAG, "Forecast JSON string: " + forecastJsonStr); - try { - return getWeatherDataFromJson(forecastJsonStr, numDays); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - e.printStackTrace(); - } + return rootView; + } - // This will only happen if we fail to read the forecast - return null; - - } catch (IOException e) { - Log.e(LOG_TAG, "Error ", e); - // If the code didn't successfully get the weather data, there's no point in attempting - // to parse it. - forecastJsonStr = null; - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - if (reader != null) { - try { - reader.close(); - } catch (final IOException e) { - Log.e(LOG_TAG, "Error closing stream", e); - } - } - } - return null; + @Override + public void onResume() { + super.onResume(); + if (mLocation != null && !mLocation.equals(Utility.getPreferredLocation(getActivity()))) { + getLoaderManager().restartLoader(FORECAST_LOADER, null, this); } + } - /* The date/time conversion code is going to be moved outside the asynctask later, - * so for convenience we're breaking it out into its own method now. - */ - private String getReadableDateString(long time) { - // Because the API returns a unix timestamp (measured in seconds), - // it must be converted to milliseconds in order to be converted to valid date. - Date date = new Date(time * 1000); - SimpleDateFormat format = new SimpleDateFormat("E, MMM d"); - return format.format(date).toString(); - } + @Override + public Loader onCreateLoader(int id, Bundle args) { + // 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. - /** - * Prepare the weather high/lows for presentation. - */ - private String formatHighLows(double high, double low) { - // For presentation, assume the user doesn't care about tenths of a degree. - long roundedHigh = Math.round(high); - long roundedLow = Math.round(low); + // To only show current and future dates, get the String representation for today, + // and filter the query to return weather only for dates after or including today. + // Only return data after today. + String startDate = WeatherContract.getDbDateString(new Date()); - String highLowStr = roundedHigh + "/" + roundedLow; - return highLowStr; - } + // Sort order: Ascending, by date. + String sortOrder = WeatherEntry.COLUMN_DATETEXT + " ASC"; - /** - * Take the String representing the complete forecast in JSON Format and - * pull out the data we need to construct the Strings needed for the wireframes. - *

- * Fortunately parsing is easy: constructor takes the JSON string and converts it - * into an Object hierarchy for us. - */ - private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays) - throws JSONException { - - // These are the names of the JSON objects that need to be extracted. - final String OWM_LIST = "list"; - final String OWM_WEATHER = "weather"; - final String OWM_TEMPERATURE = "temp"; - final String OWM_MAX = "max"; - final String OWM_MIN = "min"; - final String OWM_DATETIME = "dt"; - final String OWM_DESCRIPTION = "main"; - - JSONObject forecastJson = new JSONObject(forecastJsonStr); - JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST); - - String[] resultStrs = new String[numDays]; - for (int i = 0; i < weatherArray.length(); i++) { - // For now, using the format "Day, description, hi/low" - String day; - String description; - String highAndLow; - - // Get the JSON object representing the day - JSONObject dayForecast = weatherArray.getJSONObject(i); - - // The date/time is returned as a long. We need to convert that - // into something human-readable, since most people won't read "1400356800" as - // "this saturday". - long dateTime = dayForecast.getLong(OWM_DATETIME); - day = getReadableDateString(dateTime); - - // description is in a child array called "weather", which is 1 element long. - JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0); - description = weatherObject.getString(OWM_DESCRIPTION); - - // Temperatures are in a child object called "temp". Try not to name variables - // "temp" when working with temperature. It confuses everybody. - JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE); - double high = temperatureObject.getDouble(OWM_MAX); - double low = temperatureObject.getDouble(OWM_MIN); - - highAndLow = formatHighLows(high, low); - resultStrs[i] = day + " - " + description + " - " + highAndLow; - } + mLocation = Utility.getPreferredLocation(getActivity()); + Uri weatherForLocationUri = WeatherEntry.buildWeatherLocationWithStartDate( + mLocation, startDate); -// for (String s: resultStrs) { -// Log.v(LOG_TAG, "Forecast entry: " + s); -// } - return resultStrs; - } + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader( + getActivity(), + weatherForLocationUri, + FORECAST_COLUMNS, + null, + null, + sortOrder + ); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + mForecastAdapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + mForecastAdapter.swapCursor(null); } }