diff --git a/background-service/src/main/java/com/androidsx/rainnotifications/backgroundservice/WeatherService.java b/background-service/src/main/java/com/androidsx/rainnotifications/backgroundservice/WeatherService.java index 182ea68..24e56e4 100644 --- a/background-service/src/main/java/com/androidsx/rainnotifications/backgroundservice/WeatherService.java +++ b/background-service/src/main/java/com/androidsx/rainnotifications/backgroundservice/WeatherService.java @@ -14,7 +14,7 @@ import com.androidsx.rainnotifications.backgroundservice.util.NotificationHelper; import com.androidsx.rainnotifications.backgroundservice.util.UserLocationFetcher; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientException; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; import com.androidsx.rainnotifications.model.Alert; import com.androidsx.rainnotifications.model.Day; import com.androidsx.rainnotifications.model.DayTemplate; @@ -59,7 +59,7 @@ public int onStartCommand(final Intent intent, int flags, int startId) { UserLocationFetcher.getUserLocation(this, new UserLocationFetcher.UserLocationResultListener() { @Override public void onLocationSuccess(Location location) { - WeatherClientFactory.requestForecastForLocation(getApplicationContext(), location.getLatitude(), location.getLongitude(), new WeatherClientResponseListener() { + WeatherClientFactory.requestHourlyForecastForLocation(getApplicationContext(), location.getLatitude(), location.getLongitude(), new WeatherClientHourlyResponseListener() { @Override public void onForecastSuccess(ForecastTable forecastTable) { if (intent != null && intent.getIntExtra(Constants.Extras.EXTRA_DAY_ALARM, 0) == Constants.Alarms.DAY_ALARM_ID) { diff --git a/common-library/src/main/java/com/androidsx/commonlibrary/CommonConstants.java b/common-library/src/main/java/com/androidsx/commonlibrary/CommonConstants.java index c64fc0c..adf72a8 100644 --- a/common-library/src/main/java/com/androidsx/commonlibrary/CommonConstants.java +++ b/common-library/src/main/java/com/androidsx/commonlibrary/CommonConstants.java @@ -5,7 +5,7 @@ public class CommonConstants { /** * Current application environment. It is essential to switch this to {@link Env#LIVE} for the Live APKs. */ - public static final Env ENV = Env.DEV; + public static final Env ENV = Env.BETA; /** * Definition of the different runtime environments. diff --git a/dailyclothes/src/main/res/drawable-nodpi/ann_taylor_1.jpg b/dailyclothes/src/main/assets/clothes/clear/ann_taylor_1.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/ann_taylor_1.jpg rename to dailyclothes/src/main/assets/clothes/clear/ann_taylor_1.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/ann_taylor_2.jpg b/dailyclothes/src/main/assets/clothes/clear/ann_taylor_2.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/ann_taylor_2.jpg rename to dailyclothes/src/main/assets/clothes/clear/ann_taylor_2.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/ann_taylor_3.jpg b/dailyclothes/src/main/assets/clothes/clear/ann_taylor_3.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/ann_taylor_3.jpg rename to dailyclothes/src/main/assets/clothes/clear/ann_taylor_3.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/ann_taylor_4.jpg b/dailyclothes/src/main/assets/clothes/clear/ann_taylor_4.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/ann_taylor_4.jpg rename to dailyclothes/src/main/assets/clothes/clear/ann_taylor_4.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/ann_taylor_5.jpg b/dailyclothes/src/main/assets/clothes/clear/ann_taylor_5.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/ann_taylor_5.jpg rename to dailyclothes/src/main/assets/clothes/clear/ann_taylor_5.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_1.jpg b/dailyclothes/src/main/assets/clothes/cloudy/blogger_1.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_1.jpg rename to dailyclothes/src/main/assets/clothes/cloudy/blogger_1.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_2.jpg b/dailyclothes/src/main/assets/clothes/cloudy/blogger_2.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_2.jpg rename to dailyclothes/src/main/assets/clothes/cloudy/blogger_2.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_3.jpg b/dailyclothes/src/main/assets/clothes/cloudy/blogger_3.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_3.jpg rename to dailyclothes/src/main/assets/clothes/cloudy/blogger_3.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_4.jpg b/dailyclothes/src/main/assets/clothes/cloudy/blogger_4.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_4.jpg rename to dailyclothes/src/main/assets/clothes/cloudy/blogger_4.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_5.jpg b/dailyclothes/src/main/assets/clothes/cloudy/blogger_5.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_5.jpg rename to dailyclothes/src/main/assets/clothes/cloudy/blogger_5.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_10.jpg b/dailyclothes/src/main/assets/clothes/rain/blogger_10.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_10.jpg rename to dailyclothes/src/main/assets/clothes/rain/blogger_10.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_6.jpg b/dailyclothes/src/main/assets/clothes/rain/blogger_6.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_6.jpg rename to dailyclothes/src/main/assets/clothes/rain/blogger_6.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_7.jpg b/dailyclothes/src/main/assets/clothes/rain/blogger_7.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_7.jpg rename to dailyclothes/src/main/assets/clothes/rain/blogger_7.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_8.jpg b/dailyclothes/src/main/assets/clothes/rain/blogger_8.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_8.jpg rename to dailyclothes/src/main/assets/clothes/rain/blogger_8.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/blogger_9.jpg b/dailyclothes/src/main/assets/clothes/rain/blogger_9.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/blogger_9.jpg rename to dailyclothes/src/main/assets/clothes/rain/blogger_9.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/lucky_1.jpg b/dailyclothes/src/main/assets/clothes/snow/lucky_1.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/lucky_1.jpg rename to dailyclothes/src/main/assets/clothes/snow/lucky_1.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/lucky_2.jpg b/dailyclothes/src/main/assets/clothes/snow/lucky_2.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/lucky_2.jpg rename to dailyclothes/src/main/assets/clothes/snow/lucky_2.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/lucky_3.jpg b/dailyclothes/src/main/assets/clothes/snow/lucky_3.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/lucky_3.jpg rename to dailyclothes/src/main/assets/clothes/snow/lucky_3.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/lucky_4.jpg b/dailyclothes/src/main/assets/clothes/snow/lucky_4.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/lucky_4.jpg rename to dailyclothes/src/main/assets/clothes/snow/lucky_4.jpg diff --git a/dailyclothes/src/main/res/drawable-nodpi/lucky_5.jpg b/dailyclothes/src/main/assets/clothes/snow/lucky_5.jpg similarity index 100% rename from dailyclothes/src/main/res/drawable-nodpi/lucky_5.jpg rename to dailyclothes/src/main/assets/clothes/snow/lucky_5.jpg diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/Clothes.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/Clothes.java index 8c14260..74c68fa 100644 --- a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/Clothes.java +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/Clothes.java @@ -1,13 +1,9 @@ package com.androidsx.rainnotifications.dailyclothes.model; -public class Clothes { - private final int photo; +import android.content.Context; +import android.os.Parcelable; +import android.widget.ImageView; - public Clothes (int photo) { - this.photo = photo; - } - - public int getPhoto() { - return photo; - } +public abstract class Clothes implements Parcelable{ + public abstract void loadOnImageView(Context context, ImageView imageView); } diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/MockDailyForecast.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/MockDailyForecast.java deleted file mode 100644 index 14a2d98..0000000 --- a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/MockDailyForecast.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.androidsx.rainnotifications.dailyclothes.model; - -import com.androidsx.rainnotifications.dailyclothes.R; - -import java.util.ArrayList; -import java.util.List; - -public class MockDailyForecast { - public int iconRes; - public String day; - public int minTemperature; - public int maxTemperature; - - public MockDailyForecast(int iconRes, String day, int minTemperature, int maxTemperature) { - this.iconRes = iconRes; - this.day = day; - this.minTemperature = minTemperature; - this.maxTemperature = maxTemperature; - } - - public static List getMockList() { - ArrayList list = new ArrayList(); - - list.add(new MockDailyForecast(R.drawable.ic_rain, "MONDAY", 52, 68)); - list.add(new MockDailyForecast(R.drawable.ic_rain, "TUESDAY", 51, 66)); - list.add(new MockDailyForecast(R.drawable.ic_clear, "WEDNESDAY", 49, 64)); - list.add(new MockDailyForecast(R.drawable.ic_clear, "THURSDAY", 50, 61)); - list.add(new MockDailyForecast(R.drawable.ic_partly_cloudy, "FRIDAY", 48, 60)); - - return list; - } -} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoader.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoader.java new file mode 100644 index 0000000..57d8423 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoader.java @@ -0,0 +1,9 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader; + +import android.content.Context; + +import com.androidsx.rainnotifications.model.DailyWeatherWrapper; + +public interface ClothesLoader { + public void loadClothes(Context context, DailyWeatherWrapper dailyWeather, ClothesLoaderListener listener); +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderException.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderException.java new file mode 100644 index 0000000..d9df309 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderException.java @@ -0,0 +1,11 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader; + +public class ClothesLoaderException extends Exception { + + public ClothesLoaderException(String detailMessage) { + super(detailMessage); + } + public ClothesLoaderException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderFactory.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderFactory.java new file mode 100644 index 0000000..8b66525 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderFactory.java @@ -0,0 +1,26 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader; + +import android.content.Context; + +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.local.LocalClothesLoader; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.network.NetworkClothesLoader; +import com.androidsx.rainnotifications.model.DailyWeatherWrapper; + +public abstract class ClothesLoaderFactory { + + private static final ClothesLoaderType TYPE = ClothesLoaderType.LOCAL_LOADER; + + public static void getClothes(Context context, DailyWeatherWrapper dailyWeather, ClothesLoaderListener listener) { + + final ClothesLoader clothesLoader; + switch (TYPE) { + case NETWORK_LOADER: clothesLoader = new NetworkClothesLoader(); + break; + case LOCAL_LOADER: clothesLoader = new LocalClothesLoader(); + break; + default: throw new IllegalArgumentException("Unsupported type: " + TYPE); + } + + clothesLoader.loadClothes(context, dailyWeather, listener); + } +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderListener.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderListener.java new file mode 100644 index 0000000..f235162 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderListener.java @@ -0,0 +1,11 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader; + +import com.androidsx.rainnotifications.dailyclothes.model.Clothes; + +import java.util.List; + +public interface ClothesLoaderListener { + public void onClothesLoaderSuccess(List clothesList); + + public void onClothesLoaderFailure(ClothesLoaderException exception); +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderType.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderType.java new file mode 100644 index 0000000..9f66bf2 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/ClothesLoaderType.java @@ -0,0 +1,6 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader; + +public enum ClothesLoaderType { + LOCAL_LOADER, + NETWORK_LOADER; +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothes.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothes.java new file mode 100644 index 0000000..2bc6704 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothes.java @@ -0,0 +1,47 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader.local; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.ImageView; + +import com.androidsx.rainnotifications.dailyclothes.model.Clothes; +import com.squareup.picasso.Picasso; + +public class LocalClothes extends Clothes{ + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LocalClothes createFromParcel(Parcel in) { + return new LocalClothes(in); + } + + public LocalClothes[] newArray(int size) { + return new LocalClothes[size]; + } + }; + + private final String imagePath; + + public LocalClothes(String imagePath) { + this.imagePath = imagePath; + } + + private LocalClothes(Parcel in) { + imagePath = in.readString(); + } + + @Override + public void loadOnImageView(Context context, ImageView imageView) { + Picasso.with(context).load(imagePath).into(imageView); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(imagePath); + } +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothesLoader.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothesLoader.java new file mode 100644 index 0000000..aae1f19 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/local/LocalClothesLoader.java @@ -0,0 +1,66 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader.local; + +import android.content.Context; +import android.os.Handler; + +import com.androidsx.rainnotifications.dailyclothes.model.Clothes; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoader; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderException; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderListener; +import com.androidsx.rainnotifications.model.DailyWeatherWrapper; +import com.androidsx.rainnotifications.model.WeatherWrapper; + +import java.util.ArrayList; +import java.util.List; + +import timber.log.Timber; + +public class LocalClothesLoader implements ClothesLoader { + + private static final int SIMULATE_ASYNC_TASK_DELAY = 200; + private static final String PATH_ASSETS = "file:///android_asset/"; + private static final String PATH_CLOTHES = "clothes/"; + + + @Override + public void loadClothes(Context context, DailyWeatherWrapper dailyWeather, ClothesLoaderListener listener) { + // TODO: At this time temperature is not taken into account + + Timber.d("LOADING CLOTHES FOR " + dailyWeather.getWeatherType() + + " Min: " + dailyWeather.getMinTemperature(WeatherWrapper.TemperatureScale.CELSIUS) + + " Max: " + dailyWeather.getMaxTemperature(WeatherWrapper.TemperatureScale.CELSIUS)); + + try { + String weatherClothesPath = PATH_CLOTHES + dailyWeather.getWeatherType().toString().toLowerCase(); + String[] clothesPaths = context.getAssets().list(weatherClothesPath); + + final List clothesList = new ArrayList(); + for (String path : clothesPaths) { + clothesList.add(new LocalClothes(PATH_ASSETS + weatherClothesPath + "/" + path)); + } + + simulateAsyncLoad(listener, clothesList); + + } catch (Exception e) { + Timber.e(e, "Failed to load clothes from assets"); + simulateAsyncLoad(listener, null); + } + } + + private void simulateAsyncLoad(final ClothesLoaderListener listener, final List clothesList) { + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + if(clothesList == null) { + listener.onClothesLoaderFailure(new ClothesLoaderException("Null assets list")); + } + else if(clothesList.isEmpty()) { + listener.onClothesLoaderFailure(new ClothesLoaderException("Empty assets list")); + } + else { + listener.onClothesLoaderSuccess(clothesList); + } + } + }, SIMULATE_ASYNC_TASK_DELAY); + } +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/network/NetworkClothesLoader.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/network/NetworkClothesLoader.java new file mode 100644 index 0000000..c3fe9c4 --- /dev/null +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/model/clothesloader/network/NetworkClothesLoader.java @@ -0,0 +1,15 @@ +package com.androidsx.rainnotifications.dailyclothes.model.clothesloader.network; + +import android.content.Context; + +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoader; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderListener; +import com.androidsx.rainnotifications.model.DailyWeatherWrapper; + +public class NetworkClothesLoader implements ClothesLoader{ + + @Override + public void loadClothes(Context context, DailyWeatherWrapper dailyWeather, ClothesLoaderListener listener) { + throw new IllegalArgumentException("Unimplemented NetworkClothesLoader"); + } +} diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/ClothesPagerAdapter.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/ClothesPagerAdapter.java index 416b65c..b850d5f 100644 --- a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/ClothesPagerAdapter.java +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/ClothesPagerAdapter.java @@ -23,25 +23,30 @@ public ClothesPagerAdapter(FragmentManager fm, List clothesList) { this.clothesList = clothesList; } + public void updateClothesList(List clothesList) { + this.clothesList = clothesList; + notifyDataSetChanged(); + } + @Override public Fragment getItem(int position) { - return ClothesFragment.newInstance(clothesList.get(position).getPhoto()); + return ClothesFragment.newInstance(clothesList.get(position)); } @Override public int getCount() { - return clothesList.size(); + return clothesList != null ? clothesList.size() : 0; } public static class ClothesFragment extends Fragment { - private static final String ARG_IMAGE_RESOURCE = "ImageFragment:imageResource"; - private int imageResource; + private static final String ARG_CLOTHES = "ImageFragment:clothes"; + private Clothes clothes; - public static ClothesFragment newInstance(int imageResource) { + public static ClothesFragment newInstance(Clothes clothes) { ClothesFragment fragment = new ClothesFragment(); Bundle args = new Bundle(); - args.putInt(ARG_IMAGE_RESOURCE, imageResource); + args.putParcelable(ARG_CLOTHES, clothes); fragment.setArguments(args); return fragment; } @@ -49,15 +54,13 @@ public static ClothesFragment newInstance(int imageResource) { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - imageResource = getArguments().getInt(ARG_IMAGE_RESOURCE); + clothes = getArguments().getParcelable(ARG_CLOTHES); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_clothes, container, false); - - ((ImageView) rootView.findViewById(R.id.image_view)).setImageResource(imageResource); //TODO: Hacer con Picasso - + clothes.loadOnImageView(getActivity(), (ImageView) rootView.findViewById(R.id.image_view)); return rootView; } } diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/DailyForecastAdapter.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/DailyForecastAdapter.java deleted file mode 100644 index e9e94dc..0000000 --- a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/DailyForecastAdapter.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.androidsx.rainnotifications.dailyclothes.ui.home; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.androidsx.rainnotifications.dailyclothes.R; -import com.androidsx.rainnotifications.dailyclothes.model.MockDailyForecast; - -import java.util.List; - -public class DailyForecastAdapter extends BaseAdapter { - private List dailyForecasts; - private LayoutInflater inflater; - - public DailyForecastAdapter(LayoutInflater inflater, List dailyForecasts) { - this.inflater = inflater; - this.dailyForecasts = dailyForecasts; - } - - @Override - public int getCount() { - return dailyForecasts.size(); - } - - @Override - public Object getItem(int position) { - return dailyForecasts.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if(convertView == null) { - convertView = inflater.inflate(R.layout.item_daily_forecast, null); - convertView.setTag(new DailyForecastHolder(convertView)); - } - - ((DailyForecastHolder) convertView.getTag()).update(dailyForecasts.get(position)); - - return convertView; - } - - private class DailyForecastHolder { - private ImageView icon; - private TextView day; - private TextView minTemperature; - private TextView maxTemperature; - - public DailyForecastHolder(View v) { - icon = (ImageView) v.findViewById(R.id.daily_forecast_icon); - day = (TextView) v.findViewById(R.id.daily_forecast_day); - minTemperature = (TextView) v.findViewById(R.id.daily_forecast_min_temperature); - maxTemperature = (TextView) v.findViewById(R.id.daily_forecast_max_temperature); - } - - public void update(MockDailyForecast mockDailyForecast) { - icon.setImageResource(mockDailyForecast.iconRes); - day.setText(mockDailyForecast.day); - minTemperature.setText("" + mockDailyForecast.minTemperature); - maxTemperature.setText("" + mockDailyForecast.maxTemperature); - } - } -} \ No newline at end of file diff --git a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/HomeActivity.java b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/HomeActivity.java index 8fcc54b..daa9163 100644 --- a/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/HomeActivity.java +++ b/dailyclothes/src/main/java/com/androidsx/rainnotifications/dailyclothes/ui/home/HomeActivity.java @@ -15,8 +15,10 @@ import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.AdapterView; +import android.widget.BaseAdapter; import android.widget.Button; import android.widget.HorizontalScrollView; import android.widget.ImageView; @@ -28,10 +30,15 @@ import com.androidsx.rainnotifications.backgroundservice.util.UserLocationFetcher; import com.androidsx.rainnotifications.dailyclothes.R; import com.androidsx.rainnotifications.dailyclothes.model.Clothes; -import com.androidsx.rainnotifications.dailyclothes.model.MockDailyForecast; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderException; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderFactory; +import com.androidsx.rainnotifications.dailyclothes.model.clothesloader.ClothesLoaderListener; import com.androidsx.rainnotifications.dailyclothes.ui.widget.customfont.CustomTextView; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientDailyResponseListener; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientException; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; +import com.androidsx.rainnotifications.model.DailyForecast; +import com.androidsx.rainnotifications.model.DailyForecastTable; import com.androidsx.rainnotifications.model.Day; import com.androidsx.rainnotifications.model.DayTemplate; import com.androidsx.rainnotifications.model.DayTemplateLoaderFactory; @@ -45,6 +52,7 @@ import com.squareup.picasso.Picasso; import org.joda.time.DateTime; +import org.joda.time.DateTimeConstants; import org.joda.time.Duration; import java.lang.reflect.Field; @@ -63,6 +71,7 @@ public class HomeActivity extends FragmentActivity { private static final long HEART_BUTTON_ANIMATION_DURATION = 200; private static final int MAX_FORECAST_ITEMS = 24; private static final int COLOR_TRANSITION_DURATION = 100; + private static final int WEEK_FORECAST_DAYS = 5; private enum ForecastDataState {LOADING, ERROR_LOCATION, ERROR_FORECAST, LOADED, DONE}; @@ -86,6 +95,9 @@ public float getScrollValue() { private ForecastTable forecastTable; private Day day; private String forecastSummaryMessage; + private String city; + private DailyForecastTable dailyForecastTable; + private List clothesList; private boolean activityDestroyed = false; // Panic mode. It's used for not modify any view. private View frameLoading; @@ -105,6 +117,11 @@ public float getScrollValue() { private LinearLayout hourlyLinear; private HorizontalScrollView hourlyScroll; + private ClothesPagerAdapter clothesAdapter; + private DailyForecastAdapter dailyAdapter; + private CustomTextView cityLabel; + private CustomTextView weekTitleDivider; + private Integer todayCollapsedBackgroundColor; private Integer todayCollapsedPrimaryColor; private Integer todayCollapsedSecondaryColor; @@ -180,14 +197,21 @@ private void setForecastDataState(ForecastDataState newState) { private void getForecastData() { // FIXME: we do exactly the same in the weather service. grr.. UserLocationFetcher.getUserLocation(this, new UserLocationFetcher.UserLocationResultListener() { + + private boolean hourlyDone = false; + private boolean dailyDone = false; + @Override public void onLocationSuccess(final Location location) { - WeatherClientFactory.requestForecastForLocation(HomeActivity.this, location.getLatitude(), location.getLongitude(), new WeatherClientResponseListener() { + + // FIXME: This happens on UI Thread and skips frames. + HomeActivity.this.city = UserLocationFetcher.getLocationAddress(HomeActivity.this, location.getLatitude(), location.getLongitude()); + + WeatherClientFactory.requestHourlyForecastForLocation(HomeActivity.this, location.getLatitude(), location.getLongitude(), new WeatherClientHourlyResponseListener() { @Override public void onForecastSuccess(ForecastTable forecastTable) { // FIXME: This happens on UI Thread and skips frames. - HomeActivity.this.forecastTable = forecastTable; HomeActivity.this.forecastTableTime = new DateTime(); HomeActivity.this.day = new Day(forecastTable); @@ -199,12 +223,41 @@ public void onForecastSuccess(ForecastTable forecastTable) { HomeActivity.this.forecastSummaryMessage = template.resolveMessage(HomeActivity.this, HomeActivity.this.day); } - setForecastDataState(ForecastDataState.LOADED); + hourlyDone = true; + checkBothRequestDone(); } @Override public void onForecastFailure(WeatherClientException exception) { - Timber.e(exception, "Failed to get the forecast"); + Timber.e(exception, "Failed to get hourly forecast"); + setForecastDataState(ForecastDataState.ERROR_FORECAST); + } + }); + + WeatherClientFactory.requestDailyForecastForLocation(HomeActivity.this, location.getLatitude(), location.getLongitude(), new WeatherClientDailyResponseListener() { + @Override + public void onForecastSuccess(DailyForecastTable dailyForecastTable) { + HomeActivity.this.dailyForecastTable = dailyForecastTable; + + ClothesLoaderFactory.getClothes(HomeActivity.this, dailyForecastTable.getDailyForecastList().get(0).getWeatherWrapper(), new ClothesLoaderListener() { + @Override + public void onClothesLoaderSuccess(List clothesList) { + HomeActivity.this.clothesList = clothesList; + dailyDone = true; + checkBothRequestDone(); + } + + @Override + public void onClothesLoaderFailure(ClothesLoaderException exception) { + Timber.e(exception, "Failed to get clothes"); + setForecastDataState(ForecastDataState.ERROR_FORECAST); + } + }); + } + + @Override + public void onForecastFailure(WeatherClientException weatherClientException) { + Timber.e(weatherClientException, "Failed to get daily forecast"); setForecastDataState(ForecastDataState.ERROR_FORECAST); } }); @@ -215,6 +268,12 @@ public void onLocationFailure(UserLocationFetcher.UserLocationException exceptio Timber.e(exception, "Failed to get the location"); setForecastDataState(ForecastDataState.ERROR_LOCATION); } + + private void checkBothRequestDone() { + if(hourlyDone && dailyDone) { + setForecastDataState(ForecastDataState.LOADED); + } + } }); } @@ -237,6 +296,8 @@ private void setupUI() { heartButton = findViewById(R.id.heart_button); hourlyLinear = (LinearLayout) findViewById(R.id.hourly_forecast); hourlyScroll = (HorizontalScrollView) findViewById(R.id.hourly_scroll); + cityLabel = (CustomTextView) findViewById(R.id.week_forecast_city); + weekTitleDivider = (CustomTextView) findViewById(R.id.week_forecast_title_divider); todayDivider = findViewById(R.id.today_forecast_divider); todayMinTemperatureIcon = (ImageView) findViewById(R.id.today_min_temp_icon); @@ -272,37 +333,16 @@ public void onGlobalLayout() { } private void setupClothesViewPager() { + clothesAdapter = new ClothesPagerAdapter(getSupportFragmentManager(), clothesList); clothesPager = (ViewPager) findViewById(R.id.clothes_view_pager); - List clothesList = new ArrayList(); - - clothesList.add(new Clothes(R.drawable.lucky_3)); - clothesList.add(new Clothes(R.drawable.lucky_1)); - clothesList.add(new Clothes(R.drawable.lucky_2)); - clothesList.add(new Clothes(R.drawable.lucky_4)); - clothesList.add(new Clothes(R.drawable.lucky_5)); - clothesList.add(new Clothes(R.drawable.ann_taylor_1)); - clothesList.add(new Clothes(R.drawable.ann_taylor_2)); - clothesList.add(new Clothes(R.drawable.ann_taylor_3)); - clothesList.add(new Clothes(R.drawable.ann_taylor_4)); - clothesList.add(new Clothes(R.drawable.ann_taylor_5)); - clothesList.add(new Clothes(R.drawable.blogger_1)); - clothesList.add(new Clothes(R.drawable.blogger_2)); - clothesList.add(new Clothes(R.drawable.blogger_3)); - clothesList.add(new Clothes(R.drawable.blogger_4)); - clothesList.add(new Clothes(R.drawable.blogger_5)); - clothesList.add(new Clothes(R.drawable.blogger_6)); - clothesList.add(new Clothes(R.drawable.blogger_7)); - clothesList.add(new Clothes(R.drawable.blogger_8)); - clothesList.add(new Clothes(R.drawable.blogger_9)); - clothesList.add(new Clothes(R.drawable.blogger_10)); - - clothesPager.setAdapter(new ClothesPagerAdapter(getSupportFragmentManager(), clothesList)); + clothesPager.setAdapter(clothesAdapter); clothesPager.setOnPageChangeListener(new ClothesPagerListener()); } private void setupWeekForecastList() { ListView weekList = (ListView) findViewById(R.id.week_forecast_list_view); - weekList.setAdapter(new DailyForecastAdapter(getLayoutInflater(), MockDailyForecast.getMockList())); + dailyAdapter = new DailyForecastAdapter(getLayoutInflater(), null); + weekList.setAdapter(dailyAdapter); weekList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -351,7 +391,15 @@ private void updateUI() { maxTemperature.setText(temperatureFormat.format(day.getMaxTemperature().getWeatherWrapper().getTemperature(localeScale))); slidingPanelSummary.setText(forecastSummaryMessage); updateHourlyForecastList(); - demoPanel(); + updateDailyForecastList(); + updateClothesViewPager(); + + slidingPanel.postDelayed(new Runnable() { + @Override + public void run() { + demoPanel(); + } + }, FORECAST_DATA_DONE_DELAY/2); slidingPanel.postDelayed(new Runnable() { @Override @@ -377,6 +425,10 @@ public void run() { } } + private void updateClothesViewPager() { + clothesAdapter.updateClothesList(clothesList); + } + private void updateHourlyForecastList() { hourlyLinear.removeAllViews(); hourlyTextViews = new ArrayList(); @@ -404,6 +456,20 @@ private void updateHourlyForecastList() { } } + private void updateDailyForecastList() { + if(city != null) { + cityLabel.setText(city.toUpperCase()); + cityLabel.setVisibility(View.VISIBLE); + weekTitleDivider.setVisibility(View.VISIBLE); + } + else { + cityLabel.setVisibility(View.GONE); + weekTitleDivider.setVisibility(View.GONE); + } + + dailyAdapter.updateForecast(dailyForecastTable.getDailyForecastList()); + } + private int getWeatherIcon(WeatherType type) { // TODO: Pensar que hacer con las versiones night. switch (type) { @@ -505,7 +571,7 @@ private void showPanel() { private void demoPanel() { animateColors(PanelScrollValue.EXPANDED); - slidingPanel.expandPanel(); + slidingPanel.expandPanel(PanelScrollValue.EXPANDED.getScrollValue()); } private void hidePanel() { @@ -582,6 +648,90 @@ public void onPageScrollStateChanged(int state) { } } + private class DailyForecastAdapter extends BaseAdapter { + private List dailyForecasts; + private LayoutInflater inflater; + + public DailyForecastAdapter(LayoutInflater inflater, List dailyForecasts) { + this.inflater = inflater; + this.dailyForecasts = dailyForecasts; + } + + public void updateForecast(List dailyForecasts) { + this.dailyForecasts = dailyForecasts; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return dailyForecasts != null ? Math.min(WEEK_FORECAST_DAYS, dailyForecasts.size() - 1) : 0; // First is today + } + + @Override + public Object getItem(int position) { + return dailyForecasts.get(position + 1); // First is today + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_daily_forecast, null); + convertView.setTag(new DailyForecastHolder(convertView)); + } + + ((DailyForecastHolder) convertView.getTag()).update(dailyForecasts.get(position + 1)); // First is today + + return convertView; + } + + private class DailyForecastHolder { + private ImageView icon; + private TextView day; + private TextView minTemperature; + private TextView maxTemperature; + + public DailyForecastHolder(View v) { + icon = (ImageView) v.findViewById(R.id.daily_forecast_icon); + day = (TextView) v.findViewById(R.id.daily_forecast_day); + minTemperature = (TextView) v.findViewById(R.id.daily_forecast_min_temperature); + maxTemperature = (TextView) v.findViewById(R.id.daily_forecast_max_temperature); + } + + public void update(DailyForecast dailyForecast) { + icon.setImageResource(getWeatherIcon(dailyForecast.getWeatherWrapper().getWeatherType())); + day.setText(getDayOfWeek(dailyForecast.getDay())); + minTemperature.setText(temperatureFormat.format(dailyForecast.getWeatherWrapper().getMinTemperature(localeScale))); + maxTemperature.setText(temperatureFormat.format(dailyForecast.getWeatherWrapper().getMaxTemperature(localeScale))); + } + } + } + + private String getDayOfWeek(DateTime day) { + switch (day.getDayOfWeek()) { + case DateTimeConstants.MONDAY: + return getString(R.string.day_monday); + case DateTimeConstants.TUESDAY: + return getString(R.string.day_tuesday); + case DateTimeConstants.WEDNESDAY: + return getString(R.string.day_wednesday); + case DateTimeConstants.THURSDAY: + return getString(R.string.day_thursday); + case DateTimeConstants.FRIDAY: + return getString(R.string.day_friday); + case DateTimeConstants.SATURDAY: + return getString(R.string.day_saturday); + case DateTimeConstants.SUNDAY: + return getString(R.string.day_sunday); + default: + return ""; // Impossible + } + } + /** Linked from the XML. */ public void onErrorRetry(View v) { if(dataState.equals(ForecastDataState.ERROR_LOCATION)) { diff --git a/dailyclothes/src/main/res/layout/sliding_panel_week_forecast.xml b/dailyclothes/src/main/res/layout/sliding_panel_week_forecast.xml index 5a5bbd4..4891e33 100644 --- a/dailyclothes/src/main/res/layout/sliding_panel_week_forecast.xml +++ b/dailyclothes/src/main/res/layout/sliding_panel_week_forecast.xml @@ -27,6 +27,7 @@ android:textColor="@color/week_light"/> diff --git a/dailyclothes/src/main/res/values/strings.xml b/dailyclothes/src/main/res/values/strings.xml index 910b41f..7c99a62 100644 --- a/dailyclothes/src/main/res/values/strings.xml +++ b/dailyclothes/src/main/res/values/strings.xml @@ -12,4 +12,12 @@ Ops! There has been an error. Please check your internet connection and if the problem persists contact us at weatherup-support@androidsx.com 5-DAY FORECAST ° + + MONDAY + TUESDAY + WEDNESDAY + THURSDAY + FRIDAY + SATURDAY + SUNDAY diff --git a/mobile/src/main/java/com/androidsx/rainnotifications/ui/main/MainMobileActivity.java b/mobile/src/main/java/com/androidsx/rainnotifications/ui/main/MainMobileActivity.java index 0c13953..c70c883 100644 --- a/mobile/src/main/java/com/androidsx/rainnotifications/ui/main/MainMobileActivity.java +++ b/mobile/src/main/java/com/androidsx/rainnotifications/ui/main/MainMobileActivity.java @@ -18,10 +18,10 @@ import com.androidsx.rainnotifications.R; import com.androidsx.rainnotifications.alert.AlertGenerator; import com.androidsx.rainnotifications.alert.DayTemplateGenerator; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; import com.androidsx.rainnotifications.model.DayTemplateLoaderFactory; import com.androidsx.rainnotifications.backgroundservice.util.UserLocationFetcher; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientException; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; import com.androidsx.rainnotifications.model.ForecastTable; import com.androidsx.rainnotifications.ui.debug.DebugActivity; import com.androidsx.rainnotifications.ui.welcome.BaseWelcomeActivity; @@ -66,7 +66,7 @@ protected void onCreate(Bundle savedInstanceState) { UserLocationFetcher.getUserLocation(this, new UserLocationFetcher.UserLocationResultListener() { @Override public void onLocationSuccess(final Location location) { - WeatherClientFactory.requestForecastForLocation(MainMobileActivity.this, location.getLatitude(), location.getLongitude(), new WeatherClientResponseListener() { + WeatherClientFactory.requestHourlyForecastForLocation(MainMobileActivity.this, location.getLatitude(), location.getLongitude(), new WeatherClientHourlyResponseListener() { @Override public void onForecastSuccess(ForecastTable forecastTable) { final String locationAddress = UserLocationFetcher.getLocationAddress( diff --git a/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecast.java b/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecast.java new file mode 100644 index 0000000..fa2fe14 --- /dev/null +++ b/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecast.java @@ -0,0 +1,33 @@ +package com.androidsx.rainnotifications.model; + +import org.joda.time.DateTime; + +/** + * A weather forecast about a particular interval of time in the future. + */ +public class DailyForecast { + private final DateTime day; + private final DailyWeatherWrapper weatherWrapper; + + /** + * @param day + * @param weatherWrapper + */ + public DailyForecast(DateTime day, DailyWeatherWrapper weatherWrapper) { + this.day = day; + this.weatherWrapper = weatherWrapper; + } + + public DateTime getDay() { + return day; + } + + public DailyWeatherWrapper getWeatherWrapper() { + return weatherWrapper; + } + + @Override + public String toString() { + return weatherWrapper + " forecasted on " + day; + } +} diff --git a/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecastTable.java b/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecastTable.java new file mode 100644 index 0000000..2453cf8 --- /dev/null +++ b/model/src/main/java/com/androidsx/rainnotifications/model/DailyForecastTable.java @@ -0,0 +1,67 @@ +package com.androidsx.rainnotifications.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Table of expected forecasts. The first forecast in the table usually represents the current + * weather. + */ +public class DailyForecastTable { + + private List dailyForecastList; + + /** + * Returns an appropriate {@link com.androidsx.rainnotifications.model.DailyForecastTable} for the given list of Forecast. + * It removes not meaningful Forecasts from given list. + * + * @param dailyForecasts An ordered list of {@link Forecast} without overlaps or gaps in their Intervals. + * Intervals must be of one-hour + * @return {@link com.androidsx.rainnotifications.model.DailyForecastTable} if processed dailyForecastList isn't empty, null in other case. + * @throws IllegalArgumentException if the given dailyForecastList is empty + */ + public static DailyForecastTable fromForecastList(List dailyForecasts) { + // TODO: No teng nada claro esto de eliminar los UNKNOWN... hablarlo con Omar + // TODO: Think about if we need to check the day conditions + if (dailyForecasts.isEmpty()) { + throw new IllegalArgumentException("The list of forecasts is empty. At least one forecast is needed"); + } else { + List meaningfulForecastList = getMeaningfulForecastList(dailyForecasts); + return meaningfulForecastList.isEmpty() ? null : new DailyForecastTable(meaningfulForecastList); + } + } + + private static List getMeaningfulForecastList(List forecastList) { + List meaningfulWeatherTypes = WeatherType.getMeaningfulWeatherTypes(); + List meaningfulForecastList = new ArrayList(); + + for (DailyForecast forecast : forecastList) { + if(meaningfulWeatherTypes.contains(forecast.getWeatherWrapper().getWeatherType())) { + meaningfulForecastList.add(forecast); + } + } + + return meaningfulForecastList; + } + + private DailyForecastTable(List dailyForecastList) { + this.dailyForecastList = dailyForecastList; + } + + /** + * Returns the processed lists of daily forecasts. It is guaranteed to be non-empty. + */ + public List getDailyForecastList() { + return dailyForecastList; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DAY FORECAST_TABLE:"); + for (DailyForecast forecast : dailyForecastList) { + builder.append("\n " + forecast); + } + return builder.toString(); + } +} diff --git a/model/src/main/java/com/androidsx/rainnotifications/model/DailyWeatherWrapper.java b/model/src/main/java/com/androidsx/rainnotifications/model/DailyWeatherWrapper.java new file mode 100644 index 0000000..cc26efa --- /dev/null +++ b/model/src/main/java/com/androidsx/rainnotifications/model/DailyWeatherWrapper.java @@ -0,0 +1,119 @@ +package com.androidsx.rainnotifications.model; + +import android.content.Context; + +import java.text.DecimalFormat; + +/** + * Status of the weather at a particular point in time (past, present of future). + * + * @see #equals + */ +public class DailyWeatherWrapper { + private final WeatherType type; + private final float minTemperatureCelsius; + private final float minTemperatureFahrenheit; + private final float maxTemperatureCelsius; + private final float maxTemperatureFahrenheit; + + public DailyWeatherWrapper(WeatherType type, float minTemperature, float maxTemperature, WeatherWrapper.TemperatureScale scale) { + this.type = type; + + if(scale.equals(WeatherWrapper.TemperatureScale.CELSIUS)) { + minTemperatureCelsius = minTemperature; + minTemperatureFahrenheit = minTemperature * 1.8f + 32; //Celsius to Fahrenheit : (°C × 1.8) + 32 =°F + + maxTemperatureCelsius = maxTemperature; + maxTemperatureFahrenheit = maxTemperature * 1.8f + 32; //Celsius to Fahrenheit : (°C × 1.8) + 32 =°F + } + else { + minTemperatureFahrenheit = minTemperature; + minTemperatureCelsius = (minTemperature - 32) / 1.8f; //Fahrenheit to Celsius : (°F − 32) ÷ 1.8 =°C + + maxTemperatureFahrenheit = maxTemperature; + maxTemperatureCelsius = (maxTemperature - 32) / 1.8f; //Fahrenheit to Celsius : (°F − 32) ÷ 1.8 =°C + } + } + + public WeatherType getWeatherType() { + return type; + } + + public float getMinTemperature(WeatherWrapper.TemperatureScale scale) { + if(scale.equals(WeatherWrapper.TemperatureScale.CELSIUS)) { + return minTemperatureCelsius; + } + else { + return minTemperatureFahrenheit; + } + } + + public float getMaxTemperature(WeatherWrapper.TemperatureScale scale) { + if(scale.equals(WeatherWrapper.TemperatureScale.CELSIUS)) { + return maxTemperatureCelsius; + } + else { + return maxTemperatureFahrenheit; + } + } + + // TODO: Add some logic for allow user to choose celsius or Fahrenheit. + public String getReadableMinTemperature(Context context) { + if(WeatherWrapper.TemperatureScale.getLocaleScale(context).equals(WeatherWrapper.TemperatureScale.FAHRENHEIT)) { + return new DecimalFormat("#").format(minTemperatureFahrenheit) + WeatherWrapper.FAHRENHEIT_SYMBOL; + } + else { + return new DecimalFormat("#").format(minTemperatureCelsius) + WeatherWrapper.CELSIUS_SYMBOL; + } + } + + // TODO: Add some logic for allow user to choose celsius or Fahrenheit. + public String getReadableMaxTemperature(Context context) { + if(WeatherWrapper.TemperatureScale.getLocaleScale(context).equals(WeatherWrapper.TemperatureScale.FAHRENHEIT)) { + return new DecimalFormat("#").format(maxTemperatureFahrenheit) + WeatherWrapper.FAHRENHEIT_SYMBOL; + } + else { + return new DecimalFormat("#").format(maxTemperatureCelsius) + WeatherWrapper.CELSIUS_SYMBOL; + } + } + + @Override + public String toString() { + return type.toString() + ", " + minTemperatureCelsius + WeatherWrapper.CELSIUS_SYMBOL + ", " + minTemperatureFahrenheit + WeatherWrapper.FAHRENHEIT_SYMBOL + + ", " + maxTemperatureCelsius + WeatherWrapper.CELSIUS_SYMBOL + ", " + maxTemperatureFahrenheit + WeatherWrapper.FAHRENHEIT_SYMBOL; + } + + /** + * {@inheritDoc} + *

+ * Important note about unknown transitions: + *

+ * Transitions like UNKNOWN -> KNOWN are considered transitions. + * Transitions like KNOWN -> UNKNOWN are NOT considered transitions. + *

+ * That implies UNKNOWN -> sunny will end up being "It's gonna be sunny again", but sunny -> UNKNOWN won't trigger + * a change. Then, sunny -> UNKNOWN -> rain will essentially be considered a sunny -> rain transition. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other == null || getClass() != other.getClass()) { + return false; + } else { + final DailyWeatherWrapper otherWeather = (DailyWeatherWrapper) other; + if (getWeatherType() == WeatherType.UNKNOWN && !(otherWeather.getWeatherType() == WeatherType.UNKNOWN)) { + return false; + } else if (getWeatherType() == WeatherType.UNKNOWN || ((DailyWeatherWrapper) other).getWeatherType() == WeatherType.UNKNOWN) { + return true; + } else { + return getWeatherType() == otherWeather.getWeatherType(); + } + } + } + + @Override + public int hashCode() { + return getWeatherType() != null ? getWeatherType().hashCode() : 0; + } +} diff --git a/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientDailyResponseListener.java b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientDailyResponseListener.java new file mode 100644 index 0000000..91b12b7 --- /dev/null +++ b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientDailyResponseListener.java @@ -0,0 +1,22 @@ +package com.androidsx.rainnotifications.forecastapislibrary; + +import com.androidsx.rainnotifications.model.DailyForecastTable; + +/** + * Listener for a response from a weather client. + */ +public interface WeatherClientDailyResponseListener { + + /** + * Handles the case when the request for weather information succeeded. Note that this method + * is executed in the UI thread. + */ + public void onForecastSuccess(DailyForecastTable dailyForecastTable); + + /** + * Handles the case when the request for weather information failed. + */ + public void onForecastFailure(WeatherClientException weatherClientException); + + +} diff --git a/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientExecutor.java b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientExecutor.java index 663d385..fa69b04 100644 --- a/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientExecutor.java +++ b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientExecutor.java @@ -8,12 +8,16 @@ public interface WeatherClientExecutor { /** - * Executes the request in a background thread. + * Executes an hourly request in a background thread. * * @param responseListener where the results will be returned after the call is performed */ - public void execute(Context context, - double latitude, - double longitude, - WeatherClientResponseListener responseListener); + public void executeHourly(Context context, double latitude, double longitude, WeatherClientHourlyResponseListener responseListener); + + /** + * Executes a daily request in a background thread. + * + * @param responseListener where the results will be returned after the call is performed + */ + public void executeDaily(Context context, double latitude, double longitude, WeatherClientDailyResponseListener responseListener); } diff --git a/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientResponseListener.java b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientHourlyResponseListener.java similarity index 91% rename from weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientResponseListener.java rename to weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientHourlyResponseListener.java index edeeb5c..fa30e06 100644 --- a/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientResponseListener.java +++ b/weather-client-api/src/main/java/com/androidsx/rainnotifications/forecastapislibrary/WeatherClientHourlyResponseListener.java @@ -5,7 +5,7 @@ /** * Listener for a response from a weather client. */ -public interface WeatherClientResponseListener { +public interface WeatherClientHourlyResponseListener { /** * Handles the case when the request for weather information succeeded. Note that this method diff --git a/weather-client-factory/src/main/java/com/androidsx/rainnotifications/weatherclientfactory/WeatherClientFactory.java b/weather-client-factory/src/main/java/com/androidsx/rainnotifications/weatherclientfactory/WeatherClientFactory.java index 150e80a..9a2d113 100644 --- a/weather-client-factory/src/main/java/com/androidsx/rainnotifications/weatherclientfactory/WeatherClientFactory.java +++ b/weather-client-factory/src/main/java/com/androidsx/rainnotifications/weatherclientfactory/WeatherClientFactory.java @@ -2,24 +2,36 @@ import android.content.Context; -import com.androidsx.rainnotifications.forecast_io.ForecastIoNetworkServiceTask; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientDailyResponseListener; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientExecutor; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; import com.androidsx.rainnotifications.wunderground.WundergroundNetworkServiceTask; public abstract class WeatherClientFactory { - //WARNING: At the moment only WUNDERGROUND implement temperature, so we can't use forecast.io See also WeatherBuilder on forecast.io module + //WARNING: At the moment only WUNDERGROUND implement temperature and daily, so we can't use forecast.io See also WeatherBuilder on forecast.io module private static final WeatherClient CLIENT = WeatherClient.WUNDERGROUND; - public static void requestForecastForLocation(Context context, double latitude, double longitude, WeatherClientResponseListener responseListener) { + public static void requestHourlyForecastForLocation(Context context, double latitude, double longitude, WeatherClientHourlyResponseListener responseListener) { final WeatherClientExecutor weatherClientExecutor; switch (CLIENT) { - case FORECAST_IO: weatherClientExecutor = new ForecastIoNetworkServiceTask(); break; + //case FORECAST_IO: weatherClientExecutor = new ForecastIoNetworkServiceTask(); break; case WUNDERGROUND: weatherClientExecutor = new WundergroundNetworkServiceTask(); break; default: throw new IllegalArgumentException("Unsupported client: " + CLIENT); } - weatherClientExecutor.execute(context, latitude, longitude, responseListener); + weatherClientExecutor.executeHourly(context, latitude, longitude, responseListener); + } + + public static void requestDailyForecastForLocation(Context context, double latitude, double longitude, WeatherClientDailyResponseListener responseListener) { + + final WeatherClientExecutor weatherClientExecutor; + switch (CLIENT) { + //case FORECAST_IO: weatherClientExecutor = new ForecastIoNetworkServiceTask(); break; + case WUNDERGROUND: weatherClientExecutor = new WundergroundNetworkServiceTask(); break; + default: throw new IllegalArgumentException("Unsupported client: " + CLIENT); + } + + weatherClientExecutor.executeDaily(context, latitude, longitude, responseListener); } } diff --git a/weather-client-forecast-io/src/main/java/com/androidsx/rainnotifications/forecast_io/ForecastIoNetworkServiceTask.java b/weather-client-forecast-io/src/main/java/com/androidsx/rainnotifications/forecast_io/ForecastIoNetworkServiceTask.java index 02cfb32..815c5ec 100644 --- a/weather-client-forecast-io/src/main/java/com/androidsx/rainnotifications/forecast_io/ForecastIoNetworkServiceTask.java +++ b/weather-client-forecast-io/src/main/java/com/androidsx/rainnotifications/forecast_io/ForecastIoNetworkServiceTask.java @@ -3,9 +3,10 @@ import android.content.Context; import android.util.Log; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientDailyResponseListener; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientException; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientExecutor; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; import com.androidsx.rainnotifications.model.ForecastTable; import com.androidsx.rainnotifications.model.ForecastTableBuilder; import com.forecast.io.network.responses.INetworkResponse; @@ -21,14 +22,19 @@ */ public final class ForecastIoNetworkServiceTask extends NetworkServiceTask implements WeatherClientExecutor { private static final String TAG = ForecastIoNetworkServiceTask.class.getSimpleName(); - private WeatherClientResponseListener responseListener; + private WeatherClientHourlyResponseListener responseListener; @Override - public void execute(Context context, double latitude, double longitude, WeatherClientResponseListener responseListener) { + public void executeHourly(Context context, double latitude, double longitude, WeatherClientHourlyResponseListener responseListener) { this.responseListener = responseListener; this.execute(new ForecastIoRequest(latitude, longitude).getRequest()); } + @Override + public void executeDaily(Context context, double latitude, double longitude, WeatherClientDailyResponseListener responseListener) { + throw new IllegalArgumentException("Unimplemented method for Forecast.io client"); + } + @Override protected void onPostExecute(INetworkResponse rawNetworkResponse) { if (rawNetworkResponse == null || rawNetworkResponse.getStatus() == NetworkResponse.Status.FAIL) { diff --git a/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyTableBuilder.java b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyTableBuilder.java new file mode 100644 index 0000000..3272cc6 --- /dev/null +++ b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyTableBuilder.java @@ -0,0 +1,47 @@ +package com.androidsx.rainnotifications.model; + +import org.joda.time.DateTime; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * Builder for {@link com.androidsx.rainnotifications.model.DailyForecastTable}. + *

+ * Should not be used from outside of this project. + */ +public class WundergroundDailyTableBuilder { + public static DailyForecastTable buildFromWunderground(JSONObject response) throws JSONException { + + if(response.has("forecast")) { + JSONObject forecast = response.getJSONObject("forecast"); + if(forecast.has("simpleforecast")) { + JSONObject simpleforecast = forecast.getJSONObject("simpleforecast"); + if(simpleforecast.has("forecastday")) { + JSONArray forecastday = simpleforecast.getJSONArray("forecastday"); + if(forecastday != null && forecastday.length() > 0) { + return DailyForecastTable.fromForecastList(getForecastListFromDaily(forecastday)); + } + } + + } + } + + return null; + } + + private static List getForecastListFromDaily(JSONArray daily) throws JSONException { + List forecasts = new ArrayList(); + for (int i = 0 ; i < daily.length() - 1 ; i++) { + forecasts.add(new DailyForecast(getDay(daily.getJSONObject(i)), WundergroundDailyWeatherBuilder.buildFromWunderground(daily.getJSONObject(i)))); + } + return forecasts; + } + + private static DateTime getDay(JSONObject dayForecast) throws JSONException { + return new DateTime(Long.parseLong(dayForecast.getJSONObject("date").get("epoch").toString()) * 1000); + } +} diff --git a/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyWeatherBuilder.java b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyWeatherBuilder.java new file mode 100644 index 0000000..37f79c1 --- /dev/null +++ b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/model/WundergroundDailyWeatherBuilder.java @@ -0,0 +1,27 @@ +package com.androidsx.rainnotifications.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Builder for {@link com.androidsx.rainnotifications.model.WeatherWrapper}. + *

+ * Should not be used from outside of this project. + */ +public class WundergroundDailyWeatherBuilder { + public static DailyWeatherWrapper buildFromWunderground(JSONObject weather) throws JSONException { + return new DailyWeatherWrapper(retrieveWeatherType(weather), retrieveMinCelsiusTemperature(weather), retrieveMaxCelsiusTemperature(weather), WeatherWrapper.TemperatureScale.CELSIUS); + } + + private static WeatherType retrieveWeatherType(JSONObject weather) throws JSONException { + return WundergroundWeatherTypeBuilder.buildFromWunderground(weather.getString("icon")); + } + + private static float retrieveMinCelsiusTemperature(JSONObject weather) throws JSONException { + return (float) weather.getJSONObject("low").getDouble("celsius"); + } + + private static float retrieveMaxCelsiusTemperature(JSONObject weather) throws JSONException { + return (float) weather.getJSONObject("high").getDouble("celsius"); + } +} diff --git a/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/wunderground/WundergroundNetworkServiceTask.java b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/wunderground/WundergroundNetworkServiceTask.java index 45513f5..7a0ff06 100644 --- a/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/wunderground/WundergroundNetworkServiceTask.java +++ b/weather-client-wunderground/src/main/java/com/androidsx/rainnotifications/wunderground/WundergroundNetworkServiceTask.java @@ -3,11 +3,14 @@ import android.content.Context; import android.util.Log; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientDailyResponseListener; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientException; import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientExecutor; -import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientResponseListener; +import com.androidsx.rainnotifications.forecastapislibrary.WeatherClientHourlyResponseListener; +import com.androidsx.rainnotifications.model.DailyForecastTable; import com.androidsx.rainnotifications.model.Day; import com.androidsx.rainnotifications.model.ForecastTable; +import com.androidsx.rainnotifications.model.WundergroundDailyTableBuilder; import com.androidsx.rainnotifications.model.WundergroundTableBuilder; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.JsonHttpResponseHandler; @@ -20,15 +23,19 @@ public final class WundergroundNetworkServiceTask implements WeatherClientExecut private static final String TAG = WundergroundNetworkServiceTask.class.getSimpleName(); private static final String WUNDERGROUND_BASE_URL = "http://api.wunderground.com/api/" + Constants.WUNDERGROUND_API_KEY; - private static final String[] FEATURES = { + private static final String[] FEATURES_HOURLY = { "conditions", // Current time, http://www.wunderground.com/weather/api/d/docs?d=data/conditions "hourly", // Hourly forecast, http://www.wunderground.com/weather/api/d/docs?d=data/hourly "astronomy"}; // Sunrise/Sunset time, http://www.wunderground.com/weather/api/d/docs?d=data/astronomy + private static final String[] FEATURES_DAILY = { + "forecast10day" }; // Daily forecast, http://www.wunderground.com/weather/api/d/docs?d=data/forecast10day + + @Override - public void execute(Context context, double latitude, double longitude, final WeatherClientResponseListener responseListener) { + public void executeHourly(Context context, double latitude, double longitude, final WeatherClientHourlyResponseListener responseListener) { String url = WUNDERGROUND_BASE_URL; - for(String f : FEATURES) { + for(String f : FEATURES_HOURLY) { url += "/" + f; } url += "/q/" + latitude + "," + longitude + ".json"; @@ -39,7 +46,7 @@ public void execute(Context context, double latitude, double longitude, final We public void onSuccess(int statusCode, Header[] headers, JSONObject response) { super.onSuccess(statusCode, headers, response); try { - Log.v(TAG, "Raw response from Wunderground:\n" + response.toString(1)); + Log.v(TAG, "Raw hourly response from Wunderground:\n" + response.toString(1)); final ForecastTable forecastTable = WundergroundTableBuilder.buildFromWunderground(response); if (forecastTable != null) { Log.d(TAG, "ForecastTable: " + forecastTable); @@ -47,17 +54,53 @@ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { responseListener.onForecastSuccess(forecastTable); } else { responseListener.onForecastFailure(new WeatherClientException( - "The forecast table is null for the WUnderground response " + response)); + "The forecast table is null for the hourly WUnderground response " + response)); + } + } catch (JSONException e) { + responseListener.onForecastFailure(new WeatherClientException("Failed to process hourly WUnderground response", e)); + } + } + + @Override + public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { + responseListener.onForecastFailure(new WeatherClientException( + "Failed to read from hourly WUnderground: " + statusCode, throwable)); + } + }); + } + + @Override + public void executeDaily(Context context, double latitude, double longitude, final WeatherClientDailyResponseListener responseListener) { + String url = WUNDERGROUND_BASE_URL; + for(String f : FEATURES_DAILY) { + url += "/" + f; + } + url += "/q/" + latitude + "," + longitude + ".json"; + + AsyncHttpClient httpClient = new AsyncHttpClient(); + httpClient.get(context, url, new JsonHttpResponseHandler(){ + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + super.onSuccess(statusCode, headers, response); + try { + Log.v(TAG, "Raw daily response from Wunderground:\n" + response.toString(1)); + final DailyForecastTable dailyForecastTable = WundergroundDailyTableBuilder.buildFromWunderground(response); + if (dailyForecastTable != null) { + Log.d(TAG, "DailyForecastTable: " + dailyForecastTable); + responseListener.onForecastSuccess(dailyForecastTable); + } else { + responseListener.onForecastFailure(new WeatherClientException( + "The forecast table is null for the daily WUnderground response " + response)); } } catch (JSONException e) { - responseListener.onForecastFailure(new WeatherClientException("Failed to process WUnderground response", e)); + responseListener.onForecastFailure(new WeatherClientException("Failed to process daily WUnderground response", e)); } } @Override public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { responseListener.onForecastFailure(new WeatherClientException( - "Failed to read from WUnderground: " + statusCode, throwable)); + "Failed to read from daily WUnderground: " + statusCode, throwable)); } }); }