Completed 1, 2, 3 tasks

This commit is contained in:
Stepan 2025-07-05 02:55:06 +02:00
parent df7c5e6e3f
commit f29fe8ad71
27 changed files with 688 additions and 560 deletions

1
.idea/gradle.xml generated
View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>

1
.idea/misc.xml generated
View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -25,6 +25,10 @@ android {
)
}
}
buildFeatures {
viewBinding = true;
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@ -41,4 +45,6 @@ dependencies {
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation("com.android.volley:volley:1.2.1")
implementation("com.github.bumptech.glide:glide:4.15.1")
annotationProcessor("com.github.bumptech.glide:compiler:4.15.1")
}

View File

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
@ -14,9 +15,6 @@
android:supportsRtl="true"
android:theme="@style/Theme.AirQuality"
tools:targetApi="31">
<activity
android:name=".AQActivity"
android:exported="false" />
<activity
android:name=".DetailsActivity"
android:exported="false" />

View File

@ -1,191 +0,0 @@
package com.example.airquality;
import android.os.Bundle;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
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.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class AQActivity extends AppCompatActivity {
private static final Map<String, double[]> cityCoordinates = new HashMap<>();
static {
cityCoordinates.put("Subotica", new double[]{46.1, 19.6667});
cityCoordinates.put("Novi Sad", new double[]{45.2517, 19.8369});
cityCoordinates.put("Beograd", new double[]{44.804, 20.4651});
cityCoordinates.put("San Francisko", new double[]{37.7749, -122.4194});
cityCoordinates.put("Sidnej", new double[]{-33.8679, 151.2073});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_aqactivity);
Toolbar toolbar = findViewById(R.id.toolbar);
// Using toolbar as ActionBar
setSupportActionBar(toolbar);
// Display application icon in the toolbar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setLogo(R.drawable.weather_clear_symbolic);
getSupportActionBar().setDisplayUseLogoEnabled(true);
}
loadAQ();
}
@Override
protected void onResume() {
super.onResume();
loadAQ();
}
public boolean onCreateOptionsMenu(android.view.Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_aq:
Intent intentAQ = new Intent(this, AQActivity.class);
startActivity(intentAQ);
return true;
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private String getAQIDescription(int aqi) {
switch (aqi) {
case 1: return "Good";
case 2: return "Fair";
case 3: return "Moderate";
case 4: return "Poor";
case 5: return "Very Poor";
default: return "Unknown";
}
}
public void loadAQ(){
String city = PreferencesManager.getCity(this);
double[] coords = cityCoordinates.get(city);
if (coords == null) {
Toast.makeText(this, "Unknown city: " + city, Toast.LENGTH_SHORT).show();
return;
}
String apiKey = getString(R.string.weather_api_key);
String url = String.format(
"https://api.openweathermap.org/data/2.5/air_pollution?lat=%.6f&lon=%.6f&appid=%s",
coords[0], coords[1], apiKey
);
RequestQueue queue = Volley.newRequestQueue(this);
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// Get JSON fields
JSONObject listItem = response.getJSONArray("list").getJSONObject(0);
int aqi = listItem.getJSONObject("main").getInt("aqi");
View rootLayout = findViewById(R.id.main);
int colorResId;
switch (aqi) {
case 1:
colorResId = R.color.aqi_good;
break;
case 2:
colorResId = R.color.aqi_fair;
break;
case 3:
colorResId = R.color.aqi_moderate;
break;
case 4:
colorResId = R.color.aqi_poor;
break;
case 5:
colorResId = R.color.aqi_very_poor;
break;
default:
colorResId = android.R.color.background_light;
}
rootLayout.setBackgroundColor(getResources().getColor(colorResId));
JSONObject components = listItem.getJSONObject("components");
double pm25 = components.getDouble("pm2_5");
double pm10 = components.getDouble("pm10");
double o3 = components.getDouble("o3");
double co = components.getDouble("co");
double no2 = components.getDouble("no2");
TextView tvCity = findViewById(R.id.tvCity);
TextView tvAQ = findViewById(R.id.tvAQ);
TextView tvComponents = findViewById(R.id.tvComponents);
String city = PreferencesManager.getCity(AQActivity.this);
tvCity.setText("City: " + city);
tvAQ.setText("AQI Index: " + aqi + " (" + getAQIDescription(aqi) + ")");
tvComponents.setText(
String.format("PM2.5: %.2f µg/m³\nPM10: %.2f µg/m³\nO₃: %.2f ppb\nCO: %.2f ppm\nNO₂: %.2f ppb",
pm25, pm10, o3, co, no2)
);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(AQActivity.this, "Failed to parse air data", Toast.LENGTH_SHORT).show();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(AQActivity.this, "Failed to load air pollution data", Toast.LENGTH_SHORT).show();
error.printStackTrace();
}
}
);
queue.add(request);
}
}

