From: Gustavo Martin Morcuende Date: Sun, 19 Oct 2014 23:09:50 +0000 (+0200) Subject: WeatherInformation: using Android Studio X-Git-Tag: weatherinformation-1.0~98 X-Git-Url: https://git.gumartinm.name/?a=commitdiff_plain;h=441b05e0fb0e6c75066ceb5b6ff035441501e764;p=AndroidWeatherInformation WeatherInformation: using Android Studio --- diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index 55771d1..0000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2f990f1 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 18 + buildToolsVersion "20.0.0" + + defaultConfig { + applicationId "de.example.exampletdd" + minSdkVersion 18 + targetSdkVersion 18 + + testApplicationId "de.example.exampletdd.test" + testInstrumentationRunner "android.test.InstrumentationTestRunner" + } + + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.android.support:support-v4:+' + compile 'com.google.android.gms:play-services:+' + compile files('libs/jackson-core-2.3.3.jar') +} diff --git a/app/libs/jackson-core-2.3.3.jar b/app/libs/jackson-core-2.3.3.jar new file mode 100644 index 0000000..8312650 Binary files /dev/null and b/app/libs/jackson-core-2.3.3.jar differ diff --git a/app/lint.xml b/app/lint.xml new file mode 100644 index 0000000..ee0eead --- /dev/null +++ b/app/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/androidTest/java/de/example/exampletdd/test/JPOSWeatherParserTest.java b/app/src/androidTest/java/de/example/exampletdd/test/JPOSWeatherParserTest.java new file mode 100644 index 0000000..42e89eb --- /dev/null +++ b/app/src/androidTest/java/de/example/exampletdd/test/JPOSWeatherParserTest.java @@ -0,0 +1,35 @@ +package de.example.exampletdd.test; + +import junit.framework.TestCase; + +import org.json.JSONException; + +import de.example.exampletdd.model.WeatherData; +import de.example.exampletdd.parser.JPOSWeatherParser; + +public class JPOSWeatherParserTest extends TestCase { + private JPOSWeatherParser jposWeatherParser; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + this.jposWeatherParser = new JPOSWeatherParser(); + } + + public void testRetrieveWeatherFromJPOS() throws JSONException { + // Arrange + final String jsonData = "{\"coord\":{\"lon\":139,\"lat\":35}}"; + final double longitude = 139; + final double latitude = 35; + final WeatherData.Coord coord = new WeatherData.Coord(longitude, latitude); + final WeatherData expectedWeather = new WeatherData.Builder().setCoord(coord).build(); + + // Act + final WeatherData finalWeather = this.jposWeatherParser.retrieveWeatherFromJPOS(jsonData); + + // Assert + assertEquals(expectedWeather.toString(), finalWeather.toString()); + } + +} diff --git a/app/src/androidTest/java/de/example/exampletdd/test/WeatherInformationActivityUnitTest.java b/app/src/androidTest/java/de/example/exampletdd/test/WeatherInformationActivityUnitTest.java new file mode 100644 index 0000000..90c0d07 --- /dev/null +++ b/app/src/androidTest/java/de/example/exampletdd/test/WeatherInformationActivityUnitTest.java @@ -0,0 +1,44 @@ +package de.example.exampletdd.test; + +import android.content.Intent; +import android.test.ActivityUnitTestCase; +import android.widget.Button; +import de.example.exampletdd.WeatherInformationActivity; + +public class WeatherInformationActivityUnitTest extends + ActivityUnitTestCase { + + private WeatherInformationActivity activity; + + public WeatherInformationActivityUnitTest() { + super(WeatherInformationActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Intent intent = new Intent(this.getInstrumentation().getTargetContext(), + WeatherInformationActivity.class); + this.startActivity(intent, null, null); + this.activity = this.getActivity(); + } + + public void testIntentTriggerViaOnClick() { + final int buttonweather = de.example.exampletdd.R.id.buttonweather; + final Button view = (Button) this.activity.findViewById(buttonweather); + assertNotNull("Button Weather not allowed to be null", view); + + view.performClick(); + + // TouchUtils cannot be used, only allowed in + // InstrumentationTestCase or ActivityInstrumentationTestCase2 + + // Check the intent which was started + final Intent triggeredIntent = this.getStartedActivityIntent(); + assertNotNull("Intent was null", triggeredIntent); + final String data = triggeredIntent.getDataString(); + + assertEquals("Incorrect data passed via the intent", + "http://gumartinm.name", data); + } +} diff --git a/app/src/androidTest/res/drawable-hdpi/ic_launcher.png b/app/src/androidTest/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/app/src/androidTest/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/androidTest/res/drawable-ldpi/ic_launcher.png b/app/src/androidTest/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..9923872 Binary files /dev/null and b/app/src/androidTest/res/drawable-ldpi/ic_launcher.png differ diff --git a/app/src/androidTest/res/drawable-mdpi/ic_launcher.png b/app/src/androidTest/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/app/src/androidTest/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/androidTest/res/drawable-xhdpi/ic_launcher.png b/app/src/androidTest/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/app/src/androidTest/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/androidTest/res/values/strings.xml b/app/src/androidTest/res/values/strings.xml new file mode 100644 index 0000000..00a3f66 --- /dev/null +++ b/app/src/androidTest/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + Weather Information Test + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..55771d1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/de/example/exampletdd/MapActivity.java b/app/src/main/java/de/example/exampletdd/MapActivity.java new file mode 100644 index 0000000..01a3c41 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/MapActivity.java @@ -0,0 +1,408 @@ +package de.example.exampletdd; + +import android.app.ActionBar; +import android.content.Context; +import android.location.Criteria; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; +import com.google.android.gms.maps.MapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import de.example.exampletdd.fragment.map.MapButtonsFragment; +import de.example.exampletdd.fragment.map.MapProgressFragment; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; + +public class MapActivity extends FragmentActivity implements + LocationListener, + MapProgressFragment.TaskCallbacks { + private static final String PROGRESS_FRAGMENT_TAG = "PROGRESS_FRAGMENT"; + private static final String BUTTONS_FRAGMENT_TAG = "BUTTONS_FRAGMENT"; + private WeatherLocation mRestoreUI; + + // Google Play Services Map + private GoogleMap mMap; + // TODO: read and store from different threads? Hopefully always from UI thread. + private Marker mMarker; + + private LocationManager mLocationManager; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.weather_map); + + // Acquire a reference to the system Location Manager + this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + + // Google Play Services Map + final MapFragment mapFragment = (MapFragment) this.getFragmentManager() + .findFragmentById(R.id.weather_map_fragment_map); + this.mMap = mapFragment.getMap(); + this.mMap.setMyLocationEnabled(false); + this.mMap.getUiSettings().setMyLocationButtonEnabled(false); + this.mMap.getUiSettings().setZoomControlsEnabled(true); + this.mMap.getUiSettings().setCompassEnabled(true); + this.mMap.setOnMapLongClickListener(new MapActivityOnMapLongClickListener(this)); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + // Instead of restoring the state during onCreate() you may choose to + // implement onRestoreInstanceState(), which the system calls after the + // onStart() method. The system calls onRestoreInstanceState() only if + // there is a saved state to restore, so you do not need to check whether + // the Bundle is null: + super.onRestoreInstanceState(savedInstanceState); + + // Restore UI state + this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation"); + } + + @Override + public void onResume() { + super.onResume(); + + final ActionBar actionBar = this.getActionBar(); + // TODO: string resource + actionBar.setTitle("Mark your location"); + + WeatherLocation weatherLocation; + if (this.mRestoreUI != null) { + // Restore UI state + weatherLocation = this.mRestoreUI; + // just once + this.mRestoreUI = null; + } else if (this.mMarker != null ) { + final TextView city = (TextView) this.findViewById(R.id.weather_map_city); + final TextView country = (TextView) this.findViewById(R.id.weather_map_country); + final String cityString = city.getText().toString(); + final String countryString = country.getText().toString(); + + final LatLng point = this.mMarker.getPosition(); + double latitude = point.latitude; + double longitude = point.longitude; + + weatherLocation = new WeatherLocation() + .setCity(cityString) + .setCountry(countryString) + .setLatitude(latitude) + .setLongitude(longitude); + } else { + final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext()); + weatherLocation = query.queryDataBase(); + } + + if (weatherLocation != null) { + this.updateUI(weatherLocation); + } + } + + /** + * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing. + * + * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult} + */ + @Override + public void onPostResume() { + super.onPostResume(); + + final FragmentManager fm = getSupportFragmentManager(); + final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG); + if (progressFragment == null) { + this.addButtonsFragment(); + } else { + this.removeProgressFragment(); + final Bundle bundle = progressFragment.getArguments(); + double latitude = bundle.getDouble("latitude"); + double longitude = bundle.getDouble("longitude"); + this.addProgressFragment(latitude, longitude); + } + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + // Save UI state + // Save Google Maps Marker + if (this.mMarker != null) { + final TextView city = (TextView) this.findViewById(R.id.weather_map_city); + final TextView country = (TextView) this.findViewById(R.id.weather_map_country); + final String cityString = city.getText().toString(); + final String countryString = country.getText().toString(); + + final LatLng point = this.mMarker.getPosition(); + double latitude = point.latitude; + double longitude = point.longitude; + + final WeatherLocation location = new WeatherLocation() + .setCity(cityString) + .setCountry(countryString) + .setLatitude(latitude) + .setLongitude(longitude); + savedInstanceState.putSerializable("WeatherLocation", location); + } + + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onPause() { + super.onPause(); + + this.mLocationManager.removeUpdates(this); + } + + public void onClickSaveLocation(final View v) { + if (this.mMarker != null) { + final LatLng position = this.mMarker.getPosition(); + + final TextView city = (TextView) this.findViewById(R.id.weather_map_city); + final TextView country = (TextView) this.findViewById(R.id.weather_map_country); + final String cityString = city.getText().toString(); + final String countryString = country.getText().toString(); + + final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + if (weatherLocation != null) { + weatherLocation + .setCity(cityString) + .setCountry(countryString) + .setLatitude(position.latitude) + .setLongitude(position.longitude) + .setLastCurrentUIUpdate(null) + .setLastForecastUIUpdate(null); + query.updateDataBase(weatherLocation); + } else { + final WeatherLocation location = new WeatherLocation() + .setCity(cityString) + .setCountry(countryString) + .setIsSelected(true) + .setLatitude(position.latitude) + .setLongitude(position.longitude); + query.insertIntoDataBase(location); + } + } + } + + public void onClickGetLocation(final View v) { + // TODO: Somehow I should show a progress dialog. + // If Google Play Services is available + if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + // TODO: Hopefully there will be results even if location did not change... + final Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.ACCURACY_FINE); + criteria.setAltitudeRequired(false); + criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT); + criteria.setBearingRequired(false); + criteria.setCostAllowed(false); + criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH); + criteria.setPowerRequirement(Criteria.POWER_MEDIUM); + criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT); + criteria.setSpeedRequired(false); + criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH); + + this.mLocationManager.requestSingleUpdate(criteria, this, null); + } else { + // TODO: string resource + Toast.makeText(this, "You do not have enabled location.", Toast.LENGTH_LONG).show(); + } + // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads. + // new GetLocationTask(this).execute(); + } + + private void updateUI(final WeatherLocation weatherLocation) { + + final TextView city = (TextView) this.findViewById(R.id.weather_map_city); + final TextView country = (TextView) this.findViewById(R.id.weather_map_country); + city.setText(weatherLocation.getCity()); + country.setText(weatherLocation.getCountry()); + + final LatLng point = new LatLng(weatherLocation.getLatitude(), weatherLocation.getLongitude()); + if (this.mMarker != null) { + // Just one marker on map + this.mMarker.remove(); + } + this.mMarker = this.mMap.addMarker(new MarkerOptions().position(point).draggable(true)); + this.mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(point, 5)); + this.mMap.animateCamera(CameraUpdateFactory.zoomIn()); + this.mMap.animateCamera(CameraUpdateFactory.zoomTo(8), 2000, null); + } + + private class MapActivityOnMapLongClickListener implements OnMapLongClickListener { + // Store the context passed to the AsyncTask when the system instantiates it. + private final Context localContext; + + private MapActivityOnMapLongClickListener(final Context context) { + this.localContext = context; + } + + @Override + public void onMapLongClick(final LatLng point) { + final MapActivity activity = (MapActivity) this.localContext; + activity.getAddressAndUpdateUI(point.latitude, point.longitude); + } + + } + + /** + * Getting the address of the current location, using reverse geocoding only works if + * a geocoding service is available. + * + */ + private void getAddressAndUpdateUI(final double latitude, final double longitude) { + // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) { + this.removeButtonsFragment(); + this.removeProgressFragment(); + this.addProgressFragment(latitude, longitude); + } else { + this.removeProgressFragment(); + this.addButtonsFragment(); + // No geocoder is present. Issue an error message. + // TODO: string resource + Toast.makeText(this, "Cannot get address. No geocoder available.", Toast.LENGTH_LONG).show(); + + // Default values + final String city = this.getString(R.string.city_not_found); + final String country = this.getString(R.string.country_not_found); + final WeatherLocation weatherLocation = new WeatherLocation() + .setLatitude(latitude) + .setLongitude(longitude) + .setCity(city) + .setCountry(country); + + updateUI(weatherLocation); + } + } + + /***************************************************************************************************** + * + * MapProgressFragment.TaskCallbacks + * + *****************************************************************************************************/ + @Override + public void onPostExecute(WeatherLocation weatherLocation) { + + this.updateUI(weatherLocation); + this.removeProgressFragment(); + + this.addButtonsFragment(); + } + + /***************************************************************************************************** + * + * MapProgressFragment + * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing. + * Android sucks. + * + * "Avoid performing transactions inside asynchronous callback methods." :( + * see: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult + * see: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html + * How do you do what I am doing in a different way without using fragments? + *****************************************************************************************************/ + + private void addProgressFragment(final double latitude, final double longitude) { + final Fragment progressFragment = new MapProgressFragment(); + progressFragment.setRetainInstance(true); + final Bundle args = new Bundle(); + args.putDouble("latitude", latitude); + args.putDouble("longitude", longitude); + progressFragment.setArguments(args); + + final FragmentManager fm = this.getSupportFragmentManager(); + final FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress); + fragmentTransaction.add(R.id.weather_map_buttons_container, progressFragment, PROGRESS_FRAGMENT_TAG).commit(); + fm.executePendingTransactions(); + } + + private void removeProgressFragment() { + final FragmentManager fm = this.getSupportFragmentManager(); + final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG); + if (progressFragment != null) { + final FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.remove(progressFragment).commit(); + fm.executePendingTransactions(); + } + } + + private void addButtonsFragment() { + final FragmentManager fm = this.getSupportFragmentManager(); + Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG); + if (buttonsFragment == null) { + buttonsFragment = new MapButtonsFragment(); + buttonsFragment.setRetainInstance(true); + final FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress); + fragmentTransaction.add(R.id.weather_map_buttons_container, buttonsFragment, BUTTONS_FRAGMENT_TAG).commit(); + fm.executePendingTransactions(); + } + + if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation); + getLocationButton.setEnabled(true); + } + } + + private void removeButtonsFragment() { + final FragmentManager fm = this.getSupportFragmentManager(); + final Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG); + if (buttonsFragment != null) { + final FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.remove(buttonsFragment).commit(); + fm.executePendingTransactions(); + } + } + + /***************************************************************************************************** + * + * android.location.LocationListener + * + *****************************************************************************************************/ + + @Override + public void onLocationChanged(final Location location) { + // It was called from onClickGetLocation (UI thread) This method will run in the same thread (the UI thread) + // so, I do no think there should be any problem. + + // Display the current location in the UI + // TODO: May location not be null? + this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude()); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderEnabled(String provider) { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderDisabled(String provider) { + // TODO Auto-generated method stub + + } +} diff --git a/app/src/main/java/de/example/exampletdd/NotificationIntentService.java b/app/src/main/java/de/example/exampletdd/NotificationIntentService.java new file mode 100644 index 0000000..596b43f --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/NotificationIntentService.java @@ -0,0 +1,234 @@ +package de.example.exampletdd; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Calendar; +import java.util.Locale; + +import org.apache.http.client.ClientProtocolException; + +import android.app.IntentService; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.http.AndroidHttpClient; +import android.preference.PreferenceManager; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.app.TaskStackBuilder; +import android.util.Log; +import android.widget.RemoteViews; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.httpclient.CustomHTTPClient; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.parser.JPOSWeatherParser; +import de.example.exampletdd.service.IconsList; +import de.example.exampletdd.service.ServiceParser; + +public class NotificationIntentService extends IntentService { + private static final String TAG = "NotificationIntentService"; + + + public NotificationIntentService() { + super("NIS-Thread"); + } + + @Override + protected void onHandleIntent(final Intent intent) { + final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + + if (weatherLocation != null) { + final ServiceParser weatherService = new ServiceParser(new JPOSWeatherParser()); + final CustomHTTPClient HTTPClient = new CustomHTTPClient( + AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")); + + Current current = null; + try { + current = this.doInBackgroundThrowable(weatherLocation, HTTPClient, weatherService); + + } catch (final JsonParseException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final ClientProtocolException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final MalformedURLException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final URISyntaxException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final IOException e) { + // logger infrastructure swallows UnknownHostException :/ + Log.e(TAG, "doInBackground exception: " + e.getMessage(), e); + } finally { + HTTPClient.close(); + } + + if (current != null) { + this.showNotification(current, weatherLocation); + } + } + } + + private Current doInBackgroundThrowable(final WeatherLocation weatherLocation, + final CustomHTTPClient HTTPClient, final ServiceParser weatherService) + throws ClientProtocolException, MalformedURLException, URISyntaxException, + JsonParseException, IOException { + + final String APIVersion = this.getResources().getString(R.string.api_version); + + final String urlAPI = this.getResources().getString(R.string.uri_api_weather_today); + final String url = weatherService.createURIAPICurrent(urlAPI, APIVersion, + weatherLocation.getLatitude(), weatherLocation.getLongitude()); + final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis()); + final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache)); + final Current current = weatherService.retrieveCurrentFromJPOS(jsonData); + // TODO: what is this for? I guess I could skip it :/ + final Calendar now = Calendar.getInstance(); + current.setDate(now.getTime()); + + return current; + } + + private interface UnitsConversor { + + public double doConversion(final double value); + } + + private void showNotification(final Current current, final WeatherLocation weatherLocation) { + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this.getApplicationContext()); + + // TODO: repeating the same code in Overview, Specific and Current!!! + // 1. Update units of measurement. + // 1.1 Temperature + String tempSymbol; + UnitsConversor tempUnitsConversor; + String keyPreference = this.getResources().getString(R.string.weather_preferences_temperature_key); + String unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + if (unitsPreferenceValue.equals(values[0])) { + tempSymbol = values[0]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value - 273.15; + } + + }; + } else if (unitsPreferenceValue.equals(values[1])) { + tempSymbol = values[1]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return (value * 1.8) - 459.67; + } + + }; + } else { + tempSymbol = values[2]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value; + } + + }; + } + + + // 2. Formatters + final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + tempFormatter.applyPattern("#####.#####"); + + + // 3. Prepare data for RemoteViews. + String tempMax = ""; + if (current.getMain().getTemp_max() != null) { + double conversion = (Double) current.getMain().getTemp_max(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMax = tempFormatter.format(conversion) + tempSymbol; + } + String tempMin = ""; + if (current.getMain().getTemp_min() != null) { + double conversion = (Double) current.getMain().getTemp_min(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMin = tempFormatter.format(conversion) + tempSymbol; + } + Bitmap picture; + if ((current.getWeather().size() > 0) + && (current.getWeather().get(0).getIcon() != null) + && (IconsList.getIcon(current.getWeather().get(0).getIcon()) != null)) { + final String icon = current.getWeather().get(0).getIcon(); + picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon) + .getResourceDrawable()); + } else { + picture = BitmapFactory.decodeResource(this.getResources(), + R.drawable.weather_severe_alert); + } + final String city = weatherLocation.getCity(); + final String country = weatherLocation.getCountry(); + + // 4. Insert data in RemoteViews. + final RemoteViews remoteView = new RemoteViews(this.getApplicationContext().getPackageName(), R.layout.notification); + remoteView.setImageViewBitmap(R.id.weather_notification_image, picture); + remoteView.setTextViewText(R.id.weather_notification_temperature_max, tempMax); + remoteView.setTextViewText(R.id.weather_notification_temperature_min, tempMin); + remoteView.setTextViewText(R.id.weather_notification_city, city); + remoteView.setTextViewText(R.id.weather_notification_country, country); + + // 5. Activity launcher. + final Intent resultIntent = new Intent(this.getApplicationContext(), WeatherTabsActivity.class); + // The PendingIntent to launch our activity if the user selects this notification +// final PendingIntent contentIntent = PendingIntent.getActivity( +// this.getApplicationContext(), 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); + // The stack builder object will contain an artificial back stack for the started Activity. + // This ensures that navigating backward from the Activity leads out of + // your application to the Home screen. + final TaskStackBuilder stackBuilder = TaskStackBuilder.create(this.getApplicationContext()); + // Adds the back stack for the Intent (but not the Intent itself) + stackBuilder.addParentStack(WeatherTabsActivity.class); + // Adds the Intent that starts the Activity to the top of the stack + stackBuilder.addNextIntent(resultIntent); + final PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); + + final NotificationManagerCompat notificationManager = + NotificationManagerCompat.from(this.getApplicationContext()); + + + // 6. Create notification. + final NotificationCompat.Builder notificationBuilder = + new NotificationCompat.Builder(this.getApplicationContext()) + .setContent(remoteView) + .setSmallIcon(R.drawable.ic_launcher) + .setAutoCancel(true) + .setLocalOnly(true) + .setWhen(System.currentTimeMillis()) + .setContentIntent(resultPendingIntent) + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + final Notification notification = notificationBuilder.build(); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + + // Send the notification. + // Sets an ID for the notification, so it can be updated (just in case) + int notifyID = 1; + notificationManager.notify(notifyID, notification); + } +} diff --git a/app/src/main/java/de/example/exampletdd/SpecificActivity.java b/app/src/main/java/de/example/exampletdd/SpecificActivity.java new file mode 100644 index 0000000..3cb5da4 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/SpecificActivity.java @@ -0,0 +1,37 @@ +package de.example.exampletdd; + +import android.app.ActionBar; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; + +public class SpecificActivity extends FragmentActivity { + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.weather_specific); + + final ActionBar actionBar = this.getActionBar(); + + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); + actionBar.setDisplayHomeAsUpEnabled(true); + + } + + @Override + public void onResume() { + super.onResume(); + + // 1. Update title. + final DatabaseQueries query = new DatabaseQueries(this); + final WeatherLocation weatherLocation = query.queryDataBase(); + if (weatherLocation != null) { + final ActionBar actionBar = this.getActionBar(); + // TODO: I18N and comma :/ + actionBar.setTitle(weatherLocation.getCity() + "," + weatherLocation.getCountry()); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/WeatherInformationBootReceiver.java b/app/src/main/java/de/example/exampletdd/WeatherInformationBootReceiver.java new file mode 100644 index 0000000..05b751d --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/WeatherInformationBootReceiver.java @@ -0,0 +1,57 @@ +package de.example.exampletdd; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.preference.PreferenceManager; + +public class WeatherInformationBootReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, final Intent intent) { + + if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { + + // Update Time Rate + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); + final String keyPreference = context + .getString(R.string.weather_preferences_update_time_rate_key); + final String updateTimeRate = sharedPreferences.getString(keyPreference, ""); + long chosenInterval = 0; + if (updateTimeRate.equals("900")) { + chosenInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES; + } else if (updateTimeRate.equals("1800")) { + chosenInterval = AlarmManager.INTERVAL_HALF_HOUR; + } else if (updateTimeRate.equals("3600")) { + chosenInterval = AlarmManager.INTERVAL_HOUR; + } else if (updateTimeRate.equals("43200")) { + chosenInterval = AlarmManager.INTERVAL_HALF_DAY; + } else if (updateTimeRate.equals("86400")) { + chosenInterval = AlarmManager.INTERVAL_DAY; + } + + if (chosenInterval != 0) { + final AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + // TODO: better use some string instead of .class? In case I change the service class + // this could be a problem (I guess) + final Intent serviceIntent = new Intent(context, NotificationIntentService.class); + final PendingIntent alarmIntent = PendingIntent.getService( + context, + 0, + serviceIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + alarmMgr.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + chosenInterval, + chosenInterval, + alarmIntent); + } + } + } + +} diff --git a/app/src/main/java/de/example/exampletdd/WeatherInformationPreferencesActivity.java b/app/src/main/java/de/example/exampletdd/WeatherInformationPreferencesActivity.java new file mode 100644 index 0000000..1e9b0d4 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/WeatherInformationPreferencesActivity.java @@ -0,0 +1,27 @@ +package de.example.exampletdd; + +import android.app.ActionBar; +import android.app.Activity; +import android.os.Bundle; +import de.example.exampletdd.fragment.preferences.WeatherInformationPreferencesFragment; + +public class WeatherInformationPreferencesActivity extends Activity { + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.getFragmentManager() + .beginTransaction() + .replace(android.R.id.content, + new WeatherInformationPreferencesFragment()).commit(); + } + + @Override + public void onResume() { + super.onResume(); + + final ActionBar actionBar = this.getActionBar(); + actionBar.setTitle(this.getString(R.string.weather_preferences_action_settings)); + } +} diff --git a/app/src/main/java/de/example/exampletdd/WeatherTabsActivity.java b/app/src/main/java/de/example/exampletdd/WeatherTabsActivity.java new file mode 100644 index 0000000..168ab2e --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/WeatherTabsActivity.java @@ -0,0 +1,182 @@ +package de.example.exampletdd; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.FragmentTransaction; +import android.content.ComponentName; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; +import de.example.exampletdd.fragment.current.CurrentFragment; +import de.example.exampletdd.fragment.overview.OverviewFragment; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; + +public class WeatherTabsActivity extends FragmentActivity { + private static final int NUM_ITEMS = 2; + private ViewPager mPager; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.fragment_pager); + + this.mPager = (ViewPager)this.findViewById(R.id.pager); + this.mPager.setAdapter(new TabsAdapter(this.getSupportFragmentManager())); + + + this.mPager.setOnPageChangeListener( + new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(final int position) { + WeatherTabsActivity.this.getActionBar().setSelectedNavigationItem(position); + } + }); + + + final ActionBar actionBar = this.getActionBar(); + + PreferenceManager.setDefaultValues(this, R.xml.weather_preferences, false); + + // Specify that tabs should be displayed in the action bar. + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); + actionBar.setDisplayHomeAsUpEnabled(true); + + // Create a tab listener that is called when the user changes tabs. + final ActionBar.TabListener tabListener = new ActionBar.TabListener() { + + @Override + public void onTabSelected(final Tab tab, final FragmentTransaction ft) { + WeatherTabsActivity.this.mPager.setCurrentItem(tab.getPosition()); + + } + + @Override + public void onTabUnselected(final Tab tab, final FragmentTransaction ft) { + + } + + @Override + public void onTabReselected(final Tab tab, final FragmentTransaction ft) { + + } + + }; + + actionBar.addTab(actionBar.newTab().setText("CURRENTLY").setTabListener(tabListener)); + actionBar.addTab(actionBar.newTab().setText("FORECAST").setTabListener(tabListener)); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + + this.getMenuInflater().inflate(R.menu.weather_main_menu, menu); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final 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. + super.onOptionsItemSelected(item); + + Intent intent; + final int itemId = item.getItemId(); + if (itemId == R.id.weather_menu_settings) { + intent = new Intent("de.example.exampletdd.WEATHERINFO") + .setComponent(new ComponentName("de.example.exampletdd", + "de.example.exampletdd.WeatherInformationPreferencesActivity")); + this.startActivity(intent); + return true; + } else if (itemId == R.id.weather_menu_map) { + intent = new Intent("de.example.exampletdd.WEATHERINFO"). + setComponent(new ComponentName("de.example.exampletdd", + "de.example.exampletdd.MapActivity")); + this.startActivity(intent); + return true; + } else { + } + + // TODO: calling again super method? + return super.onOptionsItemSelected(item); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + } + + + @Override + public void onResume() { + super.onResume(); + + final ActionBar actionBar = this.getActionBar(); + + // 1. Update title. + final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + if (weatherLocation != null) { + // TODO: I18N and comma :/ + actionBar.setTitle(weatherLocation.getCity() + "," + weatherLocation.getCountry()); + } else { + // TODO: static resource + actionBar.setTitle("no chosen location"); + } + + // 2. Update forecast tab text. + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this); + final String keyPreference = this.getString(R.string.weather_preferences_day_forecast_key); + final String value = sharedPreferences.getString(keyPreference, ""); + String humanValue = ""; + if (value.equals("5")) { + humanValue = "5 DAY FORECAST"; + } else if (value.equals("10")) { + humanValue = "10 DAY FORECAST"; + } else if (value.equals("14")) { + humanValue = "14 DAY FORECAST"; + } + actionBar.getTabAt(1).setText(humanValue); + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + } + + private class TabsAdapter extends FragmentPagerAdapter { + public TabsAdapter(final FragmentManager fm) { + super(fm); + } + + @Override + public int getCount() { + return NUM_ITEMS; + } + + @Override + public Fragment getItem(final int position) { + if (position == 0) { + // TODO: new instance every time I click on tab? + return new CurrentFragment(); + } else { + // TODO: new instance every time I click on tab? + final Fragment fragment = new OverviewFragment(); + return fragment; + } + + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/WidgetIntentService.java b/app/src/main/java/de/example/exampletdd/WidgetIntentService.java new file mode 100644 index 0000000..ed38545 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/WidgetIntentService.java @@ -0,0 +1,304 @@ +package de.example.exampletdd; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Calendar; +import java.util.Locale; + +import org.apache.http.client.ClientProtocolException; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.net.http.AndroidHttpClient; +import android.preference.PreferenceManager; +import android.support.v4.app.TaskStackBuilder; +import android.util.Log; +import android.widget.RemoteViews; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.httpclient.CustomHTTPClient; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.parser.JPOSWeatherParser; +import de.example.exampletdd.service.IconsList; +import de.example.exampletdd.service.PermanentStorage; +import de.example.exampletdd.service.ServiceParser; +import de.example.exampletdd.widget.WidgetConfigure; + +public class WidgetIntentService extends IntentService { + private static final String TAG = "WidgetIntentService"; + + + public WidgetIntentService() { + super("WIS-Thread"); + } + + @Override + protected void onHandleIntent(final Intent intent) { + Log.i(TAG, "onHandleIntent"); + final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + final boolean isUpdateByApp = intent.getBooleanExtra("updateByApp", false); + + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + // Nothing to do. Something went wrong. Show error. + return; + } + + + final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + + if (weatherLocation == null) { + // Nothing to do. Show error. + final RemoteViews view = this.makeErrorView(appWidgetId); + this.updateWidget(view, appWidgetId); + return; + } + + if (isUpdateByApp) { + this.updateByApp(weatherLocation, appWidgetId); + } else { + this.updateByTimeout(weatherLocation, appWidgetId); + } + + } + + private void updateByApp(final WeatherLocation weatherLocation, final int appWidgetId) { + final PermanentStorage store = new PermanentStorage(this.getApplicationContext()); + final Current current = store.getCurrent(); + + this.updateWidget(current, weatherLocation, appWidgetId); + } + + private void updateByTimeout(final WeatherLocation weatherLocation, final int appWidgetId) { + + final Current current = this.getRemoteCurrent(weatherLocation); + + this.updateWidget(current, weatherLocation, appWidgetId); + } + + private void updateWidget(final Current current, final WeatherLocation weatherLocation, final int appWidgetId) { + + if (current != null) { + final RemoteViews view = this.makeView(current, weatherLocation, appWidgetId); + this.updateWidget(view, appWidgetId); + } else { + // Show error. + final RemoteViews view = this.makeErrorView(appWidgetId); + this.updateWidget(view, appWidgetId); + } + } + + + + private Current getRemoteCurrent(final WeatherLocation weatherLocation) { + + final ServiceParser weatherService = new ServiceParser(new JPOSWeatherParser()); + final CustomHTTPClient HTTPClient = new CustomHTTPClient( + AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")); + + try { + return this.getRemoteCurrentThrowable(weatherLocation, HTTPClient, weatherService); + + } catch (final JsonParseException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final ClientProtocolException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final MalformedURLException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final URISyntaxException e) { + Log.e(TAG, "doInBackground exception: ", e); + } catch (final IOException e) { + // logger infrastructure swallows UnknownHostException :/ + Log.e(TAG, "doInBackground exception: " + e.getMessage(), e); + } finally { + HTTPClient.close(); + } + + return null; + } + + private Current getRemoteCurrentThrowable(final WeatherLocation weatherLocation, + final CustomHTTPClient HTTPClient, final ServiceParser weatherService) + throws ClientProtocolException, MalformedURLException, URISyntaxException, + JsonParseException, IOException { + + final String APIVersion = this.getResources().getString(R.string.api_version); + + final String urlAPI = this.getResources().getString(R.string.uri_api_weather_today); + final String url = weatherService.createURIAPICurrent(urlAPI, APIVersion, + weatherLocation.getLatitude(), weatherLocation.getLongitude()); + final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis()); + final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache)); + final Current current = weatherService.retrieveCurrentFromJPOS(jsonData); + // TODO: what is this for? I guess I could skip it :/ + final Calendar now = Calendar.getInstance(); + current.setDate(now.getTime()); + + return current; + } + + private interface UnitsConversor { + + public double doConversion(final double value); + } + + private RemoteViews makeView(final Current current, final WeatherLocation weatherLocation, final int appWidgetId) { + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this.getApplicationContext()); + + // TODO: repeating the same code in Overview, Specific and Current!!! + // 1. Update units of measurement. + // 1.1 Temperature + String tempSymbol; + UnitsConversor tempUnitsConversor; + String keyPreference = this.getResources().getString(R.string.weather_preferences_temperature_key); + String unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + if (unitsPreferenceValue.equals(values[0])) { + tempSymbol = values[0]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value - 273.15; + } + + }; + } else if (unitsPreferenceValue.equals(values[1])) { + tempSymbol = values[1]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return (value * 1.8) - 459.67; + } + + }; + } else { + tempSymbol = values[2]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value; + } + + }; + } + + + // 2. Formatters + final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + tempFormatter.applyPattern("#####.#####"); + + + // 3. Prepare data for RemoteViews. + String tempMax = ""; + if (current.getMain().getTemp_max() != null) { + double conversion = (Double) current.getMain().getTemp_max(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMax = tempFormatter.format(conversion) + tempSymbol; + } + String tempMin = ""; + if (current.getMain().getTemp_min() != null) { + double conversion = (Double) current.getMain().getTemp_min(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMin = tempFormatter.format(conversion) + tempSymbol; + } + Bitmap picture; + if ((current.getWeather().size() > 0) + && (current.getWeather().get(0).getIcon() != null) + && (IconsList.getIcon(current.getWeather().get(0).getIcon()) != null)) { + final String icon = current.getWeather().get(0).getIcon(); + picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon) + .getResourceDrawable()); + } else { + picture = BitmapFactory.decodeResource(this.getResources(), + R.drawable.weather_severe_alert); + } + final String city = weatherLocation.getCity(); + final String country = weatherLocation.getCountry(); + + // 4. Insert data in RemoteViews. + final RemoteViews remoteView = new RemoteViews(this.getApplicationContext().getPackageName(), R.layout.appwidget); + remoteView.setImageViewBitmap(R.id.weather_appwidget_image, picture); + remoteView.setTextViewText(R.id.weather_appwidget_temperature_max, tempMax); + remoteView.setTextViewText(R.id.weather_appwidget_temperature_min, tempMin); + remoteView.setTextViewText(R.id.weather_appwidget_city, city); + remoteView.setTextViewText(R.id.weather_appwidget_country, country); + + // 5. Activity launcher. + final Intent resultIntent = new Intent(this.getApplicationContext(), WidgetConfigure.class); + resultIntent.putExtra("actionFromUser", true); + resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + // From: http://stackoverflow.com/questions/4011178/multiple-instances-of-widget-only-updating-last-widget + final Uri data = Uri.withAppendedPath(Uri.parse("PAIN" + "://widget/id/") ,String.valueOf(appWidgetId)); + resultIntent.setData(data); + + final TaskStackBuilder stackBuilder = TaskStackBuilder.create(this.getApplicationContext()); + // Adds the back stack for the Intent (but not the Intent itself) + stackBuilder.addParentStack(WidgetConfigure.class); + // Adds the Intent that starts the Activity to the top of the stack + stackBuilder.addNextIntent(resultIntent); + final PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); + remoteView.setOnClickPendingIntent(R.id.weather_appwidget, resultPendingIntent); + + return remoteView; + } + + private RemoteViews makeErrorView(final int appWidgetId) { + final RemoteViews remoteView = new RemoteViews(this.getApplicationContext().getPackageName(), R.layout.appwidget_error); + + // 5. Activity launcher. + final Intent resultIntent = new Intent(this.getApplicationContext(), WidgetConfigure.class); + resultIntent.putExtra("actionFromUser", true); + resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + // From: http://stackoverflow.com/questions/4011178/multiple-instances-of-widget-only-updating-last-widget + final Uri data = Uri.withAppendedPath(Uri.parse("PAIN" + "://widget/id/") ,String.valueOf(appWidgetId)); + resultIntent.setData(data); + + final TaskStackBuilder stackBuilder = TaskStackBuilder.create(this.getApplicationContext()); + // Adds the back stack for the Intent (but not the Intent itself) + stackBuilder.addParentStack(WidgetConfigure.class); + // Adds the Intent that starts the Activity to the top of the stack + stackBuilder.addNextIntent(resultIntent); + final PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); + remoteView.setOnClickPendingIntent(R.id.weather_appwidget_error, resultPendingIntent); + + return remoteView; + } + + private void updateWidget(final RemoteViews remoteView, final int appWidgetId) { + + final AppWidgetManager manager = AppWidgetManager.getInstance(this.getApplicationContext()); + manager.updateAppWidget(appWidgetId, remoteView); + } + +// private void updateWidgets(final RemoteViews remoteView) { +// +// final ComponentName widgets = new ComponentName(this.getApplicationContext(), WidgetProvider.class); +// final AppWidgetManager manager = AppWidgetManager.getInstance(this.getApplicationContext()); +// manager.updateAppWidget(widgets, remoteView); +// } +} diff --git a/app/src/main/java/de/example/exampletdd/dummy/DummyContent.java b/app/src/main/java/de/example/exampletdd/dummy/DummyContent.java new file mode 100644 index 0000000..6c09241 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/dummy/DummyContent.java @@ -0,0 +1,55 @@ +package de.example.exampletdd.dummy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Helper class for providing sample content for user interfaces created by + * Android template wizards. + *

