first version solarCast

This commit is contained in:
woheller69 2023-04-01 15:27:34 +02:00
commit da720ba6dc
471 changed files with 10774 additions and 0 deletions

View file

@ -0,0 +1,68 @@
package org.woheller69.weather;
import net.e175.klaus.solarpositioning.AzimuthZenithAngle;
import net.e175.klaus.solarpositioning.DeltaT;
import net.e175.klaus.solarpositioning.Grena3;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class SolarPowerPlant {
double latitude;
double longitude;
double cellsMaxPower;
double cellsArea;
double cellsEfficiency;
double diffuseEfficiency;
double converterPowerLimit;
double converterEfficiency;
double azimuthAngle;
double elevationAngle;
public SolarPowerPlant(double latitude, double longitude, double cellsMaxPower, double cellsArea, double cellsEfficiency, double diffuseEfficiency, double converterPowerLimit, double converterEfficiency, double azimuthAngle, double elevationAngle) {
this.latitude = latitude;
this.longitude = longitude;
this.cellsMaxPower = cellsMaxPower;
this.cellsArea = cellsArea;
this.cellsEfficiency = cellsEfficiency / 100;
this.diffuseEfficiency = diffuseEfficiency / 100;
this.converterPowerLimit = converterPowerLimit;
this.converterEfficiency = converterEfficiency / 100;
this.azimuthAngle = azimuthAngle;
this.elevationAngle = elevationAngle;
}
public float getPower(double solarPowerNormal, double solarPowerDiffuse, long epochTimeSeconds) {
Instant i = Instant.ofEpochSecond(epochTimeSeconds); //currentTimeMillis is in GMT
ZonedDateTime dateTime = ZonedDateTime.ofInstant(i, ZoneId.of("GMT"));
AzimuthZenithAngle position = Grena3.calculateSolarPosition(
dateTime,
latitude,
longitude,
DeltaT.estimate(dateTime.toLocalDate())); // delta T (s)
double solarAzimuth = position.getAzimuth();
double solarElevation = 90 - position.getZenithAngle();
Double[] directionSun = {Math.sin(solarAzimuth / 180 * Math.PI) * Math.cos(solarElevation / 180 * Math.PI), Math.cos(solarAzimuth / 180 * Math.PI) * Math.cos(solarElevation / 180 * Math.PI), Math.sin(solarElevation / 180 * Math.PI)};
Double[] normalPanel = {Math.sin(azimuthAngle / 180 * Math.PI) * Math.cos((90 - elevationAngle) / 180 * Math.PI), Math.cos(azimuthAngle / 180 * Math.PI) * Math.cos((90 - elevationAngle) / 180 * Math.PI), Math.sin((90 - elevationAngle) / 180 * Math.PI)};
double efficiency = 0; //calculate scalar product of sunDirection and normalPanel vectors
for (int j = 0; j < directionSun.length; j++) {
efficiency += directionSun[j] * normalPanel[j];
}
efficiency = Math.max(0,efficiency); //scalar product is negative if sun points to back of module. set 0 in this case
//TODO solarPowerDiffuse ignored so far
double dcPower = (solarPowerNormal * efficiency + solarPowerDiffuse * diffuseEfficiency )* cellsEfficiency * cellsArea;
double acPower = Math.min(dcPower * converterEfficiency, converterPowerLimit);
return (float) acPower;
}
}

View file

@ -0,0 +1,31 @@
package org.woheller69.weather.activities;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.R;
/**
* Created by yonjuni on 15.06.16.
*/
public class AboutActivity extends NavigationActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
overridePendingTransition(0, 0);
((TextView) findViewById(R.id.openmeteoURL)).setMovementMethod(LinkMovementMethod.getInstance());
((TextView) findViewById(R.id.githubURL)).setMovementMethod(LinkMovementMethod.getInstance());
((TextView) findViewById(R.id.textFieldVersionName)).setText(BuildConfig.VERSION_NAME);
}
@Override
protected int getNavigationDrawerID() {
return R.id.nav_about;
}
}

View file

@ -0,0 +1,274 @@
package org.woheller69.weather.activities;
import static java.lang.Boolean.TRUE;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.TextView;
import android.widget.Toast;
import org.woheller69.weather.R;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.ui.updater.IUpdateableCityUI;
import org.woheller69.weather.ui.updater.ViewUpdater;
import org.woheller69.weather.ui.viewPager.WeatherPagerAdapter;
import static org.woheller69.weather.database.SQLiteHelper.getWidgetCityID;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Locale;
public class ForecastCityActivity extends NavigationActivity implements IUpdateableCityUI {
private WeatherPagerAdapter pagerAdapter;
private static MenuItem refreshActionButton;
private int cityId = -1;
private ViewPager2 viewPager2;
private TabLayout tabLayout;
private TextView noCityText;
Context context;
@Override
protected void onPause() {
super.onPause();
ViewUpdater.removeSubscriber(this);
ViewUpdater.removeSubscriber(pagerAdapter);
}
@Override
protected void onResume() {
super.onResume();
SQLiteHelper db = SQLiteHelper.getInstance(this);
if (db.getAllCitiesToWatch().isEmpty()) {
// no cities selected.. don't show the viewPager - rather show a text that tells the user that no city was selected
viewPager2.setVisibility(View.GONE);
noCityText.setVisibility(View.VISIBLE);
} else {
noCityText.setVisibility(View.GONE);
viewPager2.setVisibility(View.VISIBLE);
pagerAdapter.loadCities();
viewPager2.setAdapter(pagerAdapter);
TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager2,false,false, (tab, position) -> tab.setText(pagerAdapter.getPageTitle(position)));
tabLayoutMediator.attach();
}
ViewUpdater.addSubscriber(this);
ViewUpdater.addSubscriber(pagerAdapter);
if (pagerAdapter.getItemCount()>0) { //only if at least one city is watched
//if pagerAdapter has item with current cityId go there, otherwise use cityId from current item
if (pagerAdapter.getPosForCityID(cityId)==-1) cityId=pagerAdapter.getCityIDForPos(viewPager2.getCurrentItem());
CurrentWeatherData currentWeather = db.getCurrentWeatherByCityId(cityId);
long timestamp = currentWeather.getTimestamp();
long systemTime = System.currentTimeMillis() / 1000;
SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
long updateInterval = (long) (Float.parseFloat(prefManager.getString("pref_updateInterval", "2")) * 60 * 60);
if (timestamp + updateInterval - systemTime <= 0) {
WeatherPagerAdapter.refreshSingleData(getApplicationContext(), true, cityId); //only update current tab at start
ForecastCityActivity.startRefreshAnimation();
}
if (viewPager2.getCurrentItem()!=pagerAdapter.getPosForCityID(cityId)) viewPager2.setCurrentItem(pagerAdapter.getPosForCityID(cityId),false);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context=this;
setContentView(R.layout.activity_forecast_city);
overridePendingTransition(0, 0);
initResources();
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
//Update current tab if outside update interval, show animation
SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SQLiteHelper database = SQLiteHelper.getInstance(getApplicationContext().getApplicationContext());
CurrentWeatherData currentWeather = database.getCurrentWeatherByCityId(pagerAdapter.getCityIDForPos(position));
long timestamp = currentWeather.getTimestamp();
long systemTime = System.currentTimeMillis() / 1000;
long updateInterval = (long) (Float.parseFloat(prefManager.getString("pref_updateInterval", "2")) * 60 * 60);
if (timestamp + updateInterval - systemTime <= 0) {
WeatherPagerAdapter.refreshSingleData(getApplicationContext(),true, pagerAdapter.getCityIDForPos(position));
ForecastCityActivity.startRefreshAnimation();
}
//post method needed to avoid Illegal State Exception: Cannot call this method in a scroll callback.
viewPager2.post(() -> {
pagerAdapter.notifyItemChanged(position); //fix crash with StaggeredGridLayoutManager when moving back and forth between items
});
cityId=pagerAdapter.getCityIDForPos(viewPager2.getCurrentItem()); //save current cityId for next resume
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (intent.hasExtra("cityId")) {
cityId = intent.getIntExtra("cityId",-1);
if (pagerAdapter.getItemCount()>0) viewPager2.setCurrentItem(pagerAdapter.getPosForCityID(cityId),false);
}
}
private void initResources() {
viewPager2 = findViewById(R.id.viewPager2);
reduceViewpager2DragSensitivity(viewPager2,2);
tabLayout = findViewById(R.id.tab_layout);
pagerAdapter = new WeatherPagerAdapter(this, getSupportFragmentManager(),getLifecycle());
noCityText = findViewById(R.id.noCitySelectedText);
}
@Override
protected int getNavigationDrawerID() {
return R.id.nav_weather;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_forecast_city, menu);
final Menu m = menu;
refreshActionButton = menu.findItem(R.id.menu_refresh);
refreshActionButton.setActionView(R.layout.menu_refresh_action_view);
refreshActionButton.getActionView().setOnClickListener(v -> m.performIdentifierAction(refreshActionButton.getItemId(), 0));
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
SQLiteHelper db = SQLiteHelper.getInstance(this);
if (id==R.id.menu_refresh){
if (!db.getAllCitiesToWatch().isEmpty()) { //only if at least one city is watched, otherwise crash
WeatherPagerAdapter.refreshSingleData(getApplicationContext(),true, pagerAdapter.getCityIDForPos(viewPager2.getCurrentItem()));
ForecastCityActivity.startRefreshAnimation();
}
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onPostResume() {
super.onPostResume();
}
@Override
public void processNewCurrentWeatherData(CurrentWeatherData data) {
if (refreshActionButton != null && refreshActionButton.getActionView() != null) {
refreshActionButton.getActionView().clearAnimation();
}
}
@Override
public void processNewWeekForecasts(List<WeekForecast> forecasts) {
if (refreshActionButton != null && refreshActionButton.getActionView() != null) {
refreshActionButton.getActionView().clearAnimation();
}
}
@Override
public void processNewForecasts(List<HourlyForecast> hourlyForecasts) {
if (refreshActionButton != null && refreshActionButton.getActionView() != null) {
refreshActionButton.getActionView().clearAnimation();
}
}
public static void startRefreshAnimation(){
{
if(refreshActionButton !=null && refreshActionButton.getActionView() != null) {
RotateAnimation rotate = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(500);
rotate.setRepeatCount(5);
rotate.setInterpolator(new LinearInterpolator());
rotate.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
refreshActionButton.getActionView().setActivated(false);
refreshActionButton.getActionView().setEnabled(false);
refreshActionButton.getActionView().setClickable(false);
}
@Override
public void onAnimationEnd(Animation animation) {
refreshActionButton.getActionView().setActivated(true);
refreshActionButton.getActionView().setEnabled(true);
refreshActionButton.getActionView().setClickable(true);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
refreshActionButton.getActionView().startAnimation(rotate);
}
}
}
//https://devdreamz.com/question/348298-how-to-modify-sensitivity-of-viewpager
private void reduceViewpager2DragSensitivity(ViewPager2 viewPager, int sensitivity) {
try {
Field ff = ViewPager2.class.getDeclaredField("mRecyclerView") ;
ff.setAccessible(true);
RecyclerView recyclerView = (RecyclerView) ff.get(viewPager);
Field touchSlopField = RecyclerView.class.getDeclaredField("mTouchSlop") ;
touchSlopField.setAccessible(true);
int touchSlop = (int) touchSlopField.get(recyclerView);
touchSlopField.set(recyclerView,touchSlop*sensitivity);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,190 @@
package org.woheller69.weather.activities;
import android.content.Context;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.Toast;
import org.woheller69.weather.R;
import org.woheller69.weather.database.City;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.dialogs.AddLocationDialogOmGeocodingAPI;
import org.woheller69.weather.ui.RecycleList.RecyclerItemClickListener;
import org.woheller69.weather.ui.RecycleList.RecyclerOverviewListAdapter;
import org.woheller69.weather.ui.RecycleList.SimpleItemTouchHelperCallback;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
//in-App: where cities get added & sorted
public class ManageLocationsActivity extends NavigationActivity {
private SQLiteHelper database;
private ItemTouchHelper.Callback callback;
private ItemTouchHelper touchHelper;
RecyclerOverviewListAdapter adapter;
List<CityToWatch> cities;
Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_manage_locations);
overridePendingTransition(0, 0);
context=this;
database = SQLiteHelper.getInstance(getApplicationContext());
try {
cities = database.getAllCitiesToWatch();
Collections.sort(cities, new Comparator<CityToWatch>() {
@Override
public int compare(CityToWatch o1, CityToWatch o2) {
return o1.getRank() - o2.getRank();
}
});
} catch (NullPointerException e) {
e.printStackTrace();
Toast toast = Toast.makeText(getBaseContext(), "No cities in DB", Toast.LENGTH_SHORT);
toast.show();
}
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list_view_cities);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(getBaseContext(), recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
AlertDialog.Builder alert = new AlertDialog.Builder(context);
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_edit_location, null);
alert.setTitle(getString(R.string.edit_location_title));
alert.setView(dialogView);
EditText editLatitude = (EditText) dialogView.findViewById(R.id.EditLocation_Lat);
EditText editLongitude = (EditText) dialogView.findViewById(R.id.EditLocation_Lon);
EditText editCity = (EditText) dialogView.findViewById(R.id.EditLocation_Name);
EditText editAzimuth = (EditText) dialogView.findViewById(R.id.EditLocation_Azimuth);
EditText editElevation = (EditText) dialogView.findViewById(R.id.EditLocation_Elevation);
EditText editCellsMaxPower = (EditText) dialogView.findViewById(R.id.EditLocation_Cell_Max_Power);
EditText editCellsArea = (EditText) dialogView.findViewById(R.id.EditLocation_Cells_Area);
EditText editCellsEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Cell_Efficiency);
EditText editDiffuseEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Diffuse_Efficiency);
EditText editConverterPowerLimit = (EditText) dialogView.findViewById(R.id.EditLocation_Converter_Power_Limit);
EditText editConverterEfficiency = (EditText) dialogView.findViewById(R.id.EditLocation_Converter_Efficiency);
editCity.setText(adapter.getCitytoWatch(position).getCityName());
editLatitude.setText(Float.toString(adapter.getCitytoWatch(position).getLatitude()));
editLongitude.setText(Float.toString(adapter.getCitytoWatch(position).getLongitude()));
editAzimuth.setText(Float.toString(adapter.getCitytoWatch(position).getAzimuthAngle()));
editElevation.setText(Float.toString(adapter.getCitytoWatch(position).getElevationAngle()));
editCellsMaxPower.setText(Float.toString(adapter.getCitytoWatch(position).getCellsMaxPower()));
editCellsArea.setText(Float.toString(adapter.getCitytoWatch(position).getCellsArea()));
editCellsEfficiency.setText(Float.toString(adapter.getCitytoWatch(position).getCellsEfficiency()));
editDiffuseEfficiency.setText(Float.toString(adapter.getCitytoWatch(position).getDiffuseEfficiency()));
editConverterPowerLimit.setText(Float.toString(adapter.getCitytoWatch(position).getConverterPowerLimit()));
editConverterEfficiency.setText(Float.toString(adapter.getCitytoWatch(position).getConverterEfficiency()));
alert.setPositiveButton(getString(R.string.dialog_edit_change_button), (dialog, whichButton) -> {
adapter.updateCity(position, String.valueOf(editCity.getText()),
Float.parseFloat(editLatitude.getText().toString()),
Float.parseFloat(editLongitude.getText().toString()),
Float.parseFloat(editAzimuth.getText().toString()),
Float.parseFloat(editElevation.getText().toString()),
Float.parseFloat(editCellsMaxPower.getText().toString()),
Float.parseFloat(editCellsArea.getText().toString()),
Float.parseFloat(editCellsEfficiency.getText().toString()),
Float.parseFloat(editDiffuseEfficiency.getText().toString()),
Float.parseFloat(editConverterPowerLimit.getText().toString()),
Float.parseFloat(editConverterEfficiency.getText().toString())
);
});
alert.setNegativeButton(getString(R.string.dialog_add_close_button), (dialog, whichButton) -> {
});
alert.show();
}
public void onLongItemClick(View view, int position) {
}
})
);
adapter = new RecyclerOverviewListAdapter(getApplicationContext(), cities);
recyclerView.setAdapter(adapter);
recyclerView.setFocusable(false);
callback = new SimpleItemTouchHelperCallback(adapter);
touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
FloatingActionButton addFab1 = (FloatingActionButton) findViewById(R.id.fabAddLocation);
if (addFab1 != null) {
addFab1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
AddLocationDialogOmGeocodingAPI addLocationDialog = new AddLocationDialogOmGeocodingAPI();
addLocationDialog.show(fragmentManager, "AddLocationDialog");
getSupportFragmentManager().executePendingTransactions();
addLocationDialog.getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
});
}
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected int getNavigationDrawerID() {
return R.id.nav_manage;
}
public void addCityToList(City city) {
CityToWatch newCity=convertCityToWatched(city);
long id=database.addCityToWatch(newCity);
newCity.setId((int) id);
newCity.setCityId((int) id); //use id also instead of city id as unique identifier
cities.add(newCity);
adapter.notifyDataSetChanged();
}
private CityToWatch convertCityToWatched(City selectedCity) {
return new CityToWatch(
database.getMaxRank() + 1,
-1,
selectedCity.getCityId(), selectedCity.getLongitude(),selectedCity.getLatitude(),
selectedCity.getCityName()
);
}
}

View file

@ -0,0 +1,228 @@
package org.woheller69.weather.activities;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.TaskStackBuilder;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.os.Looper;
import android.view.MenuItem;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.R;
import org.woheller69.weather.preferences.AppPreferencesManager;
import static java.lang.Boolean.TRUE;
/**
* Created by Chris on 04.07.2016.
*/
public class NavigationActivity extends AppCompatActivity implements OnNavigationItemSelectedListener {
// delay to launch nav drawer item, to allow close animation to play
static final int NAVDRAWER_LAUNCH_DELAY = 250;
public static boolean isVisible = false;
// Navigation drawer:
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
// Helper
private Handler mHandler;
protected SharedPreferences mSharedPreferences;
protected AppPreferencesManager prefManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mHandler = new Handler(Looper.getMainLooper());
prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(this));
if (prefManager.showStarDialog(this)) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.dialog_StarOnGitHub);
alertDialogBuilder.setPositiveButton(getString(R.string.dialog_OK_button), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(BuildConfig.GITHUB_URL)));
prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
prefManager.setAskForStar(false);
}
});
alertDialogBuilder.setNegativeButton(getString(R.string.dialog_NO_button), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
prefManager.setAskForStar(false);
}
});
alertDialogBuilder.setNeutralButton(getString(R.string.dialog_Later_button), null);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
overridePendingTransition(0, 0);
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
if (getNavigationDrawerID()!=R.id.nav_weather)
{
Intent intent = new Intent(this, ForecastCityActivity.class);
startActivity(intent);
}else{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
}
}
}
protected int getNavigationDrawerID() {
return 0;
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
final int itemId = item.getItemId();
return goToNavigationItem(itemId);
}
protected boolean goToNavigationItem(final int itemId) {
if (itemId == getNavigationDrawerID()) {
// just close drawer because we are already in this activity
mDrawerLayout.closeDrawer(GravityCompat.START);
return true;
}
// delay transition so the drawer can close
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
callDrawerItem(itemId);
}
}, NAVDRAWER_LAUNCH_DELAY);
mDrawerLayout.closeDrawer(GravityCompat.START);
selectNavigationItem(itemId);
return true;
}
// set active navigation item
private void selectNavigationItem(int itemId) {
for (int i = 0; i < mNavigationView.getMenu().size(); i++) {
boolean b = itemId == mNavigationView.getMenu().getItem(i).getItemId();
mNavigationView.getMenu().getItem(i).setChecked(b);
}
}
/**
* Enables back navigation for activities that are launched from the NavBar. See
* {@code AndroidManifest.xml} to find out the parent activity names for each activity.
*
* @param intent
*/
private void createBackStack(Intent intent) {
TaskStackBuilder builder = TaskStackBuilder.create(this);
builder.addNextIntentWithParentStack(intent);
builder.startActivities();
}
private void callDrawerItem(final int itemId) {
Intent intent;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if(sharedPreferences.getBoolean("pref_DarkMode", false)==TRUE) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
if (itemId==R.id.nav_weather) {
intent = new Intent(this, ForecastCityActivity.class);
startActivity(intent);
}else if (itemId==R.id.nav_manage){
intent = new Intent(this, ManageLocationsActivity.class);
startActivity(intent);
}else if (itemId==R.id.nav_about) {
intent = new Intent(this, AboutActivity.class);
createBackStack(intent);
}else if(itemId==R.id.nav_settings) {
intent = new Intent(this, SettingsActivity.class);
createBackStack(intent);
}else if (itemId==R.id.star_on_github){
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(BuildConfig.GITHUB_URL)));
prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
prefManager.setAskForStar(false);
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Toolbar toolbar = findViewById(R.id.toolbar);
if (getSupportActionBar() == null) {
setSupportActionBar(toolbar);
}
mDrawerLayout = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, mDrawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
mDrawerLayout.addDrawerListener(toggle);
toggle.syncState();
mNavigationView = findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
selectNavigationItem(getNavigationDrawerID());
}
@Override
protected void onResume() {
super.onResume();
isVisible=true;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if(sharedPreferences.getBoolean("pref_DarkMode", false)==TRUE) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
@Override
protected void onPause() {
super.onPause();
isVisible=false;
}
}

View file

@ -0,0 +1,86 @@
package org.woheller69.weather.activities;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.SeekBarPreference;
import org.woheller69.weather.R;
public class SettingsActivity extends NavigationActivity implements SharedPreferences.OnSharedPreferenceChangeListener{
@Override
protected void onRestart() {
super.onRestart();
recreate();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onResume() {
super.onResume();
PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
overridePendingTransition(0, 0);
}
@Override
protected int getNavigationDrawerID() {
return R.id.nav_settings;
}
@RequiresApi(api = Build.VERSION_CODES.Q)
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
public static class GeneralPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.pref_general, rootKey);
}
@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("pref_number_days")){
SeekBarPreference numberDays = findPreference("pref_number_days");
if (numberDays.getValue()<3) numberDays.setValue(3);
}
}
}
}

View file

