diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dbb0b6c..8f21e33 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ - + android:label="@string/settings_name"> diff --git a/app/src/main/java/eur/one00kb/minipogodynka/LocationSearchView.java b/app/src/main/java/eur/one00kb/minipogodynka/LocationSearchView.java new file mode 100644 index 0000000..a9dd658 --- /dev/null +++ b/app/src/main/java/eur/one00kb/minipogodynka/LocationSearchView.java @@ -0,0 +1,105 @@ +package eur.one00kb.minipogodynka; + +import android.content.Context; +import android.location.Address; +import android.location.Geocoder; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.AutoCompleteTextView; +import android.widget.LinearLayout; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import eur.one00kb.minipogodynka.adapters.AddressAdapter; + +public class LocationSearchView extends LinearLayout { + private static final String TAG = "LocationDialogView"; + private static final int SUGGESTIONS_DELAY = 300; + private final Geocoder mGeocoder; + private final Handler mGeocodingHandler; + private final AddressAdapter mAddressAdapter; + private AutoCompleteTextView mSearchField; + + public LocationSearchView(Context context, AttributeSet attrs) { + super(context, attrs); + + mGeocodingHandler = new Handler(Looper.myLooper()); + mAddressAdapter = new AddressAdapter(getContext()); + mAddressAdapter.setNotifyOnChange(false); + + mGeocoder = new Geocoder(getContext(), Locale.getDefault()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && !Geocoder.isPresent()) { + Log.w(TAG, "Geocoder is not present"); + } + + // TODO: Add check for "disabled" error if on a custom ROM without any location provider, + // and fallback to some other stuff, like an Internet API or something, or just change + // the layout to be latitude/longitude setting. + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mSearchField = findViewById(R.id.dl_search); + mSearchField.setAdapter(mAddressAdapter); + + mSearchField.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) { + mGeocodingHandler.removeCallbacksAndMessages(null); + mGeocodingHandler.postDelayed(() -> + new GeocodeAsyncTask(mGeocoder, mAddressAdapter) + .execute(s.toString()), SUGGESTIONS_DELAY); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + } + + // TODO: Perhaps get rid of this and use Thread or something. + private static class GeocodeAsyncTask extends AsyncTask> { + private final static String TAG = "GeocodeAsyncTask"; + private final Geocoder mGeocoder; + private final AddressAdapter mAdapter; + + public GeocodeAsyncTask(final Geocoder geocoder, final AddressAdapter adapter) { + super(); + mGeocoder = geocoder; + mAdapter = adapter; + } + + @Override + protected List
doInBackground(String... queries) { + try { + return mGeocoder.getFromLocationName(queries[0], 10); + } catch (IOException e) { + Log.w(TAG, "Geocoder could not provide suggestions", e); + return Collections.emptyList(); + } + } + + @Override + protected void onPostExecute(List
addresses) { + mAdapter.setData(addresses); + mAdapter.notifyDataSetChanged(); + } + } +} diff --git a/app/src/main/java/eur/one00kb/minipogodynka/MainActivity.java b/app/src/main/java/eur/one00kb/minipogodynka/MainActivity.java index c9ef488..a7fc871 100644 --- a/app/src/main/java/eur/one00kb/minipogodynka/MainActivity.java +++ b/app/src/main/java/eur/one00kb/minipogodynka/MainActivity.java @@ -1,54 +1,78 @@ package eur.one00kb.minipogodynka; -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; +import android.app.AlertDialog; import android.app.TabActivity; -import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.location.Address; -import android.location.Geocoder; import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; -import android.view.View; -import android.widget.GridView; -import android.widget.TabHost; +import android.widget.ListView; -import java.io.IOException; import java.util.ArrayList; import java.util.Date; -import java.util.List; import java.util.Locale; import java.util.Map; +import eur.one00kb.minipogodynka.adapters.DetailsAdapter; +import eur.one00kb.minipogodynka.adapters.HourlyAdapter; import eur.one00kb.minipogodynka.weather.WeatherAPIFetcher; +import eur.one00kb.minipogodynka.weather.WeatherAPIParser; import eur.one00kb.minipogodynka.weather.WeatherData; -import eur.one00kb.minipogodynka.weather.WeatherJSONParser; + +// TODO: Searching functionality +// TODO: Location history +// TODO: Replace Date with Calendar, apparently it's better +// TODO: Action icons /** * @noinspection deprecation */ -public class MainActivity extends TabActivity implements LocationListener, TabHost.TabContentFactory { - private PreferenceHolder mPreferenceHolder; - private LocationManager mLocManager; - private Geocoder mGeocoder; +public class MainActivity extends TabActivity { + private static final String TAG = "MainActivity"; + private PreferenceHolder mPreferenceHolder; private Location mCurrentLocation; - private Address mCurrentAddress; private Map mWeatherData; + private LocationSearchView mLocationSearchView; + private ListView mHourlyView; + + private AlertDialog mLocationDialog; + private AlertDialog.Builder mDetailsDialogBuilder; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPreferenceHolder = new PreferenceHolder(this); - mLocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); - mGeocoder = new Geocoder(this, Locale.getDefault()); + + mLocationSearchView = (LocationSearchView) + getLayoutInflater().inflate(R.layout.dialog_location, null); + + mLocationDialog = new AlertDialog.Builder(this) + .setCancelable(false) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, null) + .setTitle(R.string.alert_location_title) + .setView(mLocationSearchView).create(); + + mDetailsDialogBuilder = new AlertDialog.Builder(this) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, null); + + mHourlyView = new ListView(this); + mHourlyView.setOnItemClickListener((parent, view, position, id) -> { + WeatherData data = (WeatherData) mHourlyView.getItemAtPosition(position); + mDetailsDialogBuilder + .setTitle(String.format("%tR, % mHourlyView)); setDefaultTab(0); } @@ -63,103 +87,44 @@ public class MainActivity extends TabActivity implements LocationListener, TabHo public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - if (id == R.id.action_locate) { - updateWeather(); + if (id == R.id.action_update) { + updateApiData(); + } else if (id == R.id.action_location) { + mLocationDialog.show(); } else if (id == R.id.action_settings) { - Intent itn = new Intent(this, SettingsActivity.class); - startActivity(itn); + startActivity(new Intent(this, SettingsActivity.class)); } 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) - private void checkAndRequestPerms() { - if (isLocationAllowed()) { - requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0); - } - } - - @SuppressLint("MissingPermission") - private void updateWeather() { - checkAndRequestPerms(); - // First, we try to get the location of the user. - if (isLocationAllowed()) { - setTitle("Locating…"); - mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); - } - } - - private boolean isLocationAllowed() { - return checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, - android.os.Process.myPid(), android.os.Process.myUid()) - == PackageManager.PERMISSION_GRANTED; - } - private void updateInfo() { - if (mCurrentAddress != null) { - setTitle(mCurrentAddress.getLocality()); - } else { - setTitle("Unknown location"); - } - - createTabsForDays(Utilities.getDateOnly()); - } - - private void getJson() { - new Thread(() -> { - String apiData = WeatherAPIFetcher.fetchOpenMeteo(mCurrentLocation.getLatitude(), - mCurrentLocation.getLongitude()); - mWeatherData = WeatherJSONParser.parseOpenMeteo(apiData); - runOnUiThread(this::updateInfo); - }).start(); - } - - @Override - public void onLocationChanged(Location location) { - mCurrentLocation = location; - mLocManager.removeUpdates(this); - - try { - List
addresses = mGeocoder.getFromLocation(location.getLatitude(), - location.getLongitude(), 1); - if (addresses != null && addresses.size() > 0) { - mCurrentAddress = addresses.get(0); - } - } catch (IOException e) { - e.printStackTrace(); - } - - getJson(); - } - - @Override - public View createTabContent(String tag) { - GridView view = (GridView) getLayoutInflater().inflate(R.layout.tab_weather, null); + setTitle(String.format(Locale.getDefault(), "%.2f°; %.2f°", + mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude())); Date forApiDate = Utilities.getDateOnly(); - forApiDate.setDate(forApiDate.getDate() + Integer.parseInt(tag)); ArrayList forAdapter = new ArrayList<>(); - for (int i = forApiDate.getHours(); i < 24; i += mPreferenceHolder.getForecastType()) { + for (int i = forApiDate.getHours(); i < 24; ++i) { forApiDate.setHours(i); forAdapter.add(mWeatherData.get(forApiDate)); } - WeatherDataAdapter adapter = new WeatherDataAdapter(this, forAdapter); - view.setAdapter(adapter); + mHourlyView.setAdapter(new HourlyAdapter(this, forAdapter)); + } - return view; + private void updateApiData() { + if (mCurrentLocation == null) { + return; + } + + Utilities.quickToast(this, "Fetching weather data…"); + + new Thread(() -> { + String apiData = WeatherAPIFetcher.fetch(mCurrentLocation.getLatitude(), + mCurrentLocation.getLongitude(), mPreferenceHolder.getProvider()); + mWeatherData = WeatherAPIParser.parse(apiData, mPreferenceHolder.getProvider()); + runOnUiThread(this::updateInfo); + }).start(); } } diff --git a/app/src/main/java/eur/one00kb/minipogodynka/PreferenceHolder.java b/app/src/main/java/eur/one00kb/minipogodynka/PreferenceHolder.java index 2beba1f..1966ea9 100644 --- a/app/src/main/java/eur/one00kb/minipogodynka/PreferenceHolder.java +++ b/app/src/main/java/eur/one00kb/minipogodynka/PreferenceHolder.java @@ -39,16 +39,4 @@ public class PreferenceHolder implements SharedPreferences.OnSharedPreferenceCha 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))); - } } \ No newline at end of file diff --git a/app/src/main/java/eur/one00kb/minipogodynka/Utilities.java b/app/src/main/java/eur/one00kb/minipogodynka/Utilities.java index 1169693..04e25f3 100644 --- a/app/src/main/java/eur/one00kb/minipogodynka/Utilities.java +++ b/app/src/main/java/eur/one00kb/minipogodynka/Utilities.java @@ -1,5 +1,8 @@ package eur.one00kb.minipogodynka; +import android.content.Context; +import android.widget.Toast; + import java.util.Date; public class Utilities { @@ -11,4 +14,8 @@ public class Utilities { date.setTime(date.getTime() - (date.getTime() % 1000)); return date; } + + public static void quickToast(Context context, String message) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } } diff --git a/app/src/main/java/eur/one00kb/minipogodynka/WeatherDataAdapter.java b/app/src/main/java/eur/one00kb/minipogodynka/WeatherDataAdapter.java deleted file mode 100644 index f7b2387..0000000 --- a/app/src/main/java/eur/one00kb/minipogodynka/WeatherDataAdapter.java +++ /dev/null @@ -1,36 +0,0 @@ -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 { - public WeatherDataAdapter(Context context, ArrayList 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:% { + private final List
mAddresses; + + public AddressAdapter(Context context) { + super(context, 0); + mAddresses = new ArrayList<>(); + } + + public void setData(List
addresses) { + mAddresses.clear(); + mAddresses.addAll(addresses); + } + + @Override + public int getCount() { + return mAddresses.size(); + } + + @Override + public Address getItem(int position) { + return mAddresses.get(position); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = + LayoutInflater.from(getContext()) + .inflate(android.R.layout.simple_list_item_2, parent, false); + } + + Address address = getItem(position); + TextView t1 = convertView.findViewById(android.R.id.text1); + TextView t2 = convertView.findViewById(android.R.id.text2); + + if (address != null) { + t1.setText(address.getAddressLine(0)); + t2.setText(address.getAddressLine(1)); + } + + return convertView; + } +} diff --git a/app/src/main/java/eur/one00kb/minipogodynka/adapters/DetailsAdapter.java b/app/src/main/java/eur/one00kb/minipogodynka/adapters/DetailsAdapter.java new file mode 100644 index 0000000..850a3d9 --- /dev/null +++ b/app/src/main/java/eur/one00kb/minipogodynka/adapters/DetailsAdapter.java @@ -0,0 +1,36 @@ +package eur.one00kb.minipogodynka.adapters; + +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 eur.one00kb.minipogodynka.R; +import eur.one00kb.minipogodynka.weather.WeatherData; + +public class DetailsAdapter extends ArrayAdapter { + public DetailsAdapter(Context context, WeatherData.MemberInfo[] objects) { + super(context, 0, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_detail, parent, + false); + } + + WeatherData.MemberInfo info = getItem(position); + TextView label = convertView.findViewById(R.id.detail_label); + TextView value = convertView.findViewById(R.id.detail_value); + + if (info != null) { + label.setText(info.label + ":"); + value.setText(String.format("%d %s", Math.round(info.value), info.unit)); + } + + return convertView; + } +} diff --git a/app/src/main/java/eur/one00kb/minipogodynka/adapters/HourlyAdapter.java b/app/src/main/java/eur/one00kb/minipogodynka/adapters/HourlyAdapter.java new file mode 100644 index 0000000..475c7de --- /dev/null +++ b/app/src/main/java/eur/one00kb/minipogodynka/adapters/HourlyAdapter.java @@ -0,0 +1,41 @@ +package eur.one00kb.minipogodynka.adapters; + +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.List; + +import eur.one00kb.minipogodynka.R; +import eur.one00kb.minipogodynka.weather.WeatherData; + +public class HourlyAdapter extends ArrayAdapter { + public HourlyAdapter(Context context, List objects) { + super(context, 0, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_hour, parent, + false); + } + + WeatherData data = getItem(position); + TextView time = convertView.findViewById(R.id.hour_time); + TextView temperature = convertView.findViewById(R.id.hour_temp); + TextView precipitation = convertView.findViewById(R.id.hour_precp); + + if (data != null) { + time.setText(String.format("%tR", data.date)); + // No one cares about the decimals. + temperature.setText(String.format("%d °C", Math.round(data.temperature))); + precipitation.setText(String.format("%d %%", Math.round(data.precipitation))); + } + + return convertView; + } +} diff --git a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIFetcher.java b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIFetcher.java index e9834fb..0fe407d 100644 --- a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIFetcher.java +++ b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIFetcher.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; +import java.net.URLConnection; import java.util.Locale; public class WeatherAPIFetcher { @@ -12,11 +13,33 @@ public class WeatherAPIFetcher { ".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"; + private static final String MET_FORMAT = "https://api.met.no/weatherapi/locationforecast/2" + + ".0/complete?lat=%f&lon=%f"; + // API key taken off the Internet, some GitHub example. + private static final String MF_FORMAT = "https://webservice.meteofrance" + + ".com/v2/forecast?lat=%f&lon=%f" + + "&hourly=true&lang=en&token=__Wj7dVSTjV9YGu1guveLyDq0g7S7TfTjaHBTPTpO0kj8__"; - public static String fetchOpenMeteo(double lat, double lon) { + public static String fetch(double lat, double lon, String provider) { try { - URL url = new URL(String.format(Locale.US, OM_FORMAT, lat, lon)); - return isToString(url.openStream()); + URL url; + + switch (provider) { + case "met": + url = new URL(String.format(Locale.US, MET_FORMAT, lat, lon)); + break; + case "mf": + url = new URL(String.format(Locale.US, MF_FORMAT, lat, lon)); + break; + case "om": + default: + url = new URL(String.format(Locale.US, OM_FORMAT, lat, lon)); + break; + } + + URLConnection conn = url.openConnection(); + conn.setRequestProperty("User-Agent", "MiniPogodynka"); + return isToString(conn.getInputStream()); } catch (IOException e) { e.printStackTrace(); return null; diff --git a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIParser.java b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIParser.java new file mode 100644 index 0000000..b5111b7 --- /dev/null +++ b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherAPIParser.java @@ -0,0 +1,111 @@ +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.Locale; +import java.util.Map; + +public class WeatherAPIParser { + private static final DateFormat OM_DATEFORMAT = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.US); + private static final DateFormat MET_DATEFORMAT = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + + public static Map parse(String json, String provider) { + switch (provider) { + case "met": + return parseMetNo(json); + case "mf": + return null; + case "om": + default: + return parseOpenMeteo(json); + } + } + + public static Map parseOpenMeteo(String json) { + HashMap 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 hours; + } + + public static Map parseMetNo(String json) { + HashMap hours = new HashMap<>(); + + try { + JSONObject root = new JSONObject(json); + JSONArray timeseries = root.getJSONObject("properties") + .getJSONArray("timeseries"); + + for (int i = 0; i < timeseries.length(); ++i) { + JSONObject obj = timeseries.getJSONObject(i); + JSONObject instant = obj.getJSONObject("data") + .getJSONObject("instant").getJSONObject("details"); + JSONObject next1hr = obj.getJSONObject("data") + .getJSONObject("next_1_hours").getJSONObject("details"); + WeatherData data = new WeatherData(); + + data.date = MET_DATEFORMAT.parse(obj.getString("time")); + data.temperature = instant.getDouble("air_temperature"); + data.humidity = instant.getDouble("relative_humidity"); + // I think this should be correct. If the time is 13, then it's the data for the + // next 60 minutes, isn't it? + data.precipitation = next1hr.getDouble("precipitation_amount"); + data.pressure = instant.getDouble("air_pressure_at_sea_level"); + data.cloudCover = instant.getDouble("cloud_area_fraction"); + data.windSpeed = instant.getDouble("wind_speed"); + data.windDirection = instant.getDouble("wind_from_direction"); + data.uvIndex = instant.getDouble("ultraviolet_index_clear_sky"); + + hours.put(MET_DATEFORMAT.parse(obj.getString("time")), data); + } + } catch (JSONException | ParseException e) { + e.printStackTrace(); + } + + return hours; + } + + public static void parseMeteoFrance() { + + } +} diff --git a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherData.java b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherData.java index a2b9d66..93f73d1 100644 --- a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherData.java +++ b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherData.java @@ -8,11 +8,36 @@ public class WeatherData { // simplify code. public Date date; public double temperature; - public double humidity; public double precipitation; + public double humidity; public double pressure; public double cloudCover; public double windSpeed; public double windDirection; public double uvIndex; + + public MemberInfo[] getMemberInfo() { + return new MemberInfo[]{ + new MemberInfo("Temperature", temperature, "°C"), + new MemberInfo("Precipitation", precipitation, "%"), + new MemberInfo("Humidity", humidity, "%"), + new MemberInfo("Pressure", pressure, "hPa"), + new MemberInfo("Cloud cover", cloudCover, "%"), + new MemberInfo("Wind speed", windSpeed, "m/s"), + new MemberInfo("Wind direction", windDirection, "°"), + new MemberInfo("UV index", uvIndex, ""), + }; + } + + public static class MemberInfo { + public String label; + public Double value; + public String unit; + + public MemberInfo(String labela, Double valuea, String unita) { + label = labela; + value = valuea; + unit = unita; + } + } } diff --git a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherJSONParser.java b/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherJSONParser.java deleted file mode 100644 index 0992766..0000000 --- a/app/src/main/java/eur/one00kb/minipogodynka/weather/WeatherJSONParser.java +++ /dev/null @@ -1,63 +0,0 @@ -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 parseOpenMeteo(String json) { - HashMap 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() { - - } -} diff --git a/app/src/main/res/layout/dialog_location.xml b/app/src/main/res/layout/dialog_location.xml new file mode 100644 index 0000000..450259f --- /dev/null +++ b/app/src/main/res/layout/dialog_location.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_detail.xml b/app/src/main/res/layout/item_detail.xml new file mode 100644 index 0000000..336a46e --- /dev/null +++ b/app/src/main/res/layout/item_detail.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_hour.xml b/app/src/main/res/layout/item_hour.xml new file mode 100644 index 0000000..61794f0 --- /dev/null +++ b/app/src/main/res/layout/item_hour.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_weather.xml b/app/src/main/res/layout/item_weather.xml deleted file mode 100644 index eeeaa69..0000000 --- a/app/src/main/res/layout/item_weather.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/tab_weather.xml b/app/src/main/res/layout/tab_weather.xml deleted file mode 100644 index 7e1a061..0000000 --- a/app/src/main/res/layout/tab_weather.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index b9324fc..67f422f 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -6,13 +6,9 @@ android:showAsAction="ifRoom" android:title="@string/menu_item_update" /> - + android:title="@string/menu_item_location" /> met mf - - - 3 - 4 - 5 - 6 - 7 - - - - Sparse - Regular - Detailed - - - - 6 - 3 - 1 - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e5646e..541fc3a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,27 +1,23 @@ MiniPogodynka - Settings + Settings + Location Placeholder + Set location + Search for location + Update - Search - Locate + Location Settings + Search weather_provider Weather provider om - forecast_days - Forecast days - 3 - - forecast_type - Forecast type - 6 - Copyleft 🄯 Emil Kosz, 2025. License: GPLv3 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8dc0b61..5e86d92 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -8,18 +8,6 @@ android:key="@string/preferences_key_provider" android:title="@string/preferences_title_provider" /> - -