View File

@ -1,6 +1,9 @@
package com.example.airquality;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
@ -8,13 +11,106 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class DetailsActivity extends AppCompatActivity {
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;
import com.bumptech.glide.Glide;
import com.example.airquality.api.ForecastAPIRequest;
import com.example.airquality.databinding.ActivityDetailsBinding;
import com.example.airquality.types.City;
import com.example.airquality.types.FormattedTemp;
import com.example.airquality.types.Weather3h;
import com.example.airquality.utils.PreferencesManager;
import com.example.airquality.utils.ToolbarCompatActivity;
import com.example.airquality.utils.Weather3hAdapter;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONObject;
import java.util.Arrays;
public class DetailsActivity extends ToolbarCompatActivity {
private ActivityDetailsBinding binding;
private Weather3h weather3h;
private City city;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_details);
binding = ActivityDetailsBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
Intent intent = getIntent();
weather3h = (Weather3h) intent.getSerializableExtra("WEATHER_3H");
city = (City) intent.getSerializableExtra("CITY");
initToolbar();
setToolbarTitle(weather3h.getDateTime());
updateData();
}
protected void updateData() {
FormattedTemp formattedTemp = weather3h.formattedTemp(this);
if(checkInternet())
{
Glide.with(this)
.load(weather3h.getIconUrl())
.into(binding.icon);
binding.iconCode.setText("");
}
else
{
binding.icon.setImageDrawable(null);
binding.iconCode.setText(weather3h.getIconCode());
}
binding.cityText.setText(city.name);
binding.datetimeText.setText(weather3h.getDateTime());
binding.tempWeatherText.setText(formattedTemp.tempText + " | " + weather3h.getDescription());
binding.tempText.setText(formattedTemp.tempMinText + " ~ " + formattedTemp.tempMaxText);
binding.pressureText.setText(weather3h.getPressure() + " hPa");
binding.humidityText.setText(weather3h.getHumidity() + "%");
binding.windSpeedText.setText(weather3h.getWindSpeed() + " m/s");
}
@Override
protected void onRefreshOptionToolbar() {
String units = PreferencesManager.getUnits(this);
RequestQueue queue = Volley.newRequestQueue(this);
ForecastAPIRequest forecastAPI = new ForecastAPIRequest(this, city, units) {
@Override
public void onSuccess(Weather3h[] weatherList) {
boolean isFound = false;
for (Weather3h w : weatherList) {
if (w.getDateTime().equals(weather3h.getDateTime())) {
// Update to new data
weather3h = w;
isFound = true;
break;
}
}
if(isFound) {
Toast.makeText(DetailsActivity.this, "Data was updated!", Toast.LENGTH_SHORT).show();
updateData();
}
else {
Toast.makeText(DetailsActivity.this, "This time is deprecated", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(VolleyError error) {
Toast.makeText(DetailsActivity.this, "Failed to load weather data", Toast.LENGTH_SHORT).show();
}
};
queue.add(forecastAPI.getRequest());
}
}

View File

@ -2,20 +2,16 @@ package com.example.airquality;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuInflater;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
@ -23,20 +19,25 @@ import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.google.android.material.appbar.MaterialToolbar;
import com.example.airquality.api.ForecastAPIRequest;
import com.example.airquality.types.City;
import com.example.airquality.types.Weather3h;
import com.example.airquality.utils.PreferencesManager;
import com.example.airquality.utils.ToolbarCompatActivity;
import com.example.airquality.utils.Weather3hAdapter;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONObject;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
public class MainActivity extends ToolbarCompatActivity {
List<Weather3h> prognoza;
private ListView listView;
private RecyclerView recyclerView;
private static final Map<String, double[]> cityCoordinates = new HashMap<>();
static {
@ -54,81 +55,10 @@ public class MainActivity extends AppCompatActivity {
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
listView=findViewById(R.id.listView);
recyclerView=findViewById(R.id.weatherList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// Assigning ID of the toolbar to a variable
Toolbar toolbar = findViewById(R.id.toolbar);
// Using toolbar as ActionBar
setSupportActionBar(toolbar);
// Display application icon in the toolbar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setLogo(R.drawable.weather_clear_symbolic);
getSupportActionBar().setDisplayUseLogoEnabled(true);
}
loadForecast();
}
// private void loadForecast() {
// String city = PreferencesManager.getCity(this);
// int days = PreferencesManager.getDays(this);
// String units = PreferencesManager.getUnits(this);
//
// Log.d("SETTINGS", "City: " + city + ", Days: " + days + ", Units: " + units);
//
// try {
// // Učitavanje lokalnog JSON fajla
// InputStream is = getAssets().open("forecast_sample_40.json");
// int size = is.available();
// byte[] buffer = new byte[size];
// is.read(buffer);
// is.close();
//
// String jsonString = new String(buffer, "UTF-8");
// JSONObject jsonObject = new JSONObject(jsonString);
//
// // Računamo maksimalan broj vremenskih tačaka
// int maxPoints = days * 8; // jer ima 8 tačaka dnevno na svakih 3h
//
// // Parsiramo podatke
// List<Weather3h> prognoza = Weather3hParser.parse(jsonObject, maxPoints);
//
// // Povezujemo adapter sa ListView-om
// Weather3hAdapter adapter = new Weather3hAdapter(this, prognoza);
// ListView listView = findViewById(R.id.listView);
// listView.setAdapter(adapter);
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
public boolean onCreateOptionsMenu(android.view.Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_aq:
Intent intentAQ = new Intent(this, AQActivity.class);
startActivity(intentAQ);
return true;
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
initToolbar();
}
protected void onResume() {
@ -136,59 +66,47 @@ public class MainActivity extends AppCompatActivity {
loadForecast();
}
@Override
protected void onRefreshOptionToolbar() {
loadForecast(true);
}
private void loadForecast() {
String city = PreferencesManager.getCity(this);
loadForecast(false);
}
private void loadForecast(boolean isUpdated) {
City city = PreferencesManager.getCity(this);
int days = PreferencesManager.getDays(this);
String units = PreferencesManager.getUnits(this);
double[] coords = cityCoordinates.get(city);
if (coords == null) {
Toast.makeText(this, "Unknown city: " + city, Toast.LENGTH_SHORT).show();
setToolbarTitle(city.name);
if (city == null) {
Toast.makeText(this, "Unknown city: " + city.name, Toast.LENGTH_SHORT).show();
return;
}
String apiKey = getString(R.string.weather_api_key);
String url = String.format(
"https://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=%s&appid=%s",
coords[0], coords[1], units, apiKey
);
RequestQueue queue = Volley.newRequestQueue(this);
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
prognoza = Weather3hParser.parse(response, days);;
Weather3hAdapter adapter = new Weather3hAdapter(MainActivity.this, prognoza);
ListView listView = findViewById(R.id.listView);
listView.setAdapter(adapter);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
Toast.makeText(MainActivity.this, "Failed to load weather data", Toast.LENGTH_SHORT).show();
}
}
);
ForecastAPIRequest forecastAPI = new ForecastAPIRequest(this, city, units) {
@Override
public void onSuccess(Weather3h[] weatherList) {
Weather3h[] arr = Arrays.copyOfRange(weatherList, 0, days);
prognoza = Arrays.asList(arr);
queue.add(request);
Weather3hAdapter adapter = new Weather3hAdapter(MainActivity.this, prognoza, city);
recyclerView.setAdapter(adapter);
if(isUpdated)
Toast.makeText(MainActivity.this, "List Updated!", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(VolleyError error) {
Toast.makeText(MainActivity.this, "Failed to load weather data", Toast.LENGTH_SHORT).show();
}
};
queue.add(forecastAPI.getRequest());
}
}
}

View File

@ -17,10 +17,11 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class SettingsActivity extends AppCompatActivity {
import com.example.airquality.utils.BaseCompatActivity;
public class SettingsActivity extends BaseCompatActivity {
Spinner spinnerCity;
RadioGroup radioGroupDays, radioGroupUnits;
CheckBox checkBoxRemember;
Button buttonSave;
String[] cities = {"Subotica", "Novi Sad", "Beograd", "San Francisko", "Sidnej"};
@ -33,7 +34,6 @@ public class SettingsActivity extends AppCompatActivity {
spinnerCity = findViewById(R.id.spinnerCity);
radioGroupDays = findViewById(R.id.radioGroupDays);
radioGroupUnits = findViewById(R.id.radioGroupUnits);
checkBoxRemember = findViewById(R.id.checkBoxRemember);
buttonSave = findViewById(R.id.buttonSave);
// Podesi spinner
@ -59,22 +59,13 @@ public class SettingsActivity extends AppCompatActivity {
units = "imperial";
}
boolean remember = checkBoxRemember.isChecked();
SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("city", selectedCity);
editor.putInt("days", days);
editor.putString("units", units);
editor.apply();
if (remember) {
SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("city", selectedCity);
editor.putInt("days", days);
editor.putString("units", units);
editor.apply();
}
Intent resultIntent = new Intent();
resultIntent.putExtra("city", selectedCity);
resultIntent.putExtra("days", days);
resultIntent.putExtra("units", units);
setResult(RESULT_OK, resultIntent);
finish();
}
});

View File

@ -1,54 +0,0 @@
package com.example.airquality;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
public class Weather3hAdapter extends BaseAdapter {
private Context context;
private List<Weather3h> weatherList;
private LayoutInflater inflater;
public Weather3hAdapter(Context context, List<Weather3h> weatherList) {
this.context = context;
this.weatherList = weatherList;
this.inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return weatherList.size();
}
@Override
public Object getItem(int position) {
return weatherList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = inflater.inflate(R.layout.item_weather3h, parent, false);
TextView txtDate = rowView.findViewById(R.id.txtDate);
TextView txtTemp = rowView.findViewById(R.id.txtTemp);
TextView txtDesc = rowView.findViewById(R.id.txtDesc);
Weather3h item = weatherList.get(position);
txtDate.setText(item.getDateTime());
txtTemp.setText(String.format("%.1f°C", item.getTemp()));
txtDesc.setText(item.getDescription());
return rowView;
}
}

View File

@ -0,0 +1,54 @@
package com.example.airquality.api;
import android.annotation.SuppressLint;
import android.content.Context;
import android.widget.Toast;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.example.airquality.types.City;
import com.example.airquality.types.Weather3h;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONObject;
import java.util.Arrays;
public class ForecastAPIRequest extends JsonAPIRequest {
@SuppressLint("DefaultLocale")
public ForecastAPIRequest(Context context, City city, String units) {
super(context);
this.url = String.format(
"https://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=%s&appid=%s",
city.latitude, city.longitude, units, getKey()
);
}
@Override
public JsonObjectRequest getRequest() {
return new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
onSuccess(Weather3hParser.parse(response));
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
onError(error);
}
}
);
}
public void onSuccess(Weather3h[] weatherList) { }
public void onError(VolleyError error) { }
}