@ -0,0 +1,32 @@
package org.woheller69.weather.activities;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import org.woheller69.weather.firststart.TutorialActivity;
import org.woheller69.weather.preferences.AppPreferencesManager;
/**
* Created by yonjuni on 24.10.16.
*/
public class SplashActivity extends AppCompatActivity {
private AppPreferencesManager prefManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(this));
if (prefManager.isFirstTimeLaunch(this)){ //First time got to TutorialActivity
Intent mainIntent = new Intent(SplashActivity.this, TutorialActivity.class);
SplashActivity.this.startActivity(mainIntent);
} else { //otherwise directly start ForecastCityActivity
Intent mainIntent = new Intent(SplashActivity.this, ForecastCityActivity.class);
SplashActivity.this.startActivity(mainIntent);
}
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,76 @@
package org.woheller69.weather.database;
import java.util.Locale;
/**
* Created by yonjuni on 04.01.17.
* data object for city
* <p>
* Structure taken from the old orm package from previous versions of this app.
*/
public class City {
private int cityId;
private String cityName;
private String countryCode;
private float lon;
private float lat;
public City() {
}
public City(int cityId, String cityName, String countryCode, float lon, float lat) {
this.cityId = cityId;
this.cityName = cityName;
this.countryCode = countryCode;
this.lon = lon;
this.lat = lat;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
@Override
public String toString() {
return String.format(Locale.getDefault(),"%s, %s (%.2f / %.2f)", cityName, countryCode, lat, lon);
}
public void setLatitude(float latitude) {
lat = latitude;
}
public float getLatitude() {
return lat;
}
public float getLongitude() {
return lon;
}
public void setLongitude(float lon) {
this.lon = lon;
}
}

View file

@ -0,0 +1,149 @@
package org.woheller69.weather.database;
/**
* This class is the database model for the cities to watch. 'Cities to watch' means the locations
* for which a user would like to see the weather for. This includes those locations that will be
* deleted after app close (non-persistent locations).
*/
public class CityToWatch {
private int id;
private int cityId;
private String cityName;
private float lon;
private float lat;
private float cellsMaxPower;
private float cellsArea;
private float cellsEfficiency;
private float diffuseEfficiency;
private float converterPowerLimit;
private float converterEfficiency;
private float azimuthAngle;
private float elevationAngle;
private int rank;
public CityToWatch() {
}
public CityToWatch(int rank, int id, int cityId, float lon, float lat, String cityName) {
this.rank = rank;
this.lon = lon;
this.lat = lat;
this.id = id;
this.cityId = cityId;
this.cityName = cityName;
this.cellsMaxPower=650;
this.cellsArea=3.18f;
this.cellsEfficiency=19.3f;
this.diffuseEfficiency=40;
this.converterPowerLimit=600;
this.converterEfficiency=95;
this.azimuthAngle=170;
this.elevationAngle=90;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public void setLongitude(float lon) { this.lon = lon; }
public float getLongitude() { return lon; }
public float getLatitude() { return lat; }
public void setLatitude(float lat) { this.lat = lat; }
public float getCellsMaxPower() {
return cellsMaxPower;
}
public float getCellsArea() {
return cellsArea;
}
public float getAzimuthAngle() {
return azimuthAngle;
}
public float getCellsEfficiency() {
return cellsEfficiency;
}
public float getDiffuseEfficiency() {
return diffuseEfficiency;
}
public float getConverterEfficiency() {
return converterEfficiency;
}
public float getConverterPowerLimit() {
return converterPowerLimit;
}
public float getElevationAngle() {
return elevationAngle;
}
public void setCellsMaxPower(float cellsMaxPower) {
this.cellsMaxPower = cellsMaxPower;
}
public void setCellsArea(float cellsArea) {
this.cellsArea = cellsArea;
}
public void setAzimuthAngle(float azimuthAngle) {
this.azimuthAngle = azimuthAngle;
}
public void setCellsEfficiency(float cellsEfficiency) {
this.cellsEfficiency = cellsEfficiency;
}
public void setConverterEfficiency(float converterEfficiency) {
this.converterEfficiency = converterEfficiency;
}
public void setConverterPowerLimit(float converterPowerLimit) {
this.converterPowerLimit = converterPowerLimit;
}
public void setDiffuseEfficiency(float diffuseEfficiency) {
this.diffuseEfficiency = diffuseEfficiency;
}
public void setElevationAngle(float elevationAngle) {
this.elevationAngle = elevationAngle;
}
}

View file

@ -0,0 +1,184 @@
package org.woheller69.weather.database;
import android.content.Context;
import java.util.Calendar;
import java.util.TimeZone;
/**
* This class represents the database model for current weather data of cities.
*/
public class CurrentWeatherData {
private int id;
private int city_id;
private long timestamp;
private int weatherID;
private float temperatureCurrent;
private float humidity;
private float pressure;
private float windSpeed;
private float windDirection;
private float cloudiness;
private long timeSunrise;
private long timeSunset;
private int timeZoneSeconds;
private String Rain60min;
private String city_name;
public CurrentWeatherData() {
this.city_id = Integer.MIN_VALUE;
}
public CurrentWeatherData(int id, int city_id, long timestamp, int weatherID, float temperatureCurrent, float temperatureMin, float temperatureMax, float humidity, float pressure, float windSpeed, float windDirection, float cloudiness, long timeSunrise, long timeSunset, int timeZoneSeconds) {
this.id = id;
this.city_id = city_id;
this.timestamp = timestamp;
this.weatherID = weatherID;
this.temperatureCurrent = temperatureCurrent;
this.humidity = humidity;
this.pressure = pressure;
this.windSpeed = windSpeed;
this.windDirection = windDirection;
this.cloudiness = cloudiness;
this.timeSunrise = timeSunrise;
this.timeSunset = timeSunset;
this.timeZoneSeconds = timeZoneSeconds;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getCity_id() {
return city_id;
}
public void setCity_id(int city_id) {
this.city_id = city_id;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public int getWeatherID() {
return weatherID;
}
public void setWeatherID(int weatherID) {
this.weatherID = weatherID;
}
public float getTemperatureCurrent() {
return temperatureCurrent;
}
public void setTemperatureCurrent(float temperatureCurrent) {
this.temperatureCurrent = temperatureCurrent;
}
public float getHumidity() {
return humidity;
}
public void setHumidity(float humidity) {
this.humidity = humidity;
}
public float getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
public float getWindSpeed() {
return windSpeed;
}
public void setWindSpeed(float windSpeed) {
this.windSpeed = windSpeed;
}
public float getWindDirection() {
return windDirection;
}
public void setWindDirection(float windDirection) {
this.windDirection = windDirection;
}
public float getCloudiness() {
return cloudiness;
}
public void setCloudiness(float cloudiness) {
this.cloudiness = cloudiness;
}
public boolean isDay(Context context){
Calendar timeStamp = Calendar.getInstance();
timeStamp.setTimeZone(TimeZone.getTimeZone("GMT"));
timeStamp.setTimeInMillis((timestamp+timeZoneSeconds)*1000);
SQLiteHelper dbHelper = SQLiteHelper.getInstance(context);
if (timeSunrise==0 || timeSunset==0){
if ((dbHelper.getCityToWatch(city_id).getLatitude())>0){ //northern hemisphere
return timeStamp.get(Calendar.DAY_OF_YEAR) >= 80 && timeStamp.get(Calendar.DAY_OF_YEAR) <= 265; //from March 21 to September 22 (incl)
}else{ //southern hemisphere
return timeStamp.get(Calendar.DAY_OF_YEAR) < 80 || timeStamp.get(Calendar.DAY_OF_YEAR) > 265;
}
}else {
return timestamp > timeSunrise && timestamp < timeSunset;
}
}
public long getTimeSunrise() { return timeSunrise; }
public void setTimeSunrise(long timeSunrise) {
this.timeSunrise = timeSunrise;
}
public long getTimeSunset() {
return timeSunset;
}
public void setTimeSunset(long timeSunset) {
this.timeSunset = timeSunset;
}
public String getCity_name() {
return city_name;
}
public void setCity_name(String city_name) {
this.city_name = city_name;
}
public int getTimeZoneSeconds() {
return timeZoneSeconds;
}
public void setTimeZoneSeconds(int timeZoneSeconds) {
this.timeZoneSeconds = timeZoneSeconds;
}
public String getRain60min() {
return Rain60min;
}
public void setRain60min(String Rain60min) {
this.Rain60min = Rain60min;
}
}

View file

@ -0,0 +1,107 @@
package org.woheller69.weather.database;
import android.content.Context;
/**
* This class is the database model for the forecasts table.
*/
public class HourlyForecast {
public static final float NO_RAIN_VALUE = 0;
private int id;
private int city_id;
private long timestamp;
private long forecastFor;
private int weatherID;
private float directRadiationNormal;
private float diffuseRadiation;
private float power;
private String city_name;
public HourlyForecast() {
}
/**
* @return Returns the ID of the record (which uniquely identifies the record).
*/
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/**
* @return Returns the date and time for the forecast.
*/
public long getForecastTime() {
return forecastFor;
}
/**
* @return Returns the local time for the forecast in UTC epoch
*/
public long getLocalForecastTime(Context context) {
SQLiteHelper dbhelper = SQLiteHelper.getInstance(context);
int timezoneseconds = dbhelper.getCurrentWeatherByCityId(city_id).getTimeZoneSeconds();
return forecastFor + timezoneseconds * 1000L;
}
/**
* @param forecastFor The point of time for the forecast.
*/
public void setForecastTime(long forecastFor) {
this.forecastFor = forecastFor;
}
/**
* @return Returns the point of time when the data was inserted into the database in Unix, UTC.
*/
public long getTimestamp() {
return timestamp;
}
/**
* @param timestamp The point of time to set when the data was inserted into the database in
* Unix, UTC.
*/
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public int getCity_id() {
return city_id;
}
public void setCity_id(int city_id) {
this.city_id = city_id;
}
/**
* @return Returns the weather condition ID.
*/
public int getWeatherID() {
return weatherID;
}
/**
* @param weatherID The weather condition ID to set.
*/
public void setWeatherID(int weatherID) {
this.weatherID = weatherID;
}
public float getDiffuseRadiation() { return diffuseRadiation; }
public float getDirectRadiationNormal() { return directRadiationNormal; }
public float getPower() { return power; }
public void setDirectRadiationNormal(float directRadiationNormal) { this.directRadiationNormal = directRadiationNormal; }
public void setDiffuseRadiation(float diffuseRadiation) { this.diffuseRadiation = diffuseRadiation; }
public void setPower(float power) { this.power = power; }
}

View file

@ -0,0 +1,665 @@
package org.woheller69.weather.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
import static androidx.core.app.JobIntentService.enqueueWork;
/**
* @author Karola Marky, Christopher Beckmann
* @version 1.0
* @since 25.01.2018
* created 02.01.2017
*/
public class SQLiteHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private Context context;
private List<City> allCities = new ArrayList<>();
private static SQLiteHelper instance = null;
private static final String DATABASE_NAME = "SQLITE.db";
//Names of tables in the database
private static final String TABLE_CITIES_TO_WATCH = "CITIES_TO_WATCH";
private static final String TABLE_HOURLY_FORECAST = "FORECASTS";
private static final String TABLE_WEEKFORECAST = "WEEKFORECASTS";
private static final String TABLE_CURRENT_WEATHER = "CURRENT_WEATHER";
//Names of columns in TABLE_CITIES_TO_WATCH
private static final String CITIES_TO_WATCH_ID = "cities_to_watch_id";
private static final String CITIES_TO_WATCH_CITY_ID = "city_id";
private static final String CITIES_TO_WATCH_COLUMN_RANK = "rank";
private static final String CITIES_TO_WATCH_NAME = "city_name";
private static final String CITIES_TO_WATCH_LONGITUDE = "longitude";
private static final String CITIES_TO_WATCH_LATITUDE = "latitude";
private static final String CITIES_TO_WATCH_CELLS_MAX_POWER = "cells_max_pPower";
private static final String CITIES_TO_WATCH_CELLS_AREA = "cells_area";
private static final String CITIES_TO_WATCH_CELLS_EFFICIENCY = "cells_efficiency";
private static final String CITIES_TO_WATCH_DIFFUSE_EFFICIENCY = "diffuse_efficiency";
private static final String CITIES_TO_WATCH_CONVERTER_POWER_LIMIT = "converter_power_limit";
private static final String CITIES_TO_WATCH_CONVERTER_EFFICIENCY = "converter_efficiency";
private static final String CITIES_TO_WATCH_AZIMUTH_ANGLE = "azimuth_angle";
private static final String CITIES_TO_WATCH_ELEVATION_ANGLE = "elevation_angle";
//Names of columns in TABLE_FORECAST
private static final String FORECAST_ID = "forecast_id";
private static final String FORECAST_CITY_ID = "city_id";
private static final String FORECAST_COLUMN_TIME_MEASUREMENT = "time_of_measurement";
private static final String FORECAST_COLUMN_FORECAST_FOR = "forecast_for";
private static final String FORECAST_COLUMN_WEATHER_ID = "weather_id";
private static final String FORECAST_COLUMN_DIRECT_RADIATION_NORMAL = "direct_radiation_normal";
private static final String FORECAST_COLUMN_DIFFUSE_RADIATION = "diffuse_radiation";
private static final String FORECAST_COLUMN_POWER = "power";
//Names of columns in TABLE_WEEKFORECAST
private static final String WEEKFORECAST_ID = "forecast_id";
private static final String WEEKFORECAST_CITY_ID = "city_id";
private static final String WEEKFORECAST_COLUMN_TIME_MEASUREMENT = "time_of_measurement";
private static final String WEEKFORECAST_COLUMN_FORECAST_FOR = "forecast_for";
private static final String WEEKFORECAST_COLUMN_WEATHER_ID = "weather_id";
private static final String WEEKFORECAST_COLUMN_TEMPERATURE_CURRENT = "temperature_current";
private static final String WEEKFORECAST_COLUMN_TEMPERATURE_MIN = "temperature_min";
private static final String WEEKFORECAST_COLUMN_TEMPERATURE_MAX = "temperature_max";
private static final String WEEKFORECAST_COLUMN_HUMIDITY = "humidity";
private static final String WEEKFORECAST_COLUMN_PRESSURE = "pressure";
private static final String WEEKFORECAST_COLUMN_PRECIPITATION = "precipitation";
private static final String WEEKFORECAST_COLUMN_WIND_SPEED = "wind_speed";
private static final String WEEKFORECAST_COLUMN_WIND_DIRECTION = "wind_direction";
private static final String WEEKFORECAST_COLUMN_UV_INDEX = "uv_index";
private static final String WEEKFORECAST_COLUMN_TIME_SUNRISE = "time_sunrise";
private static final String WEEKFORECAST_COLUMN_TIME_SUNSET = "time_sunset";
//Names of columns in TABLE_CURRENT_WEATHER
private static final String CURRENT_WEATHER_ID = "current_weather_id";
private static final String CURRENT_WEATHER_CITY_ID = "city_id";
private static final String COLUMN_TIME_MEASUREMENT = "time_of_measurement";
private static final String COLUMN_WEATHER_ID = "weather_id";
private static final String COLUMN_TEMPERATURE_CURRENT = "temperature_current";
private static final String COLUMN_HUMIDITY = "humidity";
private static final String COLUMN_PRESSURE = "pressure";
private static final String COLUMN_WIND_SPEED = "wind_speed";
private static final String COLUMN_WIND_DIRECTION = "wind_direction";
private static final String COLUMN_CLOUDINESS = "cloudiness";
private static final String COLUMN_TIME_SUNRISE = "time_sunrise";
private static final String COLUMN_TIME_SUNSET = "time_sunset";
private static final String COLUMN_TIMEZONE_SECONDS = "timezone_seconds";
private static final String COLUMN_RAIN60MIN = "Rain60min";
/**
* Create Table statements for all tables
*/
private static final String CREATE_CURRENT_WEATHER = "CREATE TABLE " + TABLE_CURRENT_WEATHER +
"(" +
CURRENT_WEATHER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
CURRENT_WEATHER_CITY_ID + " INTEGER," +
COLUMN_TIME_MEASUREMENT + " LONG NOT NULL," +
COLUMN_WEATHER_ID + " INTEGER," +
COLUMN_TEMPERATURE_CURRENT + " REAL," +
COLUMN_HUMIDITY + " REAL," +
COLUMN_PRESSURE + " REAL," +
COLUMN_WIND_SPEED + " REAL," +
COLUMN_WIND_DIRECTION + " REAL," +
COLUMN_CLOUDINESS + " REAL," +
COLUMN_TIME_SUNRISE + " LONG NOT NULL," +
COLUMN_TIME_SUNSET + " LONG NOT NULL," +
COLUMN_TIMEZONE_SECONDS + " INTEGER," +
COLUMN_RAIN60MIN + " VARCHAR(25) NOT NULL) ;";
private static final String CREATE_TABLE_FORECASTS = "CREATE TABLE " + TABLE_HOURLY_FORECAST +
"(" +
FORECAST_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
FORECAST_CITY_ID + " INTEGER," +
FORECAST_COLUMN_TIME_MEASUREMENT + " LONG NOT NULL," +
FORECAST_COLUMN_FORECAST_FOR + " VARCHAR(200) NOT NULL," +
FORECAST_COLUMN_WEATHER_ID + " INTEGER," +
FORECAST_COLUMN_DIRECT_RADIATION_NORMAL + " REAL," +
FORECAST_COLUMN_DIFFUSE_RADIATION + " REAL," +
FORECAST_COLUMN_POWER + " REAL)";
private static final String CREATE_TABLE_WEEKFORECASTS = "CREATE TABLE " + TABLE_WEEKFORECAST +
"(" +
WEEKFORECAST_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
WEEKFORECAST_CITY_ID + " INTEGER," +
WEEKFORECAST_COLUMN_TIME_MEASUREMENT + " LONG NOT NULL," +
WEEKFORECAST_COLUMN_FORECAST_FOR + " VARCHAR(200) NOT NULL," +
WEEKFORECAST_COLUMN_WEATHER_ID + " INTEGER," +
WEEKFORECAST_COLUMN_TEMPERATURE_CURRENT + " REAL," +
WEEKFORECAST_COLUMN_TEMPERATURE_MIN + " REAL," +
WEEKFORECAST_COLUMN_TEMPERATURE_MAX + " REAL," +
WEEKFORECAST_COLUMN_HUMIDITY + " REAL," +
WEEKFORECAST_COLUMN_PRESSURE + " REAL," +
WEEKFORECAST_COLUMN_PRECIPITATION + " REAL," +
WEEKFORECAST_COLUMN_WIND_SPEED + " REAL," +
WEEKFORECAST_COLUMN_WIND_DIRECTION + " REAL," +
WEEKFORECAST_COLUMN_UV_INDEX + " REAL," +
WEEKFORECAST_COLUMN_TIME_SUNRISE + " LONG NOT NULL," +
WEEKFORECAST_COLUMN_TIME_SUNSET + " LONG NOT NULL)";
private static final String CREATE_TABLE_CITIES_TO_WATCH = "CREATE TABLE " + TABLE_CITIES_TO_WATCH +
"(" +
CITIES_TO_WATCH_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
CITIES_TO_WATCH_CITY_ID + " INTEGER," +
CITIES_TO_WATCH_COLUMN_RANK + " INTEGER," +
CITIES_TO_WATCH_NAME + " VARCHAR(100) NOT NULL," +
CITIES_TO_WATCH_LONGITUDE + " REAL NOT NULL," +
CITIES_TO_WATCH_LATITUDE + " REAL NOT NULL," +
CITIES_TO_WATCH_CELLS_MAX_POWER + " REAL NOT NULL," +
CITIES_TO_WATCH_CELLS_AREA + " REAL NOT NULL," +
CITIES_TO_WATCH_CELLS_EFFICIENCY + " REAL NOT NULL," +
CITIES_TO_WATCH_DIFFUSE_EFFICIENCY + " REAL NOT NULL," +
CITIES_TO_WATCH_CONVERTER_POWER_LIMIT + " REAL NOT NULL," +
CITIES_TO_WATCH_CONVERTER_EFFICIENCY + " REAL NOT NULL," +
CITIES_TO_WATCH_AZIMUTH_ANGLE + " REAL NOT NULL," +
CITIES_TO_WATCH_ELEVATION_ANGLE + " REAL NOT NULL)";
public static SQLiteHelper getInstance(Context context) {
if (instance == null && context != null) {
instance = new SQLiteHelper(context.getApplicationContext());
}
return instance;
}
private SQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.context = context.getApplicationContext();
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_CITIES_TO_WATCH);
db.execSQL(CREATE_CURRENT_WEATHER);
db.execSQL(CREATE_TABLE_FORECASTS);
db.execSQL(CREATE_TABLE_WEEKFORECASTS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/**
* Methods for TABLE_CITIES_TO_WATCH
*/
public synchronized long addCityToWatch(CityToWatch city) {
SQLiteDatabase database = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(CITIES_TO_WATCH_CITY_ID, city.getCityId());
values.put(CITIES_TO_WATCH_COLUMN_RANK, city.getRank());
values.put(CITIES_TO_WATCH_NAME,city.getCityName());
values.put(CITIES_TO_WATCH_LATITUDE,city.getLatitude());
values.put(CITIES_TO_WATCH_LONGITUDE,city.getLongitude());
values.put(CITIES_TO_WATCH_CELLS_MAX_POWER,city.getCellsMaxPower());
values.put(CITIES_TO_WATCH_CELLS_AREA,city.getCellsArea());
values.put(CITIES_TO_WATCH_CELLS_EFFICIENCY,city.getCellsEfficiency());
values.put(CITIES_TO_WATCH_DIFFUSE_EFFICIENCY,city.getDiffuseEfficiency());
values.put(CITIES_TO_WATCH_CONVERTER_POWER_LIMIT,city.getConverterPowerLimit());
values.put(CITIES_TO_WATCH_CONVERTER_EFFICIENCY,city.getConverterEfficiency());
values.put(CITIES_TO_WATCH_AZIMUTH_ANGLE,city.getAzimuthAngle());
values.put(CITIES_TO_WATCH_ELEVATION_ANGLE,city.getElevationAngle());
long id=database.insert(TABLE_CITIES_TO_WATCH, null, values);
//use id also instead of city id as unique identifier
values.put(CITIES_TO_WATCH_CITY_ID,id);
database.update(TABLE_CITIES_TO_WATCH, values, CITIES_TO_WATCH_ID + " = ?",
new String[]{String.valueOf(id)});
database.close();
return id;
}
public synchronized CityToWatch getCityToWatch(int id) {
SQLiteDatabase database = this.getWritableDatabase();
String[] arguments = {String.valueOf(id)};
Cursor cursor = database.rawQuery(
"SELECT " + CITIES_TO_WATCH_ID +
", " + CITIES_TO_WATCH_CITY_ID +
", " + CITIES_TO_WATCH_NAME +
", " + CITIES_TO_WATCH_LONGITUDE +
", " + CITIES_TO_WATCH_LATITUDE +
", " + CITIES_TO_WATCH_CELLS_MAX_POWER +
", " + CITIES_TO_WATCH_CELLS_AREA +
", " + CITIES_TO_WATCH_CELLS_EFFICIENCY +
", " + CITIES_TO_WATCH_DIFFUSE_EFFICIENCY +
", " + CITIES_TO_WATCH_CONVERTER_POWER_LIMIT +
", " + CITIES_TO_WATCH_CONVERTER_EFFICIENCY +
", " + CITIES_TO_WATCH_AZIMUTH_ANGLE +
", " + CITIES_TO_WATCH_ELEVATION_ANGLE +
", " + CITIES_TO_WATCH_COLUMN_RANK +
" FROM " + TABLE_CITIES_TO_WATCH +
" WHERE " + CITIES_TO_WATCH_CITY_ID + " = ?", arguments);
CityToWatch cityToWatch = new CityToWatch();
if (cursor != null && cursor.moveToFirst()) {
cityToWatch.setId(Integer.parseInt(cursor.getString(0)));
cityToWatch.setCityId(Integer.parseInt(cursor.getString(1)));
cityToWatch.setCityName(cursor.getString(2));
cityToWatch.setLongitude(Float.parseFloat(cursor.getString(3)));
cityToWatch.setLatitude(Float.parseFloat(cursor.getString(4)));
cityToWatch.setCellsMaxPower(Float.parseFloat(cursor.getString(5)));
cityToWatch.setCellsArea(Float.parseFloat(cursor.getString(6)));
cityToWatch.setCellsEfficiency(Float.parseFloat(cursor.getString(7)));
cityToWatch.setDiffuseEfficiency(Float.parseFloat(cursor.getString(8)));
cityToWatch.setConverterPowerLimit(Float.parseFloat(cursor.getString(9)));
cityToWatch.setConverterEfficiency(Float.parseFloat(cursor.getString(10)));
cityToWatch.setAzimuthAngle(Float.parseFloat(cursor.getString(11)));
cityToWatch.setElevationAngle(Float.parseFloat(cursor.getString(12)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(13)));
cursor.close();
}
database.close();
return cityToWatch;
}
public synchronized List<CityToWatch> getAllCitiesToWatch() {
List<CityToWatch> cityToWatchList = new ArrayList<>();
SQLiteDatabase database = this.getWritableDatabase();
Cursor cursor = database.rawQuery(
"SELECT " + CITIES_TO_WATCH_ID +
", " + CITIES_TO_WATCH_CITY_ID +
", " + CITIES_TO_WATCH_NAME +
", " + CITIES_TO_WATCH_LONGITUDE +
", " + CITIES_TO_WATCH_LATITUDE +
", " + CITIES_TO_WATCH_CELLS_MAX_POWER +
", " + CITIES_TO_WATCH_CELLS_AREA +
", " + CITIES_TO_WATCH_CELLS_EFFICIENCY +
", " + CITIES_TO_WATCH_DIFFUSE_EFFICIENCY +
", " + CITIES_TO_WATCH_CONVERTER_POWER_LIMIT +
", " + CITIES_TO_WATCH_CONVERTER_EFFICIENCY +
", " + CITIES_TO_WATCH_AZIMUTH_ANGLE +
", " + CITIES_TO_WATCH_ELEVATION_ANGLE +
", " + CITIES_TO_WATCH_COLUMN_RANK +
" FROM " + TABLE_CITIES_TO_WATCH
, new String[]{});
CityToWatch cityToWatch;
if (cursor.moveToFirst()) {
do {
cityToWatch = new CityToWatch();
cityToWatch.setId(Integer.parseInt(cursor.getString(0)));
cityToWatch.setCityId(Integer.parseInt(cursor.getString(1)));
cityToWatch.setCityName(cursor.getString(2));
cityToWatch.setLongitude(Float.parseFloat(cursor.getString(3)));
cityToWatch.setLatitude(Float.parseFloat(cursor.getString(4)));
cityToWatch.setCellsMaxPower(Float.parseFloat(cursor.getString(5)));
cityToWatch.setCellsArea(Float.parseFloat(cursor.getString(6)));
cityToWatch.setCellsEfficiency(Float.parseFloat(cursor.getString(7)));
cityToWatch.setDiffuseEfficiency(Float.parseFloat(cursor.getString(8)));
cityToWatch.setConverterPowerLimit(Float.parseFloat(cursor.getString(9)));
cityToWatch.setConverterEfficiency(Float.parseFloat(cursor.getString(10)));
cityToWatch.setAzimuthAngle(Float.parseFloat(cursor.getString(11)));
cityToWatch.setElevationAngle(Float.parseFloat(cursor.getString(12)));
cityToWatch.setRank(Integer.parseInt(cursor.getString(13)));
cityToWatchList.add(cityToWatch);
} while (cursor.moveToNext());
}
cursor.close();
database.close();
return cityToWatchList;
}
public synchronized void updateCityToWatch(CityToWatch cityToWatch) {
SQLiteDatabase database = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(CITIES_TO_WATCH_CITY_ID, cityToWatch.getCityId());
values.put(CITIES_TO_WATCH_COLUMN_RANK, cityToWatch.getRank());
values.put(CITIES_TO_WATCH_NAME,cityToWatch.getCityName());
values.put(CITIES_TO_WATCH_LATITUDE,cityToWatch.getLatitude());
values.put(CITIES_TO_WATCH_LONGITUDE,cityToWatch.getLongitude());
values.put(CITIES_TO_WATCH_CELLS_MAX_POWER,cityToWatch.getCellsMaxPower());
values.put(CITIES_TO_WATCH_CELLS_AREA,cityToWatch.getCellsArea());
values.put(CITIES_TO_WATCH_CELLS_EFFICIENCY,cityToWatch.getCellsEfficiency());
values.put(CITIES_TO_WATCH_DIFFUSE_EFFICIENCY,cityToWatch.getDiffuseEfficiency());
values.put(CITIES_TO_WATCH_CONVERTER_POWER_LIMIT,cityToWatch.getConverterPowerLimit());
values.put(CITIES_TO_WATCH_CONVERTER_EFFICIENCY,cityToWatch.getConverterEfficiency());
values.put(CITIES_TO_WATCH_AZIMUTH_ANGLE,cityToWatch.getAzimuthAngle());
values.put(CITIES_TO_WATCH_ELEVATION_ANGLE,cityToWatch.getElevationAngle());
database.update(TABLE_CITIES_TO_WATCH, values, CITIES_TO_WATCH_ID + " = ?",
new String[]{String.valueOf(cityToWatch.getId())});
database.close();
}
public synchronized void deleteCityToWatch(CityToWatch cityToWatch) {
//First delete all weather data for city which is deleted
deleteCurrentWeatherByCityId(cityToWatch.getCityId());
deleteForecastsByCityId(cityToWatch.getCityId());
deleteWeekForecastsByCityId(cityToWatch.getCityId());
//Now remove city from CITIES_TO_WATCH
SQLiteDatabase database = this.getWritableDatabase();
database.delete(TABLE_CITIES_TO_WATCH, CITIES_TO_WATCH_ID + " = ?",
new String[]{Integer.toString(cityToWatch.getId())});
database.close();
}
public synchronized int getWatchedCitiesCount() {
SQLiteDatabase database = this.getWritableDatabase();
long count = DatabaseUtils.queryNumEntries(database, TABLE_CITIES_TO_WATCH);
database.close();
return (int) count;
}
public int getMaxRank() {
List<CityToWatch> cities = getAllCitiesToWatch();
int maxRank = 0;
for (CityToWatch ctw : cities) {
if (ctw.getRank() > maxRank) maxRank = ctw.getRank();
}
return maxRank;
}
/**
* Methods for TABLE_FORECAST
*/
public synchronized void addForecasts(List<HourlyForecast> hourlyForecasts) {
SQLiteDatabase database = this.getWritableDatabase();
for (HourlyForecast hourlyForecast: hourlyForecasts) {
ContentValues values = new ContentValues();
values.put(FORECAST_CITY_ID, hourlyForecast.getCity_id());
values.put(FORECAST_COLUMN_TIME_MEASUREMENT, hourlyForecast.getTimestamp());
values.put(FORECAST_COLUMN_FORECAST_FOR, hourlyForecast.getForecastTime());
values.put(FORECAST_COLUMN_WEATHER_ID, hourlyForecast.getWeatherID());
values.put(FORECAST_COLUMN_DIRECT_RADIATION_NORMAL, hourlyForecast.getDirectRadiationNormal());
values.put(FORECAST_COLUMN_DIFFUSE_RADIATION, hourlyForecast.getDiffuseRadiation());
values.put(FORECAST_COLUMN_POWER, hourlyForecast.getPower());
database.insert(TABLE_HOURLY_FORECAST, null, values);
}
database.close();
}
public synchronized void deleteForecastsByCityId(int cityId) {
SQLiteDatabase database = this.getWritableDatabase();
database.delete(TABLE_HOURLY_FORECAST, FORECAST_CITY_ID + " = ?",
new String[]{Integer.toString(cityId)});
database.close();
}
public synchronized List<HourlyForecast> getForecastsByCityId(int cityId) {
SQLiteDatabase database = this.getWritableDatabase();
Cursor cursor = database.query(TABLE_HOURLY_FORECAST,
new String[]{FORECAST_ID,
FORECAST_CITY_ID,
FORECAST_COLUMN_TIME_MEASUREMENT,
FORECAST_COLUMN_FORECAST_FOR,
FORECAST_COLUMN_WEATHER_ID,
FORECAST_COLUMN_DIRECT_RADIATION_NORMAL,
FORECAST_COLUMN_DIFFUSE_RADIATION,
FORECAST_COLUMN_POWER}
, FORECAST_CITY_ID + "=?",
new String[]{String.valueOf(cityId)}, null, null, null, null);
List<HourlyForecast> list = new ArrayList<>();
HourlyForecast hourlyForecast;
if (cursor != null && cursor.moveToFirst()) {
do {
hourlyForecast = new HourlyForecast();
hourlyForecast.setId(Integer.parseInt(cursor.getString(0)));
hourlyForecast.setCity_id(Integer.parseInt(cursor.getString(1)));
hourlyForecast.setTimestamp(Long.parseLong(cursor.getString(2)));
hourlyForecast.setForecastTime(Long.parseLong(cursor.getString(3)));
hourlyForecast.setWeatherID(Integer.parseInt(cursor.getString(4)));
hourlyForecast.setDirectRadiationNormal(Float.parseFloat(cursor.getString(5)));
hourlyForecast.setDiffuseRadiation(Float.parseFloat(cursor.getString(6)));
hourlyForecast.setPower(Float.parseFloat(cursor.getString(7)));
list.add(hourlyForecast);
} while (cursor.moveToNext());
cursor.close();
}
return list;
}
/**
* Methods for TABLE_WEEKFORECAST
*/
public synchronized void addWeekForecasts(List<WeekForecast> weekForecasts) {
SQLiteDatabase database = this.getWritableDatabase();
for (WeekForecast weekForecast: weekForecasts) {
ContentValues values = new ContentValues();
values.put(WEEKFORECAST_CITY_ID, weekForecast.getCity_id());
values.put(WEEKFORECAST_COLUMN_TIME_MEASUREMENT, weekForecast.getTimestamp());
values.put(WEEKFORECAST_COLUMN_FORECAST_FOR, weekForecast.getForecastTime());
values.put(WEEKFORECAST_COLUMN_WEATHER_ID, weekForecast.getWeatherID());
values.put(WEEKFORECAST_COLUMN_TEMPERATURE_CURRENT, weekForecast.getTemperature());
values.put(WEEKFORECAST_COLUMN_TEMPERATURE_MIN, weekForecast.getMinTemperature());
values.put(WEEKFORECAST_COLUMN_TEMPERATURE_MAX, weekForecast.getMaxTemperature());
values.put(WEEKFORECAST_COLUMN_HUMIDITY, weekForecast.getHumidity());
values.put(WEEKFORECAST_COLUMN_PRESSURE, weekForecast.getPressure());
values.put(WEEKFORECAST_COLUMN_PRECIPITATION, weekForecast.getPrecipitation());
values.put(WEEKFORECAST_COLUMN_WIND_SPEED, weekForecast.getWind_speed());
values.put(WEEKFORECAST_COLUMN_WIND_DIRECTION, weekForecast.getWind_direction());
values.put(WEEKFORECAST_COLUMN_UV_INDEX, weekForecast.getUv_index());
values.put(WEEKFORECAST_COLUMN_TIME_SUNRISE, weekForecast.getTimeSunrise());
values.put(WEEKFORECAST_COLUMN_TIME_SUNSET, weekForecast.getTimeSunset());
database.insert(TABLE_WEEKFORECAST, null, values);
}
database.close();
}
public synchronized void deleteWeekForecastsByCityId(int cityId) {
SQLiteDatabase database = this.getWritableDatabase();
database.delete(TABLE_WEEKFORECAST, WEEKFORECAST_CITY_ID + " = ?",
new String[]{Integer.toString(cityId)});
database.close();
}
public synchronized List<WeekForecast> getWeekForecastsByCityId(int cityId) {
SQLiteDatabase database = this.getWritableDatabase();
Cursor cursor = database.query(TABLE_WEEKFORECAST,
new String[]{WEEKFORECAST_ID,
WEEKFORECAST_CITY_ID,
WEEKFORECAST_COLUMN_TIME_MEASUREMENT,
WEEKFORECAST_COLUMN_FORECAST_FOR,
WEEKFORECAST_COLUMN_WEATHER_ID,
WEEKFORECAST_COLUMN_TEMPERATURE_CURRENT,
WEEKFORECAST_COLUMN_TEMPERATURE_MIN,
WEEKFORECAST_COLUMN_TEMPERATURE_MAX,
WEEKFORECAST_COLUMN_HUMIDITY,
WEEKFORECAST_COLUMN_PRESSURE,
WEEKFORECAST_COLUMN_PRECIPITATION,
WEEKFORECAST_COLUMN_WIND_SPEED,
WEEKFORECAST_COLUMN_WIND_DIRECTION,
WEEKFORECAST_COLUMN_UV_INDEX,
WEEKFORECAST_COLUMN_TIME_SUNRISE,
WEEKFORECAST_COLUMN_TIME_SUNSET}
, WEEKFORECAST_CITY_ID + "=?",
new String[]{String.valueOf(cityId)}, null, null, null, null);
List<WeekForecast> list = new ArrayList<>();
WeekForecast weekForecast;
if (cursor != null && cursor.moveToFirst()) {
do {
weekForecast = new WeekForecast();
weekForecast.setId(Integer.parseInt(cursor.getString(0)));
weekForecast.setCity_id(Integer.parseInt(cursor.getString(1)));
weekForecast.setTimestamp(Long.parseLong(cursor.getString(2)));
weekForecast.setForecastTime(Long.parseLong(cursor.getString(3)));
weekForecast.setWeatherID(Integer.parseInt(cursor.getString(4)));
weekForecast.setTemperature(Float.parseFloat(cursor.getString(5)));
weekForecast.setMinTemperature(Float.parseFloat(cursor.getString(6)));
weekForecast.setMaxTemperature(Float.parseFloat(cursor.getString(7)));
weekForecast.setHumidity(Float.parseFloat(cursor.getString(8)));
weekForecast.setPressure(Float.parseFloat(cursor.getString(9)));
weekForecast.setPrecipitation(Float.parseFloat(cursor.getString(10)));
weekForecast.setWind_speed(Float.parseFloat(cursor.getString(11)));
weekForecast.setWind_direction(Float.parseFloat(cursor.getString(12)));
weekForecast.setUv_index(Float.parseFloat(cursor.getString(13)));
weekForecast.setTimeSunrise(Long.parseLong(cursor.getString(14)));
weekForecast.setTimeSunset(Long.parseLong(cursor.getString(15)));
list.add(weekForecast);
} while (cursor.moveToNext());
cursor.close();
}
return list;
}
/**
* Methods for TABLE_CURRENT_WEATHER
*/
public synchronized void addCurrentWeather(CurrentWeatherData currentWeather) {
SQLiteDatabase database = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(CURRENT_WEATHER_CITY_ID, currentWeather.getCity_id());
values.put(COLUMN_TIME_MEASUREMENT, currentWeather.getTimestamp());
values.put(COLUMN_WEATHER_ID, currentWeather.getWeatherID());
values.put(COLUMN_TEMPERATURE_CURRENT, currentWeather.getTemperatureCurrent());
values.put(COLUMN_HUMIDITY, currentWeather.getHumidity());
values.put(COLUMN_PRESSURE, currentWeather.getPressure());
values.put(COLUMN_WIND_SPEED, currentWeather.getWindSpeed());
values.put(COLUMN_WIND_DIRECTION, currentWeather.getWindDirection());
values.put(COLUMN_CLOUDINESS, currentWeather.getCloudiness());
values.put(COLUMN_TIME_SUNRISE, currentWeather.getTimeSunrise());
values.put(COLUMN_TIME_SUNSET, currentWeather.getTimeSunset());
values.put(COLUMN_TIMEZONE_SECONDS, currentWeather.getTimeZoneSeconds());
values.put(COLUMN_RAIN60MIN, currentWeather.getRain60min());
database.insert(TABLE_CURRENT_WEATHER, null, values);
database.close();
}
public synchronized CurrentWeatherData getCurrentWeatherByCityId(int cityId) {
SQLiteDatabase database = this.getReadableDatabase();
Cursor cursor = database.query(TABLE_CURRENT_WEATHER,
new String[]{CURRENT_WEATHER_ID,
CURRENT_WEATHER_CITY_ID,
COLUMN_TIME_MEASUREMENT,
COLUMN_WEATHER_ID,
COLUMN_TEMPERATURE_CURRENT,
COLUMN_HUMIDITY,
COLUMN_PRESSURE,
COLUMN_WIND_SPEED,
COLUMN_WIND_DIRECTION,
COLUMN_CLOUDINESS,
COLUMN_TIME_SUNRISE,
COLUMN_TIME_SUNSET,
COLUMN_TIMEZONE_SECONDS,
COLUMN_RAIN60MIN},
CURRENT_WEATHER_CITY_ID + " = ?",
new String[]{String.valueOf(cityId)}, null, null, null, null);
CurrentWeatherData currentWeather = new CurrentWeatherData();
if (cursor != null && cursor.moveToFirst()) {
currentWeather.setId(Integer.parseInt(cursor.getString(0)));
currentWeather.setCity_id(Integer.parseInt(cursor.getString(1)));
currentWeather.setTimestamp(Long.parseLong(cursor.getString(2)));
currentWeather.setWeatherID(Integer.parseInt(cursor.getString(3)));
currentWeather.setTemperatureCurrent(Float.parseFloat(cursor.getString(4)));
currentWeather.setHumidity(Float.parseFloat(cursor.getString(5)));
currentWeather.setPressure(Float.parseFloat(cursor.getString(6)));
currentWeather.setWindSpeed(Float.parseFloat(cursor.getString(7)));
currentWeather.setWindDirection(Float.parseFloat(cursor.getString(8)));
currentWeather.setCloudiness(Float.parseFloat(cursor.getString(9)));
currentWeather.setTimeSunrise(Long.parseLong(cursor.getString(10)));
currentWeather.setTimeSunset(Long.parseLong(cursor.getString(11)));
currentWeather.setTimeZoneSeconds(Integer.parseInt(cursor.getString(12)));
currentWeather.setRain60min(cursor.getString(13));
cursor.close();
}
return currentWeather;
}
public synchronized void updateCurrentWeather(CurrentWeatherData currentWeather) {
SQLiteDatabase database = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(CURRENT_WEATHER_CITY_ID, currentWeather.getCity_id());
values.put(COLUMN_TIME_MEASUREMENT, currentWeather.getTimestamp());
values.put(COLUMN_WEATHER_ID, currentWeather.getWeatherID());
values.put(COLUMN_TEMPERATURE_CURRENT, currentWeather.getTemperatureCurrent());
values.put(COLUMN_HUMIDITY, currentWeather.getHumidity());
values.put(COLUMN_PRESSURE, currentWeather.getPressure());
values.put(COLUMN_WIND_SPEED, currentWeather.getWindSpeed());
values.put(COLUMN_WIND_DIRECTION, currentWeather.getWindDirection());
values.put(COLUMN_CLOUDINESS, currentWeather.getCloudiness());
values.put(COLUMN_TIME_SUNRISE, currentWeather.getTimeSunrise());
values.put(COLUMN_TIME_SUNSET, currentWeather.getTimeSunset());
values.put(COLUMN_TIMEZONE_SECONDS, currentWeather.getTimeZoneSeconds());
values.put(COLUMN_RAIN60MIN, currentWeather.getRain60min());
database.update(TABLE_CURRENT_WEATHER, values, CURRENT_WEATHER_CITY_ID + " = ?",
new String[]{String.valueOf(currentWeather.getCity_id())});
}
public synchronized void deleteCurrentWeather(CurrentWeatherData currentWeather) {
SQLiteDatabase database = this.getWritableDatabase();
database.delete(TABLE_CURRENT_WEATHER, CURRENT_WEATHER_ID + " = ?",
new String[]{Integer.toString(currentWeather.getId())});
database.close();
}
public synchronized void deleteCurrentWeatherByCityId(int cityId) {
SQLiteDatabase database = this.getWritableDatabase();
database.delete(TABLE_CURRENT_WEATHER, CURRENT_WEATHER_CITY_ID + " = ?",
new String[]{Integer.toString(cityId)});
database.close();
}
public static int getWidgetCityID(Context context) {
SQLiteHelper db = SQLiteHelper.getInstance(context);
int cityID=0;
List<CityToWatch> cities = db.getAllCitiesToWatch();
int rank=cities.get(0).getRank();
for (int i = 0; i < cities.size(); i++) { //find cityID for first city to watch = lowest Rank
CityToWatch city = cities.get(i);
//Log.d("debugtag",Integer.toString(city.getRank()));
if (city.getRank() <= rank ){
rank=city.getRank();
cityID = city.getCityId();
}
}
return cityID;
}
}

View file

@ -0,0 +1,206 @@
package org.woheller69.weather.database;
import android.content.Context;
/**
* This class is the database model for the forecasts table.
*/
public class WeekForecast {
public static final float NO_RAIN_VALUE = 0;
private int id;
private int city_id;
private long timestamp;
private long forecastFor;
private int weatherID;
private float temperature;
private float temperature_min;
private float temperature_max;
private float humidity;
private float pressure;
private float precipitation;
private float wind_speed;
private float wind_direction;
private float uv_index;
private long timeSunrise;
private long timeSunset;
public WeekForecast() {
}
public WeekForecast(int id, int city_id, long timestamp, long forecastFor, int weatherID, float temperature, float temperature_min, float temperature_max, float humidity, float pressure, float precipitation, float wind_speed, float wind_direction, float uv_index) {
this.id = id;
this.city_id = city_id;
this.timestamp = timestamp;
this.forecastFor = forecastFor;
this.weatherID = weatherID;
this.temperature = temperature;
this.temperature_min = temperature_min;
this.temperature_max = temperature_max;
this.humidity = humidity;
this.pressure = pressure;
this.precipitation=precipitation;
this.wind_speed=wind_speed;
this.wind_direction=wind_direction;
this.uv_index=uv_index;
}
/**
* @return Returns the ID of the record (which uniquely identifies the record).
*/
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/**
* @return Returns the date and time for the forecast.
*/
public long getForecastTime() {
return forecastFor;
}
/**
* @return Returns the local time for the forecast in UTC epoch
*/
public long getLocalForecastTime(Context context) {
SQLiteHelper dbhelper = SQLiteHelper.getInstance(context);
int timezoneseconds = dbhelper.getCurrentWeatherByCityId(city_id).getTimeZoneSeconds();
return forecastFor + timezoneseconds * 1000L;
}
/**
* @param forecastFor The point of time for the forecast.
*/
public void setForecastTime(long forecastFor) {
this.forecastFor = forecastFor;
}
/**
* @return Returns the point of time when the data was inserted into the database in Unix, UTC.
*/
public long getTimestamp() {
return timestamp;
}
/**
* @param timestamp The point of time to set when the data was inserted into the database in
* Unix, UTC.
*/
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public int getCity_id() {
return city_id;
}
public void setCity_id(int city_id) {
this.city_id = city_id;
}
/**
* @return Returns the weather condition ID.
*/
public int getWeatherID() {
return weatherID;
}
/**
* @param weatherID The weather condition ID to set.
*/
public void setWeatherID(int weatherID) {
this.weatherID = weatherID;
}
/**
* @return Returns the current temperature in Celsius.
*/
public float getTemperature() {
return temperature;
}
/**
* @param temperature The current temperature to set in Celsius.
*/
public void setTemperature(float temperature) {
this.temperature = temperature;
}
/**
* @return Returns the min temperature in Celsius.
*/
public float getMinTemperature() {
return temperature_min;
}
/**
* @param temperature_min The min temperature to set in Celsius.
*/
public void setMinTemperature(float temperature_min) {
this.temperature_min = temperature_min;
}
/**
* @return Returns the max temperature in Celsius.
*/
public float getMaxTemperature() {
return temperature_max;
}
/**
* @param temperature_max The max temperature to set in Celsius.
*/
public void setMaxTemperature(float temperature_max) {
this.temperature_max = temperature_max;
}
/**
* @return Returns the humidity value in percent.
*/
public float getHumidity() {
return humidity;
}
/**
* @param humidity The humidity value in percent to set.
*/
public void setHumidity(float humidity) {
this.humidity = humidity;
}
public float getPressure() { return pressure;}
public void setPressure(float pressure) {this.pressure=pressure;}
public float getPrecipitation() {return precipitation; }
public void setPrecipitation(float precipitation) {this.precipitation=precipitation;}
public float getWind_speed() { return wind_speed;}
public void setWind_speed(float wind_speed) {this.wind_speed=wind_speed;}
public float getWind_direction() { return wind_direction;}
public void setWind_direction(float wind_direction) {this.wind_direction=wind_direction;}
public float getUv_index() { return uv_index; }
public void setUv_index(float uv_index) {this.uv_index=uv_index;}
public long getTimeSunrise() { return timeSunrise; }
public void setTimeSunrise(long timeSunrise) {
this.timeSunrise = timeSunrise;
}
public long getTimeSunset() {
return timeSunset;
}
public void setTimeSunset(long timeSunset) {
this.timeSunset = timeSunset;
}
}

View file

@ -0,0 +1,263 @@
package org.woheller69.weather.dialogs;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.os.ConfigurationCompat;
import androidx.fragment.app.DialogFragment;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import org.json.JSONArray;
import org.json.JSONObject;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.R;
import org.woheller69.weather.activities.ManageLocationsActivity;
import org.woheller69.weather.database.City;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.ui.util.geocodingApiCall;
import org.woheller69.weather.ui.util.AutoSuggestAdapter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class AddLocationDialogOmGeocodingAPI extends DialogFragment {
Activity activity;
View rootView;
SQLiteHelper database;
private AutoCompleteTextView autoCompleteTextView;
City selectedCity;
private static final int TRIGGER_AUTO_COMPLETE = 100;
private static final long AUTO_COMPLETE_DELAY = 300;
private Handler handler;
private AutoSuggestAdapter autoSuggestAdapter;
String url="https://geocoding-api.open-meteo.com/v1/search?name=";
String lang="default";
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Activity){
this.activity=(Activity) context;
}
}
@NonNull
@SuppressLint("SetJavaScriptEnabled")
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Locale locale = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).get(0);
lang=locale.getLanguage();
LayoutInflater inflater = getActivity().getLayoutInflater();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
View view = inflater.inflate(R.layout.dialog_add_location, null);
rootView = view;
builder.setView(view);
builder.setTitle(getActivity().getString(R.string.dialog_add_label));
this.database = SQLiteHelper.getInstance(getActivity());
final WebView webview= rootView.findViewById(R.id.webViewAddLocation);
webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setUserAgentString(BuildConfig.APPLICATION_ID+"/"+BuildConfig.VERSION_NAME);
webview.setBackgroundColor(0x00000000);
webview.setBackgroundResource(R.drawable.map_back);
autoCompleteTextView = (AutoCompleteTextView) rootView.findViewById(R.id.autoCompleteTvAddDialog);
//Setting up the adapter for AutoSuggest
autoSuggestAdapter = new AutoSuggestAdapter(requireContext(),
R.layout.list_item_autocomplete);
autoCompleteTextView.setThreshold(2);
autoCompleteTextView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
autoCompleteTextView.setAdapter(autoSuggestAdapter);
autoCompleteTextView.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
selectedCity=autoSuggestAdapter.getObject(position);
//Hide keyboard to have more space
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(rootView.getWindowToken(), 0);
//Show city on map
webview.loadUrl("file:///android_asset/map.html?lat=" + selectedCity.getLatitude() + "&lon=" + selectedCity.getLongitude());
}
});
autoCompleteTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int
count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
handler.removeMessages(TRIGGER_AUTO_COMPLETE);
handler.sendEmptyMessageDelayed(TRIGGER_AUTO_COMPLETE,
AUTO_COMPLETE_DELAY);
}
@Override
public void afterTextChanged(Editable s) {
}
});
handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == TRIGGER_AUTO_COMPLETE) {
if (!TextUtils.isEmpty(autoCompleteTextView.getText())) {
try {
makeApiCall(URLEncoder.encode(autoCompleteTextView.getText().toString(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
return false;
}
});
builder.setPositiveButton(getActivity().getString(R.string.dialog_add_add_button), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
performDone();
}
});
builder.setNegativeButton(getActivity().getString(R.string.dialog_add_close_button), null);
return builder.create();
}
private void makeApiCall(String text) {
geocodingApiCall.make(getContext(), text, url,lang, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
//parsing logic, please change it as per your requirement
List<String> stringList = new ArrayList<>();
List<City> cityList = new ArrayList<>();
try {
JSONObject responseObject = new JSONObject(response);
JSONArray array = responseObject.getJSONArray("results");
for (int i = 0; i < array.length(); i++) {
City city =new City();
String citystring="";
JSONObject jsonFeatures = array.getJSONObject(i);
String name="";
if (jsonFeatures.has("name")) {
name=jsonFeatures.getString("name");
citystring=citystring+name;
}
String countrycode="";
if (jsonFeatures.has("country_code")) {
countrycode=jsonFeatures.getString("country_code");
citystring=citystring+", "+countrycode;
}
String admin1="";
if (jsonFeatures.has("admin1")) {
admin1=jsonFeatures.getString("admin1");
citystring=citystring+", "+admin1;
}
String admin2="";
if (jsonFeatures.has("admin2")) {
admin2=jsonFeatures.getString("admin2");
citystring=citystring+", "+admin2;
}
String admin3="";
if (jsonFeatures.has("admin3")) {
admin3=jsonFeatures.getString("admin3");
citystring=citystring+", "+admin3;
}
String admin4="";
if (jsonFeatures.has("admin4")) {
admin4=jsonFeatures.getString("admin4");
citystring=citystring+", "+admin4;
}
city.setCityName(name);
city.setCountryCode(countrycode);
city.setLatitude((float) jsonFeatures.getDouble("latitude"));
city.setLongitude((float) jsonFeatures.getDouble("longitude"));
cityList.add(city);
stringList.add(citystring);
}
} catch (Exception e) {
e.printStackTrace();
}
//IMPORTANT: set data here and notify
autoSuggestAdapter.setData(stringList,cityList);
autoSuggestAdapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Handler h = new Handler(activity.getMainLooper());
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, error.toString(), Toast.LENGTH_LONG).show();
}
});
}
});
}
private void performDone() {
if (selectedCity == null) {
Toast.makeText(activity, R.string.dialog_add_no_city_found, Toast.LENGTH_SHORT).show();
}else {
((ManageLocationsActivity) activity).addCityToList(selectedCity);
dismiss();
}
}
}

