Initial commit

This commit is contained in:
William Brawner 2017-09-05 12:10:54 -05:00
commit a8938e8fba
46 changed files with 1396 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/gradle.properties
/.idea/
.DS_Store
/build
/captures
.externalNativeBuild

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

41
app/build.gradle Normal file
View file

@ -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'
}

25
app/proguard-rules.pro vendored Normal file
View file

@ -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

View file

@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@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());
}
}

View file

@ -0,0 +1,24 @@
<resources>
<!--
TODO: Before you run your application, you need a Google Maps API key.
To get one, follow this link, follow the directions and press "Create" at the end:
https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=0D:28:96:0F:5A:AA:F4:9F:87:65:EF:D2:0D:A5:50:26:5F:79:01:53%3Bcom.wbrawner.weathermap
You can also add your credentials to an existing key, using these values:
Package name:
0D:28:96:0F:5A:AA:F4:9F:87:65:EF:D2:0D:A5:50:26:5F:79:01:53
SHA-1 certificate fingerprint:
0D:28:96:0F:5A:AA:F4:9F:87:65:EF:D2:0D:A5:50:26:5F:79:01:53
Alternatively, follow the directions here:
https://developers.google.com/maps/documentation/android/start#get-key
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">AIzaSyDiaGrAl8z-6t5dP5UGOF28m6qdbDvkW0s</string>
</resources>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wbrawner.weathermap">
<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but you must specify either coarse or fine
location permissions for the 'MyLocation' functionality.
-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name=".view.MapsActivity"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.FetchWeatherIntentService"
android:exported="false" />
<activity
android:name=".view.WeatherActivity"
android:parentActivityName=".view.MapsActivity" />
</application>
</manifest>

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.
* <p>
* 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<WeatherResponse> responseCall =
weatherMapInterface.getWeatherForCoordinates(lat, lon, BuildConfig.API_KEY);
responseCall.enqueue(new WeatherFetchHandler());
}
}

View file

@ -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<WeatherResponse> getWeatherForCoordinates(
@Query("lat") String lat,
@Query("lon") String lon,
@Query("APPID") String appId
);
}

View file

@ -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;
}
}

View file

@ -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<WeatherResponse> {
private static final String TAG = WeatherFetchHandler.class.getSimpleName();
@Override
public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> 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<WeatherResponse> call, Throwable t) {
Log.e(TAG, "Error retrieving weather: ", t);
}
}

View file

@ -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;
}
}

View file

@ -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> 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<Weather> getWeather() {
return weather;
}
public void setWeather(List<Weather> 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;
}
}

View file

@ -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<Location>() {
@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));
}
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,7 @@
package com.wbrawner.weathermap.view;
import com.google.android.gms.maps.SupportMapFragment;
public class WeatherMapFragment extends SupportMapFragment {
}

View file

@ -0,0 +1,8 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wbrawner.weathermap.view.MapsActivity" />

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.wbrawner.weathermap.view.WeatherActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/forecastImg" />
<LinearLayout
android:layout_width="368dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/description"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/description" />
</LinearLayout>
<LinearLayout
android:layout_width="368dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current"
android:paddingRight="5dp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/temp" />
</LinearLayout>
<LinearLayout
android:layout_width="368dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/high"
android:paddingRight="5dp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/high" />
</LinearLayout>
<LinearLayout
android:layout_width="368dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/low"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/low" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/loading"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="0dp" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View file

@ -0,0 +1,8 @@
<resources>
<string name="app_name">WeatherMap</string>
<string name="title_activity_maps">Map</string>
<string name="description">Description: </string>
<string name="high">High: </string>
<string name="low">Low: </string>
<string name="current">Current Temperature:</string>
</resources>

View file

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View file

@ -0,0 +1,22 @@
<resources>
<!--
TODO: Before you release your application, you need a Google Maps API key.
To do this, you can either add your release key credentials to your existing
key, or create a new key.
Note that this file specifies the API key for the release build target.
If you have previously set up a key for the debug target with the debug signing certificate,
you will also need to set up a key for your release certificate.
Follow the directions here:
https://developers.google.com/maps/documentation/android/signup
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
YOUR_KEY_HERE
</string>
</resources>

View file

@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

24
build.gradle Normal file
View file

@ -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
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -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

160
gradlew vendored Normal file
View file

@ -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 "$@"

90
gradlew.bat vendored Normal file
View file

@ -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

1
settings.gradle Normal file
View file

@ -0,0 +1 @@
include ':app'