Better data display, tabs for each day, preferences
This commit is contained in:
@@ -2,8 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<!-- There is no need for fine location. Weather stations don't exist on every square meter of
|
|
||||||
the world. -->
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
@@ -11,13 +9,23 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:targetApi="35">
|
tools:targetApi="35">
|
||||||
<activity android:name=".MainActivity"
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/preferences_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -3,48 +3,85 @@ package eur.one00kb.minipogodynka;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.TabActivity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.location.Address;
|
||||||
|
import android.location.Geocoder;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Button;
|
import android.view.Menu;
|
||||||
import android.widget.TextView;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.GridView;
|
||||||
|
import android.widget.TabHost;
|
||||||
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.util.ArrayList;
|
||||||
import java.net.MalformedURLException;
|
import java.util.Date;
|
||||||
import java.net.URL;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import eur.one00kb.minipogodynka.weather.WeatherAPIFetcher;
|
||||||
import eur.one00kb.minipogodynka.weather.WeatherData;
|
import eur.one00kb.minipogodynka.weather.WeatherData;
|
||||||
|
import eur.one00kb.minipogodynka.weather.WeatherJSONParser;
|
||||||
|
|
||||||
public class MainActivity extends Activity implements LocationListener {
|
/**
|
||||||
private static final String API_FORMAT = "https://api.open-meteo" +
|
* @noinspection deprecation
|
||||||
".com/v1/forecast?latitude=%f&longitude=%f¤t=temperature_2m," +
|
*/
|
||||||
"relative_humidity_2m,rain,surface_pressure,wind_speed_10m";
|
public class MainActivity extends TabActivity implements LocationListener, TabHost.TabContentFactory {
|
||||||
|
private PreferenceHolder mPreferenceHolder;
|
||||||
private LocationManager mLocManager;
|
private LocationManager mLocManager;
|
||||||
private Button mUpdateButton;
|
private Geocoder mGeocoder;
|
||||||
|
|
||||||
private double mLat, mLong;
|
private Location mCurrentLocation;
|
||||||
private WeatherData mData;
|
private Address mCurrentAddress;
|
||||||
|
private Map<Date, WeatherData> mWeatherData;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mData = new WeatherData();
|
mPreferenceHolder = new PreferenceHolder(this);
|
||||||
mLocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
mLocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
mGeocoder = new Geocoder(this, Locale.getDefault());
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setDefaultTab(0);
|
||||||
mUpdateButton = findViewById(R.id.btn_update);
|
}
|
||||||
mUpdateButton.setOnClickListener((button) -> updateWeather());
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
if (id == R.id.action_locate) {
|
||||||
|
updateWeather();
|
||||||
|
} else if (id == R.id.action_settings) {
|
||||||
|
Intent itn = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(itn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTabsForDays(Date date) {
|
||||||
|
TabHost host = getTabHost();
|
||||||
|
host.clearAllTabs();
|
||||||
|
|
||||||
|
for (int i = 0; i < mPreferenceHolder.getForecastDays(); ++i) {
|
||||||
|
host.addTab(host.newTabSpec(Integer.toString(i))
|
||||||
|
.setIndicator(String.format("%ta", date)).setContent(this));
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
@@ -59,6 +96,7 @@ public class MainActivity extends Activity implements LocationListener {
|
|||||||
checkAndRequestPerms();
|
checkAndRequestPerms();
|
||||||
// First, we try to get the location of the user.
|
// First, we try to get the location of the user.
|
||||||
if (isLocationAllowed()) {
|
if (isLocationAllowed()) {
|
||||||
|
setTitle("Locating…");
|
||||||
mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
|
mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,39 +108,58 @@ public class MainActivity extends Activity implements LocationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateInfo() {
|
private void updateInfo() {
|
||||||
TextView test1 = findViewById(R.id.temp_day);
|
if (mCurrentAddress != null) {
|
||||||
TextView test2 = findViewById(R.id.temp_night);
|
setTitle(mCurrentAddress.getLocality());
|
||||||
test1.setText(mData.getTemperature());
|
} else {
|
||||||
test2.setText(mData.getTemperature());
|
setTitle("Unknown location");
|
||||||
|
}
|
||||||
|
|
||||||
|
createTabsForDays(Utilities.getDateOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getJson() {
|
private void getJson() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
String apiData = WeatherAPIFetcher.fetchOpenMeteo(mCurrentLocation.getLatitude(),
|
||||||
URL url = new URL(String.format(Locale.ROOT, API_FORMAT, mLat, mLong));
|
mCurrentLocation.getLongitude());
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
|
mWeatherData = WeatherJSONParser.parseOpenMeteo(apiData);
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
builder.append(line);
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
mData.fromJson(builder.toString());
|
|
||||||
runOnUiThread(this::updateInfo);
|
runOnUiThread(this::updateInfo);
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
mLat = location.getLatitude();
|
mCurrentLocation = location;
|
||||||
mLong = location.getLongitude();
|
|
||||||
mLocManager.removeUpdates(this);
|
mLocManager.removeUpdates(this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Address> addresses = mGeocoder.getFromLocation(location.getLatitude(),
|
||||||
|
location.getLongitude(), 1);
|
||||||
|
if (addresses != null && addresses.size() > 0) {
|
||||||
|
mCurrentAddress = addresses.get(0);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
getJson();
|
getJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View createTabContent(String tag) {
|
||||||
|
GridView view = (GridView) getLayoutInflater().inflate(R.layout.tab_weather, null);
|
||||||
|
|
||||||
|
Date forApiDate = Utilities.getDateOnly();
|
||||||
|
forApiDate.setDate(forApiDate.getDate() + Integer.parseInt(tag));
|
||||||
|
ArrayList<WeatherData> forAdapter = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = forApiDate.getHours(); i < 24; i += mPreferenceHolder.getForecastType()) {
|
||||||
|
forApiDate.setHours(i);
|
||||||
|
forAdapter.add(mWeatherData.get(forApiDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherDataAdapter adapter = new WeatherDataAdapter(this, forAdapter);
|
||||||
|
view.setAdapter(adapter);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package eur.one00kb.minipogodynka;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection deprecation
|
||||||
|
*/
|
||||||
|
public class PreferenceHolder implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
private final Map<String, Object> mPreferenceValues;
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") //NON-NLS
|
||||||
|
public PreferenceHolder(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||||
|
PreferenceManager.setDefaultValues(mContext, R.xml.preferences, false);
|
||||||
|
mPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
mPreferenceValues = (Map<String, Object>) mPreferences.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
Object value = sharedPreferences.getAll().get(key);
|
||||||
|
if (value != null) {
|
||||||
|
mPreferenceValues.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getOrDefault(String key, Object defaultValue) {
|
||||||
|
Object value = mPreferenceValues.get(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvider() {
|
||||||
|
return (String) getOrDefault(mContext.getString(R.string.preferences_key_provider),
|
||||||
|
mContext.getString(R.string.preferences_default_provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getForecastDays() {
|
||||||
|
return Integer.parseInt((String)
|
||||||
|
getOrDefault(mContext.getString(R.string.preferences_key_forecast_days),
|
||||||
|
mContext.getString(R.string.preferences_default_forecast_days)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getForecastType() {
|
||||||
|
return Integer.parseInt((String)
|
||||||
|
getOrDefault(mContext.getString(R.string.preferences_key_forecast_type),
|
||||||
|
mContext.getString(R.string.preferences_default_forecast_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package eur.one00kb.minipogodynka;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection deprecation
|
||||||
|
*/
|
||||||
|
public class SettingsActivity extends PreferenceActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package eur.one00kb.minipogodynka;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Utilities {
|
||||||
|
public static Date getDateOnly() {
|
||||||
|
Date date = new Date();
|
||||||
|
date.setHours(0);
|
||||||
|
date.setMinutes(0);
|
||||||
|
date.setSeconds(0);
|
||||||
|
date.setTime(date.getTime() - (date.getTime() % 1000));
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package eur.one00kb.minipogodynka;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import eur.one00kb.minipogodynka.weather.WeatherData;
|
||||||
|
|
||||||
|
public class WeatherDataAdapter extends ArrayAdapter<WeatherData> {
|
||||||
|
public WeatherDataAdapter(Context context, ArrayList<WeatherData> wdList) {
|
||||||
|
super(context, 0, wdList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_weather, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherData data = getItem(position);
|
||||||
|
TextView time = convertView.findViewById(R.id.time);
|
||||||
|
TextView temperature = convertView.findViewById(R.id.temperature);
|
||||||
|
TextView precipitation = convertView.findViewById(R.id.precipitation);
|
||||||
|
|
||||||
|
time.setText(String.format("%tH:%<tM", data.date));
|
||||||
|
temperature.setText(String.format("%.1f", data.temperature));
|
||||||
|
precipitation.setText(String.format("%.0f %%", data.precipitation));
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package eur.one00kb.minipogodynka.weather;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class WeatherAPIFetcher {
|
||||||
|
private static final String OM_FORMAT = "https://api.open-meteo" +
|
||||||
|
".com/v1/forecast?latitude=%f&longitude=%f&hourly=temperature_2m," +
|
||||||
|
"relative_humidity_2m,precipitation,pressure_msl,cloud_cover,wind_speed_10m," +
|
||||||
|
"wind_direction_10m,uv_index&wind_speed_unit=ms&forecast_days=7";
|
||||||
|
|
||||||
|
public static String fetchOpenMeteo(double lat, double lon) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(String.format(Locale.US, OM_FORMAT, lat, lon));
|
||||||
|
return isToString(url.openStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String isToString(InputStream is) throws IOException {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||||
|
String line;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.close();
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,18 @@
|
|||||||
package eur.one00kb.minipogodynka.weather;
|
package eur.one00kb.minipogodynka.weather;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import java.util.Date;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
|
// All the data here can be acquired from the three supported providers. It's universal.
|
||||||
public class WeatherData {
|
public class WeatherData {
|
||||||
private double mTemperature, mWindSpeed, mHumidity, mPressure, mRain;
|
// Although we store the date as the key of a hash map, we store it here additionally to
|
||||||
private String mTempUnit, mWindSpeedUnit, mHumidityUnit, mPressureUnit, mRainUnit;
|
// simplify code.
|
||||||
|
public Date date;
|
||||||
public void fromJson(String jsonStr) {
|
public double temperature;
|
||||||
try {
|
public double humidity;
|
||||||
JSONObject json = new JSONObject(jsonStr);
|
public double precipitation;
|
||||||
JSONObject units = json.getJSONObject("current_units");
|
public double pressure;
|
||||||
|
public double cloudCover;
|
||||||
mTempUnit = units.getString("temperature_2m");
|
public double windSpeed;
|
||||||
mWindSpeedUnit = units.getString("wind_speed_10m");
|
public double windDirection;
|
||||||
mHumidityUnit = units.getString("relative_humidity_2m");
|
public double uvIndex;
|
||||||
mPressureUnit = units.getString("surface_pressure");
|
|
||||||
mRainUnit = units.getString("rain");
|
|
||||||
|
|
||||||
JSONObject current = json.getJSONObject("current");
|
|
||||||
|
|
||||||
mTemperature = current.getDouble("temperature_2m");
|
|
||||||
mWindSpeed = current.getDouble("wind_speed_10m");
|
|
||||||
mHumidity = current.getDouble("relative_humidity_2m");
|
|
||||||
mPressure = current.getDouble("surface_pressure");
|
|
||||||
mRain = current.getDouble("rain");
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTemperature() {
|
|
||||||
return mTemperature + " " + mTempUnit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package eur.one00kb.minipogodynka.weather;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class WeatherJSONParser {
|
||||||
|
private static final DateFormat OM_DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
|
||||||
|
|
||||||
|
public static Map<Date, WeatherData> parseOpenMeteo(String json) {
|
||||||
|
HashMap<Date, WeatherData> hours = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject root = new JSONObject(json);
|
||||||
|
JSONObject hourly = root.getJSONObject("hourly");
|
||||||
|
|
||||||
|
JSONArray times = hourly.getJSONArray("time");
|
||||||
|
JSONArray temperatures = hourly.getJSONArray("temperature_2m");
|
||||||
|
JSONArray humidities = hourly.getJSONArray("relative_humidity_2m");
|
||||||
|
JSONArray precipitations = hourly.getJSONArray("precipitation");
|
||||||
|
JSONArray pressures = hourly.getJSONArray("pressure_msl");
|
||||||
|
JSONArray cloudCovers = hourly.getJSONArray("cloud_cover");
|
||||||
|
JSONArray windSpeeds = hourly.getJSONArray("wind_speed_10m");
|
||||||
|
JSONArray windDirections = hourly.getJSONArray("wind_direction_10m");
|
||||||
|
JSONArray uvIndexes = hourly.getJSONArray("uv_index");
|
||||||
|
|
||||||
|
for (int i = 0; i < times.length(); ++i) {
|
||||||
|
WeatherData data = new WeatherData();
|
||||||
|
data.date = OM_DATEFORMAT.parse(times.getString(i));
|
||||||
|
data.temperature = temperatures.getDouble(i);
|
||||||
|
data.humidity = humidities.getDouble(i);
|
||||||
|
data.precipitation = precipitations.getDouble(i);
|
||||||
|
data.pressure = pressures.getDouble(i);
|
||||||
|
data.cloudCover = cloudCovers.getDouble(i);
|
||||||
|
data.windSpeed = windSpeeds.getDouble(i);
|
||||||
|
data.windDirection = windDirections.getDouble(i);
|
||||||
|
data.uvIndex = uvIndexes.getDouble(i);
|
||||||
|
|
||||||
|
hours.put(OM_DATEFORMAT.parse(times.getString(i)), data);
|
||||||
|
}
|
||||||
|
} catch (JSONException | ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void parseMetNo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void parseMeteoFrance() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/temp_day"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="0 °C"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/temp_night"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="2 °C"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_update"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Update" />
|
|
||||||
|
|
||||||
</TableLayout>
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/placeholder"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/temperature"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/time"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/placeholder"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="34sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/precipitation"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/temperature"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/placeholder"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</RelativeLayout>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:numColumns="3">
|
||||||
|
|
||||||
|
</GridView>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_update"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_item_update" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_search"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_item_search" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_locate"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_item_locate" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_item_settings" />
|
||||||
|
</menu>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string-array name="preferences_entries_provider">
|
||||||
|
<item>Open-Meteo</item>
|
||||||
|
<item>Meteorologisk institutt (MET)</item>
|
||||||
|
<item>Météo-France</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="preferences_values_provider">
|
||||||
|
<item>om</item>
|
||||||
|
<item>met</item>
|
||||||
|
<item>mf</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="preferences_entryvalues_forecast_days">
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
<item>7</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="preferences_entries_forecast_type">
|
||||||
|
<item>Sparse</item>
|
||||||
|
<item>Regular</item>
|
||||||
|
<item>Detailed</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="preferences_values_forecast_type">
|
||||||
|
<item>6</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>1</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -1,4 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">MiniPogodynka</string>
|
<string name="app_name">MiniPogodynka</string>
|
||||||
|
<string name="preferences_name">Settings</string>
|
||||||
|
|
||||||
|
<string name="placeholder">Placeholder</string>
|
||||||
|
|
||||||
|
<string name="menu_item_update">Update</string>
|
||||||
|
<string name="menu_item_search">Search</string>
|
||||||
|
<string name="menu_item_locate">Locate</string>
|
||||||
|
<string name="menu_item_settings">Settings</string>
|
||||||
|
|
||||||
|
<string name="preferences_key_provider">weather_provider</string>
|
||||||
|
<string name="preferences_title_provider">Weather provider</string>
|
||||||
|
<string name="preferences_default_provider">om</string>
|
||||||
|
|
||||||
|
<string name="preferences_key_forecast_days">forecast_days</string>
|
||||||
|
<string name="preferences_title_forecast_days">Forecast days</string>
|
||||||
|
<string name="preferences_default_forecast_days">3</string>
|
||||||
|
|
||||||
|
<string name="preferences_key_forecast_type">forecast_type</string>
|
||||||
|
<string name="preferences_title_forecast_type">Forecast type</string>
|
||||||
|
<string name="preferences_default_forecast_type">6</string>
|
||||||
|
|
||||||
|
<string name="preferences_summary_copyright_info">
|
||||||
|
Copyleft 🄯 Emil Kosz, 2025. License: GPLv3
|
||||||
|
</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--suppress DeprecatedClassUsageInspection -->
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="@string/preferences_default_provider"
|
||||||
|
android:entries="@array/preferences_entries_provider"
|
||||||
|
android:entryValues="@array/preferences_values_provider"
|
||||||
|
android:key="@string/preferences_key_provider"
|
||||||
|
android:title="@string/preferences_title_provider" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="@string/preferences_default_forecast_days"
|
||||||
|
android:entries="@array/preferences_entryvalues_forecast_days"
|
||||||
|
android:entryValues="@array/preferences_entryvalues_forecast_days"
|
||||||
|
android:key="@string/preferences_key_forecast_days"
|
||||||
|
android:title="@string/preferences_title_forecast_days" />
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="@string/preferences_default_forecast_type"
|
||||||
|
android:entries="@array/preferences_entries_forecast_type"
|
||||||
|
android:entryValues="@array/preferences_values_forecast_type"
|
||||||
|
android:key="@string/preferences_key_forecast_type"
|
||||||
|
android:title="@string/preferences_title_forecast_type" />
|
||||||
|
<Preference
|
||||||
|
android:selectable="false"
|
||||||
|
android:summary="@string/preferences_summary_copyright_info" />
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.4.1"
|
agp = "8.7.3"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Wed Jan 15 14:30:26 GMT 2025
|
#Wed Jan 15 14:30:26 GMT 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user