View file

@ -0,0 +1,209 @@
package org.woheller69.weather.firststart;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.woheller69.weather.R;
import org.woheller69.weather.activities.ForecastCityActivity;
import org.woheller69.weather.activities.SettingsActivity;
/**
* Class structure taken from tutorial at http://www.androidhive.info/2016/05/android-build-intro-slider-app/
*
* @author Karola Marky
* @version 20161214
*/
public class TutorialActivity extends AppCompatActivity {
private ViewPager viewPager;
private MyViewPagerAdapter myViewPagerAdapter;
private LinearLayout dotsLayout;
private TextView[] dots;
private int[] layouts;
private Button btnNext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tutorial);
viewPager = (ViewPager) findViewById(R.id.view_pager);
dotsLayout = (LinearLayout) findViewById(R.id.layoutDots);
btnNext = (Button) findViewById(R.id.btn_next);
// layouts of all welcome sliders
// add few more layouts if you want
layouts = new int[]{
R.layout.tutorial_slide1,
R.layout.tutorial_slide2,
R.layout.tutorial_slide3};
// adding bottom dots
addBottomDots(0);
// making notification bar transparent
changeStatusBarColor();
myViewPagerAdapter = new MyViewPagerAdapter();
viewPager.setAdapter(myViewPagerAdapter);
viewPager.addOnPageChangeListener(viewPagerPageChangeListener);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// checking for last page
// if last page home screen will be launched
int current = getItem(+1);
if (current < layouts.length) {
// move to next screen
viewPager.setCurrentItem(current);
} else {
launchSettings();
}
}
});
}
private void addBottomDots(int currentPage) {
dots = new TextView[layouts.length];
int[] colorsActive = getResources().getIntArray(R.array.array_dot_active);
int[] colorsInactive = getResources().getIntArray(R.array.array_dot_inactive);
dotsLayout.removeAllViews();
for (int i = 0; i < dots.length; i++) {
dots[i] = new TextView(this);
dots[i].setText("\u2022");
dots[i].setTextSize(35);
dots[i].setTextColor(colorsInactive[currentPage]);
dotsLayout.addView(dots[i]);
}
if (dots.length > 0)
dots[currentPage].setTextColor(colorsActive[currentPage]);
}
private int getItem(int i) {
return viewPager.getCurrentItem() + i;
}
private void launchHomeScreen() {
startActivity(new Intent(TutorialActivity.this, ForecastCityActivity.class));
finish();
}
private void launchSettings() {
startActivity(new Intent(TutorialActivity.this, SettingsActivity.class));
finish();
}
@Override
protected void onResume() {
super.onResume();
viewPagerPageChangeListener.onPageSelected(viewPager.getCurrentItem());
}
// viewpager change listener
ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
addBottomDots(position);
// changing the next button text 'NEXT' / 'GOT IT'
if (position == layouts.length - 1) {
// last page. make button text to GOT IT
btnNext.setText(getString(R.string.okay));
} else {
// still pages are left
btnNext.setText(getString(R.string.next));
}
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
};
/**
* Making notification bar transparent
*/
private void changeStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
}
/**
* View pager adapter
*/
public class MyViewPagerAdapter extends PagerAdapter {
private LayoutInflater layoutInflater;
public MyViewPagerAdapter() {
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view = layoutInflater.inflate(layouts[position], container, false);
container.addView(view);
return view;
}
@Override
public int getCount() {
return layouts.length;
}
@Override
public boolean isViewFromObject(View view, Object obj) {
return view == obj;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
container.removeView(view);
}
}
}

