commit a8938e8fba46703875fa8e6c57393d95654273e5 Author: Billy Brawner Date: Tue Sep 5 12:10:54 2017 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10fa6bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/gradle.properties +/.idea/ +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..805cbfc --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + defaultConfig { + applicationId "com.wbrawner.weathermap" + minSdkVersion 19 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + buildConfigField("String", "API_KEY", API_KEY) + buildConfigField("String", "API_URL", API_URL) + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:26.+' + compile 'com.google.android.gms:play-services-maps:11.0.4' + compile 'com.google.android.gms:play-services-location:11.0.4' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.squareup.retrofit2:retrofit:2.3.0' + compile 'com.squareup.retrofit2:converter-moshi:2.3.0' + compile 'com.squareup.moshi:moshi:1.5.0' + compile 'com.jakewharton:butterknife:8.8.1' + compile 'com.squareup.picasso:picasso:2.5.2' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + testCompile 'junit:junit:4.12' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..9786e21 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/wbrawner/weathermap/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/wbrawner/weathermap/ExampleInstrumentedTest.java new file mode 100644 index 0000000..1057ed7 --- /dev/null +++ b/app/src/androidTest/java/com/wbrawner/weathermap/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wbrawner.weathermap; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.wbrawner.weathermap", appContext.getPackageName()); + } +} diff --git a/app/src/debug/res/values/google_maps_api.xml b/app/src/debug/res/values/google_maps_api.xml new file mode 100644 index 0000000..a31b547 --- /dev/null +++ b/app/src/debug/res/values/google_maps_api.xml @@ -0,0 +1,24 @@ + + + AIzaSyDiaGrAl8z-6t5dP5UGOF28m6qdbDvkW0s + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..77ec1f9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Clouds.java b/app/src/main/java/com/wbrawner/weathermap/model/Clouds.java new file mode 100644 index 0000000..720f374 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Clouds.java @@ -0,0 +1,19 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Clouds { + + @Json(name = "all") + private Integer all; + + public Integer getAll() { + return all; + } + + public void setAll(Integer all) { + this.all = all; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Coord.java b/app/src/main/java/com/wbrawner/weathermap/model/Coord.java new file mode 100644 index 0000000..5582c52 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Coord.java @@ -0,0 +1,29 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Coord { + + @Json(name = "lon") + private String lon; + @Json(name = "lat") + private String lat; + + public String getLon() { + return lon; + } + + public void setLon(String lon) { + this.lon = lon; + } + + public String getLat() { + return lat; + } + + public void setLat(String lat) { + this.lat = lat; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Main.java b/app/src/main/java/com/wbrawner/weathermap/model/Main.java new file mode 100644 index 0000000..15cbc0f --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Main.java @@ -0,0 +1,59 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Main { + + @Json(name = "temp") + private Double temp; + @Json(name = "humidity") + private Integer humidity; + @Json(name = "pressure") + private Integer pressure; + @Json(name = "temp_min") + private Double tempMin; + @Json(name = "temp_max") + private Double tempMax; + + public Double getTemp() { + return temp; + } + + public void setTemp(Double temp) { + this.temp = temp; + } + + public Integer getHumidity() { + return humidity; + } + + public void setHumidity(Integer humidity) { + this.humidity = humidity; + } + + public Integer getPressure() { + return pressure; + } + + public void setPressure(Integer pressure) { + this.pressure = pressure; + } + + public Double getTempMin() { + return tempMin; + } + + public void setTempMin(Double tempMin) { + this.tempMin = tempMin; + } + + public Double getTempMax() { + return tempMax; + } + + public void setTempMax(Double tempMax) { + this.tempMax = tempMax; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Rain.java b/app/src/main/java/com/wbrawner/weathermap/model/Rain.java new file mode 100644 index 0000000..65646e3 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Rain.java @@ -0,0 +1,19 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Rain { + + @Json(name = "3h") + private Integer _3h; + + public Integer get3h() { + return _3h; + } + + public void set3h(Integer _3h) { + this._3h = _3h; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Sys.java b/app/src/main/java/com/wbrawner/weathermap/model/Sys.java new file mode 100644 index 0000000..510c9ed --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Sys.java @@ -0,0 +1,39 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Sys { + + @Json(name = "country") + private String country; + @Json(name = "sunrise") + private Integer sunrise; + @Json(name = "sunset") + private Integer sunset; + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Integer getSunrise() { + return sunrise; + } + + public void setSunrise(Integer sunrise) { + this.sunrise = sunrise; + } + + public Integer getSunset() { + return sunset; + } + + public void setSunset(Integer sunset) { + this.sunset = sunset; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Weather.java b/app/src/main/java/com/wbrawner/weathermap/model/Weather.java new file mode 100644 index 0000000..c3d4eaf --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Weather.java @@ -0,0 +1,49 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Weather { + + @Json(name = "id") + private Integer id; + @Json(name = "main") + private String main; + @Json(name = "description") + private String description; + @Json(name = "icon") + private String icon; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getMain() { + return main; + } + + public void setMain(String main) { + this.main = main; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/model/Wind.java b/app/src/main/java/com/wbrawner/weathermap/model/Wind.java new file mode 100644 index 0000000..f9158b0 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/model/Wind.java @@ -0,0 +1,29 @@ + +package com.wbrawner.weathermap.model; + +import com.squareup.moshi.Json; + +public class Wind { + + @Json(name = "speed") + private Double speed; + @Json(name = "deg") + private Double deg; + + public Double getSpeed() { + return speed; + } + + public void setSpeed(Double speed) { + this.speed = speed; + } + + public Double getDeg() { + return deg; + } + + public void setDeg(Double deg) { + this.deg = deg; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/service/FetchWeatherIntentService.java b/app/src/main/java/com/wbrawner/weathermap/service/FetchWeatherIntentService.java new file mode 100644 index 0000000..eb3cf6c --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/service/FetchWeatherIntentService.java @@ -0,0 +1,76 @@ +package com.wbrawner.weathermap.service; + +import android.app.IntentService; +import android.content.Intent; +import android.content.Context; + +import com.wbrawner.weathermap.BuildConfig; +import com.wbrawner.weathermap.util.OpenWeatherMapInterface; +import com.wbrawner.weathermap.util.WeatherFetchHandler; +import com.wbrawner.weathermap.util.WeatherForecast; +import com.wbrawner.weathermap.util.WeatherResponse; + +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.moshi.MoshiConverterFactory; + +/** + * An {@link IntentService} subclass for handling asynchronous task requests in + * a service on a separate handler thread. + *

+ * TODO: Customize class - update intent actions, extra parameters and static + * helper methods. + */ +public class FetchWeatherIntentService extends IntentService { + private static final String ACTION_FETCH = "com.wbrawner.weathermap.service.action.fetch"; + + private static final String EXTRA_LAT = "com.wbrawner.weathermap.service.extra.LAT"; + private static final String EXTRA_LON = "com.wbrawner.weathermap.service.extra.LOM"; + + public FetchWeatherIntentService() { + super("FetchWeatherIntentService"); + } + + /** + * Starts this service to perform action Foo with the given parameters. If + * the service is already performing a task this action will be queued. + * + * @see IntentService + */ + public static void startActionFetch(Context context, String lat, String lon) { + Intent intent = new Intent(context, FetchWeatherIntentService.class); + intent.setAction(ACTION_FETCH); + intent.putExtra(EXTRA_LAT, lat); + intent.putExtra(EXTRA_LON, lon); + WeatherForecast.getInstance().setContext(context); + context.startService(intent); + } + + + @Override + protected void onHandleIntent(Intent intent) { + if (intent != null) { + final String action = intent.getAction(); + if (ACTION_FETCH.equals(action)) { + final String lat = intent.getStringExtra(EXTRA_LAT); + final String lon = intent.getStringExtra(EXTRA_LON); + handleActionFetch(lat, lon); + } + } + } + + /** + * Handle action Foo in the provided background thread with the provided + * parameters. + */ + private void handleActionFetch(String lat, String lon) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(BuildConfig.API_URL) + .addConverterFactory(MoshiConverterFactory.create()) + .build(); + OpenWeatherMapInterface weatherMapInterface = retrofit.create(OpenWeatherMapInterface.class); + Call responseCall = + weatherMapInterface.getWeatherForCoordinates(lat, lon, BuildConfig.API_KEY); + responseCall.enqueue(new WeatherFetchHandler()); + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/util/OpenWeatherMapInterface.java b/app/src/main/java/com/wbrawner/weathermap/util/OpenWeatherMapInterface.java new file mode 100644 index 0000000..56a03e3 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/util/OpenWeatherMapInterface.java @@ -0,0 +1,16 @@ +package com.wbrawner.weathermap.util; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface OpenWeatherMapInterface { + + @GET("/data/2.5/weather") + Call getWeatherForCoordinates( + @Query("lat") String lat, + @Query("lon") String lon, + @Query("APPID") String appId + ); + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/util/TempUtils.java b/app/src/main/java/com/wbrawner/weathermap/util/TempUtils.java new file mode 100644 index 0000000..671f499 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/util/TempUtils.java @@ -0,0 +1,14 @@ +package com.wbrawner.weathermap.util; + +/** + * A class to provide utility functions for conversion between temperature formats + */ +public class TempUtils { + public static double kelvinToFahrenheit(double kelvin) { + return (kelvin * (9/5)) - 459.67; + } + + public static double kelvinToCelsius(double kelvin) { + return kelvin - 273.15; + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/util/WeatherFetchHandler.java b/app/src/main/java/com/wbrawner/weathermap/util/WeatherFetchHandler.java new file mode 100644 index 0000000..161a64c --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/util/WeatherFetchHandler.java @@ -0,0 +1,26 @@ +package com.wbrawner.weathermap.util; + +import android.util.Log; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class WeatherFetchHandler implements Callback { + private static final String TAG = WeatherFetchHandler.class.getSimpleName(); + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + WeatherForecast.getInstance().setWeatherResponse(response.body()); + Log.d(TAG, "Weather retrieved. Conditions: " + response.body().getName()); + } else if (response.code() == 401) { + Log.e(TAG, "Authentication error. Check API Key"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e(TAG, "Error retrieving weather: ", t); + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/util/WeatherForecast.java b/app/src/main/java/com/wbrawner/weathermap/util/WeatherForecast.java new file mode 100644 index 0000000..402a943 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/util/WeatherForecast.java @@ -0,0 +1,38 @@ +package com.wbrawner.weathermap.util; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; + +import com.wbrawner.weathermap.view.WeatherActivity; + +public class WeatherForecast { + static WeatherForecast instance; + private WeatherResponse weatherResponse; + private Context mContext; + + public static WeatherForecast getInstance() { + if (instance == null) { + instance = new WeatherForecast(); + } + return instance; + } + + public WeatherResponse getWeatherResponse() { + return weatherResponse; + } + + public void setWeatherResponse(WeatherResponse weatherResponse) { + this.weatherResponse = weatherResponse; + if (mContext != null) { + Intent updateIntent = + new Intent(mContext, WeatherActivity.WeatherBroadcastReceiver.class); + updateIntent.setAction(WeatherActivity.ACTION_UPDATE); + LocalBroadcastManager.getInstance(mContext).sendBroadcast(updateIntent); + } + } + + public void setContext(Context mContext) { + this.mContext = mContext; + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/util/WeatherResponse.java b/app/src/main/java/com/wbrawner/weathermap/util/WeatherResponse.java new file mode 100644 index 0000000..d31edd6 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/util/WeatherResponse.java @@ -0,0 +1,127 @@ + +package com.wbrawner.weathermap.util; + +import java.util.List; +import com.squareup.moshi.Json; +import com.wbrawner.weathermap.model.Clouds; +import com.wbrawner.weathermap.model.Coord; +import com.wbrawner.weathermap.model.Main; +import com.wbrawner.weathermap.model.Rain; +import com.wbrawner.weathermap.model.Sys; +import com.wbrawner.weathermap.model.Weather; +import com.wbrawner.weathermap.model.Wind; + +public class WeatherResponse { + + @Json(name = "coord") + private Coord coord; + @Json(name = "sys") + private Sys sys; + @Json(name = "weather") + private List weather = null; + @Json(name = "main") + private Main main; + @Json(name = "wind") + private Wind wind; + @Json(name = "rain") + private Rain rain; + @Json(name = "clouds") + private Clouds clouds; + @Json(name = "dt") + private Integer dt; + @Json(name = "id") + private Integer id; + @Json(name = "name") + private String name; + @Json(name = "cod") + private Integer cod; + + public Coord getCoord() { + return coord; + } + + public void setCoord(Coord coord) { + this.coord = coord; + } + + public Sys getSys() { + return sys; + } + + public void setSys(Sys sys) { + this.sys = sys; + } + + public List getWeather() { + return weather; + } + + public void setWeather(List weather) { + this.weather = weather; + } + + public Main getMain() { + return main; + } + + public void setMain(Main main) { + this.main = main; + } + + public Wind getWind() { + return wind; + } + + public void setWind(Wind wind) { + this.wind = wind; + } + + public Rain getRain() { + return rain; + } + + public void setRain(Rain rain) { + this.rain = rain; + } + + public Clouds getClouds() { + return clouds; + } + + public void setClouds(Clouds clouds) { + this.clouds = clouds; + } + + public Integer getDt() { + return dt; + } + + public void setDt(Integer dt) { + this.dt = dt; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getCod() { + return cod; + } + + public void setCod(Integer cod) { + this.cod = cod; + } + +} diff --git a/app/src/main/java/com/wbrawner/weathermap/view/MapsActivity.java b/app/src/main/java/com/wbrawner/weathermap/view/MapsActivity.java new file mode 100644 index 0000000..660a050 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/view/MapsActivity.java @@ -0,0 +1,145 @@ +package com.wbrawner.weathermap.view; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.Location; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.util.Log; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +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 com.google.android.gms.tasks.OnSuccessListener; +import com.wbrawner.weathermap.R; +import com.wbrawner.weathermap.service.FetchWeatherIntentService; + +public class MapsActivity extends FragmentActivity implements OnMapReadyCallback { + + private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 0; + private static final String TAG = MapsActivity.class.getSimpleName(); + private boolean mapLoaded = false; + private FusedLocationProviderClient mFusedLocationClient; + private GoogleMap mMap; + private LatLng currentLocation; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_maps); + // Obtain the SupportMapFragment and get notified when the map is ready to be used. + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.map); + // Begin loading the map asynchronously + mapFragment.getMapAsync(this); + // Set Sydney as the default loading location in case we don't have access to the user location + currentLocation = new LatLng(-34, 151); + mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + // While the map is loading, check that we have permissions to access user location + checkLocationPermission(); + } + + + /** + * Manipulates the map once available. + * This callback is triggered when the map is ready to be used. + * This is where we can add markers or lines, add listeners or move the camera. In this case, + * we just add a marker near Sydney, Australia. + * If Google Play services is not installed on the device, the user will be prompted to install + * it inside the SupportMapFragment. This method will only be triggered once the user has + * installed Google Play services and returned to the app. + */ + @Override + public void onMapReady(GoogleMap googleMap) { + mMap = googleMap; + // Add a marker in Sydney and move the camera + mapLoaded = true; + mMap.addMarker(new MarkerOptions().position(currentLocation)); + mMap.moveCamera(CameraUpdateFactory.newLatLng(currentLocation)); + mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { + @Override + public void onMapClick(LatLng latLng) { + Log.d(TAG, "Map clicked"); + updateLocationMarker(latLng); + } + }); + mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + Log.d(TAG, "Marker tapped"); + Intent weatherDetailIntent = new Intent(MapsActivity.this, WeatherActivity.class); + startActivity(weatherDetailIntent); + return true; + } + }); + } + + /** + * Ensure that we have the permissions needed, otherwise request them + */ + public void checkLocationPermission() { + if (ContextCompat.checkSelfPermission(MapsActivity.this, + Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(MapsActivity.this, + new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, + PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); + } else { + getLocation(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode) { + case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION: + getLocation(); + } + } + + public void getLocation() { + try { + mFusedLocationClient.getLastLocation() + .addOnSuccessListener(this, new OnSuccessListener() { + @Override + public void onSuccess(Location location) { + // Got last known location. In some rare situations this can be null. + if (location != null) { + updateLocationMarker( + new LatLng( + location.getLatitude(), + location.getLongitude() + ) + ); + } + } + }); + } catch (SecurityException e) { + Log.e(TAG, "Security exception: ", e); + } + } + public void updateLocationMarker(LatLng latLng) { + // Update the location + currentLocation = latLng; + // Fetch the weather in the background + FetchWeatherIntentService.startActionFetch( + MapsActivity.this, + String.valueOf(latLng.latitude), + String.valueOf(latLng.longitude) + ); + if (mapLoaded) { + mMap.clear(); + mMap.addMarker(new MarkerOptions().position(currentLocation)); + mMap.moveCamera(CameraUpdateFactory.newLatLng(currentLocation)); + } + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/view/WeatherActivity.java b/app/src/main/java/com/wbrawner/weathermap/view/WeatherActivity.java new file mode 100644 index 0000000..035a6fc --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/view/WeatherActivity.java @@ -0,0 +1,91 @@ +package com.wbrawner.weathermap.view; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.Image; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.widget.ImageView; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; +import com.wbrawner.weathermap.R; +import com.wbrawner.weathermap.util.TempUtils; +import com.wbrawner.weathermap.util.WeatherForecast; +import com.wbrawner.weathermap.util.WeatherResponse; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class WeatherActivity extends AppCompatActivity { + private WeatherResponse forecast; + @BindView(R.id.description) + TextView description; + + @BindView(R.id.forecastImg) + ImageView forecastImg; + + @BindView(R.id.temp) + TextView temp; + + @BindView(R.id.high) + TextView high; + + @BindView(R.id.low) + TextView low; + + + public static final String ACTION_UPDATE = "com.wbrawner.weathermap.view.action.UPDATE"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_weather); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + ButterKnife.bind(this); + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_UPDATE); + LocalBroadcastManager.getInstance(this).registerReceiver(new WeatherBroadcastReceiver(), filter); +// WeatherForecast.getInstance().setContext(this); + checkForecast(); + } + + private void checkForecast() { + forecast = WeatherForecast.getInstance().getWeatherResponse(); + if (forecast != null) + updateForecast(); + } + + private void updateForecast() { + setTitle(forecast.getName()); + Picasso.with(this) + .load("http://openweathermap.org/img/w/" + forecast.getWeather().get(0).getIcon() + ".png") + .into(forecastImg); + description.setText(forecast.getWeather().get(0).getDescription()); + temp.setText(String.format( + getResources().getConfiguration().locale, + "%.1f °C", + TempUtils.kelvinToCelsius(forecast.getMain().getTemp()) + )); + high.setText(String.format( + getResources().getConfiguration().locale, + "%.1f °C", + TempUtils.kelvinToCelsius(forecast.getMain().getTempMax()) + )); + low.setText(String.format( + getResources().getConfiguration().locale, + "%.1f °C", + TempUtils.kelvinToCelsius(forecast.getMain().getTempMin()) + )); + } + + public class WeatherBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + checkForecast(); + } + } +} diff --git a/app/src/main/java/com/wbrawner/weathermap/view/WeatherMapFragment.java b/app/src/main/java/com/wbrawner/weathermap/view/WeatherMapFragment.java new file mode 100644 index 0000000..c198cf4 --- /dev/null +++ b/app/src/main/java/com/wbrawner/weathermap/view/WeatherMapFragment.java @@ -0,0 +1,7 @@ +package com.wbrawner.weathermap.view; + +import com.google.android.gms.maps.SupportMapFragment; + +public class WeatherMapFragment extends SupportMapFragment { + +} diff --git a/app/src/main/res/layout/activity_maps.xml b/app/src/main/res/layout/activity_maps.xml new file mode 100644 index 0000000..f541e15 --- /dev/null +++ b/app/src/main/res/layout/activity_maps.xml @@ -0,0 +1,8 @@ + diff --git a/app/src/main/res/layout/activity_weather.xml b/app/src/main/res/layout/activity_weather.xml new file mode 100644 index 0000000..ed9bbaa --- /dev/null +++ b/app/src/main/res/layout/activity_weather.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a078e3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..efc028a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3af2608 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9bec2e6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..34947cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..819a698 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + WeatherMap + Map + Description: + High: + Low: + Current Temperature: + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/release/res/values/google_maps_api.xml b/app/src/release/res/values/google_maps_api.xml new file mode 100644 index 0000000..fa88c61 --- /dev/null +++ b/app/src/release/res/values/google_maps_api.xml @@ -0,0 +1,22 @@ + + + + YOUR_KEY_HERE + + diff --git a/app/src/test/java/com/wbrawner/weathermap/ExampleUnitTest.java b/app/src/test/java/com/wbrawner/weathermap/ExampleUnitTest.java new file mode 100644 index 0000000..55df9bc --- /dev/null +++ b/app/src/test/java/com/wbrawner/weathermap/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.wbrawner.weathermap; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..cf08918 --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'me.tatarka:gradle-retrolambda:3.7.0' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..5a6875a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Sep 05 07:45:53 CDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'