/*
* Copyright 2014 Sebastiano Poggi and Francesco Pontillo
*
* 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 net.frakbot.FWeather.util;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.text.Html;
import android.text.Spanned;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import net.frakbot.FWeather.FWeatherWidgetProvider;
import net.frakbot.FWeather.R;
import net.frakbot.FWeather.updater.UpdaterService;
import net.frakbot.FWeather.updater.weather.model.WeatherData;
import net.frakbot.FWeather.widget.FontTextView;
import net.frakbot.global.Const;
import net.frakbot.util.log.FLog;
/**
* Helper class that deals with finding resources to assign to
* the widget views.
* <p/>
* <b>WARNING!</b> Unavoidable spaghetti lies in here.
*/
public class WidgetHelper {
private static final String PLACEHOLDER_COLOR = "%%COLOR%%";
private Context mContext;
public WidgetHelper(Context c) {
mContext = c;
}
/**
* Gets the instances of the FWeather widget.
* @param context the Context
* @return Array of int containing all of the widget ids
*/
public static int[] getWidgetIds(Context context) {
AppWidgetManager mgr = AppWidgetManager.getInstance(context);
return mgr.getAppWidgetIds(new ComponentName(context, FWeatherWidgetProvider.class));
}
/**
* Builds an update Intent of all the widgets we currently have. It can optionally
* also be silent (no UI).
* The Intent is not started, you will have to do it yourself.
*
* @param forced True if this is a forced update request, false otherwise
* @param silent True if this is a silent forced update request, false otherwise
*/
public static Intent getUpdaterIntent(Context context, boolean forced, boolean silent) {
Intent i = new Intent(context, UpdaterService.class);
int[] ids = getWidgetIds(context);
i.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
i.putExtra(UpdaterService.EXTRA_WIDGET_IDS, ids);
if (forced) {
if (silent) {
// Code-originated forced update (config changes, etc)
i.putExtra(UpdaterService.EXTRA_SILENT_FORCE_UPDATE, true);
}
else {
// User-forced updated
i.putExtra(UpdaterService.EXTRA_USER_FORCE_UPDATE, true);
}
}
return i;
}
/**
* Gets the string representing the weather.
*
*
* @param weather The weather to get the string for
* @param darkMode True if the widget is in dark mode, false otherwise
*
* @return Returns the corresponding weather string
*/
public Spanned getWeatherString(WeatherData weather, boolean darkMode) {
final int weatherId;
if (weather != null) {
weatherId = weather.conditionCode;
}
else {
weatherId = -1;
}
// Codes list: http://developer.yahoo.com/weather/
if (weatherId == 3 || weatherId == 4 || (weatherId >= 37 && weatherId <= 39) ||
weatherId == 45 || weatherId == 47) {// Thunderstorm
return getColoredSpannedString(R.string.weather_thunderstorm, R.color.weather_thunderstorm,
R.color.weather_thunderstorm_dark, darkMode);
}
else if (weatherId == 8 || weatherId == 9) {
// Drizzle
return getColoredSpannedString(R.string.weather_drizzle, R.color.weather_drizzle,
R.color.weather_drizzle_dark, darkMode);
}
else if ((weatherId >= 10 && weatherId <= 12) || weatherId == 40 || weatherId == 6) {
// Rain
return getColoredSpannedString(R.string.weather_rainy, R.color.weather_rainy,
R.color.weather_rainy_dark, darkMode);
}
else if (weatherId == 17 || weatherId == 35) {
// Hail
return getColoredSpannedString(R.string.weather_hail, R.color.weather_hail,
R.color.weather_hail_dark, darkMode);
}
else if ((weatherId >= 13 && weatherId <= 16) || weatherId == 18 ||
(weatherId >= 41 && weatherId <= 43) || weatherId == 46 ||
weatherId == 5 || weatherId == 7) {
// Snow
return getColoredSpannedString(R.string.weather_snowy, R.color.weather_snowy,
R.color.weather_snowy_dark, darkMode);
}
else if (weatherId >= 19 && weatherId <= 22) {
// Atmosphere (mist, smoke, etc)
return getColoredSpannedString(R.string.weather_haze, R.color.weather_haze,
R.color.weather_haze_dark, darkMode);
}
else if (weatherId == 32 || weatherId == 34 ||
weatherId == 31 || weatherId == 33) {
// Sunny or mostly sunny (day&night)
return getColoredSpannedString(R.string.weather_sunny, R.color.weather_sunny,
R.color.weather_sunny_dark, darkMode);
}
else if (weatherId == 30 || weatherId == 44 || weatherId == 29) {
// Partly cloudy (day&night)
return getColoredSpannedString(R.string.weather_partly_cloudy, R.color.weather_partly_cloudy,
R.color.weather_partly_cloudy_dark, darkMode);
}
else if (weatherId >= 26 && weatherId <= 28) {
// Cloudy
return getColoredSpannedString(R.string.weather_cloudy, R.color.weather_cloudy,
R.color.weather_cloudy_dark, darkMode);
}
else if (weatherId == 23 || weatherId == 24) {
// Windy
return getColoredSpannedString(R.string.weather_windy, R.color.weather_windy,
R.color.weather_windy_dark, darkMode);
}
else if (weatherId == 25) {
// Cold
return getColoredSpannedString(R.string.weather_cold, R.color.weather_cold,
R.color.weather_cold_dark, darkMode);
}
else if (weatherId == 36) {
// Hot
return getColoredSpannedString(R.string.weather_hot, R.color.weather_hot,
R.color.weather_hot_dark, darkMode);
}
else if (weatherId >= 0 && weatherId <= 2) {
// Extreme weather
return getColoredSpannedString(R.string.weather_extreme, R.color.weather_extreme,
R.color.weather_extreme_dark, darkMode);
}
else if (weatherId == 3200) {
// Error: no weather available
return getColoredSpannedString(R.string.weather_no_weather, R.color.weather_no_weather,
R.color.weather_no_weather_dark, darkMode);
}
else if (weatherId == WeatherData.WEATHER_ID_ERR_NO_LOCATION ||
weatherId == WeatherData.WEATHER_ID_ERR_NO_NETWORK) {
// Error: no location available
return getColoredSpannedString(R.string.weather_no_location, R.color.weather_no_location,
R.color.weather_no_location_dark, darkMode);
}
else {
return getColoredSpannedString(R.string.weather_wtf, R.color.weather_wtf,
R.color.weather_wtf_dark, darkMode);
}
}
/**
* Returns the specified spanned string with the correct highlighting color.
*
* @param stringId The Resource ID of the string
* @param lightColorId The Resource ID of the highlight color in normal (light) mode
* @param darkColorId The Resource ID of the highlight color in dark mode
* @param darkMode True if the widget is in dark mode, false otherwise
*
* @return Returns the spanned, colored string
*/
public Spanned getColoredSpannedString(int stringId, int lightColorId, int darkColorId, boolean darkMode) {
int color = mContext.getResources().getColor(!darkMode ? lightColorId : darkColorId);
String string = mContext.getString(stringId)
.replace(PLACEHOLDER_COLOR, String.format("#%06X", (0xFFFFFF & color)));
return Html.fromHtml(string);
}
/**
* Gets the ID of the image representing the weather.
*
*
* @param weather The weather to get the image for
* @param darkMode True if the widget is in dark mode, false otherwise
*
* @return Returns the corresponding weather image ID
*/
public int getWeatherImageId(WeatherData weather, boolean darkMode) {
final int weatherId;
if (weather != null) {
weatherId = weather.conditionCode;
}
else {
weatherId = -1;
}
// TODO: improve the weather image selection with an access list, maybe?
// Codes list: http://developer.yahoo.com/weather/
if (weatherId == 3 || weatherId == 4 || (weatherId >= 37 && weatherId <= 39) ||
weatherId == 45 || weatherId == 47) {
// Thunderstorm
return !darkMode ? R.drawable.weather_thunderstorm : R.drawable.weather_thunderstorm_dark;
}
else if (weatherId == 8 || weatherId == 9) {
// Drizzle
return !darkMode ? R.drawable.weather_drizzle : R.drawable.weather_drizzle_dark;
}
else if ((weatherId >= 10 && weatherId <= 12) || weatherId == 40 || weatherId == 6) {
// Rain
return !darkMode ? R.drawable.weather_rain : R.drawable.weather_rain_dark;
}
else if (weatherId == 17 || weatherId == 35) {
// Hail
return !darkMode ? R.drawable.weather_hail : R.drawable.weather_hail_dark;
}
else if ((weatherId >= 13 && weatherId <= 16) || weatherId == 18 ||
(weatherId >= 41 && weatherId <= 43) || weatherId == 46 ||
weatherId == 5 || weatherId == 7) {
// Snow
return !darkMode ? R.drawable.weather_snow : R.drawable.weather_snow_dark;
}
else if (weatherId >= 19 && weatherId <= 22) {
// Atmosphere (mist, smoke, etc)
return !darkMode ? R.drawable.weather_haze : R.drawable.weather_haze_dark;
}
else if (weatherId == 32 || weatherId == 34) {
// Sunny or mostly sunny (day)
return !darkMode ? R.drawable.weather_clear_day : R.drawable.weather_clear_day_dark;
}
else if (weatherId == 31 || weatherId == 33) {
// Clear (night)
return !darkMode ? R.drawable.weather_clear_night : R.drawable.weather_clear_night_dark;
}
else if (weatherId >= 26 && weatherId <= 28) {
// Cloudy
return !darkMode ? R.drawable.weather_cloudy : R.drawable.weather_cloudy_dark;
}
else if (weatherId == 30 || weatherId == 44) {
// Partly cloudy (day)
return !darkMode ? R.drawable.weather_partly_cloudy_day : R.drawable.weather_partly_cloudy_day_dark;
}
else if (weatherId == 29) {
// Partly cloudy (night)
return !darkMode ? R.drawable.weather_partly_cloudy_night : R.drawable.weather_partly_cloudy_night_dark;
}
else if (weatherId == 23 || weatherId == 24) {
// Windy
return !darkMode ? R.drawable.weather_windy : R.drawable.weather_windy_dark;
}
else if (weatherId == 25) {
// Cold
return !darkMode ? R.drawable.weather_cold : R.drawable.weather_cold_dark;
}
else if (weatherId == 36) {
// Hot
return !darkMode ? R.drawable.weather_hot : R.drawable.weather_hot_dark;
}
else if (weatherId >= 0 && weatherId <= 2) {
// Extreme weather
return !darkMode ? R.drawable.weather_extreme : R.drawable.weather_extreme_dark;
}
else if (weatherId == 3200) {
// Error: no weather available (ATM has a generic error icon)
return !darkMode ? R.drawable.err_wtf : R.drawable.err_wtf_dark;
}
else if (weatherId == WeatherData.WEATHER_ID_ERR_NO_LOCATION) {
// Error: no location available
return !darkMode ? R.drawable.err_no_location : R.drawable.err_no_location_dark;
}
else if (weatherId == WeatherData.WEATHER_ID_ERR_NO_NETWORK) {
// Error: no network available
return !darkMode ? R.drawable.err_no_network : R.drawable.err_no_network_dark;
}
else {
// Unknown weather
return !darkMode ? R.drawable.err_wtf : R.drawable.err_wtf_dark;
}
}
/**
* Gets the temperature string for the weather.
*
*
* @param weather The weather to get the temperature string for
* @param darkMode True if the widget is in dark mode, false otherwise
*
* @return Returns the temperature string
*/
public CharSequence getTempString(WeatherData weather, boolean darkMode) {
final float temp;
if (weather != null) {
if (weather.conditionCode == WeatherData.WEATHER_ID_ERR_NO_LOCATION) {
// Error: no location available
return getColoredSpannedString(R.string.temp_no_location, R.color.temp_no_location,
R.color.temp_no_location_dark, darkMode);
} else if (weather.conditionCode == WeatherData.WEATHER_ID_ERR_NO_NETWORK) {
// Error: no location or no network available
return getColoredSpannedString(R.string.temp_no_network, R.color.temp_no_network,
R.color.temp_no_network_dark, darkMode);
}
temp = weather.temperature;
}
else {
return getColoredSpannedString(R.string.temp_wtf, R.color.temp_wtf,
R.color.temp_wtf_dark, darkMode);
}
if (temp < 0f) {
return getColoredSpannedString(R.string.temp_freezing, R.color.temp_freezing,
R.color.temp_freezing_dark, darkMode);
}
else if (temp < 15f) {
return getColoredSpannedString(R.string.temp_cold, R.color.temp_cold,
R.color.temp_cold_dark, darkMode);
}
else if (temp < 28f) {
return getColoredSpannedString(R.string.temp_warm, R.color.temp_warm,
R.color.temp_warm_dark, darkMode);
}
else {
return getColoredSpannedString(R.string.temp_hot, R.color.temp_hot,
R.color.temp_hot_dark, darkMode);
}
}
/**
* Gets the right color for the widget background, depending on the user
* preferences and the dark mode state.
*
* @param bgOpacityPrefValue The value of the {@link net.frakbot.FWeather.R.string#pref_key_ui_bgopacity}
* preference
* @param darkMode True if the widget is in dark mode (and thus requires a bright BG), false otherwise
* @return Returns the color to be assigned to the widget BG
*/
public int getWidgetBGColor(int bgOpacityPrefValue, boolean darkMode) {
TypedArray colors = mContext.getResources()
.obtainTypedArray(darkMode ? R.array.bg_colors_darkmode : R.array.bg_colors);
// We assume a fully transparent BG color as default
switch (bgOpacityPrefValue) {
case 0:
return colors.getColor(0, 0x00000000);
case 25:
return colors.getColor(1, 0x00000000);
case 50:
return colors.getColor(2, 0x00000000);
case 75:
return colors.getColor(3, 0x00000000);
case 100:
return colors.getColor(4, 0x00000000);
default:
FLog.w("WidgetHelper", "Invalid BG preference value detected: " + bgOpacityPrefValue);
return 0x00000000;
}
}
/**
* Makes a customized Toast on the specified Context,
* using the text and duration provided.
*
* @param c The Context to build the Toast within
* @param text The text of the Toast
* @param duration The duration of the Toast (see Toast's duration)
*
* @return Returns the initialized Toast
*/
public static Toast makeToast(Context c, CharSequence text, int duration) {
final View view = LayoutInflater.from(c).inflate(R.layout.toast_layout, null);
((FontTextView) view.findViewById(android.R.id.content)).setText(text);
Toast t = new Toast(c);
t.setView(view);
t.setDuration(duration);
return t;
}
/**
* Makes a customized Toast on the specified Context,
* using the text and duration provided.
*
* @param c The Context to build the Toast within
* @param text The Resource ID of the text of the Toast
* @param duration The duration of the Toast (see Toast's duration)
*
* @return Returns the initialized Toast
*/
public static Toast makeToast(Context c, int text, int duration) {
final View view = LayoutInflater.from(c).inflate(R.layout.toast_layout, null);
((FontTextView) view.findViewById(android.R.id.content)).setText(text);
Toast t = new Toast(c);
t.setView(view);
t.setDuration(duration);
t.setGravity(Gravity.BOTTOM, 0, c.getResources().getDimensionPixelSize(R.dimen.toast_yoffset));
return t;
}
/**
* Get the sharing string by using the given weather data.
* @param weatherData The {@link net.frakbot.FWeather.updater.weather.model.WeatherData} to build the share
* string from
* @return The sharing string
*/
public String getShareString(WeatherData weatherData) {
String weather = getWeatherString(weatherData, false).toString();
return new StringBuilder().append(weather).append(" ").append(Const.Share.VIA).toString();
}
}