View file

@ -0,0 +1,12 @@
package org.woheller69.weather.http;
/**
* A list of all the possible HTTP request types (there are more, for sure, but for this project
* the four below are definitely sufficient).
*/
public enum HttpRequestType {
POST,
GET,
PUT,
DELETE
}

View file

@ -0,0 +1,22 @@
package org.woheller69.weather.http;
import org.woheller69.weather.weather_api.IProcessHttpRequest;
/**
* This interface defines the template for making HTTP request. Furthermore, it provides a generic
* way for handling the responses.
*/
public interface IHttpRequest {
/**
* Makes an HTTP request and processes the response.
*
* @param URL The target of the HTTP request.
* @param method Which method to use for the HTTP request (e.g. GET or POST)
* @param requestProcessor This object with its implemented methods processSuccessScenario and
* processFailScenario defines how to handle the response in the success
* and error case respectively.
*/
void make(final String URL, HttpRequestType method, IProcessHttpRequest requestProcessor);
}

View file

@ -0,0 +1,110 @@
package org.woheller69.weather.http;
import android.content.Context;
import android.util.Log;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.weather_api.IProcessHttpRequest;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/**
* This class implements the IHttpRequest interface. It provides HTTP requests by using Volley.
* See: https://developer.android.com/training/volley/simple.html
*/
public class VolleyHttpRequest implements IHttpRequest {
private Context context;
private int cityId;
/**
* Constructor.
*
* @param context Volley needs a context "for creating the cache dir".
* @see Volley#newRequestQueue(Context)
*/
public VolleyHttpRequest(Context context, int cityId) {
this.context = context;
this.cityId = cityId;
}
/**
* @see IHttpRequest#make(String, HttpRequestType, IProcessHttpRequest)
*/
@Override
public void make(String URL, HttpRequestType method, final IProcessHttpRequest requestProcessor) {
RequestQueue queue = Volley.newRequestQueue(context);
// Set the request method
int requestMethod;
switch (method) {
case POST:
requestMethod = Request.Method.POST;
break;
case GET:
requestMethod = Request.Method.GET;
break;
case PUT:
requestMethod = Request.Method.PUT;
break;
case DELETE:
requestMethod = Request.Method.DELETE;
break;
default:
requestMethod = Request.Method.GET;
}
// Execute the request and handle the response
StringRequest stringRequest = new StringRequest(requestMethod, URL,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
requestProcessor.processSuccessScenario(response,cityId);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
requestProcessor.processFailScenario(error);
}
}
){
@Override
public Map<String, String> getHeaders() { //from https://stackoverflow.com/questions/17049473/how-to-set-custom-header-in-volley-request
Map<String, String> params = new HashMap<String, String>();
params.put("User-Agent", BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_NAME);
return params;
}
};
queue.add(stringRequest);
}
}

View file

@ -0,0 +1,164 @@
package org.woheller69.weather.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.R;
/**
* This class provides access and methods for relevant preferences.
*/
public class AppPreferencesManager {
/**
* Member variables
*/
SharedPreferences preferences;
/**
* Constructor.
*
* @param preferences Source for the preferences to use.
*/
public AppPreferencesManager(SharedPreferences preferences) {
this.preferences = preferences;
}
public boolean isFirstTimeLaunch(Context context) {
boolean result = preferences.getBoolean("firstLaunch", true);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstLaunch",false);
editor.apply();
return result;
}
/**
* This method converts a given temperature value into the unit that was set in the preferences.
*
* @param temperature The temperature to convert into the unit that is set in the preferences.
* Make sure to pass a value in celsius.
* @return Returns the converted temperature.
**
*/
public float convertTemperatureFromCelsius(float temperature) {
// 1 = Celsius (fallback), 2 = Fahrenheit
int prefValue = Integer.parseInt(preferences.getString("temperatureUnit", "1"));
if (prefValue == 1) {
return temperature;
} else {
return (((temperature * 9) / 5) + 32);
}
}
/**
* This method converts a given distance value into the unit that was set in the preferences.
*
* @param kilometers The kilometers to convert into the unit that is set in the preferences.
* Make sure to pass a value in kilometers.
* @return Returns the converted distance.
*/
public float convertDistanceFromKilometers(float kilometers) {
// 1 = kilometers, 2 = miles
int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "1"));
if (prefValue == 1) {
return kilometers;
} else {
return convertKmInMiles(kilometers);
}
}
/**
* @return Returns true if kilometers was set as distance unit in the preferences else false.
*/
public boolean isDistanceUnitKilometers() {
int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "0"));
return (prefValue == 1);
}
/**
* @return Returns true if miles was set as distance unit in the preferences else false.
*/
public boolean isDistanceUnitMiles() {
int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "0"));
return (prefValue == 2);
}
/**
* Converts a kilometer value in miles.
*
* @param km The value to convert to miles.
* @return Returns the converted value.
*/
public float convertKmInMiles(float km) {
// TODO: Is this the right class for the function???
return (float) (km / 1.609344);
}
/**
* Converts a miles value in kilometers.
*
* @param miles The value to convert to kilometers.
* @return Returns the converted value.
*/
public float convertMilesInKm(float miles) {
// TODO: Is this the right class for the function???
return (float) (miles * 1.609344);
}
/**
* @return Returns "°C" in case Celsius is set and "°F" if Fahrenheit was selected.
*/
public String getTemperatureUnit() {
int prefValue = Integer.parseInt(preferences.getString("temperatureUnit", "1"));
if (prefValue == 1) {
return "°C";
} else {
return "°F";
}
}
/**
* @return Returns "km" in case kilometer is set and "mi" if miles was selected.
* @param applicationContext
*/
public String getDistanceUnit(Context applicationContext) {
int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "1"));
if (prefValue == 1) {
return applicationContext.getString(R.string.units_km);
} else {
return "mi";
}
}
public boolean showStarDialog(Context context) {
int versionCode = preferences.getInt("versionCode",BuildConfig.VERSION_CODE);
boolean askForStar=preferences.getBoolean("askForStar",true);
if (!isFirstTimeLaunch(context) && BuildConfig.VERSION_CODE>versionCode && askForStar){ //not at first start, only after upgrade and only if use has not yet given a star or has declined
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("versionCode", BuildConfig.VERSION_CODE);
editor.apply();
return true;
} else {
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("versionCode", BuildConfig.VERSION_CODE);
editor.apply();
return false;
}
}
public void setAskForStar(boolean askForStar){
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("askForStar", askForStar);
editor.apply();
}
}

View file