+ * TODO: Replace all uses of this class before publishing your app. + */ +public class DummyContent { + + /** + * An array of sample (dummy) items. + */ + public static List ITEMS = new ArrayList(); + + /** + * A map of sample (dummy) items, by ID. + */ + public static Map ITEM_MAP = new HashMap(); + + static { + // Add 3 sample items. + addItem(new DummyItem("1", "Item 1")); + addItem(new DummyItem("2", "Item 2")); + addItem(new DummyItem("3", "Item 3")); + } + + private static void addItem(DummyItem item) { + ITEMS.add(item); + ITEM_MAP.put(item.id, item); + } + + /** + * A dummy item representing a piece of content. + */ + public static class DummyItem { + public String id; + public String content; + + public DummyItem(String id, String content) { + this.id = id; + this.content = content; + } + + @Override + public String toString() { + return content; + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/ErrorDialogFragment.java b/app/src/main/java/de/example/exampletdd/fragment/ErrorDialogFragment.java new file mode 100644 index 0000000..5d00707 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/ErrorDialogFragment.java @@ -0,0 +1,45 @@ +package de.example.exampletdd.fragment; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; + +public class ErrorDialogFragment extends DialogFragment { + + public static ErrorDialogFragment newInstance(final int title) { + final ErrorDialogFragment frag = new ErrorDialogFragment(); + final Bundle args = new Bundle(); + + args.putInt("title", title); + frag.setArguments(args); + + return frag; + } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final int title = this.getArguments().getInt("title"); + + return new AlertDialog.Builder(this.getActivity()) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(title) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, + final int whichButton) { + + } + }).create(); + } + + @Override + public void onDestroyView() { + if (getDialog() != null && getRetainInstance()) { + getDialog().setDismissMessage(null); + } + super.onDestroyView(); + } +} \ No newline at end of file diff --git a/app/src/main/java/de/example/exampletdd/fragment/ProgressDialogFragment.java b/app/src/main/java/de/example/exampletdd/fragment/ProgressDialogFragment.java new file mode 100644 index 0000000..7ff9960 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/ProgressDialogFragment.java @@ -0,0 +1,54 @@ +package de.example.exampletdd.fragment; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.KeyEvent; + +public class ProgressDialogFragment extends DialogFragment { + + public static ProgressDialogFragment newInstance(final int title) { + return newInstance(title, null); + } + + public static ProgressDialogFragment newInstance(final int title, + final String message) { + final ProgressDialogFragment frag = new ProgressDialogFragment(); + final Bundle args = new Bundle(); + + args.putInt("title", title); + args.putString("message", message); + frag.setArguments(args); + return frag; + } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final int title = this.getArguments().getInt("title"); + final String message = this.getArguments().getString("message"); + + final ProgressDialog dialog = new ProgressDialog(this.getActivity()); + dialog.setIcon(android.R.drawable.ic_dialog_info); + if (title != 0) { + dialog.setTitle(title); + } + if (message != null) { + dialog.setMessage(message); + } + dialog.setCancelable(false); + this.setCancelable(false); + dialog.setIndeterminate(true); + dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + + @Override + public final boolean onKey(final DialogInterface dialog, + final int keyCode, final KeyEvent event) { + return false; + } + }); + + return dialog; + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/current/CurrentFragment.java b/app/src/main/java/de/example/exampletdd/fragment/current/CurrentFragment.java new file mode 100644 index 0000000..13f9a77 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/current/CurrentFragment.java @@ -0,0 +1,528 @@ +package de.example.exampletdd.fragment.current; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import org.apache.http.client.ClientProtocolException; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.http.AndroidHttpClient; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.R; +import de.example.exampletdd.WidgetIntentService; +import de.example.exampletdd.httpclient.CustomHTTPClient; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.parser.JPOSWeatherParser; +import de.example.exampletdd.service.IconsList; +import de.example.exampletdd.service.PermanentStorage; +import de.example.exampletdd.service.ServiceParser; + +public class CurrentFragment extends Fragment { + private static final String TAG = "CurrentFragment"; + private BroadcastReceiver mReceiver; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.weather_current_fragment, container, false); + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) { + // Restore UI state + final Current current = (Current) savedInstanceState.getSerializable("Current"); + + // TODO: Could it be better to store in global forecast data even if it is null value? + // So, perhaps do not check for null value and always store in global variable. + if (current != null) { + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + store.saveCurrent(current); + } + } + + this.setHasOptionsMenu(false); + + this.getActivity().findViewById(R.id.weather_current_data_container).setVisibility(View.GONE); + this.getActivity().findViewById(R.id.weather_current_progressbar).setVisibility(View.VISIBLE); + this.getActivity().findViewById(R.id.weather_current_error_message).setVisibility(View.GONE); + } + + @Override + public void onResume() { + super.onResume(); + + + this.mReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent.getAction(); + if (action.equals("de.example.exampletdd.UPDATECURRENT")) { + final Current currentRemote = (Current) intent.getSerializableExtra("current"); + + if (currentRemote != null) { + + // 1. Check conditions. They must be the same as the ones that triggered the AsyncTask. + final DatabaseQueries query = new DatabaseQueries(context.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + final PermanentStorage store = new PermanentStorage(context.getApplicationContext()); + final Current current = store.getCurrent(); + + if (current == null || !CurrentFragment.this.isDataFresh(weatherLocation.getLastCurrentUIUpdate())) { + // 2. Update UI. + CurrentFragment.this.updateUI(currentRemote); + + // 3. Update Data. + store.saveCurrent(currentRemote); + weatherLocation.setLastCurrentUIUpdate(new Date()); + query.updateDataBase(weatherLocation); + } + + } else { + // Empty UI and show error message + CurrentFragment.this.getActivity().findViewById(R.id.weather_current_data_container).setVisibility(View.GONE); + CurrentFragment.this.getActivity().findViewById(R.id.weather_current_progressbar).setVisibility(View.GONE); + CurrentFragment.this.getActivity().findViewById(R.id.weather_current_error_message).setVisibility(View.VISIBLE); + } + } + } + }; + + // Register receiver + final IntentFilter filter = new IntentFilter(); + filter.addAction("de.example.exampletdd.UPDATECURRENT"); + LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()) + .registerReceiver(this.mReceiver, filter); + + // Empty UI + this.getActivity().findViewById(R.id.weather_current_data_container).setVisibility(View.GONE); + + final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + if (weatherLocation == null) { + // Nothing to do. + // Show error message + final ProgressBar progress = (ProgressBar) getActivity().findViewById(R.id.weather_current_progressbar); + progress.setVisibility(View.GONE); + final TextView errorMessage = (TextView) getActivity().findViewById(R.id.weather_current_error_message); + errorMessage.setVisibility(View.VISIBLE); + return; + } + + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Current current = store.getCurrent(); + + if (current != null && this.isDataFresh(weatherLocation.getLastCurrentUIUpdate())) { + this.updateUI(current); + } else { + // Load remote data (aynchronous) + // Gets the data from the web. + this.getActivity().findViewById(R.id.weather_current_progressbar).setVisibility(View.VISIBLE); + this.getActivity().findViewById(R.id.weather_current_error_message).setVisibility(View.GONE); + final CurrentTask task = new CurrentTask( + this.getActivity().getApplicationContext(), + new CustomHTTPClient(AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")), + new ServiceParser(new JPOSWeatherParser())); + + task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude()); + // TODO: make sure UI thread keeps running in parallel after that. I guess. + } + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + + // Save UI state + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Current current = store.getCurrent(); + + // TODO: Could it be better to save current data even if it is null value? + // So, perhaps do not check for null value. + if (current != null) { + savedInstanceState.putSerializable("Current", current); + } + + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onPause() { + LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).unregisterReceiver(this.mReceiver); + + super.onPause(); + } + + private interface UnitsConversor { + + public double doConversion(final double value); + } + + private void updateUI(final Current current) { + + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this.getActivity().getApplicationContext()); + + // TODO: repeating the same code in Overview, Specific and Current!!! + // 1. Update units of measurement. + // 1.1 Temperature + String tempSymbol; + UnitsConversor tempUnitsConversor; + String keyPreference = this.getResources().getString(R.string.weather_preferences_temperature_key); + String unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + if (unitsPreferenceValue.equals(values[0])) { + tempSymbol = values[0]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value - 273.15; + } + + }; + } else if (unitsPreferenceValue.equals(values[1])) { + tempSymbol = values[1]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return (value * 1.8) - 459.67; + } + + }; + } else { + tempSymbol = values[2]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value; + } + + }; + } + + // 1.2 Wind + String windSymbol; + UnitsConversor windUnitsConversor; + keyPreference = this.getResources().getString(R.string.weather_preferences_wind_key); + unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + values = this.getResources().getStringArray(R.array.weather_preferences_wind); + if (unitsPreferenceValue.equals(values[0])) { + windSymbol = values[0]; + windUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value; + } + }; + } else { + windSymbol = values[1]; + windUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value * 2.237; + } + }; + } + + // 1.3 Pressure + String pressureSymbol; + UnitsConversor pressureUnitsConversor; + keyPreference = this.getResources().getString(R.string.weather_preferences_pressure_key); + unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + values = this.getResources().getStringArray(R.array.weather_preferences_pressure); + if (unitsPreferenceValue.equals(values[0])) { + pressureSymbol = values[0]; + pressureUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value; + } + }; + } else { + pressureSymbol = values[1]; + pressureUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value / 113.25d; + } + }; + } + + + // 2. Formatters + final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + tempFormatter.applyPattern("#####.#####"); + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.US); + + + // 3. Prepare data for UI. + String tempMax = ""; + if (current.getMain().getTemp_max() != null) { + double conversion = (Double) current.getMain().getTemp_max(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMax = tempFormatter.format(conversion) + tempSymbol; + } + String tempMin = ""; + if (current.getMain().getTemp_min() != null) { + double conversion = (Double) current.getMain().getTemp_min(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMin = tempFormatter.format(conversion) + tempSymbol; + } + Bitmap picture; + if ((current.getWeather().size() > 0) + && (current.getWeather().get(0).getIcon() != null) + && (IconsList.getIcon(current.getWeather().get(0).getIcon()) != null)) { + final String icon = current.getWeather().get(0).getIcon(); + picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon) + .getResourceDrawable()); + } else { + picture = BitmapFactory.decodeResource(this.getResources(), + R.drawable.weather_severe_alert); + } + + // TODO: static resource + String description = "no description available"; + if (current.getWeather().size() > 0) { + description = current.getWeather().get(0).getDescription(); + } + + // TODO: units!!!! + String humidityValue = ""; + if ((current.getMain() != null) + && (current.getMain().getHumidity() != null)) { + final double conversion = (Double) current.getMain().getHumidity(); + humidityValue = tempFormatter.format(conversion); + } + String pressureValue = ""; + if ((current.getMain() != null) + && (current.getMain().getPressure() != null)) { + double conversion = (Double) current.getMain().getPressure(); + conversion = pressureUnitsConversor.doConversion(conversion); + pressureValue = tempFormatter.format(conversion); + } + String windValue = ""; + if ((current.getWind() != null) + && (current.getWind().getSpeed() != null)) { + double conversion = (Double) current.getWind().getSpeed(); + conversion = windUnitsConversor.doConversion(conversion); + windValue = tempFormatter.format(conversion); + } + String rainValue = ""; + if ((current.getRain() != null) + && (current.getRain().get3h() != null)) { + final double conversion = (Double) current.getRain().get3h(); + rainValue = tempFormatter.format(conversion); + } + String cloudsValue = ""; + if ((current.getClouds() != null) + && (current.getClouds().getAll() != null)) { + final double conversion = (Double) current.getClouds().getAll(); + cloudsValue = tempFormatter.format(conversion); + } + String snowValue = ""; + if ((current.getSnow() != null) + && (current.getSnow().get3h() != null)) { + final double conversion = (Double) current.getSnow().get3h(); + snowValue = tempFormatter.format(conversion); + } + String feelsLike = ""; + if (current.getMain().getTemp() != null) { + double conversion = (Double) current.getMain().getTemp(); + conversion = tempUnitsConversor.doConversion(conversion); + feelsLike = tempFormatter.format(conversion); + } + String sunRiseTime = ""; + if (current.getSys().getSunrise() != null) { + final long unixTime = (Long) current.getSys().getSunrise(); + final Date unixDate = new Date(unixTime * 1000L); + sunRiseTime = dateFormat.format(unixDate); + } + String sunSetTime = ""; + if (current.getSys().getSunset() != null) { + final long unixTime = (Long) current.getSys().getSunset(); + final Date unixDate = new Date(unixTime * 1000L); + sunSetTime = dateFormat.format(unixDate); + } + + + // 4. Update UI. + final TextView tempMaxView = (TextView) getActivity().findViewById(R.id.weather_current_temp_max); + tempMaxView.setText(tempMax); + final TextView tempMinView = (TextView) getActivity().findViewById(R.id.weather_current_temp_min); + tempMinView.setText(tempMin); + final ImageView pictureView = (ImageView) getActivity().findViewById(R.id.weather_current_picture); + pictureView.setImageBitmap(picture); + + final TextView descriptionView = (TextView) getActivity().findViewById(R.id.weather_current_description); + descriptionView.setText(description); + + ((TextView) getActivity().findViewById(R.id.weather_current_humidity_value)).setText(humidityValue); + ((TextView) getActivity().findViewById(R.id.weather_current_humidity_units)).setText( + this.getActivity().getApplicationContext().getString(R.string.text_units_percent)); + + ((TextView) getActivity().findViewById(R.id.weather_current_pressure_value)).setText(pressureValue); + ((TextView) getActivity().findViewById(R.id.weather_current_pressure_units)).setText(pressureSymbol); + + ((TextView) getActivity().findViewById(R.id.weather_current_wind_value)).setText(windValue); + ((TextView) getActivity().findViewById(R.id.weather_current_wind_units)).setText(windSymbol); + + ((TextView) getActivity().findViewById(R.id.weather_current_rain_value)).setText(rainValue); + ((TextView) getActivity().findViewById(R.id.weather_current_rain_units)).setText( + this.getActivity().getApplicationContext().getString(R.string.text_units_mm3h)); + + ((TextView) getActivity().findViewById(R.id.weather_current_clouds_value)).setText(cloudsValue); + ((TextView) getActivity().findViewById(R.id.weather_current_clouds_units)).setText( + this.getActivity().getApplicationContext().getString(R.string.text_units_percent)); + + ((TextView) getActivity().findViewById(R.id.weather_current_snow_value)).setText(snowValue); + ((TextView) getActivity().findViewById(R.id.weather_current_snow_units)).setText( + this.getActivity().getApplicationContext().getString(R.string.text_units_mm3h)); + + ((TextView) getActivity().findViewById(R.id.weather_current_feelslike_value)).setText(feelsLike); + ((TextView) getActivity().findViewById(R.id.weather_current_feelslike_units)).setText(tempSymbol); + + ((TextView) getActivity().findViewById(R.id.weather_current_sunrise_value)).setText(sunRiseTime); + + ((TextView) getActivity().findViewById(R.id.weather_current_sunset_value)).setText(sunSetTime); + + this.getActivity().findViewById(R.id.weather_current_data_container).setVisibility(View.VISIBLE); + this.getActivity().findViewById(R.id.weather_current_progressbar).setVisibility(View.GONE); + this.getActivity().findViewById(R.id.weather_current_error_message).setVisibility(View.GONE); + } + + private boolean isDataFresh(final Date lastUpdate) { + if (lastUpdate == null) { + return false; + } + + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences( + this.getActivity().getApplicationContext()); + final String keyPreference = this.getString(R.string.weather_preferences_refresh_interval_key); + final String refresh = sharedPreferences.getString( + keyPreference, + this.getResources().getStringArray(R.array.weather_preferences_refresh_interval)[0]); + final Date currentTime = new Date(); + if (((currentTime.getTime() - lastUpdate.getTime())) < Long.valueOf(refresh)) { + return true; + } + + return false; + } + + // TODO: How could I show just one progress dialog when I have two fragments in tabs + // activity doing the same in background? + // I mean, if OverviewTask shows one progress dialog and CurrentTask does the same I will have + // have two progress dialogs... How may I solve this problem? I HATE ANDROID. + private class CurrentTask extends AsyncTask { + // Store the context passed to the AsyncTask when the system instantiates it. + private final Context localContext; + final CustomHTTPClient HTTPClient; + final ServiceParser weatherService; + + public CurrentTask(final Context context, final CustomHTTPClient HTTPClient, + final ServiceParser weatherService) { + this.localContext = context; + this.HTTPClient = HTTPClient; + this.weatherService = weatherService; + } + + @Override + protected Current doInBackground(final Object... params) { + final double latitude = (Double) params[0]; + final double longitude = (Double) params[1]; + + Current current = null; + try { + current = this.doInBackgroundThrowable(latitude, longitude); + } catch (final JsonParseException e) { + Log.e(TAG, "CurrentTask doInBackground exception: ", e); + } catch (final ClientProtocolException e) { + Log.e(TAG, "CurrentTask doInBackground exception: ", e); + } catch (final MalformedURLException e) { + Log.e(TAG, "CurrentTask doInBackground exception: ", e); + } catch (final URISyntaxException e) { + Log.e(TAG, "CurrentTask doInBackground exception: ", e); + } catch (final IOException e) { + // logger infrastructure swallows UnknownHostException :/ + Log.e(TAG, "CurrentTask doInBackground exception: " + e.getMessage(), e); + } finally { + HTTPClient.close(); + } + + return current; + } + + private Current doInBackgroundThrowable(final double latitude, final double longitude) + throws URISyntaxException, ClientProtocolException, JsonParseException, IOException { + + final String APIVersion = localContext.getResources().getString(R.string.api_version); + final String urlAPI = localContext.getResources().getString(R.string.uri_api_weather_today); + final String url = weatherService.createURIAPICurrent(urlAPI, APIVersion, latitude, longitude); + final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis()); + final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache)); + final Current current = weatherService.retrieveCurrentFromJPOS(jsonData); + // TODO: what is this for? I guess I could skip it :/ + final Calendar now = Calendar.getInstance(); + current.setDate(now.getTime()); + + return current; + } + + @Override + protected void onPostExecute(final Current current) { + // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method? + // I hope so, otherwise I must catch(Throwable) in doInBackground method :( + + // Call updateUI on the UI thread. + final Intent currentData = new Intent("de.example.exampletdd.UPDATECURRENT"); + currentData.putExtra("current", current); + LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(currentData); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/map/MapButtonsFragment.java b/app/src/main/java/de/example/exampletdd/fragment/map/MapButtonsFragment.java new file mode 100644 index 0000000..a063eb5 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/map/MapButtonsFragment.java @@ -0,0 +1,31 @@ +package de.example.exampletdd.fragment.map; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import de.example.exampletdd.R; + +public class MapButtonsFragment extends Fragment { + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.weather_map_buttons, container, false); + } + + /** + * This method will only be called once when the retained + * Fragment is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Retain this fragment across configuration changes. + this.setRetainInstance(true); + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/map/MapProgressFragment.java b/app/src/main/java/de/example/exampletdd/fragment/map/MapProgressFragment.java new file mode 100644 index 0000000..aa4b6e4 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/map/MapProgressFragment.java @@ -0,0 +1,165 @@ +package de.example.exampletdd.fragment.map; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import android.app.Activity; +import android.content.Context; +import android.location.Address; +import android.location.Geocoder; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import de.example.exampletdd.R; +import de.example.exampletdd.model.WeatherLocation; + +/** + * {@link http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html} + * + */ +public class MapProgressFragment extends Fragment { + + /** + * + * Callback interface through which the fragment will report the + * task's progress and results back to the Activity. + */ + public static interface TaskCallbacks { + void onPostExecute(final WeatherLocation weatherLocation); + } + + private TaskCallbacks mCallbacks; + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.weather_map_progress, container, false); + } + + /** + * This method will only be called once when the retained + * Fragment is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Retain this fragment across configuration changes. + this.setRetainInstance(true); + + final Bundle bundle = this.getArguments(); + double latitude = bundle.getDouble("latitude"); + double longitude = bundle.getDouble("longitude"); + + // Create and execute the background task. + new GetAddressTask(this.getActivity().getApplicationContext()).execute(latitude, longitude); + } + + /** + * Hold a reference to the parent Activity so we can report the + * task's current progress and results. The Android framework + * will pass us a reference to the newly created Activity after + * each configuration change. + */ + @Override + public void onAttach(final Activity activity) { + super.onAttach(activity); + mCallbacks = (TaskCallbacks) activity; + } + + /** + * Set the callback to null so we don't accidentally leak the + * Activity instance. + */ +// @Override +// public void onDetach() { +// super.onDetach(); +// mCallbacks = null; +// } + + /** + * I am not using onDetach because there are problems when my activity goes to background. + * + * {@link http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html} + */ + @Override + public void onPause() { + super.onPause(); + mCallbacks = null; + } + + private class GetAddressTask extends AsyncTask { + private static final String TAG = "GetAddressTask"; + // Store the context passed to the AsyncTask when the system instantiates it. + private final Context localContext; + + private GetAddressTask(final Context context) { + this.localContext = context; + } + + @Override + protected WeatherLocation doInBackground(final Object... params) { + final double latitude = (Double) params[0]; + final double longitude = (Double) params[1]; + + WeatherLocation weatherLocation = this.doDefaultLocation(latitude, longitude); + try { + weatherLocation = this.getLocation(latitude, longitude); + } catch (final Throwable e) { // Hopefully nothing goes wrong because of catching Throwable. + Log.e(TAG, "GetAddressTask doInBackground exception: ", e); + } + + return weatherLocation; + } + + @Override + protected void onPostExecute(final WeatherLocation weatherLocation) { + // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method? + // I hope so, otherwise I must catch(Throwable) in doInBackground method :( + + // Call updateUI on the UI thread. + if (mCallbacks != null) { + mCallbacks.onPostExecute(weatherLocation); + } + } + + private WeatherLocation getLocation(final double latitude, final double longitude) throws IOException { + // TODO: i18n Locale.getDefault() + final Geocoder geocoder = new Geocoder(this.localContext, Locale.US); + final List

addresses = geocoder.getFromLocation(latitude, longitude, 1); + + // Default values + WeatherLocation weatherLocation = this.doDefaultLocation(latitude, longitude); + + if (addresses != null && addresses.size() > 0) { + if (addresses.get(0).getLocality() != null) { + weatherLocation.setCity(addresses.get(0).getLocality()); + } + if(addresses.get(0).getCountryName() != null) { + weatherLocation.setCountry(addresses.get(0).getCountryName()); + } + } + + return weatherLocation; + } + + private WeatherLocation doDefaultLocation(final double latitude, final double longitude) { + // Default values + String city = this.localContext.getString(R.string.city_not_found); + String country = this.localContext.getString(R.string.country_not_found); + + return new WeatherLocation() + .setLatitude(latitude) + .setLongitude(longitude) + .setCity(city) + .setCountry(country); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewAdapter.java b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewAdapter.java new file mode 100644 index 0000000..13641b6 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewAdapter.java @@ -0,0 +1,108 @@ +package de.example.exampletdd.fragment.overview; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import de.example.exampletdd.R; + +public class OverviewAdapter extends ArrayAdapter { + private final int resource; + + public OverviewAdapter(final Context context, final int resource) { + super(context, 0); + + this.resource = resource; + } + + @Override + public View getView(final int position, final View convertView, + final ViewGroup parent) { + + // We need to get the best view (re-used if possible) and then + // retrieve its corresponding ViewHolder, which optimizes lookup + // efficiency + final View view = this.getWorkingView(convertView); + final ViewHolder viewHolder = this.getViewHolder(view); + final OverviewEntry entry = this.getItem(position); + + + // Setting date + viewHolder.dateNameView.setText(entry.getDateName()); + viewHolder.dateNumberView.setText(entry.getDateNumber()); + + // Setting temperature max/min + viewHolder.temperatureMaxView.setText(entry.getMaxTemp()); + viewHolder.temperatureMinView.setText(entry.getMinTemp()); + + // Set image view + viewHolder.pictureView.setImageBitmap(entry.getPicture()); + + + return view; + } + + private View getWorkingView(final View convertView) { + // The workingView is basically just the convertView re-used if possible + // or inflated new if not possible + View workingView = null; + + if(null == convertView) { + final Context context = this.getContext(); + final LayoutInflater inflater = (LayoutInflater)context.getSystemService + (Context.LAYOUT_INFLATER_SERVICE); + + workingView = inflater.inflate(this.resource, null); + } else { + workingView = convertView; + } + + return workingView; + } + + private ViewHolder getViewHolder(final View workingView) { + // The viewHolder allows us to avoid re-looking up view references + // Since views are recycled, these references will never change + final Object tag = workingView.getTag(); + ViewHolder viewHolder = null; + + + if((null == tag) || !(tag instanceof ViewHolder)) { + viewHolder = new ViewHolder(); + + viewHolder.dateNameView = (TextView) workingView + .findViewById(R.id.weather_main_entry_date_name); + viewHolder.dateNumberView = (TextView) workingView + .findViewById(R.id.weather_main_entry_date_number); + viewHolder.temperatureMaxView = (TextView) workingView + .findViewById(R.id.weather_main_entry_temperature_max); + viewHolder.temperatureMinView = (TextView) workingView + .findViewById(R.id.weather_main_entry_temperature_min); + viewHolder.pictureView = (ImageView) workingView + .findViewById(R.id.weather_main_entry_image); + + workingView.setTag(viewHolder); + + } else { + viewHolder = (ViewHolder) tag; + } + + return viewHolder; + } + + /** + * ViewHolder allows us to avoid re-looking up view references + * Since views are recycled, these references will never change + */ + private static class ViewHolder { + public TextView dateNameView; + public TextView dateNumberView; + public TextView temperatureMaxView; + public TextView temperatureMinView; + public ImageView pictureView; + } + +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewEntry.java b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewEntry.java new file mode 100644 index 0000000..16135f9 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewEntry.java @@ -0,0 +1,41 @@ +package de.example.exampletdd.fragment.overview; + +import android.graphics.Bitmap; + +public class OverviewEntry { + private final String dateName; + private final String dateNumber; + private final String maxTemp; + private final String minTemp; + private final Bitmap picture; + + public OverviewEntry(final String dateName, final String dateNumber, + final String maxTemp, final String minTemp, + final Bitmap picture) { + this.dateName = dateName; + this.dateNumber = dateNumber; + this.maxTemp = maxTemp; + this.minTemp = minTemp; + this.picture = picture; + } + + public String getDateName() { + return this.dateName; + } + + public String getDateNumber() { + return this.dateNumber; + } + + public String getMaxTemp() { + return this.maxTemp; + } + + public String getMinTemp() { + return this.minTemp; + } + + public Bitmap getPicture() { + return this.picture; + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewFragment.java b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewFragment.java new file mode 100644 index 0000000..682e5bd --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/overview/OverviewFragment.java @@ -0,0 +1,410 @@ +package de.example.exampletdd.fragment.overview; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import org.apache.http.client.ClientProtocolException; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.http.AndroidHttpClient; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.ListFragment; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.view.View; +import android.widget.ListView; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.R; +import de.example.exampletdd.fragment.specific.SpecificFragment; +import de.example.exampletdd.httpclient.CustomHTTPClient; +import de.example.exampletdd.model.DatabaseQueries; +import de.example.exampletdd.model.WeatherLocation; +import de.example.exampletdd.model.forecastweather.Forecast; +import de.example.exampletdd.parser.JPOSWeatherParser; +import de.example.exampletdd.service.IconsList; +import de.example.exampletdd.service.PermanentStorage; +import de.example.exampletdd.service.ServiceParser; + +public class OverviewFragment extends ListFragment { + private static final String TAG = "OverviewFragment"; + private BroadcastReceiver mReceiver; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final ListView listWeatherView = this.getListView(); + listWeatherView.setChoiceMode(ListView.CHOICE_MODE_NONE); + + if (savedInstanceState != null) { + // Restore UI state + final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast"); + + // TODO: Could it be better to store in global forecast data even if it is null value? + // So, perhaps do not check for null value and always store in global variable. + if (forecast != null) { + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + store.saveForecast(forecast); + } + } + + this.setHasOptionsMenu(false); + + this.setEmptyText(this.getString(R.string.text_field_remote_error)); + this.setListShownNoAnimation(false); + } + + @Override + public void onResume() { + super.onResume(); + + this.mReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent.getAction(); + if (action.equals("de.example.exampletdd.UPDATEFORECAST")) { + final Forecast forecastRemote = (Forecast) intent.getSerializableExtra("forecast"); + + if (forecastRemote != null) { + + // 1. Check conditions. They must be the same as the ones that triggered the AsyncTask. + final DatabaseQueries query = new DatabaseQueries(context.getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + final PermanentStorage store = new PermanentStorage(context.getApplicationContext()); + final Forecast forecast = store.getForecast(); + + if (forecast == null || !OverviewFragment.this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) { + // 2. Update UI. + OverviewFragment.this.updateUI(forecastRemote); + + // 3. Update Data. + store.saveForecast(forecastRemote); + weatherLocation.setLastForecastUIUpdate(new Date()); + query.updateDataBase(weatherLocation); + + // 4. Show list. + OverviewFragment.this.setListShownNoAnimation(true); + } + + } else { + // Empty list and show error message (see setEmptyText in onCreate) + OverviewFragment.this.setListAdapter(null); + OverviewFragment.this.setListShownNoAnimation(true); + } + } + } + }; + + // Register receiver + final IntentFilter filter = new IntentFilter(); + filter.addAction("de.example.exampletdd.UPDATEFORECAST"); + LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()) + .registerReceiver(this.mReceiver, filter); + + final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext()); + final WeatherLocation weatherLocation = query.queryDataBase(); + if (weatherLocation == null) { + // Nothing to do. + // Empty list and show error message (see setEmptyText in onCreate) + this.setListAdapter(null); + this.setListShownNoAnimation(true); + return; + } + + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Forecast forecast = store.getForecast(); + + // TODO: store forecast data in permanent storage and check here if there is data in permanent storage + if (forecast != null && this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) { + this.updateUI(forecast); + } else { + // Load remote data (aynchronous) + // Gets the data from the web. + this.setListShownNoAnimation(false); + final OverviewTask task = new OverviewTask( + this.getActivity().getApplicationContext(), + new CustomHTTPClient(AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")), + new ServiceParser(new JPOSWeatherParser())); + + task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude()); + // TODO: make sure thread UI keeps running in parallel after that. I guess. + } + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + + // Save UI state + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Forecast forecast = store.getForecast(); + + // TODO: Could it be better to save forecast data even if it is null value? + // So, perhaps do not check for null value. + if (forecast != null) { + savedInstanceState.putSerializable("Forecast", forecast); + } + + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onPause() { + LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).unregisterReceiver(this.mReceiver); + + super.onPause(); + } + + @Override + public void onListItemClick(final ListView l, final View v, final int position, final long id) { + final SpecificFragment fragment = (SpecificFragment) this + .getFragmentManager().findFragmentById(R.id.weather_specific_fragment); + if (fragment == null) { + // handset layout + final Intent intent = new Intent("de.example.exampletdd.WEATHERINFO") + .setComponent(new ComponentName("de.example.exampletdd", + "de.example.exampletdd.SpecificActivity")); + intent.putExtra("CHOSEN_DAY", (int) id); + OverviewFragment.this.getActivity().startActivity(intent); + } else { + // tablet layout + fragment.updateUIByChosenDay((int) id); + } + } + + private interface UnitsConversor { + + public double doConversion(final double value); + } + + private void updateUI(final Forecast forecastWeatherData) { + + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this.getActivity().getApplicationContext()); + + // TODO: repeating the same code in Overview, Specific and Current!!! + // 1. Update units of measurement. + String symbol; + UnitsConversor unitsConversor; + String keyPreference = this.getResources().getString( + R.string.weather_preferences_temperature_key); + final String unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + if (unitsPreferenceValue.equals(values[0])) { + symbol = values[0]; + unitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value - 273.15; + } + + }; + } else if (unitsPreferenceValue.equals(values[1])) { + symbol = values[1]; + unitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return (value * 1.8) - 459.67; + } + + }; + } else { + symbol = values[2]; + unitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value; + } + + }; + } + + + // 2. Update number day forecast. + keyPreference = this.getResources().getString(R.string.weather_preferences_day_forecast_key); + final String dayForecast = sharedPreferences.getString(keyPreference, "5"); + final int mDayForecast = Integer.valueOf(dayForecast); + + + // 3. Formatters + final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + tempFormatter.applyPattern("#####.##"); + final SimpleDateFormat dayNameFormatter = new SimpleDateFormat("EEE", Locale.US); + final SimpleDateFormat monthAndDayNumberormatter = new SimpleDateFormat("MMM d", Locale.US); + + + // 4. Prepare data for UI. + final List entries = new ArrayList(); + final OverviewAdapter adapter = new OverviewAdapter(this.getActivity(), + R.layout.weather_main_entry_list); + final Calendar calendar = Calendar.getInstance(); + int count = mDayForecast; + for (final de.example.exampletdd.model.forecastweather.List forecast : forecastWeatherData + .getList()) { + + Bitmap picture; + + if ((forecast.getWeather().size() > 0) && + (forecast.getWeather().get(0).getIcon() != null) && + (IconsList.getIcon(forecast.getWeather().get(0).getIcon()) != null)) { + final String icon = forecast.getWeather().get(0).getIcon(); + picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon) + .getResourceDrawable()); + } else { + picture = BitmapFactory.decodeResource(this.getResources(), + R.drawable.weather_severe_alert); + } + + final Long forecastUNIXDate = (Long) forecast.getDt(); + calendar.setTimeInMillis(forecastUNIXDate * 1000L); + final Date dayTime = calendar.getTime(); + final String dayTextName = dayNameFormatter.format(dayTime); + final String monthAndDayNumberText = monthAndDayNumberormatter.format(dayTime); + + Double maxTemp = null; + if (forecast.getTemp().getMax() != null) { + maxTemp = (Double) forecast.getTemp().getMax(); + maxTemp = unitsConversor.doConversion(maxTemp); + } + + Double minTemp = null; + if (forecast.getTemp().getMin() != null) { + minTemp = (Double) forecast.getTemp().getMin(); + minTemp = unitsConversor.doConversion(minTemp); + } + + if ((maxTemp != null) && (minTemp != null)) { + entries.add(new OverviewEntry(dayTextName, monthAndDayNumberText, + tempFormatter.format(maxTemp) + symbol, tempFormatter.format(minTemp) + symbol, + picture)); + } + + count = count - 1; + if (count == 0) { + break; + } + } + + + // 5. Update UI. + adapter.addAll(entries); + this.setListAdapter(adapter); + } + + private boolean isDataFresh(final Date lastUpdate) { + if (lastUpdate == null) { + return false; + } + + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences( + this.getActivity().getApplicationContext()); + final String keyPreference = this.getString(R.string.weather_preferences_refresh_interval_key); + final String refresh = sharedPreferences.getString( + keyPreference, + this.getResources().getStringArray(R.array.weather_preferences_refresh_interval)[0]); + final Date currentTime = new Date(); + if (((currentTime.getTime() - lastUpdate.getTime())) < Long.valueOf(refresh)) { + return true; + } + + return false; + } + + // TODO: How could I show just one progress dialog when I have two fragments in tabs + // activity doing the same in background? + // I mean, if OverviewTask shows one progress dialog and CurrentTask does the same I will have + // have two progress dialogs... How may I solve this problem? I HATE ANDROID. + private class OverviewTask extends AsyncTask { + // Store the context passed to the AsyncTask when the system instantiates it. + private final Context localContext; + private final CustomHTTPClient HTTPClient; + private final ServiceParser weatherService; + + public OverviewTask(final Context context, final CustomHTTPClient HTTPClient, + final ServiceParser weatherService) { + this.localContext = context; + this.HTTPClient = HTTPClient; + this.weatherService = weatherService; + } + + @Override + protected Forecast doInBackground(final Object... params) { + final double latitude = (Double) params[0]; + final double longitude = (Double) params[1]; + + Forecast forecast = null; + + try { + forecast = this.doInBackgroundThrowable(latitude, longitude); + } catch (final JsonParseException e) { + Log.e(TAG, "OverviewTask doInBackground exception: ", e); + } catch (final ClientProtocolException e) { + Log.e(TAG, "OverviewTask doInBackground exception: ", e); + } catch (final MalformedURLException e) { + Log.e(TAG, "OverviewTask doInBackground exception: ", e); + } catch (final URISyntaxException e) { + Log.e(TAG, "OverviewTask doInBackground exception: ", e); + } catch (final IOException e) { + // logger infrastructure swallows UnknownHostException :/ + Log.e(TAG, "OverviewTask doInBackground exception: " + e.getMessage(), e); + } finally { + HTTPClient.close(); + } + + return forecast; + } + + private Forecast doInBackgroundThrowable(final double latitude, final double longitude) + throws URISyntaxException, ClientProtocolException, JsonParseException, IOException { + + final String APIVersion = localContext.getResources().getString(R.string.api_version); + final String urlAPI = localContext.getResources().getString(R.string.uri_api_weather_forecast); + // TODO: number as resource + final String url = weatherService.createURIAPIForecast(urlAPI, APIVersion, latitude, longitude, "14"); + final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis()); + final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache)); + + return weatherService.retrieveForecastFromJPOS(jsonData); + } + + @Override + protected void onPostExecute(final Forecast forecast) { + // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method? + // I hope so, otherwise I must catch(Throwable) in doInBackground method :( + + // Call updateUI on the UI thread. + final Intent forecastData = new Intent("de.example.exampletdd.UPDATEFORECAST"); + forecastData.putExtra("forecast", forecast); + LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(forecastData); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/preferences/WeatherInformationPreferencesFragment.java b/app/src/main/java/de/example/exampletdd/fragment/preferences/WeatherInformationPreferencesFragment.java new file mode 100644 index 0000000..ba19c5e --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/preferences/WeatherInformationPreferencesFragment.java @@ -0,0 +1,354 @@ +package de.example.exampletdd.fragment.preferences; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.os.SystemClock; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.SwitchPreference; +import de.example.exampletdd.R; +import de.example.exampletdd.NotificationIntentService; + +public class WeatherInformationPreferencesFragment extends PreferenceFragment + implements OnSharedPreferenceChangeListener { + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + this.addPreferencesFromResource(R.xml.weather_preferences); + + + // Temperature units + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + String[] humanValues = this.getResources().getStringArray(R.array.weather_preferences_temperature_human_value); + String keyPreference = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_temperature_key); + Preference connectionPref = this.findPreference(keyPreference); + String value = this.getPreferenceManager().getSharedPreferences() + .getString(keyPreference, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } + connectionPref.setSummary(humanValue); + + // Wind + values = this.getResources().getStringArray(R.array.weather_preferences_wind); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_wind_human_value); + keyPreference = this.getString(R.string.weather_preferences_wind_key); + connectionPref = this.findPreference(keyPreference); + value = this.getPreferenceManager().getSharedPreferences().getString(keyPreference, ""); + humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } + connectionPref.setSummary(humanValue); + + // Pressure + values = this.getResources().getStringArray(R.array.weather_preferences_pressure); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_pressure_human_value); + keyPreference = this.getString(R.string.weather_preferences_pressure_key); + connectionPref = this.findPreference(keyPreference); + value = this.getPreferenceManager().getSharedPreferences().getString(keyPreference, ""); + humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } + connectionPref.setSummary(humanValue); + + // Forecast days number + values = this.getResources().getStringArray(R.array.weather_preferences_day_forecast); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_day_forecast_human_value); + keyPreference = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_day_forecast_key); + connectionPref = this.findPreference(keyPreference); + value = this.getPreferenceManager().getSharedPreferences().getString(keyPreference, ""); + humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } + connectionPref.setSummary(humanValue); + + // Refresh interval + values = this.getResources().getStringArray(R.array.weather_preferences_refresh_interval); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_refresh_interval_human_value); + keyPreference = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_refresh_interval_key); + connectionPref = this.findPreference(keyPreference); + value = this.getPreferenceManager().getSharedPreferences().getString(keyPreference, ""); + humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } else if (value.equals(values[3])) { + humanValue = humanValues[3]; + } else if (value.equals(values[4])) { + humanValue = humanValues[4]; + } else if (value.equals(values[5])) { + humanValue = humanValues[5]; + } else if (value.equals(values[6])) { + humanValue = humanValues[6]; + } + connectionPref.setSummary(humanValue); + + // Update Time Rate + values = this.getResources().getStringArray(R.array.weather_preferences_update_time_rate); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_update_time_rate_human_value); + keyPreference = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_update_time_rate_key); + connectionPref = this.findPreference(keyPreference); + value = this.getPreferenceManager().getSharedPreferences() + .getString(keyPreference, ""); + humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } else if (value.equals(values[3])) { + humanValue = humanValues[3]; + } else if (value.equals(values[4])) { + humanValue = humanValues[4]; + } + connectionPref.setSummary(humanValue); + } + + @Override + public void onResume() { + super.onResume(); + this.getPreferenceManager().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onPause() { + super.onPause(); + this.getPreferenceManager().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged( + final SharedPreferences sharedPreferences, final String key) { + + // Temperature units + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + String[] humanValues = this.getResources().getStringArray(R.array.weather_preferences_temperature_human_value); + String keyValue = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_temperature_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } + + connectionPref.setSummary(humanValue); + return; + } + + // Wind + values = this.getResources().getStringArray(R.array.weather_preferences_wind); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_wind_human_value); + keyValue = this.getString(R.string.weather_preferences_wind_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } + + connectionPref.setSummary(humanValue); + return; + } + + // Pressure + values = this.getResources().getStringArray(R.array.weather_preferences_pressure); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_pressure_human_value); + keyValue = this.getString(R.string.weather_preferences_pressure_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } + + connectionPref.setSummary(humanValue); + return; + } + + // Forecast days number + values = this.getResources().getStringArray(R.array.weather_preferences_day_forecast); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_day_forecast_human_value); + keyValue = this.getActivity().getString( + R.string.weather_preferences_day_forecast_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } + connectionPref.setSummary(humanValue); + return; + } + + // Refresh interval + values = this.getResources().getStringArray(R.array.weather_preferences_refresh_interval); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_refresh_interval_human_value); + keyValue = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_refresh_interval_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } else if (value.equals(values[3])) { + humanValue = humanValues[3]; + } else if (value.equals(values[4])) { + humanValue = humanValues[4]; + } else if (value.equals(values[5])) { + humanValue = humanValues[5]; + } else if (value.equals(values[6])) { + humanValue = humanValues[6]; + } + connectionPref.setSummary(humanValue); + return; + } + + // Notification switch + keyValue = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_notifications_switch_key); + if (key.equals(keyValue)) { + final SwitchPreference preference = (SwitchPreference)this.findPreference(key); + if (preference.isChecked()) + { + keyValue = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_update_time_rate_key); + final String value = sharedPreferences.getString(keyValue, ""); + this.updateNotification(value); + } else { + this.cancelNotification(); + } + } + // Update Time Rate + values = this.getResources().getStringArray(R.array.weather_preferences_update_time_rate); + humanValues = this.getResources().getStringArray(R.array.weather_preferences_update_time_rate_human_value); + keyValue = this.getActivity().getApplicationContext().getString( + R.string.weather_preferences_update_time_rate_key); + if (key.equals(keyValue)) { + final Preference connectionPref = this.findPreference(key); + final String value = sharedPreferences.getString(key, ""); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } else if (value.equals(values[3])) { + humanValue = humanValues[3]; + } else if (value.equals(values[4])) { + humanValue = humanValues[4]; + } + + this.updateNotification(value); + connectionPref.setSummary(humanValue); + return; + } + } + + private void updateNotification(final String updateTimeRate) { + final String[] values = this.getResources().getStringArray(R.array.weather_preferences_update_time_rate); + long chosenInterval = 0; + if (updateTimeRate.equals(values[0])) { + chosenInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES; + } else if (updateTimeRate.equals(values[1])) { + chosenInterval = AlarmManager.INTERVAL_HALF_HOUR; + } else if (updateTimeRate.equals(values[2])) { + chosenInterval = AlarmManager.INTERVAL_HOUR; + } else if (updateTimeRate.equals(values[3])) { + chosenInterval = AlarmManager.INTERVAL_HALF_DAY; + } else if (updateTimeRate.equals(values[4])) { + chosenInterval = AlarmManager.INTERVAL_DAY; + } + + final AlarmManager alarmMgr = + (AlarmManager) this.getActivity().getApplicationContext().getSystemService(Context.ALARM_SERVICE); + // TODO: better use some string instead of .class? In case I change the service class + // this could be a problem (I guess) + final Intent serviceIntent = + new Intent(this.getActivity().getApplicationContext(), NotificationIntentService.class); + final PendingIntent alarmIntent = + PendingIntent.getService( + this.getActivity().getApplicationContext(), + 0, + serviceIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + if (chosenInterval != 0) { + alarmMgr.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime(), + chosenInterval, + alarmIntent); + } + } + + private void cancelNotification() { + final AlarmManager alarmMgr = + (AlarmManager) this.getActivity().getApplicationContext().getSystemService(Context.ALARM_SERVICE); + final Intent serviceIntent = + new Intent(this.getActivity().getApplicationContext(), NotificationIntentService.class); + final PendingIntent alarmIntent = + PendingIntent.getService( + this.getActivity().getApplicationContext(), + 0, + serviceIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + alarmMgr.cancel(alarmIntent); + } +} diff --git a/app/src/main/java/de/example/exampletdd/fragment/specific/SpecificFragment.java b/app/src/main/java/de/example/exampletdd/fragment/specific/SpecificFragment.java new file mode 100644 index 0000000..e355c71 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/fragment/specific/SpecificFragment.java @@ -0,0 +1,357 @@ +package de.example.exampletdd.fragment.specific; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import de.example.exampletdd.R; +import de.example.exampletdd.model.forecastweather.Forecast; +import de.example.exampletdd.service.IconsList; +import de.example.exampletdd.service.PermanentStorage; + + +public class SpecificFragment extends Fragment { + private int mChosenDay; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Bundle extras = this.getActivity().getIntent().getExtras(); + + if (extras != null) { + // handset layout + this.mChosenDay = extras.getInt("CHOSEN_DAY", 0); + } else { + // tablet layout + // Always 0 when tablet layout (by default shows the first day) + this.mChosenDay = 0; + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.weather_specific_fragment, container, false); + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) { + // Restore UI state + final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast"); + + // TODO: Could it be better to store in global data forecast even if it is null value? + // So, perhaps do not check for null value and always store in global variable. + if (forecast != null) { + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + store.saveForecast(forecast); + } + + this.mChosenDay = savedInstanceState.getInt("mChosenDay"); + } + + this.setHasOptionsMenu(false); + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + + // Save UI state + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Forecast forecast = store.getForecast(); + + // TODO: Could it be better to save forecast data even if it is null value? + // So, perhaps do not check for null value. + if (forecast != null) { + savedInstanceState.putSerializable("Forecast", forecast); + } + + savedInstanceState.putInt("mChosenDay", this.mChosenDay); + + super.onSaveInstanceState(savedInstanceState); + } + + /** + * This method is used by tablet layout. + * + * @param chosenDay + */ + public void updateUIByChosenDay(final int chosenDay) { + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Forecast forecast = store.getForecast(); + + if (forecast != null) { + this.updateUI(forecast, chosenDay); + } + } + + private interface UnitsConversor { + + public double doConversion(final double value); + } + + private void updateUI(final Forecast forecastWeatherData, final int chosenDay) { + + final SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(this.getActivity()); + + // TODO: repeating the same code in Overview, Specific and Current!!! + // 1. Update units of measurement. + // 1.1 Temperature + String tempSymbol; + UnitsConversor tempUnitsConversor; + String keyPreference = this.getResources().getString( + R.string.weather_preferences_temperature_key); + String unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + if (unitsPreferenceValue.equals(values[0])) { + tempSymbol = values[0]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value - 273.15; + } + + }; + } else if (unitsPreferenceValue.equals(values[1])) { + tempSymbol = values[1]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return (value * 1.8) - 459.67; + } + + }; + } else { + tempSymbol = values[2]; + tempUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(final double value) { + return value; + } + + }; + } + + // 1.2 Wind + String windSymbol; + UnitsConversor windUnitsConversor; + keyPreference = this.getResources().getString(R.string.weather_preferences_wind_key); + unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + values = this.getResources().getStringArray(R.array.weather_preferences_wind); + if (unitsPreferenceValue.equals(values[0])) { + windSymbol = values[0]; + windUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value; + } + }; + } else { + windSymbol = values[1]; + windUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value * 2.237; + } + }; + } + + // 1.3 Pressure + String pressureSymbol; + UnitsConversor pressureUnitsConversor; + keyPreference = this.getResources().getString(R.string.weather_preferences_pressure_key); + unitsPreferenceValue = sharedPreferences.getString(keyPreference, ""); + values = this.getResources().getStringArray(R.array.weather_preferences_pressure); + if (unitsPreferenceValue.equals(values[0])) { + pressureSymbol = values[0]; + pressureUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value; + } + }; + } else { + pressureSymbol = values[1]; + pressureUnitsConversor = new UnitsConversor(){ + + @Override + public double doConversion(double value) { + return value / 113.25d; + } + }; + } + + + // 2. Formatters + final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + tempFormatter.applyPattern("#####.#####"); + + + // 3. Prepare data for UI. + final de.example.exampletdd.model.forecastweather.List forecast = forecastWeatherData + .getList().get((chosenDay)); + + final SimpleDateFormat dayFormatter = new SimpleDateFormat("EEEE - MMM d", Locale.US); + final Calendar calendar = Calendar.getInstance(); + final Long forecastUNIXDate = (Long) forecast.getDt(); + calendar.setTimeInMillis(forecastUNIXDate * 1000L); + final Date date = calendar.getTime(); + + String tempMax = ""; + if (forecast.getTemp().getMax() != null) { + double conversion = (Double) forecast.getTemp().getMax(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMax = tempFormatter.format(conversion) + tempSymbol; + } + String tempMin = ""; + if (forecast.getTemp().getMin() != null) { + double conversion = (Double) forecast.getTemp().getMin(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMin = tempFormatter.format(conversion) + tempSymbol; + } + Bitmap picture; + if ((forecast.getWeather().size() > 0) && (forecast.getWeather().get(0).getIcon() != null) + && (IconsList.getIcon(forecast.getWeather().get(0).getIcon()) != null)) { + final String icon = forecast.getWeather().get(0).getIcon(); + picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon) + .getResourceDrawable()); + } else { + picture = BitmapFactory.decodeResource(this.getResources(), + R.drawable.weather_severe_alert); + } + + // TODO: string resource + String description = "no description available"; + if (forecast.getWeather().size() > 0) { + description = forecast.getWeather().get(0).getDescription(); + } + + // TODO: units!!!! + String humidityValue = ""; + if (forecast.getHumidity() != null) { + final double conversion = (Double) forecast.getHumidity(); + humidityValue = tempFormatter.format(conversion); + } + String pressureValue = ""; + if (forecast.getPressure() != null) { + double conversion = (Double) forecast.getPressure(); + conversion = pressureUnitsConversor.doConversion(conversion); + pressureValue = tempFormatter.format(conversion); + } + String windValue = ""; + if (forecast.getSpeed() != null) { + double conversion = (Double) forecast.getSpeed(); + conversion = windUnitsConversor.doConversion(conversion); + windValue = tempFormatter.format(conversion); + } + String rainValue = ""; + if (forecast.getRain() != null) { + final double conversion = (Double) forecast.getRain(); + rainValue = tempFormatter.format(conversion); + } + String cloudsValue = ""; + if (forecast.getRain() != null) { + final double conversion = (Double) forecast.getClouds(); + cloudsValue = tempFormatter.format(conversion); + } + + String tempDay = ""; + if (forecast.getTemp().getDay() != null) { + double conversion = (Double) forecast.getTemp().getDay(); + conversion = tempUnitsConversor.doConversion(conversion); + tempDay = tempFormatter.format(conversion) + tempSymbol; + } + String tempMorn = ""; + if (forecast.getTemp().getMorn() != null) { + double conversion = (Double) forecast.getTemp().getMorn(); + conversion = tempUnitsConversor.doConversion(conversion); + tempMorn = tempFormatter.format(conversion) + tempSymbol; + } + String tempEve = ""; + if (forecast.getTemp().getEve() != null) { + double conversion = (Double) forecast.getTemp().getEve(); + conversion = tempUnitsConversor.doConversion(conversion); + tempEve = tempFormatter.format(conversion) + tempSymbol; + } + String tempNight = ""; + if (forecast.getTemp().getNight() != null) { + double conversion = (Double) forecast.getTemp().getNight(); + conversion = tempUnitsConversor.doConversion(conversion); + tempNight = tempFormatter.format(conversion) + tempSymbol; + } + + + // 4. Update UI. + this.getActivity().getActionBar().setSubtitle(dayFormatter.format(date).toUpperCase()); + + final TextView tempMaxView = (TextView) getActivity().findViewById(R.id.weather_specific_temp_max); + tempMaxView.setText(tempMax); + final TextView tempMinView = (TextView) getActivity().findViewById(R.id.weather_specific_temp_min); + tempMinView.setText(tempMin); + final ImageView pictureView = (ImageView) getActivity().findViewById(R.id.weather_specific_picture); + pictureView.setImageBitmap(picture); + + final TextView descriptionView = (TextView) getActivity().findViewById(R.id.weather_specific_description); + descriptionView.setText(description); + + final TextView humidityValueView = (TextView) getActivity().findViewById(R.id.weather_specific_humidity_value); + humidityValueView.setText(humidityValue); + ((TextView) getActivity().findViewById(R.id.weather_specific_pressure_value)).setText(pressureValue); + ((TextView) getActivity().findViewById(R.id.weather_specific_pressure_units)).setText(pressureSymbol); + ((TextView) getActivity().findViewById(R.id.weather_specific_wind_value)).setText(windValue);; + ((TextView) getActivity().findViewById(R.id.weather_specific_wind_units)).setText(windSymbol); + final TextView rainValueView = (TextView) getActivity().findViewById(R.id.weather_specific_rain_value); + rainValueView.setText(rainValue); + final TextView cloudsValueView = (TextView) getActivity().findViewById(R.id.weather_specific_clouds_value); + cloudsValueView.setText(cloudsValue); + + final TextView tempDayView = (TextView) getActivity().findViewById(R.id.weather_specific_day_temperature); + tempDayView.setText(tempDay); + final TextView tempMornView = (TextView) getActivity().findViewById(R.id.weather_specific_morn_temperature); + tempMornView.setText(tempMorn); + final TextView tempEveView = (TextView) getActivity().findViewById(R.id.weather_specific_eve_temperature); + tempEveView.setText(tempEve); + final TextView tempNightView = (TextView) getActivity().findViewById(R.id.weather_specific_night_temperature); + tempNightView.setText(tempNight); + } + + @Override + public void onResume() { + super.onResume(); + + final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); + final Forecast forecast = store.getForecast(); + + if (forecast != null) { + this.updateUI(forecast, this.mChosenDay); + } + + // TODO: Overview is doing things with mListState... Why not here? + } +} diff --git a/app/src/main/java/de/example/exampletdd/httpclient/Consts.java b/app/src/main/java/de/example/exampletdd/httpclient/Consts.java new file mode 100644 index 0000000..614849f --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/httpclient/Consts.java @@ -0,0 +1,51 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package de.example.exampletdd.httpclient; + +import java.nio.charset.Charset; + +/** + * Commons constants. + * + * @since 4.2 + */ +public final class Consts { + + public static final int CR = 13; // + public static final int LF = 10; // + public static final int SP = 32; // + public static final int HT = 9; // + + public static final Charset UTF_8 = Charset.forName("UTF-8"); + public static final Charset ASCII = Charset.forName("US-ASCII"); + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + private Consts() { + } + +} diff --git a/app/src/main/java/de/example/exampletdd/httpclient/ContentType.java b/app/src/main/java/de/example/exampletdd/httpclient/ContentType.java new file mode 100644 index 0000000..8c02532 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/httpclient/ContentType.java @@ -0,0 +1,248 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package de.example.exampletdd.httpclient; + +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Locale; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.message.BasicHeaderValueParser; + +/** + * Content type information consisting of a MIME type and an optional charset. + *

+ * This class makes no attempts to verify validity of the MIME type. + * The input parameters of the {@link #create(String, String)} method, however, may not + * contain characters <">, <;>, <,> reserved by the HTTP specification. + * + * @since 4.2 + */ + +public final class ContentType { + + // constants + public static final ContentType APPLICATION_ATOM_XML = create( + "application/atom+xml", Consts.ISO_8859_1); + public static final ContentType APPLICATION_FORM_URLENCODED = create( + "application/x-www-form-urlencoded", Consts.ISO_8859_1); + public static final ContentType APPLICATION_JSON = create( + "application/json", Consts.UTF_8); + public static final ContentType APPLICATION_OCTET_STREAM = create( + "application/octet-stream", (Charset) null); + public static final ContentType APPLICATION_SVG_XML = create( + "application/svg+xml", Consts.ISO_8859_1); + public static final ContentType APPLICATION_XHTML_XML = create( + "application/xhtml+xml", Consts.ISO_8859_1); + public static final ContentType APPLICATION_XML = create( + "application/xml", Consts.ISO_8859_1); + public static final ContentType MULTIPART_FORM_DATA = create( + "multipart/form-data", Consts.ISO_8859_1); + public static final ContentType TEXT_HTML = create( + "text/html", Consts.ISO_8859_1); + public static final ContentType TEXT_PLAIN = create( + "text/plain", Consts.ISO_8859_1); + public static final ContentType TEXT_XML = create( + "text/xml", Consts.ISO_8859_1); + public static final ContentType WILDCARD = create( + "*/*", (Charset) null); + + // defaults + public static final ContentType DEFAULT_TEXT = TEXT_PLAIN; + public static final ContentType DEFAULT_BINARY = APPLICATION_OCTET_STREAM; + + private final String mimeType; + private final Charset charset; + + /** + * Given a MIME type and a character set, constructs a ContentType. + * @param mimeType The MIME type to use for the ContentType header. + * @param charset The optional character set to use with the ContentType header. + * @throws UnsupportedCharsetException + * If no support for the named charset is available in this Java virtual machine + */ + ContentType(final String mimeType, final Charset charset) { + this.mimeType = mimeType; + this.charset = charset; + } + + public String getMimeType() { + return this.mimeType; + } + + public Charset getCharset() { + return this.charset; + } + + /** + * Converts a ContentType to a string which can be used as a ContentType header. + * If a charset is provided by the ContentType, it will be included in the string. + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append(this.mimeType); + if (this.charset != null) { + buf.append("; charset="); + buf.append(this.charset); + } + return buf.toString(); + } + + private static boolean valid(final String s) { + for (int i = 0; i < s.length(); i++) { + final char ch = s.charAt(i); + if ((ch == '"') || (ch == ',') || (ch == ';')) { + return false; + } + } + return true; + } + + /** + * Creates a new instance of {@link ContentType}. + * + * @param mimeType MIME type. It may not be null or empty. It may not contain + * characters <">, <;>, <,> reserved by the HTTP specification. + * @param charset charset. + * @return content type + */ + public static ContentType create(final String mimeType, final Charset charset) { + if (mimeType == null) { + throw new IllegalArgumentException("MIME type may not be null"); + } + final String type = mimeType.trim().toLowerCase(Locale.US); + if (type.length() == 0) { + throw new IllegalArgumentException("MIME type may not be empty"); + } + if (!valid(type)) { + throw new IllegalArgumentException("MIME type may not contain reserved characters"); + } + return new ContentType(type, charset); + } + + /** + * Creates a new instance of {@link ContentType} without a charset. + * + * @param mimeType MIME type. It may not be null or empty. It may not contain + * characters <">, <;>, <,> reserved by the HTTP specification. + * @return content type + */ + public static ContentType create(final String mimeType) { + return new ContentType(mimeType, (Charset) null); + } + + /** + * Creates a new instance of {@link ContentType}. + * + * @param mimeType MIME type. It may not be null or empty. It may not contain + * characters <">, <;>, <,> reserved by the HTTP specification. + * @param charset charset. It may not contain characters <">, <;>, <,> reserved by the HTTP + * specification. This parameter is optional. + * @return content type + */ + public static ContentType create( + final String mimeType, final String charset) throws UnsupportedCharsetException { + return create(mimeType, charset != null ? Charset.forName(charset) : null); + } + + private static ContentType create(final HeaderElement helem) { + final String mimeType = helem.getName(); + String charset = null; + final NameValuePair param = helem.getParameterByName("charset"); + if (param != null) { + charset = param.getValue(); + } + return create(mimeType, charset); + } + + /** + * Parses textual representation of Content-Type value. + * + * @param s text + * @return content type + * @throws ParseException if the given text does not represent a valid + * Content-Type value. + */ + public static ContentType parse( + final String s) throws ParseException, UnsupportedCharsetException { + if (s == null) { + throw new IllegalArgumentException("Content type may not be null"); + } + final HeaderElement[] elements = BasicHeaderValueParser.parseElements(s, null); + if (elements.length > 0) { + return create(elements[0]); + } else { + throw new ParseException("Invalid content type: " + s); + } + } + + /** + * Extracts Content-Type value from {@link HttpEntity} exactly as + * specified by the Content-Type header of the entity. Returns null + * if not specified. + * + * @param entity HTTP entity + * @return content type + * @throws ParseException if the given text does not represent a valid + * Content-Type value. + */ + public static ContentType get( + final HttpEntity entity) throws ParseException, UnsupportedCharsetException { + if (entity == null) { + return null; + } + final Header header = entity.getContentType(); + if (header != null) { + final HeaderElement[] elements = header.getElements(); + if (elements.length > 0) { + return create(elements[0]); + } + } + return null; + } + + /** + * Extracts Content-Type value from {@link HttpEntity} or returns default value + * if not explicitly specified. + * + * @param entity HTTP entity + * @return content type + * @throws ParseException if the given text does not represent a valid + * Content-Type value. + */ + public static ContentType getOrDefault(final HttpEntity entity) throws ParseException { + final ContentType contentType = get(entity); + return contentType != null ? contentType : DEFAULT_TEXT; + } + +} diff --git a/app/src/main/java/de/example/exampletdd/httpclient/CustomHTTPClient.java b/app/src/main/java/de/example/exampletdd/httpclient/CustomHTTPClient.java new file mode 100644 index 0000000..ba1bb99 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/httpclient/CustomHTTPClient.java @@ -0,0 +1,126 @@ +package de.example.exampletdd.httpclient; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; + +import android.net.http.AndroidHttpClient; + +public class CustomHTTPClient { + private final AndroidHttpClient httpClient; + + public CustomHTTPClient(final AndroidHttpClient httpClient) { + this.httpClient = httpClient; + } + + public String retrieveDataAsString(final URL url) + throws URISyntaxException, ClientProtocolException, IOException { + + final ResponseHandler handler = new ResponseHandler() { + @Override + public String handleResponse( + final HttpResponse response) + throws UnsupportedEncodingException, IOException { + + if (response != null) { + final HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + final ContentType contentType = ContentType.getOrDefault(entity); + final ByteArrayOutputStream buffer = CustomHTTPClient.this + .sortResponse(response); + return new String(buffer.toByteArray(), contentType.getCharset()); + } finally { + entity.consumeContent(); + } + } + + throw new IOException("There is no entity"); + } + + throw new IOException("There is no response"); + } + }; + + final HttpGet httpGet = new HttpGet(); + httpGet.setURI(url.toURI()); + + return this.httpClient.execute(httpGet, handler); + } + + public ByteArrayOutputStream retrieveRawData(final URL url) + throws URISyntaxException, ClientProtocolException, IOException { + final ResponseHandler handler = new ResponseHandler() { + + @Override + public ByteArrayOutputStream handleResponse( + final HttpResponse response) + throws UnsupportedEncodingException, IOException { + + if (response != null) { + final HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + return CustomHTTPClient.this.sortResponse(response); + } finally { + entity.consumeContent(); + } + } + + throw new IOException("There is no entity"); + } + + throw new IOException("There is no response"); + } + }; + + final HttpGet httpGet = new HttpGet(); + httpGet.setURI(url.toURI()); + + return this.httpClient.execute(httpGet, handler); + } + + public void close() { + this.httpClient.close(); + } + + private ByteArrayOutputStream sortResponse(final HttpResponse httpResponse) throws IOException { + + if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new IOException("Unexpected response code: " + + httpResponse.getStatusLine().getStatusCode()); + } + + final HttpEntity entity = httpResponse.getEntity(); + final InputStream inputStream = entity.getContent(); + try { + return this.readInputStream(inputStream); + } finally { + inputStream.close(); + } + + } + + private ByteArrayOutputStream readInputStream (final InputStream inputStream) throws IOException { + final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + final int bufferSize = 1024; + final byte[] buffer = new byte[bufferSize]; + + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + byteBuffer.write(buffer, 0, len); + } + + return byteBuffer; + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/DatabaseQueries.java b/app/src/main/java/de/example/exampletdd/model/DatabaseQueries.java new file mode 100644 index 0000000..e53f97d --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/DatabaseQueries.java @@ -0,0 +1,44 @@ +package de.example.exampletdd.model; + +import android.content.Context; + +public class DatabaseQueries { + private final Context localContext; + + public DatabaseQueries(final Context context) { + this.localContext = context; + } + + public WeatherLocation queryDataBase() { + + final WeatherLocationDbHelper dbHelper = new WeatherLocationDbHelper(this.localContext); + try { + final WeatherLocationDbQueries queryDb = new WeatherLocationDbQueries(dbHelper); + return queryDb.queryDataBase(); + } finally { + dbHelper.close(); + } + } + + public long insertIntoDataBase(final WeatherLocation weatherLocation) { + + final WeatherLocationDbHelper dbHelper = new WeatherLocationDbHelper(this.localContext); + try { + final WeatherLocationDbQueries queryDb = new WeatherLocationDbQueries(dbHelper); + return queryDb.insertIntoDataBase(weatherLocation); + } finally { + dbHelper.close(); + } + } + + public void updateDataBase(final WeatherLocation weatherLocation) { + + final WeatherLocationDbHelper dbHelper = new WeatherLocationDbHelper(this.localContext); + try { + final WeatherLocationDbQueries queryDb = new WeatherLocationDbQueries(dbHelper); + queryDb.updateDataBase(weatherLocation); + } finally { + dbHelper.close(); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/WeatherLocation.java b/app/src/main/java/de/example/exampletdd/model/WeatherLocation.java new file mode 100644 index 0000000..f20b0b6 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/WeatherLocation.java @@ -0,0 +1,89 @@ +package de.example.exampletdd.model; + +import java.io.Serializable; +import java.util.Date; + + +public class WeatherLocation implements Serializable { + private static final long serialVersionUID = -1469725417020355109L; + private int id; + private String city; + private String country; + private boolean isSelected; + private double latitude; + private double longitude; + private Date lastCurrentUIUpdate; + private Date lastForecastUIUpdate; + + public WeatherLocation setId(int id) { + this.id = id; + return this; + } + + public WeatherLocation setCity(String city) { + this.city = city; + return this; + } + + public WeatherLocation setCountry(String country) { + this.country = country; + return this; + } + + public WeatherLocation setIsSelected(boolean isSelected) { + this.isSelected = isSelected; + return this; + } + + public WeatherLocation setLatitude(double latitude) { + this.latitude = latitude; + return this; + } + + public WeatherLocation setLongitude(double longitude) { + this.longitude = longitude; + return this; + } + + public WeatherLocation setLastCurrentUIUpdate(Date lastCurrentUIUpdate) { + this.lastCurrentUIUpdate = lastCurrentUIUpdate; + return this; + } + + public WeatherLocation setLastForecastUIUpdate(Date lastForecastUIUpdate) { + this.lastForecastUIUpdate = lastForecastUIUpdate; + return this; + } + + public int getId() { + return this.id; + } + + public String getCity() { + return this.city; + } + + public String getCountry() { + return this.country; + } + + public boolean getIsSelected() { + return this.isSelected; + } + + public double getLatitude() { + return this.latitude; + } + + public double getLongitude() { + return this.longitude; + } + + public Date getLastCurrentUIUpdate() { + return this.lastCurrentUIUpdate; + } + + public Date getLastForecastUIUpdate() { + return this.lastForecastUIUpdate; + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/WeatherLocationContract.java b/app/src/main/java/de/example/exampletdd/model/WeatherLocationContract.java new file mode 100644 index 0000000..91e3472 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/WeatherLocationContract.java @@ -0,0 +1,32 @@ +package de.example.exampletdd.model; + +import android.provider.BaseColumns; + +public class WeatherLocationContract { + + // This class can't be instantiated + private WeatherLocationContract() {} + + public static final class WeatherLocation implements BaseColumns { + + // This class can't be instantiated + private WeatherLocation() {} + + public static final String TABLE_NAME = "locations"; + + public static final String COLUMN_NAME_IS_SELECTED = "isSelected"; + + public static final String COLUMN_NAME_LATITUDE = "latitude"; + + public static final String COLUMN_NAME_LONGITUDE = "longitude"; + + public static final String COLUMN_NAME_COUNTRY = "country"; + + public static final String COLUMN_NAME_CITY = "city"; + + public static final String COLUMN_NAME_LAST_FORECAST_UI_UPDATE = "lastForecastUpdate"; + + public static final String COLUMN_NAME_LAST_CURRENT_UI_UPDATE = "lastCurrentUpdate"; + } + +} diff --git a/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbHelper.java b/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbHelper.java new file mode 100644 index 0000000..8083516 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbHelper.java @@ -0,0 +1,43 @@ +package de.example.exampletdd.model; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class WeatherLocationDbHelper extends SQLiteOpenHelper { + private static final String TAG = "LocationDbHelper"; + public static final int DATABASE_VERSION = 1; + public static final String DATABASE_NAME = "Location.db"; + + public WeatherLocationDbHelper(final Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(final SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + WeatherLocationContract.WeatherLocation.TABLE_NAME + " (" + + WeatherLocationContract.WeatherLocation._ID + " INTEGER PRIMARY KEY, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_CITY + " TEXT" + " NOT NULL, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_COUNTRY + " TEXT" + " NOT NULL, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED + " INTEGER" + " NOT NULL, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE + " INTEGER, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE + " INTEGER, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LATITUDE + " REAL" + " NOT NULL, " + + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LONGITUDE + " REAL" + " NOT NULL " + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + + newVersion + ", which will destroy all old data"); + + // Kills the table and existing data + db.execSQL("DROP TABLE IF EXISTS " + WeatherLocationContract.WeatherLocation.TABLE_NAME); + + // Recreates the database with a new version + onCreate(db); + } + +} diff --git a/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbQueries.java b/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbQueries.java new file mode 100644 index 0000000..fa97ca2 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/WeatherLocationDbQueries.java @@ -0,0 +1,176 @@ +package de.example.exampletdd.model; + +import java.util.Calendar; +import java.util.Date; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class WeatherLocationDbQueries { + private final SQLiteOpenHelper mDbHelper; + + public interface DoQuery { + + public WeatherLocation doQuery(final Cursor cursor); + } + + public WeatherLocationDbQueries(final SQLiteOpenHelper dbHelper) { + this.mDbHelper = dbHelper; + } + + public WeatherLocation queryDataBase() { + final String selection = WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED + " = ?"; + final String[] selectionArgs = { "1" }; + final String[] projection = { + WeatherLocationContract.WeatherLocation._ID, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_CITY, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_COUNTRY, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LATITUDE, + WeatherLocationContract.WeatherLocation.COLUMN_NAME_LONGITUDE + }; + + + final WeatherLocationDbQueries.DoQuery doQuery = new WeatherLocationDbQueries.DoQuery() { + + @Override + public WeatherLocation doQuery(final Cursor cursor) { + final int id = cursor.getInt(cursor.getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation._ID)); + final String city = cursor.getString(cursor. + getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_CITY)); + final String country = cursor.getString(cursor. + getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_COUNTRY)); + final boolean isSelected = (cursor.getInt(cursor + .getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED)) == 0) ? false : true; + Date lastCurrentUIUpdate = null; + if (!cursor.isNull(cursor + .getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE))) { + final long javaTime = cursor.getLong(cursor + .getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE)); + lastCurrentUIUpdate = new Date(javaTime); + } + Date lasForecastUIUpdate = null; + if (!cursor.isNull(cursor + .getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE))) { + final long javaTime = cursor.getLong(cursor + .getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE)); + lasForecastUIUpdate = new Date(javaTime); + } + final double latitude = cursor.getDouble(cursor. + getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LATITUDE)); + final double longitude = cursor.getDouble(cursor. + getColumnIndexOrThrow(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LONGITUDE)); + + + return new WeatherLocation() + .setId(id) + .setCity(city) + .setCountry(country) + .setIsSelected(isSelected) + .setLastCurrentUIUpdate(lastCurrentUIUpdate) + .setLastForecastUIUpdate(lasForecastUIUpdate) + .setLatitude(latitude) + .setLongitude(longitude); + } + }; + + return this.queryDataBase( + WeatherLocationContract.WeatherLocation.TABLE_NAME, projection, + selectionArgs, selection, doQuery); + } + + public long insertIntoDataBase(final WeatherLocation weatherLocation) { + // Create a new map of values, where column names are the keys + final ContentValues values = new ContentValues(); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_CITY, weatherLocation.getCity()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_COUNTRY, weatherLocation.getCountry()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED, weatherLocation.getIsSelected()); + Date javaTime = weatherLocation.getLastCurrentUIUpdate(); + if (javaTime != null) { + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE, javaTime.getTime()); + } + javaTime = weatherLocation.getLastForecastUIUpdate(); + if (javaTime != null) { + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE, javaTime.getTime()); + } + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LATITUDE, weatherLocation.getLatitude()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LONGITUDE, weatherLocation.getLongitude()); + + return this.insertIntoDataBase(WeatherLocationContract.WeatherLocation.TABLE_NAME, values); + } + + public void updateDataBase(final WeatherLocation weatherLocation) { + final String selection = WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED + " = ?"; + final String[] selectionArgs = { "1" }; + // Create a new map of values, where column names are the keys + final ContentValues values = new ContentValues(); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_CITY, weatherLocation.getCity()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_COUNTRY, weatherLocation.getCountry()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_IS_SELECTED, weatherLocation.getIsSelected()); + Date javaTime = weatherLocation.getLastCurrentUIUpdate(); + if (javaTime != null) { + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE, javaTime.getTime()); + } else { + values.putNull(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_CURRENT_UI_UPDATE); + } + javaTime = weatherLocation.getLastForecastUIUpdate(); + if (javaTime != null) { + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE, javaTime.getTime()); + } else { + values.putNull(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LAST_FORECAST_UI_UPDATE); + } + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LATITUDE, weatherLocation.getLatitude()); + values.put(WeatherLocationContract.WeatherLocation.COLUMN_NAME_LONGITUDE, weatherLocation.getLongitude()); + + this.updateDataBase(WeatherLocationContract.WeatherLocation.TABLE_NAME, selectionArgs, selection, values); + } + + // TODO: May I perform another query after this method (after closing almost everything but mDbHelper) + private WeatherLocation queryDataBase(final String table, + final String[] projection, final String[] selectionArgs, + final String selection, final DoQuery doQuery) { + // TODO: execute around idiom? I miss try/finally with resources + final SQLiteDatabase db = this.mDbHelper.getReadableDatabase(); + try { + final Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, null); + try { + if (!cursor.moveToFirst()) { + return null; + } + else { + return doQuery.doQuery(cursor); + } + } finally { + cursor.close(); + } + } finally { + db.close(); + } + } + + // TODO: May I perform another query after this method (after closing almost everything but mDbHelper) + private long insertIntoDataBase(final String table, final ContentValues values) { + final SQLiteDatabase db = this.mDbHelper.getWritableDatabase(); + try { + return db.insert(table, null, values); + } finally { + db.close(); + } + } + + // TODO: May I perform another query after this method (after closing almost everything but mDbHelper) + private long updateDataBase(final String table, final String[] selectionArgs, + final String selection, final ContentValues values) { + final SQLiteDatabase db = this.mDbHelper.getWritableDatabase(); + try { + return db.update(table, values, selection, selectionArgs); + } finally { + db.close(); + } + } + +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Clouds.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Clouds.java new file mode 100644 index 0000000..8da14c3 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Clouds.java @@ -0,0 +1,22 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Clouds implements Serializable { + private static final long serialVersionUID = 3034435739326030899L; + private Number all; + + public Number getAll(){ + return this.all; + } + public void setAll(final Number all){ + this.all = all; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Clouds [all=").append(this.all).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Coord.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Coord.java new file mode 100644 index 0000000..9eececd --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Coord.java @@ -0,0 +1,30 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Coord implements Serializable { + private static final long serialVersionUID = 7151637605146377486L; + private Number lat; + private Number lon; + + public Number getLat(){ + return this.lat; + } + public void setLat(final Number lat){ + this.lat = lat; + } + public Number getLon(){ + return this.lon; + } + public void setLon(final Number lon){ + this.lon = lon; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Coord [lat=").append(this.lat).append(", lon=").append(this.lon) + .append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Current.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Current.java new file mode 100644 index 0000000..a7bc22e --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Current.java @@ -0,0 +1,139 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * Auto generated by: http://jsongen.byingtondesign.com/ + * (with my own modifications) + * + */ +public class Current implements Serializable { + private static final long serialVersionUID = -730690341739860818L; + private String base; + private Clouds clouds; + private Number cod; + private Coord coord; + private Number dt; + private Number id; + private Main main; + private String name; + private Rain rain; + private Snow snow; + private Sys sys; + private List weather; + private Wind wind; + private byte[] iconData; + private Date date; + + public String getBase(){ + return this.base; + } + public void setBase(final String base){ + this.base = base; + } + public Clouds getClouds(){ + return this.clouds; + } + public void setClouds(final Clouds clouds){ + this.clouds = clouds; + } + + public Number getCod() { + return this.cod; + } + + public void setCod(final Number cod) { + this.cod = cod; + } + public Coord getCoord(){ + return this.coord; + } + public void setCoord(final Coord coord){ + this.coord = coord; + } + public Number getDt(){ + return this.dt; + } + public void setDt(final Number dt){ + this.dt = dt; + } + public Number getId(){ + return this.id; + } + public void setId(final Number id){ + this.id = id; + } + public Main getMain(){ + return this.main; + } + public void setMain(final Main main){ + this.main = main; + } + public String getName(){ + return this.name; + } + public void setName(final String name){ + this.name = name; + } + public Rain getRain(){ + return this.rain; + } + public void setRain(final Rain rain){ + this.rain = rain; + } + public Snow getSnow() { + return this.snow; + } + public void setSnow(final Snow snow) { + this.snow = snow; + } + public Sys getSys(){ + return this.sys; + } + public void setSys(final Sys sys){ + this.sys = sys; + } + public List getWeather(){ + return this.weather; + } + public void setWeather(final List weather){ + this.weather = weather; + } + public Wind getWind(){ + return this.wind; + } + public void setWind(final Wind wind){ + this.wind = wind; + } + + public byte[] getIconData() { + return this.iconData; + } + + public void setIconData(final byte[] iconData) { + this.iconData = iconData; + } + + public Date getDate() { + return this.date; + } + + public void setDate(final Date date) { + this.date = date; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Current [base=").append(this.base).append(", clouds=") + .append(this.clouds).append(", cod=").append(this.cod).append(", coord=") + .append(this.coord).append(", dt=").append(this.dt).append(", id=").append(this.id) + .append(", main=").append(this.main).append(", name=").append(this.name) + .append(", rain=").append(this.rain).append(", snow=").append(this.snow) + .append(", sys=").append(this.sys).append(", weather=").append(this.weather) + .append(", wind=").append(this.wind).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Main.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Main.java new file mode 100644 index 0000000..397f71b --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Main.java @@ -0,0 +1,73 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Main implements Serializable { + private static final long serialVersionUID = -6000879164436289447L; + private Number grnd_level; + private Number humidity; + private Number pressure; + private Number sea_level; + private Number temp; + private Number temp_max; + private Number temp_min; + + public Number getGrnd_level() { + return this.grnd_level; + } + + public void setGrnd_level(final Number grnd_level) { + this.grnd_level = grnd_level; + } + + public Number getHumidity(){ + return this.humidity; + } + public void setHumidity(final Number humidity){ + this.humidity = humidity; + } + public Number getPressure(){ + return this.pressure; + } + public void setPressure(final Number pressure){ + this.pressure = pressure; + } + + public Number getSea_level() { + return this.sea_level; + } + + public void setSea_level(final Number sea_level) { + this.sea_level = sea_level; + } + + public Number getTemp(){ + return this.temp; + } + public void setTemp(final Number temp){ + this.temp = temp; + } + public Number getTemp_max(){ + return this.temp_max; + } + public void setTemp_max(final Number temp_max){ + this.temp_max = temp_max; + } + public Number getTemp_min(){ + return this.temp_min; + } + public void setTemp_min(final Number temp_min){ + this.temp_min = temp_min; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Main [grnd_level=").append(this.grnd_level).append(", humidity=") + .append(this.humidity).append(", pressure=").append(this.pressure) + .append(", sea_level=").append(this.sea_level).append(", temp=").append(this.temp) + .append(", temp_max=").append(this.temp_max).append(", temp_min=") + .append(this.temp_min).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Rain.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Rain.java new file mode 100644 index 0000000..0e151ef --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Rain.java @@ -0,0 +1,23 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Rain implements Serializable { + private static final long serialVersionUID = 1318464783605029435L; + private Number three; + + public Number get3h(){ + return this.three; + } + + public void set3h(final Number threeh) { + this.three = threeh; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Rain [three=").append(this.three).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Snow.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Snow.java new file mode 100644 index 0000000..5174763 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Snow.java @@ -0,0 +1,23 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Snow implements Serializable { + private static final long serialVersionUID = 6769716772818311879L; + private Number three; + + public Number get3h() { + return this.three; + } + + public void set3h(final Number threeh) { + this.three = threeh; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Snow [three=").append(this.three).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Sys.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Sys.java new file mode 100644 index 0000000..3e20bef --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Sys.java @@ -0,0 +1,46 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + + +public class Sys implements Serializable { + private static final long serialVersionUID = 5333083785731053139L; + private String country; + private Number message; + private Number sunrise; + private Number sunset; + + public String getCountry(){ + return this.country; + } + public void setCountry(final String country){ + this.country = country; + } + public Number getMessage(){ + return this.message; + } + public void setMessage(final Number message){ + this.message = message; + } + public Number getSunrise(){ + return this.sunrise; + } + public void setSunrise(final Number sunrise){ + this.sunrise = sunrise; + } + public Number getSunset(){ + return this.sunset; + } + public void setSunset(final Number sunset){ + this.sunset = sunset; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Sys [country=").append(this.country).append(", message=") + .append(this.message).append(", sunrise=").append(this.sunrise).append(", sunset=") + .append(this.sunset).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Weather.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Weather.java new file mode 100644 index 0000000..fc44d20 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Weather.java @@ -0,0 +1,45 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Weather implements Serializable { + private static final long serialVersionUID = -34336548786316655L; + private String description; + private String icon; + private Number id; + private String main; + + public String getDescription(){ + return this.description; + } + public void setDescription(final String description){ + this.description = description; + } + public String getIcon(){ + return this.icon; + } + public void setIcon(final String icon){ + this.icon = icon; + } + public Number getId(){ + return this.id; + } + public void setId(final Number id){ + this.id = id; + } + public String getMain(){ + return this.main; + } + public void setMain(final String main){ + this.main = main; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Weather [description=").append(this.description).append(", icon=") + .append(this.icon).append(", id=").append(this.id).append(", main=") + .append(this.main).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/currentweather/Wind.java b/app/src/main/java/de/example/exampletdd/model/currentweather/Wind.java new file mode 100644 index 0000000..4edc55d --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/currentweather/Wind.java @@ -0,0 +1,30 @@ +package de.example.exampletdd.model.currentweather; + +import java.io.Serializable; + +public class Wind implements Serializable { + private static final long serialVersionUID = 5495842422633674631L; + private Number deg; + private Number speed; + + public Number getDeg(){ + return this.deg; + } + public void setDeg(final Number deg){ + this.deg = deg; + } + public Number getSpeed(){ + return this.speed; + } + public void setSpeed(final Number speed){ + this.speed = speed; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Wind [deg=").append(this.deg).append(", speed=").append(this.speed) + .append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/City.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/City.java new file mode 100644 index 0000000..904ff5d --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/City.java @@ -0,0 +1,53 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; + + +public class City implements Serializable { + private static final long serialVersionUID = 3079687975077030704L; + private Coord coord; + private String country; + private Number id; + private String name; + private Number population; + + public Coord getCoord(){ + return this.coord; + } + public void setCoord(final Coord coord){ + this.coord = coord; + } + public String getCountry(){ + return this.country; + } + public void setCountry(final String country){ + this.country = country; + } + public Number getId(){ + return this.id; + } + public void setId(final Number id){ + this.id = id; + } + public String getName(){ + return this.name; + } + public void setName(final String name){ + this.name = name; + } + public Number getPopulation(){ + return this.population; + } + public void setPopulation(final Number population){ + this.population = population; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("City [coord=").append(this.coord).append(", country=").append(this.country) + .append(", id=").append(this.id).append(", name=").append(this.name) + .append(", population=").append(this.population).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/Coord.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/Coord.java new file mode 100644 index 0000000..fcfee58 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/Coord.java @@ -0,0 +1,30 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; + +public class Coord implements Serializable { + private static final long serialVersionUID = 8069257976701986700L; + private Number lat; + private Number lon; + + public Number getLat(){ + return this.lat; + } + public void setLat(final Number lat){ + this.lat = lat; + } + public Number getLon(){ + return this.lon; + } + public void setLon(final Number lon){ + this.lon = lon; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Coord [lat=").append(this.lat).append(", lon=").append(this.lon) + .append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/Forecast.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/Forecast.java new file mode 100644 index 0000000..5a59a02 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/Forecast.java @@ -0,0 +1,62 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; +import java.util.List; + +/** + * Auto generated by: http://jsongen.byingtondesign.com/ + * (with my own modifications) + * + */ +public class Forecast implements Serializable { + private static final long serialVersionUID = 5095443678019686190L; + private City city; + private Number cnt; + private Number cod; + private List list; + private Number message; + + public City getCity(){ + return this.city; + } + public void setCity(final City city){ + this.city = city; + } + public Number getCnt(){ + return this.cnt; + } + public void setCnt(final Number cnt){ + this.cnt = cnt; + } + + public Number getCod() { + return this.cod; + } + + public void setCod(final Number cod) { + this.cod = cod; + } + + public List getList() { + return this.list; + } + + public void setList(final List list) { + this.list = list; + } + public Number getMessage(){ + return this.message; + } + public void setMessage(final Number message){ + this.message = message; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Forecast [city=").append(this.city).append(", cnt=") + .append(this.cnt).append(", cod=").append(this.cod).append(", list=") + .append(this.list).append(", message=").append(this.message).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/List.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/List.java new file mode 100644 index 0000000..2104579 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/List.java @@ -0,0 +1,93 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; + + +public class List implements Serializable { + private static final long serialVersionUID = 838468273188666785L; + private Number clouds; + private Number deg; + private Number dt; + private Number humidity; + private Number pressure; + private Number rain; + private Number snow; + private Number speed; + private Temp temp; + private java.util.List weather; + + public Number getClouds(){ + return this.clouds; + } + public void setClouds(final Number clouds){ + this.clouds = clouds; + } + public Number getDeg(){ + return this.deg; + } + public void setDeg(final Number deg){ + this.deg = deg; + } + public Number getDt(){ + return this.dt; + } + public void setDt(final Number dt){ + this.dt = dt; + } + public Number getHumidity(){ + return this.humidity; + } + public void setHumidity(final Number humidity){ + this.humidity = humidity; + } + public Number getPressure(){ + return this.pressure; + } + public void setPressure(final Number pressure){ + this.pressure = pressure; + } + public Number getRain(){ + return this.rain; + } + public void setRain(final Number rain){ + this.rain = rain; + } + public Number getSnow() { + return this.snow; + } + public void setSnow(final Number snow) { + this.snow = snow; + } + public Number getSpeed(){ + return this.speed; + } + public void setSpeed(final Number speed){ + this.speed = speed; + } + public Temp getTemp(){ + return this.temp; + } + public void setTemp(final Temp temp){ + this.temp = temp; + } + + public java.util.List getWeather() { + return this.weather; + } + + public void setWeather(final java.util.List weather) { + this.weather = weather; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("List [clouds=").append(this.clouds).append(", deg=").append(this.deg) + .append(", dt=").append(this.dt).append(", humidity=").append(this.humidity) + .append(", pressure=").append(this.pressure).append(", rain=").append(this.rain) + .append(", snow=").append(this.snow).append(", speed=").append(this.speed) + .append(", temp=").append(this.temp).append(", weather=").append(this.weather) + .append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/Temp.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/Temp.java new file mode 100644 index 0000000..e4b1a7a --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/Temp.java @@ -0,0 +1,60 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; + +public class Temp implements Serializable { + private static final long serialVersionUID = -7614799035018271127L; + private Number day; + private Number eve; + private Number max; + private Number min; + private Number morn; + private Number night; + + public Number getDay(){ + return this.day; + } + public void setDay(final Number day){ + this.day = day; + } + public Number getEve(){ + return this.eve; + } + public void setEve(final Number eve){ + this.eve = eve; + } + public Number getMax(){ + return this.max; + } + public void setMax(final Number max){ + this.max = max; + } + public Number getMin(){ + return this.min; + } + public void setMin(final Number min){ + this.min = min; + } + public Number getMorn(){ + return this.morn; + } + public void setMorn(final Number morn){ + this.morn = morn; + } + public Number getNight(){ + return this.night; + } + public void setNight(final Number night){ + this.night = night; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Temp [day=").append(this.day).append(", eve=").append(this.eve) + .append(", max=").append(this.max).append(", min=").append(this.min) + .append(", morn=").append(this.morn).append(", night=").append(this.night) + .append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/model/forecastweather/Weather.java b/app/src/main/java/de/example/exampletdd/model/forecastweather/Weather.java new file mode 100644 index 0000000..f0de236 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/model/forecastweather/Weather.java @@ -0,0 +1,45 @@ +package de.example.exampletdd.model.forecastweather; + +import java.io.Serializable; + +public class Weather implements Serializable { + private static final long serialVersionUID = -5066357704517363241L; + private String description; + private String icon; + private Number id; + private String main; + + public String getDescription(){ + return this.description; + } + public void setDescription(final String description){ + this.description = description; + } + public String getIcon(){ + return this.icon; + } + public void setIcon(final String icon){ + this.icon = icon; + } + public Number getId(){ + return this.id; + } + public void setId(final Number id){ + this.id = id; + } + public String getMain(){ + return this.main; + } + public void setMain(final String main){ + this.main = main; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Weather [description=").append(this.description).append(", icon=") + .append(this.icon).append(", id=").append(this.id).append(", main=") + .append(this.main).append("]"); + return builder.toString(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/parser/IJPOSParser.java b/app/src/main/java/de/example/exampletdd/parser/IJPOSParser.java new file mode 100644 index 0000000..14a1d55 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/parser/IJPOSParser.java @@ -0,0 +1,17 @@ +package de.example.exampletdd.parser; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.model.forecastweather.Forecast; + +public interface IJPOSParser { + + public Current retrieveCurrenFromJPOS(final String jsonData) + throws JsonParseException, IOException; + + public Forecast retrieveForecastFromJPOS(final String jsonData) + throws JsonParseException, IOException; +} diff --git a/app/src/main/java/de/example/exampletdd/parser/JPOSWeatherParser.java b/app/src/main/java/de/example/exampletdd/parser/JPOSWeatherParser.java new file mode 100644 index 0000000..b71e342 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/parser/JPOSWeatherParser.java @@ -0,0 +1,405 @@ +package de.example.exampletdd.parser; + +import java.io.IOException; +import java.util.ArrayList; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import de.example.exampletdd.model.currentweather.Clouds; +import de.example.exampletdd.model.currentweather.Coord; +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.model.currentweather.Main; +import de.example.exampletdd.model.currentweather.Rain; +import de.example.exampletdd.model.currentweather.Sys; +import de.example.exampletdd.model.currentweather.Wind; +import de.example.exampletdd.model.forecastweather.City; +import de.example.exampletdd.model.forecastweather.Forecast; +import de.example.exampletdd.model.forecastweather.Temp; + +public class JPOSWeatherParser implements IJPOSParser { + + @Override + public Current retrieveCurrenFromJPOS(final String jsonData) + throws JsonParseException, IOException { + final JsonFactory f = new JsonFactory(); + + final Current currentWeatherData = new Current(); + currentWeatherData.setClouds(new Clouds()); + currentWeatherData.setCoord(new Coord()); + currentWeatherData.setMain(new Main()); + currentWeatherData.setRain(new Rain()); + currentWeatherData.setSys(new Sys()); + currentWeatherData + .setWeather(new ArrayList()); + currentWeatherData.setWind(new Wind()); + final JsonParser jParser = f.createParser(jsonData); + + this.getCurrentWeatherData(currentWeatherData, jParser); + + return currentWeatherData; + } + + @Override + public Forecast retrieveForecastFromJPOS(final String jsonData) + throws JsonParseException, IOException { + final JsonFactory f = new JsonFactory(); + + final Forecast forecastWeatherData = new Forecast(); + forecastWeatherData + .setList(new ArrayList(15)); + final City city = new City(); + city.setCoord(new de.example.exampletdd.model.forecastweather.Coord()); + forecastWeatherData.setCity(city); + final JsonParser jParser = f.createParser(jsonData); + + this.getForecastWeatherData(forecastWeatherData, jParser); + + return forecastWeatherData; + } + + private void getCurrentWeatherData(final Current currentWeatherData, + final JsonParser jParser) throws JsonParseException, IOException { + if (jParser.nextToken() == JsonToken.START_OBJECT) { + + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String fieldname = jParser.getCurrentName(); + final JsonToken nextToken = jParser.nextToken(); + if (nextToken == JsonToken.START_OBJECT) { + this.getCurrentWeatherDataObjects(currentWeatherData, jParser, fieldname); + } + if (nextToken == JsonToken.START_ARRAY) { + JsonToken tokenNext = jParser.nextToken(); + while (tokenNext != JsonToken.END_ARRAY) { + if (tokenNext == JsonToken.START_OBJECT) { + this.getCurrentWeatherDataObjects(currentWeatherData, jParser, fieldname); + } + tokenNext = jParser.nextToken(); + } + } + if ((nextToken == JsonToken.VALUE_NUMBER_INT) + || (nextToken == JsonToken.VALUE_STRING)) { + this.getCurrentWeatherDataObjects(currentWeatherData, jParser, fieldname); + } + } + } + } + + private void getCurrentWeatherDataObjects(final Current currentWeatherData, + final JsonParser jParser, final String fieldname) throws JsonParseException, + IOException { + if (fieldname == "coord") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("lon".equals(namefield)) { + currentWeatherData.getCoord().setLon(jParser.getDoubleValue()); + } + if ("lat".equals(namefield)) { + currentWeatherData.getCoord().setLat(jParser.getDoubleValue()); + } + } + } + if (fieldname == "sys") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("message".equals(namefield)) { + currentWeatherData.getSys().setMessage(jParser.getDoubleValue()); + } + if ("country".equals(namefield)) { + currentWeatherData.getSys().setCountry(jParser.getValueAsString()); + } + if ("sunrise".equals(namefield)) { + currentWeatherData.getSys().setSunrise(jParser.getValueAsLong()); + } + if ("sunset".equals(namefield)) { + currentWeatherData.getSys().setSunset(jParser.getValueAsLong()); + } + } + } + if (fieldname == "weather") { + final de.example.exampletdd.model.currentweather.Weather weather = new de.example.exampletdd.model.currentweather.Weather(); + currentWeatherData.getWeather().add(weather); + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("id".equals(namefield)) { + weather.setId(jParser.getIntValue()); + } + if ("main".equals(namefield)) { + weather.setMain(jParser.getText()); + } + if ("description".equals(namefield)) { + weather.setDescription(jParser.getText()); + } + if ("icon".equals(namefield)) { + weather.setIcon(jParser.getText()); + } + + } + } + if (fieldname == "base") { + currentWeatherData.setBase(jParser.getText()); + } + if (fieldname == "main") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("temp".equals(namefield)) { + currentWeatherData.getMain().setTemp(jParser.getDoubleValue()); + } + if ("temp_min".equals(namefield)) { + currentWeatherData.getMain().setTemp_min(jParser.getDoubleValue()); + } + if ("temp_max".equals(namefield)) { + currentWeatherData.getMain().setTemp_max(jParser.getDoubleValue()); + } + if ("pressure".equals(namefield)) { + currentWeatherData.getMain().setPressure(jParser.getDoubleValue()); + } + if ("sea_level".equals(namefield)) { + currentWeatherData.getMain().setSea_level(jParser.getDoubleValue()); + } + if ("grnd_level".equals(namefield)) { + currentWeatherData.getMain().setGrnd_level(jParser.getDoubleValue()); + } + if ("humidity".equals(namefield)) { + currentWeatherData.getMain().setHumidity(jParser.getDoubleValue()); + } + } + } + if (fieldname == "wind") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("speed".equals(namefield)) { + currentWeatherData.getWind().setSpeed(jParser.getDoubleValue()); + } + if ("deg".equals(namefield)) { + currentWeatherData.getWind().setDeg(jParser.getDoubleValue()); + } + } + } + if (fieldname == "clouds") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("all".equals(namefield)) { + currentWeatherData.getClouds().setAll(jParser.getDoubleValue()); + } + } + } + if (fieldname == "dt") { + currentWeatherData.setDt(jParser.getLongValue()); + } + if (fieldname == "rain") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("3h".equals(namefield)) { + currentWeatherData.getRain().set3h(jParser.getDoubleValue()); + } + } + } + if (fieldname == "snow") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("3h".equals(namefield)) { + currentWeatherData.getSnow().set3h(jParser.getDoubleValue()); + } + } + } + if (fieldname == "id") { + currentWeatherData.setId(jParser.getLongValue()); + } + if (fieldname == "name") { + currentWeatherData.setName(jParser.getText()); + } + if (fieldname == "cod") { + currentWeatherData.setCod(jParser.getIntValue()); + } + } + + private void getForecastWeatherData(final Forecast forecastWeatherData, + final JsonParser jParser) throws JsonParseException, IOException { + if (jParser.nextToken() == JsonToken.START_OBJECT) { + + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String fieldname = jParser.getCurrentName(); + final JsonToken nextToken = jParser.nextToken(); + if (nextToken == JsonToken.START_OBJECT) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, fieldname); + } + if (nextToken == JsonToken.START_ARRAY) { + JsonToken tokenNext = jParser.nextToken(); + while (tokenNext != JsonToken.END_ARRAY) { + if (tokenNext == JsonToken.START_OBJECT) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, fieldname); + } + tokenNext = jParser.nextToken(); + } + } + if ((nextToken == JsonToken.VALUE_NUMBER_INT) + || (nextToken == JsonToken.VALUE_STRING)) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, fieldname); + } + } + } + } + + private void getForecastWeatherDataObjects(final Forecast forecastWeatherData, + final JsonParser jParser, final String fieldname) throws JsonParseException, + IOException { + + if (fieldname == "cod") { + final String stringCod = jParser.getText(); + forecastWeatherData.setCod(Long.valueOf(stringCod)); + } + if (fieldname == "message") { + forecastWeatherData.setMessage(jParser.getDoubleValue()); + } + if (fieldname == "city") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + final JsonToken nextToken = jParser.nextToken(); // move to + // value + if ("id".equals(namefield)) { + forecastWeatherData.getCity().setId(jParser.getLongValue()); + } + if ("name".equals(namefield)) { + forecastWeatherData.getCity().setName(jParser.getText()); + } + if ("coord".equals(namefield)) { + if (nextToken == JsonToken.START_OBJECT) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, namefield); + } + } + if ("country".equals(namefield)) { + forecastWeatherData.getCity().setCountry(jParser.getText()); + } + if ("population".equals(namefield)) { + forecastWeatherData.getCity().setPopulation(jParser.getLongValue()); + } + } + } + if (fieldname == "cnt") { + forecastWeatherData.setCnt(jParser.getIntValue()); + } + if (fieldname == "coord") { + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("lon".equals(namefield)) { + forecastWeatherData.getCity().getCoord().setLon(jParser.getDoubleValue()); + } + if ("lat".equals(namefield)) { + forecastWeatherData.getCity().getCoord().setLat(jParser.getDoubleValue()); + } + } + } + if (fieldname == "list") { + final de.example.exampletdd.model.forecastweather.List list = new de.example.exampletdd.model.forecastweather.List(); + list.setTemp(new Temp()); + list.setWeather(new ArrayList()); + forecastWeatherData.getList().add(list); + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + final JsonToken nextToken = jParser.nextToken(); // move to + // value + if ("dt".equals(namefield)) { + list.setDt(jParser.getLongValue()); + } + if ("temp".equals(namefield)) { + if (nextToken == JsonToken.START_OBJECT) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, namefield); + } + } + if ("pressure".equals(namefield)) { + list.setPressure(jParser.getDoubleValue()); + } + if ("humidity".equals(namefield)) { + list.setHumidity(jParser.getDoubleValue()); + } + if ("weather".equals(namefield)) { + if (nextToken == JsonToken.START_ARRAY) { + JsonToken tokenNext = jParser.nextToken(); + while (tokenNext != JsonToken.END_ARRAY) { + if (tokenNext == JsonToken.START_OBJECT) { + this.getForecastWeatherDataObjects(forecastWeatherData, jParser, + namefield); + } + tokenNext = jParser.nextToken(); + } + } + } + if ("speed".equals(namefield)) { + list.setSpeed(jParser.getDoubleValue()); + } + if ("deg".equals(namefield)) { + list.setDeg(jParser.getDoubleValue()); + } + if ("clouds".equals(namefield)) { + list.setClouds(jParser.getDoubleValue()); + } + if ("rain".equals(namefield)) { + list.setRain(jParser.getDoubleValue()); + } + } + } + if (fieldname == "temp") { + final de.example.exampletdd.model.forecastweather.List list = forecastWeatherData + .getList().get( + (forecastWeatherData.getList().size() - 1)); + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + if ("day".equals(namefield)) { + list.getTemp().setDay(jParser.getDoubleValue()); + } + if ("min".equals(namefield)) { + list.getTemp().setMin(jParser.getDoubleValue()); + } + if ("max".equals(namefield)) { + list.getTemp().setMax(jParser.getDoubleValue()); + } + if ("night".equals(namefield)) { + list.getTemp().setNight(jParser.getDoubleValue()); + } + if ("eve".equals(namefield)) { + list.getTemp().setEve(jParser.getDoubleValue()); + } + if ("morn".equals(namefield)) { + list.getTemp().setMorn(jParser.getDoubleValue()); + } + } + } + if (fieldname == "weather") { + final de.example.exampletdd.model.forecastweather.List list = forecastWeatherData + .getList().get( + (forecastWeatherData.getList().size() - 1)); + final de.example.exampletdd.model.forecastweather.Weather weather = new de.example.exampletdd.model.forecastweather.Weather(); + while (jParser.nextToken() != JsonToken.END_OBJECT) { + final String namefield = jParser.getCurrentName(); + jParser.nextToken(); // move to value + + if ("id".equals(namefield)) { + weather.setId(jParser.getIntValue()); + } + if ("main".equals(namefield)) { + weather.setMain(jParser.getText()); + } + if ("description".equals(namefield)) { + weather.setDescription(jParser.getText()); + } + if ("icon".equals(namefield)) { + weather.setIcon(jParser.getText()); + } + } + list.getWeather().add(weather); + } + } +} diff --git a/app/src/main/java/de/example/exampletdd/service/IconsList.java b/app/src/main/java/de/example/exampletdd/service/IconsList.java new file mode 100644 index 0000000..ea53085 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/service/IconsList.java @@ -0,0 +1,143 @@ +package de.example.exampletdd.service; + +import java.util.HashMap; +import java.util.Map; + +import de.example.exampletdd.R; + +public enum IconsList { + ICON_01d("01d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_clear; + } + }, + ICON_01n("01n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_clear_night; + } + }, + ICON_02d("02d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_few_clouds; + } + }, + ICON_02n("02n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_few_clouds_night; + } + }, + ICON_03d("03d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_few_clouds; + } + }, + ICON_03n("03n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_few_clouds; + } + }, + ICON_04d("04d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_overcast; + } + }, + ICON_04n("04n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_overcast; + } + }, + ICON_09d("09d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_showers; + } + }, + ICON_09n("09n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_showers; + } + }, + ICON_10d("10d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_showers_scattered; + } + }, + ICON_10n("10n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_showers_scattered; + } + }, + ICON_11d("11d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_storm; + } + }, + ICON_11n("11n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_storm; + } + }, + ICON_13d("13d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_snow; + } + }, + ICON_13n("13n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_snow; + } + }, + ICON_50d("50d") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_fog; + } + }, + ICON_50n("50n") { + @Override + public int getResourceDrawable() { + return R.drawable.weather_fog; + } + }; + + private final String icon; + // Map with every enum constant. Class variable initializer. JLS§12.4.2 + // Executed in textual order. + private static final Map codeMap = new HashMap(); + + // Static initializer. JLS§12.4.2 Executed in textual order. + static { + for (final IconsList code : IconsList.values()) { + codeMap.put(code.getIcon(), code); + } + } + + private IconsList(final String icon) { + this.icon = icon; + } + + public static final IconsList getIcon(final String icon) { + return codeMap.get(icon); + } + + private String getIcon() { + return this.icon; + } + + public abstract int getResourceDrawable(); +} diff --git a/app/src/main/java/de/example/exampletdd/service/PermanentStorage.java b/app/src/main/java/de/example/exampletdd/service/PermanentStorage.java new file mode 100644 index 0000000..49eac40 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/service/PermanentStorage.java @@ -0,0 +1,140 @@ +package de.example.exampletdd.service; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; + +import android.content.Context; +import android.util.Log; +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.model.forecastweather.Forecast; + + +/** + * TODO: show some error message when there is no enough space for saving files. :/ + * + */ +public class PermanentStorage { + private static final String TAG = "PermanentStorage"; + private static final String CURRENT_DATA_FILE = "current.file"; + private static final String FORECAST_DATA_FILE = "forecast.file"; + private final Context context; + + public PermanentStorage(final Context context) { + this.context = context; + } + + public void saveCurrent(final Current current) { + + try { + this.saveObject(CURRENT_DATA_FILE, current); + } catch (FileNotFoundException e) { + Log.e(TAG, "saveCurrent exception: ", e); + } catch (IOException e) { + Log.e(TAG, "saveCurrent exception: ", e); + } + } + + public Current getCurrent() { + + try { + return (Current) this.getObject(CURRENT_DATA_FILE); + } catch (final StreamCorruptedException e) { + Log.e(TAG, "getCurrent exception: ", e); + } catch (final FileNotFoundException e) { + Log.e(TAG, "getCurrent exception: ", e); + } catch (final IOException e) { + Log.e(TAG, "getCurrent exception: ", e); + } catch (final ClassNotFoundException e) { + Log.e(TAG, "getCurrent exception: ", e); + } + + return null; + } + + public void saveForecast(final Forecast forecast) { + + try { + this.saveObject(FORECAST_DATA_FILE, forecast); + } catch (FileNotFoundException e) { + Log.e(TAG, "saveForecast exception: ", e); + } catch (IOException e) { + Log.e(TAG, "saveForecast exception: ", e); + } + } + + public Forecast getForecast() { + + try { + return (Forecast) this.getObject(FORECAST_DATA_FILE); + } catch (final StreamCorruptedException e) { + Log.e(TAG, "getForecast exception: ", e); + } catch (final FileNotFoundException e) { + Log.e(TAG, "getForecast exception: ", e); + } catch (final IOException e) { + Log.e(TAG, "getForecast exception: ", e); + } catch (final ClassNotFoundException e) { + Log.e(TAG, "getForecast exception: ", e); + } + + return null; + } + + private void saveObject(final String fileName, final Object objectToStore) + throws FileNotFoundException, IOException { + final String temporaryFileName = fileName.concat(".tmp"); + + final FileOutputStream tmpPersistFile = this.context.openFileOutput( + temporaryFileName, Context.MODE_PRIVATE); + try { + final ObjectOutputStream oos = new ObjectOutputStream(tmpPersistFile); + try { + oos.writeObject(objectToStore); + + // Don't fear the fsync! + // http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/ + tmpPersistFile.flush(); + tmpPersistFile.getFD().sync(); + } finally { + oos.close(); + } + } finally { + tmpPersistFile.close(); + } + + this.renameFile(temporaryFileName, fileName); + } + + private Object getObject(final String fileName) throws StreamCorruptedException, FileNotFoundException, + IOException, ClassNotFoundException { + final InputStream persistFile = this.context.openFileInput(fileName); + try { + final ObjectInputStream ois = new ObjectInputStream(persistFile); + try { + return ois.readObject(); + } finally { + ois.close(); + } + } finally { + persistFile.close(); + } + } + + private void renameFile(final String fromFileName, final String toFileName) throws IOException { + final File filesDir = this.context.getFilesDir(); + final File fromFile = new File(filesDir, fromFileName); + final File toFile = new File(filesDir, toFileName); + if (!fromFile.renameTo(toFile)) { + if (!fromFile.delete()) { + throw new IOException("PermanentStorage, delete file error"); + } + throw new IOException("PermanentStorage, rename file error"); + } + } +} + diff --git a/app/src/main/java/de/example/exampletdd/service/ServiceParser.java b/app/src/main/java/de/example/exampletdd/service/ServiceParser.java new file mode 100644 index 0000000..e05a67f --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/service/ServiceParser.java @@ -0,0 +1,64 @@ +package de.example.exampletdd.service; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Locale; + +import com.fasterxml.jackson.core.JsonParseException; + +import de.example.exampletdd.model.currentweather.Current; +import de.example.exampletdd.model.forecastweather.Forecast; +import de.example.exampletdd.parser.IJPOSParser; + +public class ServiceParser { + private final IJPOSParser JPOSParser; + + public ServiceParser(final IJPOSParser JPOSWeatherParser) { + this.JPOSParser = JPOSWeatherParser; + } + + public Current retrieveCurrentFromJPOS(final String jsonData) + throws JsonParseException, IOException { + return this.JPOSParser.retrieveCurrenFromJPOS(jsonData); + } + + public Forecast retrieveForecastFromJPOS(final String jsonData) + throws JsonParseException, IOException { + return this.JPOSParser.retrieveForecastFromJPOS(jsonData); + } + + public String createURIAPIForecast(final String urlAPI, final String APIVersion, + final double latitude, final double longitude, final String resultsNumber) { + + final MessageFormat formatURIAPI = new MessageFormat(urlAPI, Locale.US); + final Object[] values = new Object[4]; + values[0] = APIVersion; + values[1] = latitude; + values[2] = longitude; + values[3] = resultsNumber; + + return formatURIAPI.format(values); + } + + public String createURIAPICurrent(final String urlAPI, final String APIVersion, + final double latitude, final double longitude) { + + final MessageFormat formatURIAPI = new MessageFormat(urlAPI, Locale.US); + final Object[] values = new Object[3]; + values[0] = APIVersion; + values[1] = latitude; + values[2] = longitude; + + return formatURIAPI.format(values); + } + + public String createURIAPIicon(final String icon, final String urlAPI) { + + final MessageFormat formatURIAPI = new MessageFormat(urlAPI, Locale.US); + final Object[] values = new Object[1]; + values[0] = icon; + + return formatURIAPI.format(values); + } + +} diff --git a/app/src/main/java/de/example/exampletdd/widget/WidgetConfigure.java b/app/src/main/java/de/example/exampletdd/widget/WidgetConfigure.java new file mode 100644 index 0000000..bb5d8ca --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/widget/WidgetConfigure.java @@ -0,0 +1,88 @@ +package de.example.exampletdd.widget; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.Fragment; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import de.example.exampletdd.R; + +public class WidgetConfigure extends Activity { + private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + + final View.OnClickListener mOnClickListener = new View.OnClickListener() { + public void onClick(View v) { + + + // When the button is clicked, save the string in our prefs and return that they + // clicked OK. + // Push widget update to surface with newly set prefix + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance( + WidgetConfigure.this.getApplicationContext()); + WidgetProvider.updateAppWidget( + WidgetConfigure.this.getApplicationContext(), + appWidgetManager, + mAppWidgetId); + + // Make sure we pass back the original appWidgetId + final Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + WidgetConfigure.this.setResult(RESULT_OK, resultValue); + finish(); + } + }; + + @Override + public void onCreate(final Bundle icicle) { + super.onCreate(icicle); + + // Find the widget id from the intent. + final Intent intent = getIntent(); + final Bundle extras = intent.getExtras(); + boolean isActionFromUser = false; + + if (extras != null) { + mAppWidgetId = extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + isActionFromUser = extras.getBoolean("actionFromUser", false); + } + + // If they gave us an intent without the widget id, just bail. + if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + this.finish(); + } + + if (!isActionFromUser) { + // Set the result to CANCELED. This will cause the widget host to cancel + // out of the widget placement if they press the back button. + this.setResult(RESULT_CANCELED); + } + + // Set the view layout resource to use. + this.setContentView(R.layout.appwidget_configure); + + final Bundle args = new Bundle(); + args.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + final Fragment preferences = new WidgetPreferences(); + preferences.setRetainInstance(true); + preferences.setArguments(args); + this.getFragmentManager() + .beginTransaction() + .replace(R.id.weather_appwidget_configure_preferences, preferences) + .commit(); + + // Bind the action for the save button. + this.findViewById(R.id.weather_appwidget_configure_save_button).setOnClickListener(mOnClickListener); + } + + @Override + public void onResume() { + super.onResume(); + + final ActionBar actionBar = this.getActionBar(); + actionBar.setTitle(this.getString(R.string.widget_preferences_action_settings)); + } +} diff --git a/app/src/main/java/de/example/exampletdd/widget/WidgetPreferences.java b/app/src/main/java/de/example/exampletdd/widget/WidgetPreferences.java new file mode 100644 index 0000000..7744ea8 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/widget/WidgetPreferences.java @@ -0,0 +1,172 @@ +package de.example.exampletdd.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.PreferenceFragment; +import android.preference.SwitchPreference; +import de.example.exampletdd.R; + +/** + * TODO: + * IT DOES NOT WORK IF USER IS WORKING WITH TWO OR MORE WIDGET PREFERENCE WINDOWS AT THE SAME TIME + * (hopefully nobody will realize...) + * How to implement custom preference activities (no extending from PreferenceActivity or PreferenceFragment) + * without pain? + */ +public class WidgetPreferences extends PreferenceFragment implements OnSharedPreferenceChangeListener { + private int appWidgetId; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Retain this fragment across configuration changes. + this.setRetainInstance(true); + + final Bundle bundle = this.getArguments(); + appWidgetId = bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + // Load the preferences from an XML resource + this.addPreferencesFromResource(R.xml.appwidget_preferences); + + + /******************* Show/hide country field *******************/ + String keyPreference = this.getActivity().getApplicationContext().getString( + R.string.widget_preferences_country_switch_key); + String realKeyPreference = keyPreference + "_" + appWidgetId; + + // What was saved to permanent storage (or default values if it is the first time) + boolean countryValue = this.getActivity().getSharedPreferences("WIDGET_PREFERENCES", Context.MODE_PRIVATE) + .getBoolean(realKeyPreference, false); + + // What is shown on the screen + final SwitchPreference countryPref = (SwitchPreference) this.findPreference(keyPreference); + countryPref.setChecked(countryValue); + + /********************* Temperature units **********************/ + final String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature); + final String[] humanValues = this.getResources().getStringArray(R.array.weather_preferences_temperature_human_value); + + keyPreference = this.getActivity().getApplicationContext().getString( + R.string.widget_preferences_temperature_key); + realKeyPreference = keyPreference + "_" + appWidgetId; + + + // What was saved to permanent storage (or default values if it is the first time) + final String tempValue = this.getActivity().getSharedPreferences("WIDGET_PREFERENCES", Context.MODE_PRIVATE) + .getString(realKeyPreference, this.getString(R.string.weather_preferences_temperature_celsius)); + String humanValue = this.getString(R.string.weather_preferences_temperature_celsius_human_value); + int index = 0; + if (tempValue.equals(values[0])) { + index = 0; + humanValue = humanValues[0]; + } else if (tempValue.equals(values[1])) { + index = 1; + humanValue = humanValues[1]; + } else if (tempValue.equals(values[2])) { + index = 2; + humanValue = humanValues[2]; + } + + + // What is shown on the screen + final ListPreference listPref = (ListPreference) this.findPreference(keyPreference); + listPref.setSummary(humanValue); + listPref.setValueIndex(index); + listPref.setValue(tempValue); + } + + @Override + public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { + + /******************* Show/hide country field *******************/ + String keyPreference = this.getActivity().getApplicationContext().getString( + R.string.widget_preferences_country_switch_key); + if (key.equals(keyPreference)) { + final String realKeyPreference = keyPreference + "_" + appWidgetId; + // Saving to permanent storage. + final SharedPreferences.Editor prefs = + this.getActivity().getSharedPreferences( + "WIDGET_PREFERENCES", + Context.MODE_PRIVATE).edit(); + // What is shown on the screen + final SwitchPreference preference = (SwitchPreference) this.findPreference(key); + if (preference.isChecked()) + { + // Saving to permanent storage. + prefs.putBoolean(realKeyPreference, true); + } else { + // Saving to permanent storage. + prefs.putBoolean(realKeyPreference, false); + } + prefs.commit(); + } + + /********************* Temperature units **********************/ + keyPreference = this.getActivity().getApplicationContext().getString( + R.string.widget_preferences_temperature_key); + if (key.equals(keyPreference)) { + final String[] values = this.getResources().getStringArray( + R.array.weather_preferences_temperature); + final String[] humanValues = this.getResources().getStringArray( + R.array.weather_preferences_temperature_human_value); + + // What is shown on the screen + final ListPreference listPref = (ListPreference) this.findPreference(key); + final String value = listPref.getValue(); + String humanValue = ""; + if (value.equals(values[0])) { + humanValue = humanValues[0]; + } else if (value.equals(values[1])) { + humanValue = humanValues[1]; + } else if (value.equals(values[2])) { + humanValue = humanValues[2]; + } + // Update data on screen + listPref.setSummary(humanValue); + + + // Saving to permanent storage. + final String realKeyPreference = keyPreference + "_" + appWidgetId; + + final SharedPreferences.Editor prefs = + this.getActivity().getSharedPreferences( + "WIDGET_PREFERENCES", + Context.MODE_PRIVATE).edit(); + prefs.putString(realKeyPreference, value); + prefs.commit(); + return; + } + + + + } + + @Override + public void onResume() { + super.onResume(); + this.getPreferenceManager().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + this.getPreferenceManager().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + } + + public static void deletePreference(final Context context, final int appWidgetId) { + final String keyPreference = context.getApplicationContext().getString( + R.string.widget_preferences_temperature_key); + final String realKeyPreference = keyPreference + "_" + appWidgetId; + + final SharedPreferences.Editor prefs = context.getSharedPreferences("WIDGET_PREFERENCES", Context.MODE_PRIVATE).edit(); + prefs.remove(realKeyPreference); + prefs.commit(); + } +} diff --git a/app/src/main/java/de/example/exampletdd/widget/WidgetProvider.java b/app/src/main/java/de/example/exampletdd/widget/WidgetProvider.java new file mode 100644 index 0000000..b5126f6 --- /dev/null +++ b/app/src/main/java/de/example/exampletdd/widget/WidgetProvider.java @@ -0,0 +1,64 @@ +package de.example.exampletdd.widget; + + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import de.example.exampletdd.WidgetIntentService; + +public class WidgetProvider extends AppWidgetProvider { + + @Override + public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { + // For each widget that needs an update, get the text that we should display: + // - Create a RemoteViews object for it + // - Set the text in the RemoteViews object + // - Tell the AppWidgetManager to show that views object for the widget. + final int N = appWidgetIds.length; + for (int i=0; i + + + diff --git a/app/src/main/res/anim/weather_map_exit_progress.xml b/app/src/main/res/anim/weather_map_exit_progress.xml new file mode 100644 index 0000000..f425b7e --- /dev/null +++ b/app/src/main/res/anim/weather_map_exit_progress.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_action_import_export.png b/app/src/main/res/drawable-hdpi/ic_action_import_export.png new file mode 100644 index 0000000..18c0fe7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_import_export.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_map.png b/app/src/main/res/drawable-hdpi/ic_action_map.png new file mode 100644 index 0000000..370cf5c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_map.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_place.png b/app/src/main/res/drawable-hdpi/ic_action_place.png new file mode 100644 index 0000000..dba994d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_place.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_refresh.png b/app/src/main/res/drawable-hdpi/ic_action_refresh.png new file mode 100644 index 0000000..dae2790 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_refresh.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_search.png b/app/src/main/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 0000000..772e359 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_settings.png b/app/src/main/res/drawable-hdpi/ic_action_settings.png new file mode 100644 index 0000000..54eecde Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_settings.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..288b665 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png new file mode 100644 index 0000000..577e055 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png differ diff --git a/app/src/main/res/drawable-hdpi/thermometer.png b/app/src/main/res/drawable-hdpi/thermometer.png new file mode 100644 index 0000000..89f39a8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/thermometer.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_clear.png b/app/src/main/res/drawable-hdpi/weather_clear.png new file mode 100644 index 0000000..974da61 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_clear.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_clear_night.png b/app/src/main/res/drawable-hdpi/weather_clear_night.png new file mode 100644 index 0000000..2db4d82 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_clear_night.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_few_clouds.png b/app/src/main/res/drawable-hdpi/weather_few_clouds.png new file mode 100644 index 0000000..ab3e430 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_few_clouds.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_few_clouds_night.png b/app/src/main/res/drawable-hdpi/weather_few_clouds_night.png new file mode 100644 index 0000000..67ee004 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_few_clouds_night.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_fog.png b/app/src/main/res/drawable-hdpi/weather_fog.png new file mode 100644 index 0000000..bc3c429 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_fog.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_overcast.png b/app/src/main/res/drawable-hdpi/weather_overcast.png new file mode 100644 index 0000000..8075b13 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_overcast.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_severe_alert.png b/app/src/main/res/drawable-hdpi/weather_severe_alert.png new file mode 100644 index 0000000..42334e8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_severe_alert.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_showers.png b/app/src/main/res/drawable-hdpi/weather_showers.png new file mode 100644 index 0000000..d2a456a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_showers.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_showers_scattered.png b/app/src/main/res/drawable-hdpi/weather_showers_scattered.png new file mode 100644 index 0000000..e58ade9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_showers_scattered.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_snow.png b/app/src/main/res/drawable-hdpi/weather_snow.png new file mode 100644 index 0000000..4812c8c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_snow.png differ diff --git a/app/src/main/res/drawable-hdpi/weather_storm.png b/app/src/main/res/drawable-hdpi/weather_storm.png new file mode 100644 index 0000000..b829abf Binary files /dev/null and b/app/src/main/res/drawable-hdpi/weather_storm.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_import_export.png b/app/src/main/res/drawable-mdpi/ic_action_import_export.png new file mode 100644 index 0000000..95914cc Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_import_export.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_map.png b/app/src/main/res/drawable-mdpi/ic_action_map.png new file mode 100644 index 0000000..50a9100 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_map.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_place.png b/app/src/main/res/drawable-mdpi/ic_action_place.png new file mode 100644 index 0000000..ce055ca Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_place.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_refresh.png b/app/src/main/res/drawable-mdpi/ic_action_refresh.png new file mode 100644 index 0000000..94ab6f4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_refresh.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_search.png b/app/src/main/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 0000000..4edb1ff Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_settings.png b/app/src/main/res/drawable-mdpi/ic_action_settings.png new file mode 100644 index 0000000..25c36db Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_settings.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..6ae570b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/thermometer.png b/app/src/main/res/drawable-mdpi/thermometer.png new file mode 100644 index 0000000..f645657 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/thermometer.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_clear.png b/app/src/main/res/drawable-mdpi/weather_clear.png new file mode 100644 index 0000000..0bdb0da Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_clear.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_clear_night.png b/app/src/main/res/drawable-mdpi/weather_clear_night.png new file mode 100644 index 0000000..2617ec1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_clear_night.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_few_clouds.png b/app/src/main/res/drawable-mdpi/weather_few_clouds.png new file mode 100644 index 0000000..5a78bd0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_few_clouds.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_few_clouds_night.png b/app/src/main/res/drawable-mdpi/weather_few_clouds_night.png new file mode 100644 index 0000000..3dcd13f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_few_clouds_night.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_fog.png b/app/src/main/res/drawable-mdpi/weather_fog.png new file mode 100644 index 0000000..7d99916 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_fog.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_overcast.png b/app/src/main/res/drawable-mdpi/weather_overcast.png new file mode 100644 index 0000000..79303a5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_overcast.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_severe_alert.png b/app/src/main/res/drawable-mdpi/weather_severe_alert.png new file mode 100644 index 0000000..308931b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_severe_alert.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_showers.png b/app/src/main/res/drawable-mdpi/weather_showers.png new file mode 100644 index 0000000..6dce4e1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_showers.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_showers_scattered.png b/app/src/main/res/drawable-mdpi/weather_showers_scattered.png new file mode 100644 index 0000000..0e0b43e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_showers_scattered.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_snow.png b/app/src/main/res/drawable-mdpi/weather_snow.png new file mode 100644 index 0000000..9364b58 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_snow.png differ diff --git a/app/src/main/res/drawable-mdpi/weather_storm.png b/app/src/main/res/drawable-mdpi/weather_storm.png new file mode 100644 index 0000000..5e37b51 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/weather_storm.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_import_export.png b/app/src/main/res/drawable-xhdpi/ic_action_import_export.png new file mode 100644 index 0000000..b247eda Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_import_export.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_map.png b/app/src/main/res/drawable-xhdpi/ic_action_map.png new file mode 100644 index 0000000..537c5a4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_map.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_place.png b/app/src/main/res/drawable-xhdpi/ic_action_place.png new file mode 100644 index 0000000..5e93aeb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_place.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_refresh.png b/app/src/main/res/drawable-xhdpi/ic_action_refresh.png new file mode 100644 index 0000000..ab4ab9d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_refresh.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search.png b/app/src/main/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 0000000..19658e4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_settings.png b/app/src/main/res/drawable-xhdpi/ic_action_settings.png new file mode 100644 index 0000000..425a8bc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_settings.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..d4fb7cd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/thermometer.png b/app/src/main/res/drawable-xhdpi/thermometer.png new file mode 100644 index 0000000..bd86520 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/thermometer.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_clear.png b/app/src/main/res/drawable-xhdpi/weather_clear.png new file mode 100644 index 0000000..42bad9c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_clear.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_clear_night.png b/app/src/main/res/drawable-xhdpi/weather_clear_night.png new file mode 100644 index 0000000..a9ab2dc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_clear_night.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_few_clouds.png b/app/src/main/res/drawable-xhdpi/weather_few_clouds.png new file mode 100644 index 0000000..954a7fd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_few_clouds.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_few_clouds_night.png b/app/src/main/res/drawable-xhdpi/weather_few_clouds_night.png new file mode 100644 index 0000000..be93567 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_few_clouds_night.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_fog.png b/app/src/main/res/drawable-xhdpi/weather_fog.png new file mode 100644 index 0000000..bcca0a8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_fog.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_overcast.png b/app/src/main/res/drawable-xhdpi/weather_overcast.png new file mode 100644 index 0000000..5096963 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_overcast.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_severe_alert.png b/app/src/main/res/drawable-xhdpi/weather_severe_alert.png new file mode 100644 index 0000000..c074f4e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_severe_alert.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_showers.png b/app/src/main/res/drawable-xhdpi/weather_showers.png new file mode 100644 index 0000000..28264f2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_showers.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_showers_scattered.png b/app/src/main/res/drawable-xhdpi/weather_showers_scattered.png new file mode 100644 index 0000000..f76f4e9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_showers_scattered.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_snow.png b/app/src/main/res/drawable-xhdpi/weather_snow.png new file mode 100644 index 0000000..5226484 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_snow.png differ diff --git a/app/src/main/res/drawable-xhdpi/weather_storm.png b/app/src/main/res/drawable-xhdpi/weather_storm.png new file mode 100644 index 0000000..7539dee Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/weather_storm.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_import_export.png b/app/src/main/res/drawable-xxhdpi/ic_action_import_export.png new file mode 100644 index 0000000..2dccb24 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_import_export.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_map.png b/app/src/main/res/drawable-xxhdpi/ic_action_map.png new file mode 100644 index 0000000..ed72ce9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_map.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_place.png b/app/src/main/res/drawable-xxhdpi/ic_action_place.png new file mode 100644 index 0000000..25623c7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_place.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_refresh.png b/app/src/main/res/drawable-xxhdpi/ic_action_refresh.png new file mode 100644 index 0000000..44ee117 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_refresh.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_search.png b/app/src/main/res/drawable-xxhdpi/ic_action_search.png new file mode 100644 index 0000000..a108638 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_settings.png b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png new file mode 100644 index 0000000..fe5fec4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..85a6081 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxhdpi/thermometer.png b/app/src/main/res/drawable-xxhdpi/thermometer.png new file mode 100644 index 0000000..37b3747 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/thermometer.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_clear.png b/app/src/main/res/drawable-xxhdpi/weather_clear.png new file mode 100644 index 0000000..73a5bbd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_clear.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_clear_night.png b/app/src/main/res/drawable-xxhdpi/weather_clear_night.png new file mode 100644 index 0000000..2c60054 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_clear_night.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_few_clouds.png b/app/src/main/res/drawable-xxhdpi/weather_few_clouds.png new file mode 100644 index 0000000..2d38702 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_few_clouds.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_few_clouds_night.png b/app/src/main/res/drawable-xxhdpi/weather_few_clouds_night.png new file mode 100644 index 0000000..333afa7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_few_clouds_night.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_fog.png b/app/src/main/res/drawable-xxhdpi/weather_fog.png new file mode 100644 index 0000000..1bebc54 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_fog.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_overcast.png b/app/src/main/res/drawable-xxhdpi/weather_overcast.png new file mode 100644 index 0000000..e3101eb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_overcast.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_severe_alert.png b/app/src/main/res/drawable-xxhdpi/weather_severe_alert.png new file mode 100644 index 0000000..b0ac4b7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_severe_alert.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_showers.png b/app/src/main/res/drawable-xxhdpi/weather_showers.png new file mode 100644 index 0000000..6a1860c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_showers.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_showers_scattered.png b/app/src/main/res/drawable-xxhdpi/weather_showers_scattered.png new file mode 100644 index 0000000..97d0f77 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_showers_scattered.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_snow.png b/app/src/main/res/drawable-xxhdpi/weather_snow.png new file mode 100644 index 0000000..ac714cc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_snow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/weather_storm.png b/app/src/main/res/drawable-xxhdpi/weather_storm.png new file mode 100644 index 0000000..c339221 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/weather_storm.png differ diff --git a/app/src/main/res/layout-large/weather_main.xml b/app/src/main/res/layout-large/weather_main.xml new file mode 100644 index 0000000..95e773e --- /dev/null +++ b/app/src/main/res/layout-large/weather_main.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/appwidget.xml b/app/src/main/res/layout/appwidget.xml new file mode 100644 index 0000000..08f33cc --- /dev/null +++ b/app/src/main/res/layout/appwidget.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/appwidget_configure.xml b/app/src/main/res/layout/appwidget_configure.xml new file mode 100644 index 0000000..f5df9a0 --- /dev/null +++ b/app/src/main/res/layout/appwidget_configure.xml @@ -0,0 +1,55 @@ + + + + + + + + + +