/*
* Copyright 2015. Appsi Mobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.appsimobile.appsii.module.weather;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.appsimobile.appsii.BitmapUtils;
import com.appsimobile.appsii.BuildConfig;
import com.appsimobile.appsii.DrawableStartTintPainter;
import com.appsimobile.appsii.R;
import com.appsimobile.appsii.dagger.AppInjector;
import com.appsimobile.appsii.module.weather.loader.WeatherData;
import com.appsimobile.appsii.preference.PreferenceHelper;
import com.appsimobile.paintjob.PaintJob;
import com.appsimobile.paintjob.ViewPainters;
import com.appsimobile.util.TimeUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.inject.Inject;
/**
* Shows the detailed weather on a single location.
* Created by nick on 12/04/15.
*/
public class WeatherActivity extends Activity {
/**
* Required extra for the activity. Specifies the woe-id to show the weather from
*/
public static final String EXTRA_WOEID = BuildConfig.APPLICATION_ID + ".woe_id";
/**
* Required extra, the time-zone of the woe-id
*/
public static final String EXTRA_TIME_ZONE = BuildConfig.APPLICATION_ID + ".timezone";
/**
* Required extra, the unit the user set to be used for this woe-id.
*/
public static final String EXTRA_UNIT = BuildConfig.APPLICATION_ID + ".unit";
String mDisplayUnit;
String mTimezone;
ImageView mBackgroundImage;
ImageView mCurrentWeatherIcon;
TextView mTemperatureView;
TextView mLocationView;
TextView mConditionView;
TextView mMinTempView;
TextView mMaxTempView;
TextView mFeelsLikeView;
TextView mWindView;
TextView mForecastHeader;
Drawable mMinTempDrawable;
Drawable mMaxTempDrawable;
SimpleRotateDrawable mWindDrawable;
RecyclerView mRecyclerView;
@Inject
SharedPreferences mSharedPreferences;
@Inject
PreferenceHelper mPreferenceHelper;
@Inject
BitmapUtils mBitmapUtils;
@Inject
WeatherUtils mWeatherUtils;
@Inject
WindowManager mWindowManager;
View mCurrentWeatherContainer;
WeatherData mWeatherData;
String mWoeid;
ForecastAdapter mAdapter;
boolean mIsDay;
Bitmap mBitmap;
static void setTemperatureText(TextView textView, int temperature, String unit,
String displayUnit) {
Context context = textView.getContext();
WeatherUtils weatherUtils = AppInjector.provideWeatherUtils();
String text = weatherUtils.formatTemperature(context, temperature, unit, displayUnit,
WeatherUtils.FLAG_TEMPERATURE_NO_UNIT);
textView.setText(text);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppInjector.inject(this);
if (savedInstanceState != null) {
mBitmap = savedInstanceState.getParcelable("selected_image");
}
String woeid = getIntent().getStringExtra(EXTRA_WOEID);
if (woeid == null) {
woeid = mSharedPreferences.getString(
WeatherLoadingService.PREFERENCE_LAST_KNOWN_WOEID, null);
}
if (woeid == null) {
woeid = mPreferenceHelper.getDefaultLocationWoeId();
}
if (woeid == null) {
// TODO: we need a default location.
finish();
return;
}
mDisplayUnit = getIntent().getStringExtra(EXTRA_UNIT);
mTimezone = getIntent().getStringExtra(EXTRA_TIME_ZONE);
mWoeid = woeid;
setContentView(R.layout.activity_weather_details);
mBackgroundImage = (ImageView) findViewById(R.id.weather_background);
mCurrentWeatherIcon = (ImageView) findViewById(R.id.current_weather);
mTemperatureView = (TextView) findViewById(R.id.temperature);
mLocationView = (TextView) findViewById(R.id.location);
mConditionView = (TextView) findViewById(R.id.condition);
mCurrentWeatherContainer = findViewById(R.id.current_weather_container);
mMinTempView = (TextView) findViewById(R.id.temp_min);
mMaxTempView = (TextView) findViewById(R.id.temp_max);
mWindView = (TextView) findViewById(R.id.wind);
mFeelsLikeView = (TextView) findViewById(R.id.feels_like);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
mForecastHeader = (TextView) findViewById(R.id.forecast_header);
mMinTempDrawable = mMinTempView.getCompoundDrawablesRelative()[0];
mMaxTempDrawable = mMaxTempView.getCompoundDrawablesRelative()[0];
BitmapDrawable windDrawable = (BitmapDrawable) mWindView.getCompoundDrawablesRelative()[0];
mWindDrawable = new SimpleRotateDrawable(getResources(), windDrawable.getBitmap());
mWindView.setCompoundDrawablesRelativeWithIntrinsicBounds(mWindDrawable, null, null, null);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new ForecastAdapter(mDisplayUnit);
mRecyclerView.setAdapter(mAdapter);
getLoaderManager().initLoader(1, null, new WeatherDataLoaderCallbacks());
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("selected_image", mBitmap);
}
void onWeatherDataReady(WeatherData weatherData,
SparseArray<WeatherUtils.ForecastInfo> forecastForDays) {
if (weatherData == null) {
Toast.makeText(this, "No weather info available", Toast.LENGTH_LONG).show();
finish();
return;
}
mWeatherData = weatherData;
showDefaultImage(weatherData.nowConditionCode);
int today = TimeUtils.getJulianDay();
WeatherUtils.ForecastInfo todaysForecast =
forecastForDays == null ? null : forecastForDays.get(today);
mAdapter.setForecast(forecastForDays);
mIsDay = mWeatherUtils.isDay(mTimezone, weatherData);
int icon = mWeatherUtils.getConditionCodeIconResId(weatherData.nowConditionCode, mIsDay);
mCurrentWeatherIcon.setImageResource(icon);
mLocationView.setText(weatherData.location);
String feelsLikeTemp = mWeatherUtils.formatTemperature(this, weatherData.windChill,
weatherData.unit, mDisplayUnit, WeatherUtils.FLAG_TEMPERATURE_NO_UNIT);
if (todaysForecast != null) {
setTemperatureText(mMinTempView, todaysForecast.tempLow, todaysForecast.unit,
mDisplayUnit);
setTemperatureText(mMaxTempView, todaysForecast.tempHigh, todaysForecast.unit,
mDisplayUnit);
} else {
mMinTempView.setText("-");
mMaxTempView.setText("-");
}
String wind = mWeatherUtils.formatWindSpeed(this, weatherData.windSpeed, weatherData.unit,
mDisplayUnit);
mWindDrawable.mAngle = weatherData.windDirection;
setTemperatureText(
mTemperatureView, weatherData.nowTemperature, weatherData.unit, mDisplayUnit);
setTemperatureText(
mTemperatureView, weatherData.nowTemperature, weatherData.unit, mDisplayUnit);
setTemperatureText(
mTemperatureView, weatherData.nowTemperature, weatherData.unit, mDisplayUnit);
String feelsLike = getString(R.string.feels_like, feelsLikeTemp);
mFeelsLikeView.setText(feelsLike);
mWindView.setText(wind);
mConditionView.setText(mWeatherUtils.formatConditionCode(weatherData.nowConditionCode));
}
private void showDefaultImage(final int conditionCode) {
final String woeid = mWoeid;
final boolean isDay = mIsDay;
View root = findViewById(android.R.id.content);
PaintJob.Builder builder;
if (mBitmap != null) {
// FIXME: check why PaintJob.newBuilder(root, mBitmap) is not woring
// as expected.
builder = PaintJob.newBuilder(root, new PaintJob.BitmapSource() {
@Override
public Bitmap loadBitmapAsync() {
return mBitmap;
}
});
} else {
builder = PaintJob.newBuilder(root, new PaintJob.BitmapSource() {
@Override
public Bitmap loadBitmapAsync() {
return loadBitmapBlocking(woeid, isDay, conditionCode);
}
});
}
builder.paintWithSwatch(PaintJob.SWATCH_DARK_VIBRANT,
ViewPainters.text(R.id.location)).
paintWithSwatch(PaintJob.SWATCH_LIGHT_MUTED,
ViewPainters.title(R.id.forecast_header)).
paintWithSwatch(PaintJob.SWATCH_VIBRANT,
ViewPainters.rgb(R.id.current_weather_container),
ViewPainters.text(R.id.temperature, R.id.temp_min, R.id.temp_max,
R.id.feels_like, R.id.wind, R.id.condition),
DrawableStartTintPainter.forIds(R.id.temp_min, R.id.temp_max, R.id.wind)
).setBitmapCallback(new PaintJob.BitmapCallback() {
@Override
public void onBitmapLoaded(Bitmap bitmap, boolean immediate) {
WeatherActivity.this.onBitmapLoaded(bitmap, immediate);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.paintWithSwatch(PaintJob.SWATCH_DARK_VIBRANT, new StatusBarTintPainter());
}
PaintJob paintJob = builder.build();
paintJob.execute(500);
mAdapter.setPaintJob(paintJob);
}
Bitmap loadBitmapBlocking(String woeid, boolean isDay, int conditionCode) {
File[] files = mWeatherUtils.getCityPhotos(WeatherActivity.this, woeid);
Bitmap bitmap;
if (files != null) {
int N = files.length;
Random random = new Random();
int idx = random.nextInt(N);
File file = files[idx];
WindowManager windowManager = mWindowManager;
Display defaultDisplay = windowManager.getDefaultDisplay();
int dimen = Math.max(defaultDisplay.getWidth(), defaultDisplay.getHeight());
bitmap = mBitmapUtils.decodeSampledBitmapFromFile(file, dimen, dimen);
return bitmap;
}
int resId = ImageDownloadHelper.getFallbackDrawableForConditionCode(
isDay, conditionCode);
return BitmapFactory.decodeResource(getResources(), resId);
}
void onBitmapLoaded(Bitmap bitmap, boolean immediate) {
mBitmap = bitmap;
showBitmap(bitmap, immediate);
}
void showBitmap(Bitmap bitmap, boolean isImmediate) {
Drawable drawable = new BitmapDrawable(getResources(), bitmap);
showDrawable(drawable, isImmediate);
}
void showDrawable(Drawable drawable, boolean isImmediate) {
mBackgroundImage.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
if (isImmediate) {
int w = drawable.getIntrinsicWidth();
int viewWidth = mBackgroundImage.getWidth();
float factor = viewWidth / (float) w;
int h = (int) (drawable.getIntrinsicHeight() * factor);
drawable.setBounds(0, 0, w, h);
mBackgroundImage.setImageDrawable(drawable);
} else {
Drawable current = mBackgroundImage.getDrawable();
if (current == null) current = new ColorDrawable(Color.TRANSPARENT);
TransitionDrawable transitionDrawable =
new TransitionDrawable(new Drawable[]{current, drawable});
transitionDrawable.setCrossFadeEnabled(true);
mBackgroundImage.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(500);
}
}
static class ForecastViewHolder extends RecyclerView.ViewHolder {
static final Time sTime;
static {
sTime = new Time(Time.TIMEZONE_UTC);
}
final String mWeatherDisplayUnit;
final ImageView mForecastImage;
final TextView mTemperatureHigh;
final TextView mTemperatureLow;
final TextView mForecastDate;
final TextView mForecastCondition;
final Drawable mLowDrawable;
final Drawable mHighDrawable;
final WeatherUtils mWeatherUtils;
PaintJob mPaintJob;
public ForecastViewHolder(View itemView, String weatherDisplayUnit) {
super(itemView);
mWeatherDisplayUnit = weatherDisplayUnit;
mForecastImage = (ImageView) itemView.findViewById(R.id.forecast_image);
mTemperatureHigh = (TextView) itemView.findViewById(R.id.forecast_temperature_high);
mTemperatureLow = (TextView) itemView.findViewById(R.id.forecast_temperature_low);
mLowDrawable = mTemperatureLow.getCompoundDrawablesRelative()[0];
mHighDrawable = mTemperatureHigh.getCompoundDrawablesRelative()[0];
mForecastDate = (TextView) itemView.findViewById(R.id.forecast_date);
mForecastCondition = (TextView) itemView.findViewById(R.id.forecast_condition);
mWeatherUtils = AppInjector.provideWeatherUtils();
}
public void setPaintJob(PaintJob paintJob) {
mPaintJob = paintJob;
mPaintJob.derive(itemView).
paintWithSwatch(PaintJob.SWATCH_LIGHT_VIBRANT,
ViewPainters.title(R.id.forecast_date),
ViewPainters.text(R.id.forecast_temperature_high,
R.id.forecast_temperature_low,
R.id.forecast_condition),
DrawableStartTintPainter.forIds(R.id.forecast_temperature_high,
R.id.forecast_temperature_low)
).build().execute(500);
}
public void bind(WeatherUtils.ForecastInfo info) {
Context context = itemView.getContext();
// for forecast icons always use the day icon.
int iconResId =
mWeatherUtils.getConditionCodeIconResId(info.conditionCode, true /* day */);
mForecastImage.setImageResource(iconResId);
setTemperatureText(mTemperatureHigh, info.tempHigh, info.unit, mWeatherDisplayUnit);
setTemperatureText(mTemperatureLow, info.tempLow, info.unit, mWeatherDisplayUnit);
mForecastCondition.setText(mWeatherUtils.formatConditionCode(info.conditionCode));
int today = TimeUtils.getJulianDay();
if (info.julianDay == today) {
mForecastDate.setText(R.string.today);
} else if (info.julianDay == today + 1) {
mForecastDate.setText(R.string.tomorrow);
} else {
sTime.setJulianDay(info.julianDay);
String time = DateUtils.formatDateTime(
context, sTime.toMillis(false), DateUtils.FORMAT_SHOW_WEEKDAY);
mForecastDate.setText(time);
}
}
}
static class ForecastAdapter extends RecyclerView.Adapter<ForecastViewHolder> {
final List<WeatherUtils.ForecastInfo> mForecasts = new ArrayList<>();
final String mWeatherDisplayUnit;
private PaintJob mPaintJob;
// Palette.Swatch mColorSwatch;
ForecastAdapter(String weatherDisplayUnit) {
mWeatherDisplayUnit = weatherDisplayUnit;
}
@Override
public ForecastViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.list_item_forecast, parent, false);
ForecastViewHolder result = new ForecastViewHolder(view, mWeatherDisplayUnit);
result.setPaintJob(mPaintJob);
return result;
}
@Override
public void onBindViewHolder(ForecastViewHolder holder, int position) {
WeatherUtils.ForecastInfo info = mForecasts.get(position);
holder.bind(info);
}
@Override
public int getItemCount() {
return mForecasts.size();
}
public void setForecast(SparseArray<WeatherUtils.ForecastInfo> forecastForDays) {
mForecasts.clear();
int N = forecastForDays.size();
for (int i = 0; i < N; i++) {
WeatherUtils.ForecastInfo forecastInfo = forecastForDays.valueAt(i);
mForecasts.add(forecastInfo);
}
Collections.sort(mForecasts);
notifyDataSetChanged();
}
public void setPaintJob(PaintJob paintJob) {
mPaintJob = paintJob;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class StatusBarTintPainter extends PaintJob.BaseViewPainter {
StatusBarTintPainter() {
super(android.R.id.content);
}
@Override
protected int getCurrentColorFromView(View view) {
return getWindow().getStatusBarColor();
}
@Override
protected int getTargetColorFromSwatch(Palette.Swatch swatch) {
return swatch.getRgb();
}
@Override
protected void applyColorToView(View view, int color) {
getWindow().setStatusBarColor(color);
getWindow().setNavigationBarColor(ColorUtils.setAlphaComponent(color, 64));
}
}
class SimpleRotateDrawable extends BitmapDrawable {
int mAngle;
public SimpleRotateDrawable(Resources resources, Bitmap bitmap) {
super(resources, bitmap);
}
@Override
public void draw(final Canvas canvas) {
Rect bounds = getBounds();
canvas.rotate(mAngle, bounds.centerX(), bounds.centerY());
super.draw(canvas);
canvas.rotate(mAngle, -bounds.centerX(), -bounds.centerY());
}
}
class WeatherDataLoaderCallbacks implements LoaderManager.LoaderCallbacks<
Pair<WeatherData, SparseArray<WeatherUtils.ForecastInfo>>> {
@Override
public Loader<Pair<WeatherData, SparseArray<WeatherUtils.ForecastInfo>>> onCreateLoader(
int id, Bundle args) {
return new WeatherDataLoader(WeatherActivity.this, mWoeid);
}
@Override
public void onLoadFinished(
Loader<Pair<WeatherData, SparseArray<WeatherUtils.ForecastInfo>>> loader,
Pair<WeatherData, SparseArray<WeatherUtils.ForecastInfo>> data) {
onWeatherDataReady(data.first, data.second);
}
@Override
public void onLoaderReset(
Loader<Pair<WeatherData, SparseArray<WeatherUtils.ForecastInfo>>> loader) {
}
}
}