@ -0,0 +1,145 @@
package org.woheller69.weather.services;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import androidx.preference.PreferenceManager;
import androidx.core.app.JobIntentService;
import android.widget.Toast;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.R;
import org.woheller69.weather.activities.NavigationActivity;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.weather_api.IHttpRequestForWeatherAPI;
import org.woheller69.weather.weather_api.open_meteo.OMHttpRequestForWeatherAPI;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.util.List;
/**
* This class provides the functionality to fetch forecast data for a given city as a background
* task.
*/
public class UpdateDataService extends JobIntentService {
public static final String UPDATE_FORECAST_ACTION = "org.woheller69.weather.services.UpdateDataService.UPDATE_FORECAST_ACTION";
public static final String UPDATE_ALL_ACTION = "org.woheller69.weather.services.UpdateDataService.UPDATE_ALL_ACTION";
public static final String UPDATE_SINGLE_ACTION = "org.woheller69.weather.services.UpdateDataService.UPDATE_SINGLE_ACTION";
public static final String CITY_ID = "cityId";
public static final String SKIP_UPDATE_INTERVAL = "skipUpdateInterval";
private static final long MIN_UPDATE_INTERVAL=20;
private SQLiteHelper dbHelper;
private SharedPreferences prefManager;
/**
* Constructor.
*/
public UpdateDataService() {
super();
}
@Override
public void onCreate() {
super.onCreate();
dbHelper = SQLiteHelper.getInstance(getApplicationContext());
prefManager = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
@Override
protected void onHandleWork(Intent intent) {
if (!isOnline()) {
Handler h = new Handler(getApplicationContext().getMainLooper());
h.post(new Runnable() {
@Override
public void run() {
if (NavigationActivity.isVisible) Toast.makeText(getApplicationContext(), getResources().getString(R.string.error_no_internet), Toast.LENGTH_LONG).show();
}
});
return;
}
if (intent != null) {
if (UPDATE_ALL_ACTION.equals(intent.getAction())) handleUpdateAll(intent);
else if (UPDATE_FORECAST_ACTION.equals(intent.getAction()))
handleUpdateForecastAction(intent);
else if (UPDATE_SINGLE_ACTION.equals(intent.getAction())) handleUpdateSingle(intent);
}
}
/**
* Be careful, with using this. It can cause many calls to the API, because it wants to update everything if the update interval allows it.
*
* @param intent contains necessary parameters for the service work
*/
private void handleUpdateAll(Intent intent) {
List<CityToWatch> cities = dbHelper.getAllCitiesToWatch();
for (CityToWatch c : cities) {
handleUpdateForecastAction(intent, c.getCityId(),c.getLatitude(),c.getLongitude());
}
}
private void handleUpdateSingle(Intent intent) {
int cityId = intent.getIntExtra("cityId",-1);
CityToWatch city = dbHelper.getCityToWatch(cityId);
handleUpdateForecastAction(intent, cityId, city.getLatitude(), city.getLongitude());
}
private void handleUpdateForecastAction(Intent intent, int cityId, float lat, float lon) {
boolean skipUpdateInterval = intent.getBooleanExtra(SKIP_UPDATE_INTERVAL, false);
long timestamp = 0;
long systemTime = System.currentTimeMillis() / 1000;
long updateInterval = (long) (Float.parseFloat(prefManager.getString("pref_updateInterval", "2")) * 60 * 60);
List<HourlyForecast> hourlyForecasts = dbHelper.getForecastsByCityId(cityId);
if (hourlyForecasts.size() > 0) { // check timestamp of the current forecasts
timestamp = hourlyForecasts.get(0).getTimestamp();
}
if (skipUpdateInterval) {
// check timestamp of the current forecasts
if ((timestamp+MIN_UPDATE_INTERVAL-systemTime)>0) skipUpdateInterval=false; //even if skipUpdateInterval is true, never update if less than MIN_UPDATE_INTERVAL s
}
// Update if update forced or if a certain time has passed
if (skipUpdateInterval || timestamp + updateInterval - systemTime <= 0) {
IHttpRequestForWeatherAPI forecastOneCallRequest = new OMHttpRequestForWeatherAPI(getApplicationContext());
forecastOneCallRequest.perform(lat,lon, cityId);
}
}
private boolean isOnline() {
try {
URL url = new URL(BuildConfig.BASE_URL);
InetAddress inetAddress = InetAddress.getByName(url.getHost());
return inetAddress.isReachable(2000);
} catch (IOException | IllegalArgumentException e) {
return false;
}
}
private void handleUpdateForecastAction(Intent intent) {
int cityId = intent.getIntExtra(CITY_ID, -1);
float lat =0;
float lon =0;
//get lat lon for cityID
List<CityToWatch> citiesToWatch = dbHelper.getAllCitiesToWatch();
for (int i = 0; i < citiesToWatch.size(); i++) {
CityToWatch city = citiesToWatch.get(i);
if (city.getCityId() == cityId) {
lat = city.getLatitude();
lon = city.getLongitude();
break;
}
}
handleUpdateForecastAction(intent, cityId, lat, lon);
}
}

View file

@ -0,0 +1,278 @@
package org.woheller69.weather.ui.Help;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import androidx.preference.PreferenceManager;
import androidx.core.content.res.ResourcesCompat;
import org.woheller69.weather.R;
import org.woheller69.weather.preferences.AppPreferencesManager;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import static java.lang.Boolean.TRUE;
public final class StringFormatUtils {
private static final DecimalFormat decimalFormat = new DecimalFormat("0.0");
private static final DecimalFormat intFormat = new DecimalFormat("0");
public static String formatDecimal(float decimal) {
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
return removeMinusIfZerosOnly(decimalFormat.format(decimal));
}
public static String formatInt(float decimal) {
intFormat.setRoundingMode(RoundingMode.HALF_UP);
return removeMinusIfZerosOnly(intFormat.format(decimal));
}
public static String formatInt(float decimal, String appendix) {
return String.format("%s\u200a%s", removeMinusIfZerosOnly(formatInt(decimal)), appendix); //\u200a adds tiny space
}
public static String formatDecimal(float decimal, String appendix) {
return String.format("%s\u200a%s", removeMinusIfZerosOnly(formatDecimal(decimal)), appendix);
}
public static String formatTimeWithoutZone(Context context, long time) {
SharedPreferences sharedPreferences= PreferenceManager.getDefaultSharedPreferences(context);
SimpleDateFormat df;
if (android.text.format.DateFormat.is24HourFormat(context) || sharedPreferences.getBoolean("pref_TimeFormat", true)==TRUE){
df = new SimpleDateFormat("HH:mm", Locale.getDefault());
df.setTimeZone(TimeZone.getTimeZone("GMT"));
}else {
df = new SimpleDateFormat("hh:mm aa", Locale.getDefault());
df.setTimeZone(TimeZone.getTimeZone("GMT"));
}
return df.format(time);
}
public static String formatWindSpeed(Context context, float wind_speed) {
SharedPreferences sharedPreferences= PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean("pref_WindFormat",true)==TRUE) {
if (wind_speed < 0.3) {
return formatInt(0, context.getString(R.string.units_Bft)); // Calm
} else if (wind_speed < 1.5) {
return formatInt(1, context.getString(R.string.units_Bft)); // Light air
} else if (wind_speed < 3.3) {
return formatInt(2, context.getString(R.string.units_Bft)); // Light breeze
} else if (wind_speed < 5.5) {
return formatInt(3, context.getString(R.string.units_Bft)); // Gentle breeze
} else if (wind_speed < 7.9) {
return formatInt(4, context.getString(R.string.units_Bft)); // Moderate breeze
} else if (wind_speed < 10.7) {
return formatInt(5, context.getString(R.string.units_Bft)); // Fresh breeze
} else if (wind_speed < 13.8) {
return formatInt(6, context.getString(R.string.units_Bft)); // Strong breeze
} else if (wind_speed < 17.1) {
return formatInt(7, context.getString(R.string.units_Bft)); // High wind
} else if (wind_speed < 20.7) {
return formatInt(8, context.getString(R.string.units_Bft)); // Gale
} else if (wind_speed < 24.4) {
return formatInt(9, context.getString(R.string.units_Bft)); // Strong gale
} else if (wind_speed < 28.4) {
return formatInt(10, context.getString(R.string.units_Bft)); // Storm
} else if (wind_speed < 32.6) {
return formatInt(11, context.getString(R.string.units_Bft)); // Violent storm
} else {
return formatInt(12, context.getString(R.string.units_Bft)); // Hurricane
}
}else{
if (sharedPreferences.getString("distanceUnit", "0").equals("1")) { //distanceUnit km
return formatInt((float) (wind_speed*3.6),context.getString(R.string.units_km_h));
}else return formatInt((float) (wind_speed*2.236),context.getString(R.string.units_mph));
}
}
public static Drawable colorWindSpeed(Context context, float wind_speed) {
if (wind_speed < 0.3) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (wind_speed < 1.5) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (wind_speed < 3.3) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (wind_speed < 5.5) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (wind_speed < 7.9) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (wind_speed < 10.7) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_yellow,null);
} else if (wind_speed < 13.8) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_yellow,null);
} else if (wind_speed < 17.1) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_yellow,null);
} else if (wind_speed < 20.7) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_orange,null);
} else if (wind_speed < 24.4) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_orange,null);
} else if (wind_speed < 28.4) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_lightred,null);
} else if (wind_speed < 32.6) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_lightred,null);
} else {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_red,null);
}
}
public static int colorWindSpeedWidget(float wind_speed) {
if (wind_speed < 0.3) {
return R.drawable.ic_wind_empty;
} else if (wind_speed < 1.5) {
return R.drawable.ic_wind_empty;
} else if (wind_speed < 3.3) {
return R.drawable.ic_wind_empty;
} else if (wind_speed < 5.5) {
return R.drawable.ic_wind_empty;
} else if (wind_speed < 7.9) {
return R.drawable.ic_wind_empty;
} else if (wind_speed < 10.7) {
return R.drawable.ic_wind_yellow;
} else if (wind_speed < 13.8) {
return R.drawable.ic_wind_yellow;
} else if (wind_speed < 17.1) {
return R.drawable.ic_wind_yellow;
} else if (wind_speed < 20.7) {
return R.drawable.ic_wind_orange;
} else if (wind_speed < 24.4) {
return R.drawable.ic_wind_orange;
} else if (wind_speed < 28.4) {
return R.drawable.ic_wind_lightred;
} else if (wind_speed < 32.6) {
return R.drawable.ic_wind_lightred;
} else {
return R.drawable.ic_wind_lightred;
}
}
public static Drawable colorUVindex(Context context, int uvindex) {
if (uvindex <=2) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null);
} else if (uvindex <= 5) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_yellow,null);
} else if (uvindex <= 7) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_orange,null);
} else if (uvindex <= 10) {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_lightred,null);
} else {
return ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_violet,null);
}
}
public static Integer widgetColorWindSpeed(Context context, float wind_speed) {
if (wind_speed < 0.3) {
return R.drawable.rounded_grey;
} else if (wind_speed < 1.5) {
return R.drawable.rounded_grey;
} else if (wind_speed < 3.3) {
return R.drawable.rounded_grey;
} else if (wind_speed < 5.5) {
return R.drawable.rounded_grey;
} else if (wind_speed < 7.9) {
return R.drawable.rounded_grey;
} else if (wind_speed < 10.7) {
return R.drawable.rounded_yellow;
} else if (wind_speed < 13.8) {
return R.drawable.rounded_yellow;
} else if (wind_speed < 17.1) {
return R.drawable.rounded_yellow;
} else if (wind_speed < 20.7) {
return R.drawable.rounded_orange;
} else if (wind_speed < 24.4) {
return R.drawable.rounded_orange;
} else if (wind_speed < 28.4) {
return R.drawable.rounded_lightred;
} else if (wind_speed < 32.6) {
return R.drawable.rounded_lightred;
} else {
return R.drawable.rounded_red;
}
}
public static Integer widgetColorUVindex(Context context, int uvindex) {
if (uvindex <=2) {
return R.drawable.rounded_green;
} else if (uvindex <= 5) {
return R.drawable.rounded_yellow;
} else if (uvindex <= 7) {
return R.drawable.rounded_orange;
} else if (uvindex <= 10) {
return R.drawable.rounded_lightred;
} else {
return R.drawable.rounded_violet;
}
}
public static Integer getDayShort(int day){
switch(day) {
case Calendar.MONDAY:
day = R.string.abbreviation_monday;
break;
case Calendar.TUESDAY:
day = R.string.abbreviation_tuesday;
break;
case Calendar.WEDNESDAY:
day = R.string.abbreviation_wednesday;
break;
case Calendar.THURSDAY:
day = R.string.abbreviation_thursday;
break;
case Calendar.FRIDAY:
day = R.string.abbreviation_friday;
break;
case Calendar.SATURDAY:
day = R.string.abbreviation_saturday;
break;
case Calendar.SUNDAY:
day = R.string.abbreviation_sunday;
break;
default:
day = R.string.abbreviation_monday;
}
return day;
}
public static Integer getDayLong(int day){
switch(day) {
case Calendar.MONDAY:
day = R.string.monday;
break;
case Calendar.TUESDAY:
day = R.string.tuesday;
break;
case Calendar.WEDNESDAY:
day = R.string.wednesday;
break;
case Calendar.THURSDAY:
day = R.string.thursday;
break;
case Calendar.FRIDAY:
day = R.string.friday;
break;
case Calendar.SATURDAY:
day = R.string.saturday;
break;
case Calendar.SUNDAY:
day = R.string.sunday;
break;
default:
day = R.string.monday;
}
return day;
}
public static String removeMinusIfZerosOnly(String string){
// It removes (replaces with "") the minus sign if it's followed by 0-n characters of "0.00000...",
// so this will work for any similar result such as "-0", "-0." or "-0.000000000"
// https://newbedev.com/negative-sign-in-case-of-zero-in-java
return string.replaceAll("^-(?=0(\\.0*)?$)", "");
}
}

View file

@ -0,0 +1,415 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.db.chart.Tools;
import com.db.chart.model.BarSet;
import com.db.chart.model.ChartSet;
import com.db.chart.model.LineSet;
import com.db.chart.view.AxisController;
import com.db.chart.view.BarChartView;
import org.woheller69.weather.R;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.preferences.AppPreferencesManager;
import org.woheller69.weather.ui.Help.StringFormatUtils;
import org.woheller69.weather.ui.UiResourceProvider;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class CityWeatherAdapter extends RecyclerView.Adapter<CityWeatherAdapter.ViewHolder> {
private static final String TAG = "Forecast_Adapter";
private int[] dataSetTypes;
private List<HourlyForecast> courseDayList;
private float[][] forecastData;
private Context context;
private ViewGroup mParent;
private RecyclerView mCourseOfDay;
private RecyclerView mWeekWeather;
private CurrentWeatherData currentWeatherDataList;
public static final int OVERVIEW = 0;
public static final int DETAILS = 1;
public static final int WEEK = 2;
public static final int DAY = 3;
public static final int CHART = 4;
public static final int EMPTY = 5;
public CityWeatherAdapter(CurrentWeatherData currentWeatherDataList, int[] dataSetTypes, Context context) {
this.currentWeatherDataList = currentWeatherDataList;
this.dataSetTypes = dataSetTypes;
this.context = context;
SQLiteHelper database = SQLiteHelper.getInstance(context.getApplicationContext());
List<HourlyForecast> hourlyForecasts = database.getForecastsByCityId(currentWeatherDataList.getCity_id());
List<WeekForecast> weekforecasts = database.getWeekForecastsByCityId(currentWeatherDataList.getCity_id());
updateForecastData(hourlyForecasts);
updateWeekForecastData(weekforecasts);
}
// function update 3-hour or 1-hour forecast list
public void updateForecastData(List<HourlyForecast> hourlyForecasts) {
courseDayList = new ArrayList<>();
for (HourlyForecast f : hourlyForecasts) {
if (f.getForecastTime() >= System.currentTimeMillis()) {
courseDayList.add(f);
}
}
notifyDataSetChanged();
}
// function for week forecast list
public void updateWeekForecastData(List<WeekForecast> forecasts) {
if (forecasts.isEmpty()) return;
int cityId = forecasts.get(0).getCity_id();
SQLiteHelper dbHelper = SQLiteHelper.getInstance(context.getApplicationContext());
int zonemilliseconds = dbHelper.getCurrentWeatherByCityId(cityId).getTimeZoneSeconds() * 1000;
//temp max 0, temp min 1, humidity 2, pressure 3, precipitation 4, wind 5, wind direction 6, uv_index 7, forecast time 8, weather ID 9, number of FCs for day 10
forecastData = new float[forecasts.size()][11];
for (int i=0;i<forecasts.size();i++){
forecastData[i][0]=forecasts.get(i).getMaxTemperature();
forecastData[i][1]=forecasts.get(i).getMinTemperature();
forecastData[i][2]=forecasts.get(i).getHumidity();
forecastData[i][3]=forecasts.get(i).getPressure();
forecastData[i][4]=forecasts.get(i).getPrecipitation();
forecastData[i][5]=forecasts.get(i).getWind_speed();
forecastData[i][6]=forecasts.get(i).getWind_direction();
forecastData[i][7]=forecasts.get(i).getUv_index();
forecastData[i][8]=forecasts.get(i).getForecastTime()+zonemilliseconds;
forecastData[i][9]=forecasts.get(i).getWeatherID();
forecastData[i][10]=1;
}
notifyDataSetChanged();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ViewHolder(View v) {
super(v);
}
}
public class OverViewHolder extends ViewHolder {
TextView temperature;
ImageView weather;
ImageView windicon;
TextView updatetime;
TextView sun;
OverViewHolder(View v) {
super(v);
this.temperature = v.findViewById(R.id.card_overview_temperature);
this.weather = v.findViewById(R.id.card_overview_weather_image);
this.sun=v.findViewById(R.id.card_overview_sunrise_sunset);
this.windicon=v.findViewById(R.id.card_overview_windicon);
this.updatetime=v.findViewById(R.id.card_overview_update_time);
}
}
public class DetailViewHolder extends ViewHolder {
TextView humidity;
TextView pressure;
TextView windspeed;
TextView rain60min;
TextView rain60minLegend;
TextView time;
ImageView winddirection;
DetailViewHolder(View v) {
super(v);
this.humidity = v.findViewById(R.id.card_details_humidity_value);
this.pressure = v.findViewById(R.id.card_details_pressure_value);
this.windspeed = v.findViewById(R.id.card_details_wind_speed_value);
this.rain60min = v.findViewById(R.id.card_details_rain60min_value);
this.rain60minLegend=v.findViewById(R.id.card_details_legend_rain60min);
this.winddirection =v.findViewById((R.id.card_details_wind_direction_value));
this.time=v.findViewById(R.id.card_details_title);
}
}
public class WeekViewHolder extends ViewHolder {
RecyclerView recyclerView;
WeekViewHolder(View v) {
super(v);
recyclerView = v.findViewById(R.id.recycler_view_week);
mWeekWeather=recyclerView;
}
}
public class DayViewHolder extends ViewHolder {
RecyclerView recyclerView;
TextView recyclerViewHeader;
DayViewHolder(View v) {
super(v);
recyclerView = v.findViewById(R.id.recycler_view_course_day);
mCourseOfDay=recyclerView;
recyclerViewHeader=v.findViewById(R.id.recycler_view_header);
}
}
public class ChartViewHolder extends ViewHolder {
TextView precipitationunit;
BarChartView barChartView;
ChartViewHolder(View v) {
super(v);
this.barChartView = v.findViewById(R.id.graph_precipitation);
this.precipitationunit=v.findViewById(R.id.graph_precipitationunit);
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v;
mParent=viewGroup;
if (viewType == OVERVIEW) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_overview, viewGroup, false);
return new OverViewHolder(v);
} else if (viewType == DETAILS) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_details, viewGroup, false);
return new DetailViewHolder(v);
} else if (viewType == WEEK) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_week, viewGroup, false);
return new WeekViewHolder(v);
} else if (viewType == DAY) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_day, viewGroup, false);
return new DayViewHolder(v);
} else if (viewType == CHART) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_chart, viewGroup, false);
return new ChartViewHolder(v);
} else {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_empty, viewGroup, false);
return new ViewHolder(v);
}
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
boolean isDay = currentWeatherDataList.isDay(context);
if (viewHolder.getItemViewType() == OVERVIEW) {
OverViewHolder holder = (OverViewHolder) viewHolder;
//correct for timezone differences
int zoneseconds = currentWeatherDataList.getTimeZoneSeconds();
long riseTime = (currentWeatherDataList.getTimeSunrise() + zoneseconds) * 1000;
long setTime = (currentWeatherDataList.getTimeSunset() + zoneseconds) * 1000;
if (riseTime==zoneseconds*1000 || setTime==zoneseconds*1000) holder.sun.setText("\u2600\u25b2 --:--" + " \u25bc --:--" );
else {
holder.sun.setText("\u2600\u25b2 " + StringFormatUtils.formatTimeWithoutZone(context, riseTime) + " \u25bc " + StringFormatUtils.formatTimeWithoutZone(context, setTime));
}
holder.windicon.setImageResource(StringFormatUtils.colorWindSpeedWidget(currentWeatherDataList.getWindSpeed()));
long time = currentWeatherDataList.getTimestamp();
long updateTime = ((time + zoneseconds) * 1000);
holder.updatetime.setText("("+StringFormatUtils.formatTimeWithoutZone(context, updateTime)+")");
setImage(currentWeatherDataList.getWeatherID(), holder.weather, isDay);
} else if (viewHolder.getItemViewType() == WEEK) {
final WeekViewHolder holder = (WeekViewHolder) viewHolder;
LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.recyclerView.setLayoutManager(layoutManager);
final WeekWeatherAdapter adapter = new WeekWeatherAdapter(context, forecastData,currentWeatherDataList.getCity_id());
holder.recyclerView.setAdapter(adapter);
holder.recyclerView.setFocusable(false);
if (mCourseOfDay!=null) { //otherwise crash if courseOfDay not visible
CourseOfDayAdapter dayadapter = (CourseOfDayAdapter) mCourseOfDay.getAdapter();
dayadapter.setWeekRecyclerView(holder.recyclerView); //provide CourseOfDayAdapter with reference to week recyclerview
adapter.setCourseOfDayHeaderDate(dayadapter.getCourseOfDayHeaderDate()); //initialize WeekWeatherAdapter with current HeaderDate from CourseOfDayAdapter
}
holder.recyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(context, holder.recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
SQLiteHelper database = SQLiteHelper.getInstance(context.getApplicationContext());
List<WeekForecast> weekforecasts = database.getWeekForecastsByCityId(currentWeatherDataList.getCity_id());
long time = weekforecasts.get(position).getForecastTime(); //time of clicked week item
time=time-6*3600000; //week item normally midday -> subtract 6h to get morning time
if (mCourseOfDay!=null){ //otherwise crash if courseOfDay not visible
LinearLayoutManager llm = (LinearLayoutManager) mCourseOfDay.getLayoutManager();
assert llm != null;
int num = llm.findLastVisibleItemPosition() - llm.findFirstVisibleItemPosition(); //get number of visible elements
int i;
for (i = 0; i < courseDayList.size(); i++) {
if (courseDayList.get(i).getForecastTime() > time) { //find first ForecastTime > time of clicked item
Calendar HeaderTime = Calendar.getInstance();
HeaderTime.setTimeZone(TimeZone.getTimeZone("GMT"));
HeaderTime.setTimeInMillis(courseDayList.get(i).getLocalForecastTime(context));
adapter.setCourseOfDayHeaderDate(HeaderTime.getTime());
break;
}
}
if (i < courseDayList.size()) { //only if element found
if (i > llm.findFirstVisibleItemPosition()) { //if scroll right
int min = Math.min(i + num, courseDayList.size()-1); //scroll to i+num so that requested element is on the left. Max scroll to courseDayList.size()
mCourseOfDay.getLayoutManager().scrollToPosition(min);
} else { //if scroll left
mCourseOfDay.getLayoutManager().scrollToPosition(i);
}
highlightSelected(view);
}
}
}
private void highlightSelected(View view) {
for (int j=0;j<courseDayList.size();j++){ //reset all items
if (holder.recyclerView.getLayoutManager().getChildAt(j)!=null){
holder.recyclerView.getLayoutManager().getChildAt(j).setBackground(ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null));
}
}
view.setBackground(ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_highlight,null)); //highlight selected item
}
public void onLongItemClick(View view, int position) {
}
})
);
} else if (viewHolder.getItemViewType() == DAY) {
DayViewHolder holder = (DayViewHolder) viewHolder;
LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.recyclerView.setLayoutManager(layoutManager);
CourseOfDayAdapter adapter = new CourseOfDayAdapter(courseDayList, context,holder.recyclerViewHeader,holder.recyclerView);
holder.recyclerView.setAdapter(adapter);
holder.recyclerView.setFocusable(false);
} else if (viewHolder.getItemViewType() == CHART) {
ChartViewHolder holder = (ChartViewHolder) viewHolder;
SQLiteHelper database = SQLiteHelper.getInstance(context.getApplicationContext());
List<WeekForecast> weekforecasts = database.getWeekForecastsByCityId(currentWeatherDataList.getCity_id());
if (weekforecasts.isEmpty()) {
return;
}
float pmax=0;
BarSet precipitationDataset = new BarSet();
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT"));
int zonemilliseconds = currentWeatherDataList.getTimeZoneSeconds()*1000;
for (int i=0 ; i< weekforecasts.size();i++) {
c.setTimeInMillis(weekforecasts.get(i).getForecastTime()+zonemilliseconds);
int day = c.get(Calendar.DAY_OF_WEEK);
float precip=weekforecasts.get(i).getPrecipitation();
String dayString = context.getResources().getString(StringFormatUtils.getDayShort(day));
if (weekforecasts.size()>8) dayString=dayString.substring(0,1); //use first character only if more than 8 days to avoid overlapping text
precipitationDataset.addBar(dayString, precip);
if (precip>pmax) pmax=precip;
}
int step;
ArrayList<ChartSet> temperature = new ArrayList<>();
ArrayList<ChartSet> precipitation = new ArrayList<>();
precipitation.add(precipitationDataset);
precipitationDataset.setColor(ContextCompat.getColor(context,R.color.yellow));
precipitationDataset.setAlpha(0.8f); // make precipitation bars transparent
step = (int) Math.ceil((Math.max(1,pmax))/4);
holder.barChartView.addData(precipitation);
holder.barChartView.setBarSpacing(10);
holder.barChartView.setXAxis(false);
holder.barChartView.setYAxis(false);
holder.barChartView.setYLabels(AxisController.LabelPosition.INSIDE); //no labels for precipitation
holder.barChartView.setLabelsColor(ContextCompat.getColor(context,R.color.colorPrimaryDark)); //transparent color, make labels invisible
holder.barChartView.setAxisColor(ContextCompat.getColor(context,R.color.colorPrimaryDark));
holder.barChartView.setFontSize((int) Tools.fromDpToPx(17));
holder.barChartView.setBorderSpacing(Tools.fromDpToPx(30));
holder.barChartView.show();
holder.precipitationunit.setText(" " + context.getResources().getString(R.string.units_kWh)+" ");
}
//No update for error needed
}
public void setImage(int value, ImageView imageView, boolean isDay) {
imageView.setImageResource(UiResourceProvider.getImageResourceForWeatherCategory(value, isDay));
}
@Override
public int getItemCount() {
return dataSetTypes.length;
}
@Override
public int getItemViewType(int position) {
return dataSetTypes[position];
}
}