View File

@ -0,0 +1,32 @@
package com.example.airquality.api;
import android.content.Context;
import android.widget.Toast;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.example.airquality.MainActivity;
import com.example.airquality.R;
import com.example.airquality.utils.Weather3hAdapter;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONObject;
public abstract class JsonAPIRequest {
private Context appContext;
protected String url;
public JsonAPIRequest(Context context)
{
this.appContext = context;
}
protected String getKey()
{
return this.appContext.getString(R.string.weather_api_key);
}
public abstract JsonObjectRequest getRequest();
}

View File

@ -0,0 +1,15 @@
package com.example.airquality.types;
import java.io.Serializable;
public class City implements Serializable {
public String name;
public double latitude;
public double longitude;
public City(String name, double latitude, double longitude) {
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
}
}

View File

@ -0,0 +1,25 @@
package com.example.airquality.types;
import android.content.Context;
import androidx.annotation.NonNull;
import com.example.airquality.utils.PreferencesManager;
import java.util.Objects;
public class FormattedTemp {
public String tempText;
public String tempMinText;
public String tempMaxText;
public FormattedTemp(Context context, double temp, double tempMin, double tempMax)
{
String units = PreferencesManager.getUnits(context);
String suffix = units.equals("metric") ? "°C" : "°F";
tempText = temp + suffix;
tempMinText = tempMin + suffix;
tempMaxText = tempMax + suffix;
}
}