View file

@ -0,0 +1,168 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.SharedPreferences;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.woheller69.weather.R;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.ui.Help.StringFormatUtils;
import org.woheller69.weather.ui.UiResourceProvider;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
//**
// * Created by yonjuni on 02.01.17.
// * Adapter for the horizontal listView for course of the day.
// */import java.util.List;
public class CourseOfDayAdapter extends RecyclerView.Adapter<CourseOfDayAdapter.CourseOfDayViewHolder> {
private List<HourlyForecast> courseOfDayList;
private Context context;
private TextView recyclerViewHeader;
private RecyclerView recyclerView;
private RecyclerView weekRecyclerView;
private Date courseOfDayHeaderDate;
CourseOfDayAdapter(List<HourlyForecast> courseOfDayList, Context context, TextView recyclerViewHeader, RecyclerView recyclerView) {
this.context = context;
this.courseOfDayList = courseOfDayList;
this.recyclerViewHeader=recyclerViewHeader;
this.recyclerView=recyclerView;
if (courseOfDayList.size()!=0 && courseOfDayList.get(0)!=null) {
this.courseOfDayHeaderDate = new Date(courseOfDayList.get(0).getLocalForecastTime(context));
}else this.courseOfDayHeaderDate=new Date(); //fallback if no data available
}
public void setWeekRecyclerView(RecyclerView weekRecyclerView){
this.weekRecyclerView=weekRecyclerView;
}
public Date getCourseOfDayHeaderDate(){
return this.courseOfDayHeaderDate;
}
@Override
public CourseOfDayViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_course_of_day, parent, false);
return new CourseOfDayViewHolder(view);
}
@Override
public void onBindViewHolder(CourseOfDayViewHolder holder, int position) {
SQLiteHelper dbHelper = SQLiteHelper.getInstance(context);
CurrentWeatherData currentWeather = dbHelper.getCurrentWeatherByCityId(courseOfDayList.get(position).getCity_id());
Calendar forecastTime = Calendar.getInstance();
forecastTime.setTimeZone(TimeZone.getTimeZone("GMT"));
forecastTime.setTimeInMillis(courseOfDayList.get(position).getLocalForecastTime(context));
boolean isDay;
if (currentWeather.getTimeSunrise()==0 || currentWeather.getTimeSunset()==0){
if ((dbHelper.getCityToWatch(courseOfDayList.get(position).getCity_id()).getLatitude())>0){ //northern hemisphere
isDay= forecastTime.get(Calendar.DAY_OF_YEAR) >= 80 && forecastTime.get(Calendar.DAY_OF_YEAR) <= 265; //from March 21 to September 22 (incl)
}else{ //southern hemisphere
isDay= forecastTime.get(Calendar.DAY_OF_YEAR) < 80 || forecastTime.get(Calendar.DAY_OF_YEAR) > 265;
}
}else {
Calendar sunSetTime = Calendar.getInstance();
sunSetTime.setTimeZone(TimeZone.getTimeZone("GMT"));
sunSetTime.setTimeInMillis(currentWeather.getTimeSunset() * 1000 + currentWeather.getTimeZoneSeconds() * 1000L);
sunSetTime.set(Calendar.DAY_OF_YEAR, forecastTime.get(Calendar.DAY_OF_YEAR));
sunSetTime.set(Calendar.YEAR, forecastTime.get(Calendar.YEAR));
Calendar sunRiseTime = Calendar.getInstance();
sunRiseTime.setTimeZone(TimeZone.getTimeZone("GMT"));
sunRiseTime.setTimeInMillis(currentWeather.getTimeSunrise() * 1000 + currentWeather.getTimeZoneSeconds() * 1000L);
sunRiseTime.set(Calendar.DAY_OF_YEAR, forecastTime.get(Calendar.DAY_OF_YEAR));
sunRiseTime.set(Calendar.YEAR, forecastTime.get(Calendar.YEAR));
isDay = forecastTime.after(sunRiseTime) && forecastTime.before(sunSetTime);
}
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
if (sp.getBoolean("pref_debug",false)) {
holder.diffuseRadiation.setVisibility(View.VISIBLE);
holder.directRadiationNormal.setVisibility(View.VISIBLE);
} else {
holder.diffuseRadiation.setVisibility(View.GONE);
holder.directRadiationNormal.setVisibility(View.GONE);
}
holder.time.setText(StringFormatUtils.formatTimeWithoutZone(context, courseOfDayList.get(position).getLocalForecastTime(context)));
holder.directRadiationNormal.setText(StringFormatUtils.formatInt(courseOfDayList.get(position).getDirectRadiationNormal()," W/qm"));
holder.diffuseRadiation.setText(StringFormatUtils.formatInt(courseOfDayList.get(position).getDiffuseRadiation()," W/qm"));
holder.power.setText(StringFormatUtils.formatInt(courseOfDayList.get(position).getPower()," "+ context.getString(R.string.units_Wh)));
updateRecyclerViewHeader(); //update header according to date in first visible item on the left
setIcon(courseOfDayList.get(position).getWeatherID(), holder.weather, isDay);
}
//update header according to date in first visible item on the left of recyclerview
private void updateRecyclerViewHeader() {
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
LinearLayoutManager llm = (LinearLayoutManager) manager;
assert llm != null;
int visiblePosition = llm.findFirstVisibleItemPosition();
if (visiblePosition>-1) {
Calendar HeaderTime = Calendar.getInstance();
HeaderTime.setTimeZone(TimeZone.getTimeZone("GMT"));
HeaderTime.setTimeInMillis(courseOfDayList.get(visiblePosition).getLocalForecastTime(context));
int headerday = HeaderTime.get(Calendar.DAY_OF_WEEK);
headerday = StringFormatUtils.getDayLong(headerday);
recyclerViewHeader.setText(context.getResources().getString(headerday));
courseOfDayHeaderDate=HeaderTime.getTime();
if (weekRecyclerView!=null){
WeekWeatherAdapter weekadapter = (WeekWeatherAdapter) weekRecyclerView.getAdapter();
weekadapter.setCourseOfDayHeaderDate(courseOfDayHeaderDate);
}
}
}
@Override
public int getItemCount() {
return courseOfDayList.size();
}
class CourseOfDayViewHolder extends RecyclerView.ViewHolder {
TextView time;
ImageView weather;
TextView directRadiationNormal;
TextView diffuseRadiation;
TextView power;
TextView wind_speed;
CourseOfDayViewHolder(View itemView) {
super(itemView);
time = itemView.findViewById(R.id.course_of_day_time);
weather = itemView.findViewById(R.id.course_of_day_weather);
directRadiationNormal = itemView.findViewById(R.id.course_of_day_direct);
diffuseRadiation = itemView.findViewById(R.id.course_of_day_diffuse);
power = itemView.findViewById(R.id.course_of_day_power);
}
}
public void setIcon(int value, ImageView imageView, boolean isDay) {
imageView.setImageResource(UiResourceProvider.getIconResourceForWeatherCategory(value, isDay));
}
}

View file

@ -0,0 +1,27 @@
package org.woheller69.weather.ui.RecycleList;
/**
* This interface defines the functionality that can be bound to touch events.
* For the most part it has been taken from
* https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.hmhbe8sku
* as of 2016-08-03
*/
public interface ItemTouchHelperAdapter {
/**
* This method removes an item from an adapter at the specified positin.
*
* @param position The position of the item to remove.
*/
void onItemDismiss(int position);
/**
* This method is required to remove items from the list that is used to display the data
* whenever an item is deleted by swiping.
*
* @param fromPosition The from position.
* @param toPosition The to position.
*/
void onItemMove(int fromPosition, int toPosition);
}

View file

@ -0,0 +1,50 @@
package org.woheller69.weather.ui.RecycleList;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import org.woheller69.weather.R;
/**
* This class holds instances of items that are to be displayed in the list.
* The idea of this class has been taken from
* https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.hmhbe8sku
* as of 2016-08-03. Parts of the code were copied from that source.
*/
public class ItemViewHolder extends RecyclerView.ViewHolder {
/**
* Member variables
*/
public TextView cityName;
public TextView elevationAngle;
public TextView azimuthAngle;
public TextView cellsMaxPower;
public TextView cellsArea;
public TextView cellsEfficiency;
public TextView diffuseEfficiency;
public TextView converterPowerLimit;
public TextView converterEfficiency;
/**
* Constructor.
*
* @param itemView The view that contains the fields that are to be set for each list item.
*/
public ItemViewHolder(View itemView) {
super(itemView);
this.cityName = (TextView) itemView.findViewById(R.id.city_overview_list_item_text);
this.elevationAngle = (TextView) itemView.findViewById(R.id.city_elevation_angle);
this.azimuthAngle = (TextView) itemView.findViewById(R.id.city_azimuth_angle);
this.cellsMaxPower = (TextView) itemView.findViewById(R.id.city_cells_max_power);
this.cellsArea = (TextView) itemView.findViewById(R.id.city_cells_area);
this.cellsEfficiency = (TextView) itemView.findViewById(R.id.city_cells_efficiency);
this.diffuseEfficiency = (TextView) itemView.findViewById(R.id.city_diffuse_efficiency);
this.converterPowerLimit = (TextView) itemView.findViewById(R.id.city_converter_power_limit);
this.converterEfficiency = (TextView) itemView.findViewById(R.id.city_converter_efficiency);
}
}

View file

@ -0,0 +1,45 @@
package org.woheller69.weather.ui.RecycleList;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
public class OnSwipeDownListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeDownListener(Context context) {
gestureDetector = new GestureDetector(context, new GestureListener());
}
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(final View view, final MotionEvent motionEvent) {
return gestureDetector.onTouchEvent(motionEvent);
}
private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 120;
private static final int SWIPE_VELOCITY_THRESHOLD = 120;
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeDown();
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
public void onSwipeDown() { }
}

View file

@ -0,0 +1,66 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
/**
* Provides the functionality to detect (long) touch events on RecyclerView items.
* The code has been taken from
* http://stackoverflow.com/questions/24471109/recyclerview-onclick (answer of H. Azizkhani) as of
* 2016-08-04.
*/
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onLongItemClick(View view, int position);
}
private GestureDetector mGestureDetector;
public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
mListener = listener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null && mListener != null) {
mListener.onLongItemClick(child, recyclerView.getChildAdapterPosition(child));
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
return true;
}
return false;
}
@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}

View file

@ -0,0 +1,137 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import org.woheller69.weather.R;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.SQLiteHelper;
import java.util.Collections;
import java.util.List;
/**
* This is the adapter for the RecyclerList that is to be used for the overview of added locations.
* For the most part, it has been taken from
* https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.hmhbe8sku
* as of 2016-08-03
*/
public class RecyclerOverviewListAdapter extends RecyclerView.Adapter<ItemViewHolder> implements ItemTouchHelperAdapter {
/**
* Member variables
*/
private Context context;
private final List<CityToWatch> cities;
SQLiteHelper database;
/**
* Constructor.
*/
public RecyclerOverviewListAdapter(Context context, List<CityToWatch> cities) {
this.context = context;
this.cities = cities;
this.database = SQLiteHelper.getInstance(context);
}
/**
* @see RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)
* Returns the template for a list item.
*/
@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_city_list, parent, false);
return new ItemViewHolder(view);
}
/**
* @see RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)
* Sets the content of items.
*/
@Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
holder.cityName.setText(cities.get(position).getCityName());
holder.azimuthAngle.setText(context.getString(R.string.edit_location_hint_azimuth) +": "+ cities.get(position).getAzimuthAngle());
holder.elevationAngle.setText(context.getString(R.string.edit_location_hint_elevation) +": "+ cities.get(position).getElevationAngle());
holder.cellsMaxPower.setText(context.getString(R.string.edit_location_hint_cells_max_power) +": "+ cities.get(position).getCellsMaxPower());
holder.cellsEfficiency.setText(context.getString(R.string.edit_location_hint_cells_efficiency) +": "+ cities.get(position).getCellsEfficiency());
holder.cellsArea.setText(context.getString(R.string.edit_location_hint_cells_area) +": "+ cities.get(position).getCellsArea());
holder.diffuseEfficiency.setText(context.getString(R.string.edit_location_hint_diffuse_efficiency) +": "+ cities.get(position).getDiffuseEfficiency());
holder.converterPowerLimit.setText(context.getString(R.string.edit_location_hint_converter_power_limit) +": "+ cities.get(position).getConverterPowerLimit());
holder.converterEfficiency.setText(context.getString(R.string.edit_location_hint_cells_efficiency) +": "+ cities.get(position).getConverterEfficiency());
}
/**
* @see RecyclerView.Adapter#getItemCount()
*/
@Override
public int getItemCount() {
return cities.size();
}
/**
* @see ItemTouchHelperAdapter#onItemDismiss(int)
* Removes an item from the list.
*/
@Override
public void onItemDismiss(int position) {
CityToWatch city = cities.get(position);
database.deleteCityToWatch(city);
cities.remove(position);
notifyItemRemoved(position);
}
/**
* @see ItemTouchHelperAdapter#onItemMove(int, int)
*/
@Override
public void onItemMove(int fromPosition, int toPosition) {
// For updating the database records
CityToWatch fromCityToWatch = cities.get(fromPosition);
int fromRank = fromCityToWatch.getRank();
CityToWatch toCityToWatch = cities.get(toPosition);
int toRank = toCityToWatch.getRank();
fromCityToWatch.setRank(toRank);
toCityToWatch.setRank(fromRank);
database.updateCityToWatch(fromCityToWatch);
database.updateCityToWatch(toCityToWatch);
Collections.swap(cities, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
}
public CityToWatch getCitytoWatch(int position){
return cities.get(position);
}
public void updateCity(int position, String cityName, float latitude, float longitude, float azimuth, float elevation, float cellsMaxPower, float cellsArea, float cellsEfficiency, float diffuseEfficiency, float converterPowerLimit, float converterEfficiency) {
CityToWatch cityToWatch = cities.get(position);
cityToWatch.setCityName(cityName);
cityToWatch.setLatitude(latitude);
cityToWatch.setLongitude(longitude);
cityToWatch.setAzimuthAngle(azimuth);
cityToWatch.setElevationAngle(elevation);
cityToWatch.setCellsMaxPower(cellsMaxPower);
cityToWatch.setCellsArea(cellsArea);
cityToWatch.setCellsEfficiency(cellsEfficiency);
cityToWatch.setDiffuseEfficiency(diffuseEfficiency);
cityToWatch.setConverterPowerLimit(converterPowerLimit);
cityToWatch.setConverterEfficiency(converterEfficiency);
database.updateCityToWatch(cityToWatch);
notifyItemChanged(position);
}
}

View file

@ -0,0 +1,47 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import org.woheller69.weather.R;
/**
* This class defines a separator for recycler views. The code has been taken from N J's answer from
* http://stackoverflow.com/questions/31242812/how-can-a-divider-line-be-added-in-an-android-recyclerview
* as of 2016-09-04 and was slightly modified.
*/
public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public SimpleDividerItemDecoration(Context context) {
mDivider = ContextCompat.getDrawable(context, R.drawable.recycle_view_line_divider);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
Log.d("debug", "Top: " + top);
int bottom = top + mDivider.getIntrinsicHeight();
Log.d("debug", "Bottom: " + top);
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}

View file

@ -0,0 +1,72 @@
package org.woheller69.weather.ui.RecycleList;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
/**
* To use the ItemTouchHelper we need to create an ItemTouchHelper.Callback which this class is.
* For the most part it has been taken from
* https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.hmhbe8sku
* as of 2016-08-03
*/
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final ItemTouchHelperAdapter adapter;
/**
* Constructor.
*
* @param adapter The adapter to bind the ItemTouchHelper to.
*/
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
this.adapter = adapter;
}
/**
* @see ItemTouchHelper.Callback#isLongPressDragEnabled()
* As it is not supported, false will be returned.
*/
@Override
public boolean isLongPressDragEnabled() {
return true;
}
/**
* @see ItemTouchHelper.Callback#isItemViewSwipeEnabled()
* As this feature is supported, true will be returned.
*/
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
/**
* @see androidx.recyclerview.widget.ItemTouchHelper.Callback#getMovementFlags(RecyclerView, RecyclerView.ViewHolder)
* Sets the swipe flags for start and end.
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
/**
* @see androidx.recyclerview.widget.ItemTouchHelper.Callback#onMove(RecyclerView, RecyclerView.ViewHolder, RecyclerView.ViewHolder)
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
adapter.onItemMove(viewHolder.getBindingAdapterPosition(), target.getBindingAdapterPosition());
return true;
}
/**
* @see androidx.recyclerview.widget.ItemTouchHelper.Callback#onSwiped(RecyclerView.ViewHolder, int)
* On swipe, the corresponding element is removed from the list.
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.onItemDismiss(viewHolder.getBindingAdapterPosition());
}
}

View file

@ -0,0 +1,146 @@
package org.woheller69.weather.ui.RecycleList;
import android.content.Context;
import androidx.core.content.res.ResourcesCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.woheller69.weather.R;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.ui.Help.StringFormatUtils;
import org.woheller69.weather.ui.UiResourceProvider;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* Created by yonjuni on 02.01.17.
*/
public class WeekWeatherAdapter extends RecyclerView.Adapter<WeekWeatherAdapter.WeekForecastViewHolder> {
private Context context;
private float[][] forecastData;
private int cityID;
private Date courseOfDayHeaderDate;
WeekWeatherAdapter(Context context, float[][] forecastData, int cityID) {
this.context = context;
this.cityID = cityID;
this.forecastData = forecastData;
if (forecastData!=null && forecastData.length!=0 && forecastData[0]!=null) {
this.courseOfDayHeaderDate = new Date((long) forecastData[0][8]); //init with date of first weekday
} else this.courseOfDayHeaderDate = new Date(); //fallback if no data available
}
public void setCourseOfDayHeaderDate(Date courseOfDayHeaderDate){
Date oldDate=this.courseOfDayHeaderDate;
this.courseOfDayHeaderDate=courseOfDayHeaderDate;
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT"));
c.setTime(oldDate);
int oldDay=c.get(Calendar.DAY_OF_MONTH);
c.setTime(courseOfDayHeaderDate);
int newDay=c.get(Calendar.DAY_OF_MONTH);
if (newDay!=oldDay){ //Refresh viewholder only of day has changed
notifyDataSetChanged();
}
}
@Override
public WeekForecastViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_week_forecast, parent, false);
return new WeekForecastViewHolder(view);
}
@Override
public void onBindViewHolder(WeekForecastViewHolder holder, int position) {
float[] dayValues = forecastData[position];
if (dayValues.length!=11) return; //Fixes app crash if forecastData not yet ready.
SQLiteHelper dbHelper = SQLiteHelper.getInstance(context);
CurrentWeatherData currentWeather = dbHelper.getCurrentWeatherByCityId(cityID);
Calendar forecastTime = Calendar.getInstance();
forecastTime.setTimeZone(TimeZone.getTimeZone("GMT"));
forecastTime.setTimeInMillis((long) dayValues[8]);
boolean isDay;
if (currentWeather.getTimeSunrise()==0 || currentWeather.getTimeSunset()==0) {
if ((dbHelper.getCityToWatch(cityID).getLatitude()) > 0) { //northern hemisphere
isDay = forecastTime.get(Calendar.DAY_OF_YEAR) >= 80 && forecastTime.get(Calendar.DAY_OF_YEAR) <= 265; //from March 21 to September 22 (incl)
} else { //southern hemisphere
isDay = forecastTime.get(Calendar.DAY_OF_YEAR) < 80 || forecastTime.get(Calendar.DAY_OF_YEAR) > 265;
}
} else {
isDay = true;
}
setIcon((int) dayValues[9], holder.weather, isDay);
if (dayValues[4] == 0)
holder.power.setText("-");
else
holder.power.setText(StringFormatUtils.formatDecimal(dayValues[4], context.getString(R.string.units_kWh)));
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT"));
c.setTimeInMillis((long) dayValues[8]);
int day = c.get(Calendar.DAY_OF_WEEK);
holder.day.setText(StringFormatUtils.getDayShort(day));
day=c.get(Calendar.DAY_OF_MONTH);
c.setTimeInMillis(courseOfDayHeaderDate.getTime());
int dayheader=c.get(Calendar.DAY_OF_MONTH);
if (dayheader==day) {
holder.itemView.setBackground(ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_highlight,null));
}else{
holder.itemView.setBackground(ResourcesCompat.getDrawable(context.getResources(),R.drawable.rounded_transparent,null));
}
}
@Override
public int getItemCount() {
if (forecastData!=null)
return forecastData.length;
else
return 0;
}
class WeekForecastViewHolder extends RecyclerView.ViewHolder {
TextView day;
ImageView weather;
TextView power;
WeekForecastViewHolder(View itemView) {
super(itemView);
day = itemView.findViewById(R.id.week_forecast_day);
weather = itemView.findViewById(R.id.week_forecast_weather);
power = itemView.findViewById(R.id.week_forecast_power);
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public void setIcon(int value, ImageView imageView, boolean isDay) {
imageView.setImageResource(UiResourceProvider.getIconResourceForWeatherCategory(value, isDay));
}
}

View file

@ -0,0 +1,304 @@
package org.woheller69.weather.ui;
import org.woheller69.weather.R;
import org.woheller69.weather.weather_api.IApiToDatabaseConversion.WeatherCategories;
/**
* This static class provides image / icon resources for the UI.
*/
public class UiResourceProvider {
/**
* Private constructor in order to make this class static.
*/
private UiResourceProvider() {
}
/**
* @param categoryNumber The category number. See IApiToDatabaseConversion#WeatherCategories
* for details.
* @param isDay True if TimeStamp between sunrise and sunset
* @return Returns the icon (resource) that belongs to the given category number.
*/
public static int getIconResourceForWeatherCategory(int categoryNumber, boolean isDay) {
if (categoryNumber == WeatherCategories.CLEAR_SKY.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_00d;
} else {
return R.mipmap.wmo_icon_00n;
}
} else if (categoryNumber == WeatherCategories.FEW_CLOUDS.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_01d;
} else {
return R.mipmap.wmo_icon_01n;
}
} else if (categoryNumber == WeatherCategories.SCATTERED_CLOUDS.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_02d;
} else {
return R.mipmap.wmo_icon_02n;
}
} else if (categoryNumber == WeatherCategories.OVERCAST_CLOUDS.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_03d;
} else {
return R.mipmap.wmo_icon_03n;
}
} else if (categoryNumber == WeatherCategories.MIST.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_45d;
} else {
return R.mipmap.wmo_icon_45n;
}
} else if (categoryNumber == WeatherCategories.DRIZZLE_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_53d;
} else {
return R.mipmap.wmo_icon_53n;
}
} else if (categoryNumber == WeatherCategories.FREEZING_DRIZZLE_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_57d;
} else {
return R.mipmap.wmo_icon_57n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_61d;
} else {
return R.mipmap.wmo_icon_61n;
}
} else if (categoryNumber == WeatherCategories.MODERATE_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_63d;
} else {
return R.mipmap.wmo_icon_63n;
}
} else if (categoryNumber == WeatherCategories.HEAVY_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_65d;
} else {
return R.mipmap.wmo_icon_65n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_80d;
} else {
return R.mipmap.wmo_icon_80n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_81d;
} else {
return R.mipmap.wmo_icon_81n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_71d;
} else {
return R.mipmap.wmo_icon_71n;
}
} else if (categoryNumber == WeatherCategories.MODERATE_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_73d;
} else {
return R.mipmap.wmo_icon_73n;
}
} else if (categoryNumber == WeatherCategories.HEAVY_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_75d;
} else {
return R.mipmap.wmo_icon_75n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_FREEZING_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_66d;
} else {
return R.mipmap.wmo_icon_66n;
}
} else if (categoryNumber == WeatherCategories.FREEZING_RAIN.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_67d;
} else {
return R.mipmap.wmo_icon_67n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SHOWER_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_85d;
} else {
return R.mipmap.wmo_icon_85n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_86d;
} else {
return R.mipmap.wmo_icon_86n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_RAIN_SNOW.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_84d;
} else {
return R.mipmap.wmo_icon_84n;
}
} else if (categoryNumber == WeatherCategories.THUNDERSTORM.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_95d;
} else {
return R.mipmap.wmo_icon_95n;
}
} else if (categoryNumber == WeatherCategories.THUNDERSTORM_HAIL.getNumVal()) {
if (isDay) {
return R.mipmap.wmo_icon_96d;
} else {
return R.mipmap.wmo_icon_96n;
}
} else { //this should not occur
return R.mipmap.wmo_icon_error;
}
}
/**
* @param categoryNumber The category number. See IApiToDatabaseConversion#WeatherCategories
* for details.
* @param isDay True if TimeStamp between sunrise and sunset
* @return Returns the image resource that belongs to the given category number.
*/
public static int getImageResourceForWeatherCategory(int categoryNumber, boolean isDay) {
if (categoryNumber == WeatherCategories.CLEAR_SKY.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_00d;
} else {
return R.drawable.wmo_image_00n;
}
} else if (categoryNumber == WeatherCategories.FEW_CLOUDS.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_01d;
} else {
return R.drawable.wmo_image_01n;
}
} else if (categoryNumber == WeatherCategories.SCATTERED_CLOUDS.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_02d;
} else {
return R.drawable.wmo_image_02n;
}
} else if (categoryNumber == WeatherCategories.OVERCAST_CLOUDS.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_03d;
} else {
return R.drawable.wmo_image_03n;
}
} else if (categoryNumber == WeatherCategories.MIST.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_45d;
} else {
return R.drawable.wmo_image_45n;
}
} else if (categoryNumber == WeatherCategories.DRIZZLE_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_53d;
} else {
return R.drawable.wmo_image_53n;
}
} else if (categoryNumber == WeatherCategories.FREEZING_DRIZZLE_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_57d;
} else {
return R.drawable.wmo_image_57n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_61d;
} else {
return R.drawable.wmo_image_61n;
}
} else if (categoryNumber == WeatherCategories.MODERATE_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_63d;
} else {
return R.drawable.wmo_image_63n;
}
} else if (categoryNumber == WeatherCategories.HEAVY_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_65d;
} else {
return R.drawable.wmo_image_65n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_80d;
} else {
return R.drawable.wmo_image_80n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_81d;
} else {
return R.drawable.wmo_image_81n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_71d;
} else {
return R.drawable.wmo_image_71n;
}
} else if (categoryNumber == WeatherCategories.MODERATE_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_73d;
} else {
return R.drawable.wmo_image_73n;
}
} else if (categoryNumber == WeatherCategories.HEAVY_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_75d;
} else {
return R.drawable.wmo_image_75n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_FREEZING_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_66d;
} else {
return R.drawable.wmo_image_66n;
}
} else if (categoryNumber == WeatherCategories.FREEZING_RAIN.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_67d;
} else {
return R.drawable.wmo_image_67n;
}
} else if (categoryNumber == WeatherCategories.LIGHT_SHOWER_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_85d;
} else {
return R.drawable.wmo_image_85n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_86d;
} else {
return R.drawable.wmo_image_86n;
}
} else if (categoryNumber == WeatherCategories.SHOWER_RAIN_SNOW.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_84d;
} else {
return R.drawable.wmo_image_84n;
}
} else if (categoryNumber == WeatherCategories.THUNDERSTORM.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_95d;
} else {
return R.drawable.wmo_image_95n;
}
} else if (categoryNumber == WeatherCategories.THUNDERSTORM_HAIL.getNumVal()) {
if (isDay) {
return R.drawable.wmo_image_96d;
} else {
return R.drawable.wmo_image_96n;
}
} else { //this should not occur
return R.drawable.wmo_image_error;
}
}
}

View file

@ -0,0 +1,150 @@
package org.woheller69.weather.ui;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.CHART;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.DAY;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.DETAILS;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.EMPTY;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.OVERVIEW;
import static org.woheller69.weather.ui.RecycleList.CityWeatherAdapter.WEEK;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.woheller69.weather.R;
import org.woheller69.weather.activities.ForecastCityActivity;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.ui.RecycleList.CityWeatherAdapter;
import org.woheller69.weather.ui.RecycleList.OnSwipeDownListener;
import org.woheller69.weather.ui.updater.IUpdateableCityUI;
import org.woheller69.weather.ui.updater.ViewUpdater;
import org.woheller69.weather.ui.viewPager.WeatherPagerAdapter;
import java.util.List;
public class WeatherCityFragment extends Fragment implements IUpdateableCityUI {
private static final int MINGRIDWIDTH = 500;
private int mCityId = -1;
private int[] mDataSetTypes = new int[]{};
private static int[] mFull = {DAY, WEEK, CHART}; //TODO Make dynamic from Settings
private static int[] mEmpty = {EMPTY};
private CityWeatherAdapter mAdapter;
private RecyclerView recyclerView;
public static WeatherCityFragment newInstance(Bundle args)
{
WeatherCityFragment weatherCityFragment = new WeatherCityFragment();
weatherCityFragment.setArguments(args);
return weatherCityFragment;
}
public void setAdapter(CityWeatherAdapter adapter) {
mAdapter = adapter;
if (recyclerView != null) {
recyclerView.setAdapter(mAdapter);
recyclerView.setFocusable(false);
recyclerView.setLayoutManager(getLayoutManager(getContext())); //fixes problems with StaggeredGrid: After refreshing data only empty space shown below tab
}
}
public void loadData() {
CurrentWeatherData currentWeatherData = SQLiteHelper.getInstance(getContext()).getCurrentWeatherByCityId(mCityId);
if (currentWeatherData.getTimestamp()==0) mDataSetTypes=mEmpty; //show empty view if no data available yet
else mDataSetTypes=mFull;
mAdapter = new CityWeatherAdapter(currentWeatherData, mDataSetTypes, getContext());
setAdapter(mAdapter);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
ViewUpdater.addSubscriber(this);
}
@Override
public void onDetach() {
ViewUpdater.removeSubscriber(this);
super.onDetach();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_weather_forecast_city_overview, container, false);
recyclerView = v.findViewById(R.id.weatherForecastRecyclerView);
recyclerView.setLayoutManager(getLayoutManager(getContext()));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(-1)){
recyclerView.setOnTouchListener(new OnSwipeDownListener(getContext()) {
public void onSwipeDown() {
WeatherPagerAdapter.refreshSingleData(getContext(),true,mCityId);
ForecastCityActivity.startRefreshAnimation();
}
});
}else recyclerView.setOnTouchListener(null);
}
});
Bundle args = getArguments();
mCityId = args.getInt("city_id");
loadData();
return v;
}
public RecyclerView.LayoutManager getLayoutManager(Context context) {
return new LinearLayoutManager(context);
}
@Override
public void processNewCurrentWeatherData(CurrentWeatherData data) {
if (data != null && data.getCity_id() == mCityId) {
mDataSetTypes= mFull;
setAdapter(new CityWeatherAdapter(data, mDataSetTypes, getContext()));
}
}
@Override
public void processNewForecasts(List<HourlyForecast> hourlyForecasts) {
if (hourlyForecasts != null && hourlyForecasts.size() > 0 && hourlyForecasts.get(0).getCity_id() == mCityId) {
if (mAdapter != null) {
mAdapter.updateForecastData(hourlyForecasts);
}
}
}
@Override
public void processNewWeekForecasts(List<WeekForecast> forecasts) {
if (forecasts != null && forecasts.size() > 0 && forecasts.get(0).getCity_id() == mCityId) {
if (mAdapter != null) {
mAdapter.updateWeekForecastData(forecasts);
}
}
}
}

View file

@ -0,0 +1,18 @@
package org.woheller69.weather.ui.updater;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.WeekForecast;
import java.util.List;
/**
* Created by chris on 24.01.2017.
*/
public interface IUpdateableCityUI {
void processNewCurrentWeatherData(CurrentWeatherData data);
void processNewForecasts(List<HourlyForecast> hourlyForecasts);
void processNewWeekForecasts(List<WeekForecast> forecasts);
}

View file

@ -0,0 +1,47 @@
package org.woheller69.weather.ui.updater;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.WeekForecast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by chris on 24.01.2017.
*/
public class ViewUpdater {
private static List<IUpdateableCityUI> subscribers = new ArrayList<>();
public static void addSubscriber(IUpdateableCityUI sub) {
if (!subscribers.contains(sub)) {
subscribers.add(sub);
}
}
public static void removeSubscriber(IUpdateableCityUI sub) {
subscribers.remove(sub);
}
public static void updateCurrentWeatherData(CurrentWeatherData data) {
ArrayList<IUpdateableCityUI> subcopy = new ArrayList<>(subscribers); //copy list needed as bugfix for concurrent modification exception
for (IUpdateableCityUI sub : subcopy) {
sub.processNewCurrentWeatherData(data);
}
}
public static void updateWeekForecasts(List<WeekForecast> forecasts) {
ArrayList<IUpdateableCityUI> subcopy = new ArrayList<>(subscribers);
for (IUpdateableCityUI sub : subcopy) {
sub.processNewWeekForecasts(forecasts);
}
}
public static void updateForecasts(List<HourlyForecast> hourlyForecasts) {
ArrayList<IUpdateableCityUI> subcopy = new ArrayList<>(subscribers);
for (IUpdateableCityUI sub : subcopy) {
sub.processNewForecasts(hourlyForecasts);
}
}
}

View file

@ -0,0 +1,85 @@
package org.woheller69.weather.ui.util;
/*
* Taken from https://github.com/Truiton/AutoSuggestTextViewAPICall
* Modified by woheller69
*/
import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.woheller69.weather.database.City;
import java.util.ArrayList;
import java.util.List;
public class AutoSuggestAdapter extends ArrayAdapter<String> implements Filterable {
private final List<String> mlistData;
private final List<City> mlistCity;
public AutoSuggestAdapter(@NonNull Context context, int resource) {
super(context, resource);
mlistData = new ArrayList<>();
mlistCity = new ArrayList<>();
}
public void setData(List<String> list, List<City> cityList) {
mlistData.clear();
mlistCity.clear();
mlistData.addAll(list);
mlistCity.addAll(cityList);
}
@Override
public int getCount() {
return mlistData.size();
}
@Nullable
@Override
public String getItem(int position) {
return mlistData.get(position);
}
/**
* Used to Return the full object directly from adapter.
*
* @param position
* @return
*/
public City getObject(int position) {
return mlistCity.get(position);
}
@NonNull
@Override
public Filter getFilter() {
Filter dataFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
if (constraint != null) {
filterResults.values = mlistData;
filterResults.count = mlistData.size();
}
return filterResults;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && (results.count > 0)) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
};
return dataFilter;
}
}

View file

@ -0,0 +1,9 @@
package org.woheller69.weather.ui.util;
/**
* Created by Thomas Glaser on 14.05.2017.
*/
public interface MyConsumer<T> {
void accept(T t);
}

View file