View File

@ -1,9 +1,16 @@
package com.example.airquality;
package com.example.airquality.types;
public class Weather3h {
import android.content.Context;
import java.io.Serializable;
public class Weather3h implements Serializable {
private int dt;
private String dateTime;
private double temp;
private double tempMin;
private double tempMax;
private int pressure;
private int humidity;
private double windSpeed;
@ -11,10 +18,13 @@ package com.example.airquality;
private String description;
private String icon;
public Weather3h(String dateTime, double temp, int pressure, int humidity,
public Weather3h(int dt, String dateTime, double temp, double tempMin, double tempMax, int pressure, int humidity,
double windSpeed, int windDeg, String description, String icon) {
this.dt = dt;
this.dateTime = dateTime;
this.temp = temp;
this.tempMin = tempMin;
this.tempMax = tempMax;
this.pressure = pressure;
this.humidity = humidity;
this.windSpeed = windSpeed;
@ -31,6 +41,22 @@ package com.example.airquality;
return temp;
}
public double getTempMin() {
return tempMin;
}
public double getTempMax() {
return tempMax;
}
public FormattedTemp formattedTemp(Context context) {
return new FormattedTemp(context, temp, tempMin, tempMax);
}
public int getDt() {
return dt;
}
public int getPressure() {
return pressure;
}
@ -51,7 +77,9 @@ package com.example.airquality;
return description;
}
public String getIcon() {
return icon;
public String getIconCode() { return icon; }
public String getIconUrl() {
return "https://openweathermap.org/img/wn/" + icon + "@4x.png";
}
}

View File

@ -0,0 +1,16 @@
package com.example.airquality.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import androidx.appcompat.app.AppCompatActivity;
public class BaseCompatActivity extends AppCompatActivity {
public boolean checkInternet()
{
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
}

View File

@ -1,27 +1,27 @@
package com.example.airquality;
package com.example.airquality.utils;
import android.content.Context;
import android.content.SharedPreferences;
import com.example.airquality.types.City;
public class PreferencesManager {
private static final String PREFS_NAME = "MyPrefs";
private static final String KEY_CITY = "city";
private static final String KEY_DAYS = "days";
private static final String KEY_UNITS = "units";
public static void savePreferences(Context context, String city, int days, String units) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_CITY, city);
editor.putInt(KEY_DAYS, days);
editor.putString(KEY_UNITS, units);
editor.apply();
}
public static String getCity(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getString(KEY_CITY, "Subotica"); // default: Subotica
public static City getCity(Context context) {
// DAVID: Realizovati iz sqlite
return new City("Subotica", 46.1, 19.6667);
}
public static int getDays(Context context) {

View File

@ -0,0 +1,56 @@
package com.example.airquality.utils;
import android.content.Intent;
import android.view.MenuInflater;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.example.airquality.R;
import com.example.airquality.SettingsActivity;
public abstract class ToolbarCompatActivity extends BaseCompatActivity {
protected Toolbar toolbar;
protected void onRefreshOptionToolbar() { }
public boolean onCreateOptionsMenu(android.view.Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
onRefreshOptionToolbar();
return true;
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
protected void initToolbar() {
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Display application icon in the toolbar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setLogo(R.drawable.weather_clear_symbolic);
getSupportActionBar().setDisplayUseLogoEnabled(true);
}
}
protected void setToolbarTitle(String title)
{
toolbar.setTitle(title);
}
}

View File

@ -0,0 +1,77 @@
package com.example.airquality.utils;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.RecyclerView;
import com.example.airquality.DetailsActivity;
import com.example.airquality.MainActivity;
import com.example.airquality.R;
import com.example.airquality.types.City;
import com.example.airquality.types.FormattedTemp;
import com.example.airquality.types.Weather3h;
import java.util.List;
public class Weather3hAdapter extends RecyclerView.Adapter<Weather3hAdapter.WeatherViewHolder> {
private Context context;
private List<Weather3h> weatherList;
private City city;
public Weather3hAdapter(Context context, List<Weather3h> weatherList, City city) {
this.context = context;
this.weatherList = weatherList;
this.city = city;
}
// ViewHolder описывает и хранит ссылки на элементы layout
public static class WeatherViewHolder extends RecyclerView.ViewHolder {
TextView txtDate, txtTemp, txtDesc;
public WeatherViewHolder(View itemView) {
super(itemView);
txtDate = itemView.findViewById(R.id.txtDate);
txtTemp = itemView.findViewById(R.id.txtTemp);
txtDesc = itemView.findViewById(R.id.txtDesc);
}
}
@Override
public WeatherViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_weather3h, parent, false);
return new WeatherViewHolder(view);
}
@Override
public void onBindViewHolder(WeatherViewHolder holder, int position) {
Weather3h item = weatherList.get(position);
FormattedTemp formattedTemp = item.formattedTemp(context);
holder.txtDate.setText(item.getDateTime());
holder.txtTemp.setText(formattedTemp.tempText);
holder.txtDesc.setText(item.getDescription());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra("WEATHER_3H", item);
intent.putExtra("CITY", city);
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return weatherList.size();
}
}

View File

@ -1,4 +1,6 @@
package com.example.airquality;
package com.example.airquality.utils;
import com.example.airquality.types.Weather3h;
import org.json.JSONArray;
import org.json.JSONObject;
@ -6,19 +8,22 @@ import java.util.ArrayList;
import java.util.List;
public class Weather3hParser {
public static List<Weather3h> parse(JSONObject jsonObject, int maxPoints) {
public static Weather3h[] parse(JSONObject jsonObject) {
List<Weather3h> forecastList = new ArrayList<>();
try {
JSONArray listArray = jsonObject.getJSONArray("list");
for (int i = 0; i < listArray.length() && i < maxPoints; i++) {
for (int i = 0; i < listArray.length(); i++) {
JSONObject item = listArray.getJSONObject(i);
int dt = item.getInt("dt");
String dateTime = item.getString("dt_txt");
JSONObject main = item.getJSONObject("main");
double temp = main.getDouble("temp");
double tempMin = main.getDouble("temp_min");
double tempMax = main.getDouble("temp_max");
int pressure = main.getInt("pressure");
int humidity = main.getInt("humidity");
@ -30,7 +35,7 @@ public class Weather3hParser {
double windSpeed = wind.getDouble("speed");
int windDeg = wind.getInt("deg");
Weather3h weather3h = new Weather3h(dateTime, temp, pressure, humidity, windSpeed, windDeg, description, icon);
Weather3h weather3h = new Weather3h(dt, dateTime, temp, tempMin, tempMax, pressure, humidity, windSpeed, windDeg, description, icon);
forecastList.add(weather3h);
}
@ -38,7 +43,7 @@ public class Weather3hParser {
e.printStackTrace();
}
return forecastList;
return forecastList.toArray(new Weather3h[0]);
}
}

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AQActivity">
<!-- AppBar layout for using Toolbar as AppBar -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ToolBar widget -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0F9D58"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="Air Pollution"
app:titleTextColor="#ffff" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="20dp"
>
<TextView
android:id="@+id/tvCity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvComponents"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="24sp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvAQ" />
<TextView
android:id="@+id/tvAQ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="24sp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvCity" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,23 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
<include
android:id="@+id/include_toolbar"
layout="@layout/component_toolbar" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/include_toolbar"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="20dp"
>
<TextView
android:id="@+id/textViewDetailsTitle"
<LinearLayout
android:id="@+id/headerInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Weather Details"
android:textSize="20sp"
android:textStyle="bold"
android:paddingBottom="12dp" />
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- Ovde ćemo kasnije dodati detalje o pritisku, vlazi itd. -->
</LinearLayout>
</ScrollView>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/icon_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Привет!"
android:textSize="14sp"
android:layout_gravity="center"
android:padding="4dp"/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:orientation="vertical"
>
<TextView
android:id="@+id/cityText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="City name"
android:textStyle="bold"
android:textSize="20sp"/>
<TextView
android:id="@+id/datetimeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date time"
android:textSize="16sp"/>
<TextView
android:id="@+id/tempWeatherText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="21 C | Cloudy"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="1,2"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/headerInfo">
<TableRow>
<TextView
android:padding="8dp"
android:text="Temperature" />
<TextView
android:id="@+id/tempText"
android:padding="8dp"
android:text="12C ~ 21C" />
</TableRow>
<TableRow>
<TextView
android:padding="8dp"
android:text="Pressure" />
<TextView
android:id="@+id/pressureText"
android:padding="8dp"
android:text="32 hPa" />
</TableRow>
<TableRow>
<TextView
android:padding="8dp"
android:text="Humidity" />
<TextView
android:id="@+id/humidityText"
android:padding="8dp"
android:text="23%" />
</TableRow>
<TableRow>
<TextView
android:padding="8dp"
android:text="Wind speed" />
<TextView
android:id="@+id/windSpeedText"
android:padding="8dp"
android:text="21 m/s" />
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,46 +7,18 @@
android:background="@color/white"
tools:context=".MainActivity">
<!-- AppBar layout for using Toolbar as AppBar -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ToolBar widget -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0F9D58"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="Weather Forcast"
app:titleTextColor="#ffff" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include_toolbar"
layout="@layout/component_toolbar" />
<!-- TextView of the Activity -->
<ListView
android:id="@+id/listView"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/weatherList"
android:layout_width="0dp"
android:layout_height="0dp"
android:divider="@android:color/darker_gray"
android:dividerHeight="1dp"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintTop_toBottomOf="@id/include_toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View File

@ -101,13 +101,6 @@
android:layout_marginTop="12dp"
android:background="#0F9D58" />
<!-- ZAPAMTI ME -->
<CheckBox
android:id="@+id/checkBoxRemember"
android:text="Remember my settings"
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- DUGME -->
<Button

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- AppBar layout for using Toolbar as AppBar -->
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ToolBar widget -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0F9D58"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="Weather"
app:titleTextColor="#ffff" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -1,4 +1,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground">
<TextView android:id="@+id/txtDate"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="DateTime"
@ -14,4 +22,10 @@
android:layout_height="wrap_content"
android:text="Description"
android:textSize="14sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CCCCCC"
android:layout_marginTop="12dp"/>
</LinearLayout>

View File

@ -1,14 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_refresh"
android:title="Refresh"
app:showAsAction="never" />
<item
android:id="@+id/menu_settings"
android:title="Settings"
app:showAsAction="never" />
<item
android:id="@+id/menu_aq"
android:title="Air Quality"
app:showAsAction="never" />
<item
android:id="@+id/menu_about"
android:title="About"