@ -0,0 +1,53 @@
package org.woheller69.weather.ui.util;
import android.content.Context;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
/**
* Created by MG on 04-03-2018.
*
* Taken from https://github.com/Truiton/AutoSuggestTextViewAPICall
* Modified by woheller69
*/
public class geocodingApiCall {
private static geocodingApiCall mInstance;
private RequestQueue mRequestQueue;
private static Context mCtx;
public geocodingApiCall(Context ctx) {
mCtx = ctx.getApplicationContext();
mRequestQueue = getRequestQueue();
}
public static synchronized geocodingApiCall getInstance(Context context) {
if (mInstance == null) {
mInstance = new geocodingApiCall(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public static void make(Context ctx, String query, String url, String lang, Response.Listener<String>
listener, Response.ErrorListener errorListener) {
url = url + query+"&language="+lang;
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
listener, errorListener);
geocodingApiCall.getInstance(ctx).addToRequestQueue(stringRequest);
}
}

View file

@ -0,0 +1,119 @@
package org.woheller69.weather.ui.viewPager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.services.UpdateDataService;
import org.woheller69.weather.ui.WeatherCityFragment;
import org.woheller69.weather.ui.updater.IUpdateableCityUI;
import java.util.Collections;
import java.util.List;
import static androidx.core.app.JobIntentService.enqueueWork;
import static org.woheller69.weather.services.UpdateDataService.SKIP_UPDATE_INTERVAL;
/**
* Created by thomagglaser on 07.08.2017.
*/
public class WeatherPagerAdapter extends FragmentStateAdapter implements IUpdateableCityUI {
private Context mContext;
private SQLiteHelper database;
private List<CityToWatch> cities;
public WeatherPagerAdapter(Context context, @NonNull FragmentManager supportFragmentManager, @NonNull Lifecycle lifecycle) {
super(supportFragmentManager,lifecycle);
this.mContext = context;
this.database = SQLiteHelper.getInstance(context);
loadCities();
}
public void loadCities() {
this.cities = database.getAllCitiesToWatch();
Collections.sort(cities, (o1, o2) -> o1.getRank() - o2.getRank());
}
@NonNull
@Override
public WeatherCityFragment createFragment(int position) {
Bundle args = new Bundle();
args.putInt("city_id", cities.get(position).getCityId());
return WeatherCityFragment.newInstance(args);
}
@Override
public int getItemCount() {
return cities.size();
}
public CharSequence getPageTitle(int position) {
return cities.get(position).getCityName();
}
public static void refreshSingleData(Context context, Boolean asap, int cityId) {
Intent intent = new Intent(context, UpdateDataService.class);
intent.setAction(UpdateDataService.UPDATE_SINGLE_ACTION);
intent.putExtra(SKIP_UPDATE_INTERVAL, asap);
intent.putExtra("cityId",cityId);
enqueueWork(context, UpdateDataService.class, 0, intent);
}
@Override
public void processNewCurrentWeatherData(CurrentWeatherData data) {
}
@Override
public void processNewForecasts(List<HourlyForecast> hourlyForecasts) {
//empty because Fragments are subscribers themselves
}
@Override
public void processNewWeekForecasts(List<WeekForecast> forecasts) {
//empty because Fragments are subscribers themselves
}
public int getCityIDForPos(int pos) {
CityToWatch city = cities.get(pos);
return city.getCityId();
}
public int getPosForCityID(int cityID) {
for (int i = 0; i < cities.size(); i++) {
CityToWatch city = cities.get(i);
if (city.getCityId() == cityID) {
return i;
}
}
return -1; //item not found
}
public float getLatForPos(int pos) {
CityToWatch city = cities.get(pos);
return city.getLatitude();
}
public float getLonForPos(int pos) {
CityToWatch city = cities.get(pos);
return city.getLongitude();
}
}

View file

@ -0,0 +1,62 @@
package org.woheller69.weather.weather_api;
/**
* This interface class defines a set of methods that guarantee that even the use of multiple APIs
* result in the same data bases.
*/
public abstract class IApiToDatabaseConversion {
/**
* This enum provides a list of all available weather categories and assigns them a numerical
* value. Please note that is ordered from best weather (CLEAR_SKY) to worst weather
* (THUNDERSTORM).
*/
public enum WeatherCategories {
ERROR(-1),
CLEAR_SKY(0),
FEW_CLOUDS(1),
SCATTERED_CLOUDS(2),
OVERCAST_CLOUDS(3),
MIST(45),
DRIZZLE_RAIN(53),
FREEZING_DRIZZLE_RAIN(57),
LIGHT_RAIN(61),
MODERATE_RAIN(63),
HEAVY_RAIN(65),
LIGHT_FREEZING_RAIN(66),
FREEZING_RAIN(67),
LIGHT_SNOW(71),
MODERATE_SNOW(73),
HEAVY_SNOW(75),
LIGHT_SHOWER_RAIN(80),
SHOWER_RAIN(81),
SHOWER_RAIN_SNOW(84), //only used as icon in week forecasts
LIGHT_SHOWER_SNOW(85),
SHOWER_SNOW(86),
THUNDERSTORM(95),
THUNDERSTORM_HAIL(96);
private int numVal;
WeatherCategories(int numVal) {
this.numVal = numVal;
}
public int getNumVal() {
return numVal;
}
}
/**
* Different APIs will use different representation for weather conditions / categories.
* Internally, they will stored uniformly.
*
* @param category The category to convert into the internal representation.
* @return Returns 10 for clear sky, 20 for (few) clouds, 30 for scattered cloud, 40 for broken
* clouds, 50 for shower rain, 60 for rain, 70 for thunderstorm, 80 for snow, 90 for mist.
*/
public abstract int convertWeatherCategory(String category);
}

View file

@ -0,0 +1,42 @@
package org.woheller69.weather.weather_api;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.WeekForecast;
import java.util.List;
/**
* This interface defines the frame of the functionality to extractCurrentWeatherData weather information from which
* is returned by some API.
*/
public interface IDataExtractor {
/**
* @param data The data that contains the information to instantiate a CurrentWeatherData
* object. In the easiest case this is the (HTTP) response of the One Call API.
* @return Returns the extracted information as a CurrentWeatherData instance.
*/
CurrentWeatherData extractCurrentWeather(String data);
/**
* @param data The data that contains the information to instantiate a Forecast object.
* @return Returns the extracted weather forecast information. In case some error occurs, null
* will be returned.
*/
List<WeekForecast> extractWeekForecast(String data);
/**
* @param data The data that contains the information to instantiate a Forecast object.
* @return Returns the extracted weather forecast information. In case some error occurs, null
* will be returned.
*/
List<HourlyForecast> extractHourlyForecast(String data, int cityID);
/**
* @param data0, data1, data2, data3, data4 contain the information to retrieve the rain for a minute within the next 60min.
* @return Returns a string with a rain drop in case of rain or a - in case of no rain
*/
String extractRain60min(String data0,String data1, String data2, String data3, String data4);
}

View file

@ -0,0 +1,15 @@
package org.woheller69.weather.weather_api;
/**
* This generic interface is for making an HTTP request to some weather API, process the data and
* finally trigger some mechanism to update the UI.
*/
public interface IHttpRequestForWeatherAPI {
/**
* @param lat The latitude of the city to get the data for.
* @param lon The longitude of the city to get the data for.
*/
void perform(float lat, float lon, int cityId);
}

View file

@ -0,0 +1,25 @@
package org.woheller69.weather.weather_api;
import com.android.volley.VolleyError;
/**
* This interface is used for providing methods for processing success and failure scenarios of
* HTTP requests. Implementations of this interface can work with any (weather) API.
*/
public interface IProcessHttpRequest {
/**
* The method that will be executed in case of a successful HTTP request.
*
* @param response The response of the HTTP request.
*/
void processSuccessScenario(String response, int cityId);
/**
* This method will be executed in case any error arose while executing the HTTP request.
*
* @param error The error that occurred while executing the HTTP request.
*/
void processFailScenario(VolleyError error);
}

View file

@ -0,0 +1,185 @@
package org.woheller69.weather.weather_api.open_meteo;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.woheller69.weather.SolarPowerPlant;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.weather_api.IApiToDatabaseConversion;
import org.woheller69.weather.weather_api.IDataExtractor;
import java.util.ArrayList;
import java.util.List;
/**
* This is a concrete implementation for extracting weather data that was retrieved by
* Open-Meteo.
*/
public class OMDataExtractor implements IDataExtractor {
private Context context;
public OMDataExtractor(Context context) {
this.context = context;
}
@Override
public CurrentWeatherData extractCurrentWeather(String data) {
try {
JSONObject jsonData = new JSONObject(data);
CurrentWeatherData weatherData = new CurrentWeatherData();
weatherData.setTimestamp(System.currentTimeMillis() / 1000);
IApiToDatabaseConversion conversion = new OMToDatabaseConversion();
if (jsonData.has("weathercode")) weatherData.setWeatherID(conversion.convertWeatherCategory(jsonData.getString("weathercode")));
if (jsonData.has("temperature")) weatherData.setTemperatureCurrent((float) jsonData.getDouble("temperature"));
if (jsonData.has("windspeed")) weatherData.setWindSpeed((float) jsonData.getDouble("windspeed"));
if (jsonData.has("winddirection")) weatherData.setWindDirection((float) jsonData.getDouble("winddirection"));
weatherData.setTimeSunrise(0L);
weatherData.setTimeSunset(0L);
weatherData.setHumidity(0);
weatherData.setPressure(0);
weatherData.setCloudiness(0);
return weatherData;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<WeekForecast> extractWeekForecast(String data) {
try {
SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context);
List<WeekForecast> weekforecasts = new ArrayList<>();
JSONObject jsonData = new JSONObject(data);
JSONArray timeArray = jsonData.getJSONArray("time");
JSONArray weathercodeArray = jsonData.has("weathercode") ? jsonData.getJSONArray("weathercode") : null;
JSONArray tempMaxArray = jsonData.has("temperature_2m_max") ? jsonData.getJSONArray("temperature_2m_max") : null;
JSONArray tempMinArray = jsonData.has("temperature_2m_min") ? jsonData.getJSONArray("temperature_2m_min") : null;
JSONArray sunriseArray = jsonData.has("sunrise") ? jsonData.getJSONArray("sunrise") : null;
JSONArray sunsetArray = jsonData.has("sunset") ? jsonData.getJSONArray("sunset") : null;
JSONArray uvIndexArray = jsonData.has("uv_index_max") ? jsonData.getJSONArray("uv_index_max") : null;
JSONArray precipitationArray = jsonData.has("precipitation_sum") ? jsonData.getJSONArray("precipitation_sum") : null;
JSONArray windSpeedArray = jsonData.has("windspeed_10m_max") ? jsonData.getJSONArray("windspeed_10m_max") : null;
JSONArray snowfallArray = jsonData.has("snowfall_sum") ? jsonData.getJSONArray("snowfall_sum") : null;
JSONArray showersArray = jsonData.has("showers_sum") ? jsonData.getJSONArray("showers_sum") : null;
JSONArray rainArray = jsonData.has("rain_sum") ? jsonData.getJSONArray("rain_sum") : null;
IApiToDatabaseConversion conversion = new OMToDatabaseConversion();
for (int i = 0; i < timeArray.length(); i++) {
WeekForecast weekForecast = new WeekForecast();
weekForecast.setTimestamp(System.currentTimeMillis() / 1000);
if (!timeArray.isNull(i)) weekForecast.setForecastTime((timeArray.getLong(i)+12*3600)*1000L); //shift to midday
if (!weathercodeArray.isNull(i)) weekForecast.setWeatherID(conversion.convertWeatherCategory(weathercodeArray.getString(i)));
if (!tempMaxArray.isNull(i)) weekForecast.setMaxTemperature((float) tempMaxArray.getDouble(i));
if (!tempMinArray.isNull(i)) weekForecast.setMinTemperature((float) tempMinArray.getDouble(i));
if (!sunriseArray.isNull(i)) weekForecast.setTimeSunrise(sunriseArray.getLong(i));
if (!sunsetArray.isNull(i)) weekForecast.setTimeSunset(sunsetArray.getLong(i));
if (!uvIndexArray.isNull(i)) {
weekForecast.setUv_index((float) uvIndexArray.getDouble(i));
} else weekForecast.setUv_index(-1);
if (!windSpeedArray.isNull(i)) weekForecast.setWind_speed((float) windSpeedArray.getDouble(i));
weekforecasts.add(weekForecast);
}
return weekforecasts;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* @see IDataExtractor#extractHourlyForecast(String,int)
*/
@Override
public List<HourlyForecast> extractHourlyForecast(String data, int cityID) {
try {
SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context);
List<HourlyForecast> hourlyForecasts = new ArrayList<>();
JSONObject jsonData = new JSONObject(data);
JSONArray timeArray = jsonData.getJSONArray("time");
JSONArray weathercodeArray = jsonData.has("weathercode") ? jsonData.getJSONArray("weathercode") : null;
JSONArray directRadiationArray = jsonData.has("direct_normal_irradiance") ? jsonData.getJSONArray("direct_normal_irradiance") : null;
JSONArray diffuseRadiationArray = jsonData.has("diffuse_radiation") ? jsonData.getJSONArray("diffuse_radiation") : null;
//TODO get Data for power plant from city to Watch
SQLiteHelper dbhelper = SQLiteHelper.getInstance(context);
CityToWatch city = dbhelper.getCityToWatch(cityID);
SolarPowerPlant spp = new SolarPowerPlant(city.getLatitude(), city.getLongitude(), city.getCellsMaxPower(), city.getCellsArea(), city.getCellsEfficiency(),city.getDiffuseEfficiency(), city.getConverterPowerLimit(), city.getConverterEfficiency(), city.getAzimuthAngle(), city.getElevationAngle());
IApiToDatabaseConversion conversion = new OMToDatabaseConversion();
for (int i = 0; i < timeArray.length(); i++) {
HourlyForecast hourlyForecast = new HourlyForecast();
hourlyForecast.setTimestamp(System.currentTimeMillis() / 1000);
if (timeArray!=null) hourlyForecast.setForecastTime(timeArray.getLong(i)*1000L);
if (weathercodeArray!=null) hourlyForecast.setWeatherID(conversion.convertWeatherCategory(weathercodeArray.getString(i)));
if (directRadiationArray!=null) hourlyForecast.setDirectRadiationNormal((float) directRadiationArray.getDouble(i));
if (diffuseRadiationArray!=null) hourlyForecast.setDiffuseRadiation((float) diffuseRadiationArray.getDouble(i));
hourlyForecast.setPower(spp.getPower(hourlyForecast.getDirectRadiationNormal(),hourlyForecast.getDiffuseRadiation(), timeArray.getLong(i)-1800)); //use solar position 1/2h earlier for calculation of average power in preceding hour
hourlyForecasts.add(hourlyForecast);
}
return hourlyForecasts;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* @see IDataExtractor#extractRain60min(String, String, String, String, String)
*/
@Override
public String extractRain60min(String data0,String data1, String data2, String data3, String data4) {
try {
String rain = "";
JSONObject jsonData0 = new JSONObject(data0);
JSONObject jsonData1 = new JSONObject(data1);
JSONObject jsonData2 = new JSONObject(data2);
JSONObject jsonData3 = new JSONObject(data3);
JSONObject jsonData4 = new JSONObject(data4);
double rain5min=jsonData0.getDouble("precipitation")+jsonData1.getDouble("precipitation")+jsonData2.getDouble("precipitation")+jsonData3.getDouble("precipitation")+jsonData4.getDouble("precipitation");
if (rain5min==0){
rain ="\u25a1";
} else if (rain5min<2.5){ // very light rain equals <0.5mm/h (2.5 = 5 x 0.5)
rain ="\u25a4";
}else if (rain5min<12.5){ //light rain equals <2.5mm/h (12.5 = 5 x 2.5)
rain ="\u25a6";
} else{
rain ="\u25a0";
}
return rain;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* @param data The data that contains the information to retrieve the ID of the city.
* If data for a single city were requested, the response string can be
* passed as an argument.
* If data for multiple cities were requested, make sure to pass only one item
* of the response list at a time!
* @return Returns the ID of the city or Integer#MIN_VALUE in case the data is not well-formed
* and the information could not be extracted.
*/
}

View file

@ -0,0 +1,36 @@
package org.woheller69.weather.weather_api.open_meteo;
import android.content.Context;
import androidx.preference.PreferenceManager;
import android.content.SharedPreferences;
import android.text.TextUtils;
import org.woheller69.weather.BuildConfig;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.preferences.AppPreferencesManager;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class OMHttpRequest {
protected String getUrlForQueryingOMweatherAPI(Context context, float lat, float lon) {
AppPreferencesManager prefManager =
new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context));
SharedPreferences sharedPreferences=PreferenceManager.getDefaultSharedPreferences(context);
return String.format(
"%sforecast?latitude=%s&longitude=%s&forecast_days=%s&hourly=diffuse_radiation,direct_normal_irradiance,weathercode&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,uv_index_max,precipitation_sum,windspeed_10m_max&current_weather=true&windspeed_unit=ms&timeformat=unixtime&timezone=auto",
BuildConfig.BASE_URL,
lat,
lon,
sharedPreferences.getInt("pref_number_days",7)
);
}
}

View file

@ -0,0 +1,35 @@
package org.woheller69.weather.weather_api.open_meteo;
import android.content.Context;
import org.woheller69.weather.http.HttpRequestType;
import org.woheller69.weather.http.IHttpRequest;
import org.woheller69.weather.http.VolleyHttpRequest;
import org.woheller69.weather.weather_api.IHttpRequestForWeatherAPI;
/**
* This class provides the functionality for making and processing HTTP requests to Open-Meteo to retrieve the latest weather data for all stored cities.
*/
public class OMHttpRequestForWeatherAPI extends OMHttpRequest implements IHttpRequestForWeatherAPI {
/**
* Member variables.
*/
private Context context;
/**
* @param context The context to use.
*/
public OMHttpRequestForWeatherAPI(Context context) {
this.context = context;
}
@Override
public void perform(float lat, float lon, int cityId) {
IHttpRequest httpRequest = new VolleyHttpRequest(context, cityId);
final String URL = getUrlForQueryingOMweatherAPI(context, lat, lon);
httpRequest.make(URL, HttpRequestType.GET, new ProcessOMweatherAPIRequest(context));
}
}

View file

@ -0,0 +1,57 @@
package org.woheller69.weather.weather_api.open_meteo;
import org.woheller69.weather.weather_api.IApiToDatabaseConversion;
public class OMToDatabaseConversion extends IApiToDatabaseConversion {
@Override
public int convertWeatherCategory(String category) {
int value = Integer.parseInt(category);
if (value == 0) {
return WeatherCategories.CLEAR_SKY.getNumVal();
} else if (value == 1) {
return WeatherCategories.FEW_CLOUDS.getNumVal();
} else if (value == 2) {
return WeatherCategories.SCATTERED_CLOUDS.getNumVal();
} else if (value == 3) {
return WeatherCategories.OVERCAST_CLOUDS.getNumVal();
} else if (value >= 45 && value <= 48) {
return WeatherCategories.MIST.getNumVal();
} else if (value >= 50 && value <= 55) {
return WeatherCategories.DRIZZLE_RAIN.getNumVal();
} else if (value >= 56 && value <= 57) {
return WeatherCategories.FREEZING_DRIZZLE_RAIN.getNumVal();
} else if (value >= 60 && value <= 61) {
return WeatherCategories.LIGHT_RAIN.getNumVal();
} else if (value >= 62 && value <=63) {
return WeatherCategories.MODERATE_RAIN.getNumVal();
} else if (value >= 64 && value <=65) {
return WeatherCategories.HEAVY_RAIN.getNumVal();
} else if (value == 66) {
return WeatherCategories.LIGHT_FREEZING_RAIN.getNumVal();
} else if (value == 67) {
return WeatherCategories.FREEZING_RAIN.getNumVal();
} else if (value == 70 || value == 71 || value == 77) { // 77=snow grain
return WeatherCategories.LIGHT_SNOW.getNumVal();
} else if (value >= 72 && value <=73) {
return WeatherCategories.MODERATE_SNOW.getNumVal();
} else if (value >= 74 && value <=75 ) {
return WeatherCategories.HEAVY_SNOW.getNumVal();
} else if (value == 80 ) {
return WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal();
} else if (value == 81 || value == 82 ) {
return WeatherCategories.SHOWER_RAIN.getNumVal();
} else if (value == 85) {
return WeatherCategories.LIGHT_SHOWER_SNOW.getNumVal();
} else if (value == 86) {
return WeatherCategories.SHOWER_SNOW.getNumVal();
} else if (value == 95) {
return WeatherCategories.THUNDERSTORM.getNumVal();
} else if (value == 96 || value == 99) {
return WeatherCategories.THUNDERSTORM_HAIL.getNumVal();
}
// Fallback: ERROR
return WeatherCategories.ERROR.getNumVal();
}
}

View file

@ -0,0 +1,217 @@
package org.woheller69.weather.weather_api.open_meteo;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.android.volley.VolleyError;
import org.json.JSONException;
import org.json.JSONObject;
import org.woheller69.weather.R;
import org.woheller69.weather.activities.NavigationActivity;
import org.woheller69.weather.database.CityToWatch;
import org.woheller69.weather.database.CurrentWeatherData;
import org.woheller69.weather.database.HourlyForecast;
import org.woheller69.weather.database.WeekForecast;
import org.woheller69.weather.database.SQLiteHelper;
import org.woheller69.weather.ui.updater.ViewUpdater;
import org.woheller69.weather.weather_api.IDataExtractor;
import org.woheller69.weather.weather_api.IProcessHttpRequest;
import androidx.preference.PreferenceManager;
import org.woheller69.weather.weather_api.IApiToDatabaseConversion.WeatherCategories;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class processes the HTTP requests that are made to the Open-Meteo API requesting the
* current weather for all stored cities.
*/
public class ProcessOMweatherAPIRequest implements IProcessHttpRequest {
/**
* Constants
*/
private final String DEBUG_TAG = "process_forecast";
/**
* Member variables
*/
private Context context;
private SQLiteHelper dbHelper;
/**
* Constructor.
*
* @param context The context of the HTTP request.
*/
public ProcessOMweatherAPIRequest(Context context) {
this.context = context;
this.dbHelper = SQLiteHelper.getInstance(context);
}
/**
* Converts the response to JSON and updates the database. Note that for this method no
* UI-related operations are performed.
*
* @param response The response of the HTTP request.
*/
@Override
public void processSuccessScenario(String response, int cityId) {
IDataExtractor extractor = new OMDataExtractor(context);
try {
JSONObject json = new JSONObject(response);
//Extract daily weather
dbHelper.deleteWeekForecastsByCityId(cityId);
List<WeekForecast> weekforecasts = new ArrayList<>();
weekforecasts = extractor.extractWeekForecast(json.getString("daily"));
if (weekforecasts!=null && !weekforecasts.isEmpty()){
for (WeekForecast weekForecast: weekforecasts){
weekForecast.setCity_id(cityId);
}
} else {
final String ERROR_MSG = context.getResources().getString(R.string.error_convert_to_json);
if (NavigationActivity.isVisible)
Toast.makeText(context, ERROR_MSG, Toast.LENGTH_LONG).show();
return;
}
//Extract current weather
String rain60min=context.getResources().getString(R.string.error_no_rain60min_data);
CurrentWeatherData weatherData = extractor.extractCurrentWeather(json.getString("current_weather"));
if (weatherData == null) {
final String ERROR_MSG = context.getResources().getString(R.string.error_convert_to_json);
if (NavigationActivity.isVisible)
Toast.makeText(context, ERROR_MSG, Toast.LENGTH_LONG).show();
} else {
weatherData.setCity_id(cityId);
weatherData.setRain60min(rain60min);
weatherData.setTimeSunrise(weekforecasts.get(0).getTimeSunrise());
weatherData.setTimeSunset(weekforecasts.get(0).getTimeSunset());
weatherData.setTimeZoneSeconds(json.getInt("utc_offset_seconds"));
CurrentWeatherData current = dbHelper.getCurrentWeatherByCityId(cityId);
if (current != null && current.getCity_id() == cityId) {
dbHelper.updateCurrentWeather(weatherData);
} else {
dbHelper.addCurrentWeather(weatherData);
}
}
//Extract hourly weather
dbHelper.deleteForecastsByCityId(cityId);
List<HourlyForecast> hourlyforecasts = new ArrayList<>();
hourlyforecasts = extractor.extractHourlyForecast(json.getString("hourly"), cityId);
if (hourlyforecasts!=null && !hourlyforecasts.isEmpty()){
for (HourlyForecast hourlyForecast: hourlyforecasts){
hourlyForecast.setCity_id(cityId);
}
} else {
final String ERROR_MSG = context.getResources().getString(R.string.error_convert_to_json);
if (NavigationActivity.isVisible)
Toast.makeText(context, ERROR_MSG, Toast.LENGTH_LONG).show();
return;
}
dbHelper.addForecasts(hourlyforecasts);
weekforecasts = reanalyzeWeekIDs(weekforecasts, hourlyforecasts);
dbHelper.addWeekForecasts(weekforecasts);
ViewUpdater.updateCurrentWeatherData(weatherData);
ViewUpdater.updateWeekForecasts(weekforecasts);
ViewUpdater.updateForecasts(hourlyforecasts);
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* Reanalyze weekforecasts and improve weather codes which are not representative for the day
* @param weekforecasts
* @param hourlyforecasts
* @return
*/
private List<WeekForecast> reanalyzeWeekIDs(List<WeekForecast> weekforecasts, List<HourlyForecast> hourlyforecasts) {
Map<Integer,Integer> mappingTable = new HashMap<>();
mappingTable.put(WeatherCategories.OVERCAST_CLOUDS.getNumVal(),WeatherCategories.SCATTERED_CLOUDS.getNumVal());
mappingTable.put(WeatherCategories.MIST.getNumVal(),WeatherCategories.SCATTERED_CLOUDS.getNumVal());
mappingTable.put(WeatherCategories.DRIZZLE_RAIN.getNumVal(),WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.FREEZING_DRIZZLE_RAIN.getNumVal(),WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.LIGHT_RAIN.getNumVal(),WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.LIGHT_FREEZING_RAIN.getNumVal(),WeatherCategories.LIGHT_SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.MODERATE_RAIN.getNumVal(),WeatherCategories.SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.HEAVY_RAIN.getNumVal(),WeatherCategories.SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.FREEZING_RAIN.getNumVal(),WeatherCategories.SHOWER_RAIN.getNumVal());
mappingTable.put(WeatherCategories.LIGHT_SNOW.getNumVal(),WeatherCategories.LIGHT_SHOWER_SNOW.getNumVal());
mappingTable.put(WeatherCategories.MODERATE_SNOW.getNumVal(),WeatherCategories.SHOWER_SNOW.getNumVal());
mappingTable.put(WeatherCategories.HEAVY_SNOW.getNumVal(),WeatherCategories.SHOWER_SNOW.getNumVal());
Map<Integer,Integer> sunTable = new HashMap<>();
sunTable.put(WeatherCategories.CLEAR_SKY.getNumVal(), 0);
sunTable.put(WeatherCategories.FEW_CLOUDS.getNumVal(), 0);
sunTable.put(WeatherCategories.SCATTERED_CLOUDS.getNumVal(), 0);
for (WeekForecast weekForecast: weekforecasts){
Integer ID = weekForecast.getWeatherID();
if (mappingTable.containsKey(ID)){
int totalCount = 0;
int sunCount = 0;
long sunrise = weekForecast.getTimeSunrise()*1000L;
long sunset = weekForecast.getTimeSunset()*1000L;
for (HourlyForecast hourlyForecast: hourlyforecasts){
if(hourlyForecast.getForecastTime() >= sunrise && hourlyForecast.getForecastTime() <= sunset){
totalCount++;
if(sunTable.containsKey(hourlyForecast.getWeatherID())) sunCount++;
}
}
if (totalCount>0 && (float)sunCount/totalCount>0.2f) weekForecast.setWeatherID(mappingTable.get(ID));
}
}
for (WeekForecast weekForecast: weekforecasts){
float totalEnergy = 0;
Long timeNoon = weekForecast.getForecastTime();
for (HourlyForecast hourlyForecast: hourlyforecasts){
if ((hourlyForecast.getForecastTime()>=timeNoon-12*3600*1000L) && (hourlyForecast.getForecastTime()< timeNoon + 12*3600*1000L)){
totalEnergy+=hourlyForecast.getPower();
}
}
weekForecast.setPrecipitation(totalEnergy/1000);
}
return weekforecasts;
}
/**
* Shows an error that the data could not be retrieved.
*
* @param error The error that occurred while executing the HTTP request.
*/
@Override
public void processFailScenario(final VolleyError error) {
Handler h = new Handler(this.context.getMainLooper());
h.post(new Runnable() {
@Override
public void run() {
if (NavigationActivity.isVisible) Toast.makeText(context, context.getResources().getString(R.string.error_fetch_forecast), Toast.LENGTH_LONG).show();
}
});
}
}