Compare commits

..

No commits in common. "c91ca6be43edc0c36e495893572ff9207d4271b6" and "5f8f3e8e9c4537cfdc0c3ee84fe19388f1cb271b" have entirely different histories.

25 changed files with 560 additions and 681 deletions

1
.idea/misc.xml generated
View File

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

View File

@ -25,10 +25,6 @@ android {
) )
} }
} }
buildFeatures {
viewBinding = true;
}
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
@ -45,6 +41,4 @@ dependencies {
androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.espresso.core)
implementation("com.android.volley:volley:1.2.1") 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,7 +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">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
@ -15,6 +14,9 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.AirQuality" android:theme="@style/Theme.AirQuality"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".AQActivity"
android:exported="false" />
<activity <activity
android:name=".DetailsActivity" android:name=".DetailsActivity"
android:exported="false" /> android:exported="false" />

View File

@ -0,0 +1,191 @@
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,9 +1,6 @@
package com.example.airquality; package com.example.airquality;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -11,106 +8,13 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import com.android.volley.RequestQueue; public class DetailsActivity extends AppCompatActivity {
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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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,16 +2,20 @@ package com.example.airquality;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.core.graphics.Insets;
import androidx.recyclerview.widget.RecyclerView; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.android.volley.Request; import com.android.volley.Request;
import com.android.volley.RequestQueue; import com.android.volley.RequestQueue;
@ -19,25 +23,20 @@ import com.android.volley.Response;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley; import com.android.volley.toolbox.Volley;
import com.example.airquality.api.ForecastAPIRequest; import com.google.android.material.appbar.MaterialToolbar;
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 org.json.JSONObject;
import java.util.Arrays; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class MainActivity extends ToolbarCompatActivity { public class MainActivity extends AppCompatActivity {
List<Weather3h> prognoza; List<Weather3h> prognoza;
private RecyclerView recyclerView; private ListView listView;
private static final Map<String, double[]> cityCoordinates = new HashMap<>(); private static final Map<String, double[]> cityCoordinates = new HashMap<>();
static { static {
@ -55,10 +54,81 @@ public class MainActivity extends ToolbarCompatActivity {
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
recyclerView=findViewById(R.id.weatherList); listView=findViewById(R.id.listView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
initToolbar(); // 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);
}
} }
protected void onResume() { protected void onResume() {
@ -66,47 +136,59 @@ public class MainActivity extends ToolbarCompatActivity {
loadForecast(); loadForecast();
} }
@Override
protected void onRefreshOptionToolbar() {
loadForecast(true);
}
private void loadForecast() { private void loadForecast() {
loadForecast(false); String city = PreferencesManager.getCity(this);
}
private void loadForecast(boolean isUpdated) {
City city = PreferencesManager.getCity(this);
int days = PreferencesManager.getDays(this); int days = PreferencesManager.getDays(this);
String units = PreferencesManager.getUnits(this); String units = PreferencesManager.getUnits(this);
setToolbarTitle(city.name); double[] coords = cityCoordinates.get(city);
if (coords == null) {
if (city == null) { Toast.makeText(this, "Unknown city: " + city, Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Unknown city: " + city.name, Toast.LENGTH_SHORT).show();
return; 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); RequestQueue queue = Volley.newRequestQueue(this);
ForecastAPIRequest forecastAPI = new ForecastAPIRequest(this, city, units) { JsonObjectRequest request = new JsonObjectRequest(
@Override Request.Method.GET,
public void onSuccess(Weather3h[] weatherList) { url,
Weather3h[] arr = Arrays.copyOfRange(weatherList, 0, days); null,
prognoza = Arrays.asList(arr); 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();
}
}
);
Weather3hAdapter adapter = new Weather3hAdapter(MainActivity.this, prognoza, city); queue.add(request);
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

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

View File

@ -17,11 +17,10 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import com.example.airquality.utils.BaseCompatActivity; public class SettingsActivity extends AppCompatActivity {
public class SettingsActivity extends BaseCompatActivity {
Spinner spinnerCity; Spinner spinnerCity;
RadioGroup radioGroupDays, radioGroupUnits; RadioGroup radioGroupDays, radioGroupUnits;
CheckBox checkBoxRemember;
Button buttonSave; Button buttonSave;
String[] cities = {"Subotica", "Novi Sad", "Beograd", "San Francisko", "Sidnej"}; String[] cities = {"Subotica", "Novi Sad", "Beograd", "San Francisko", "Sidnej"};
@ -34,6 +33,7 @@ public class SettingsActivity extends BaseCompatActivity {
spinnerCity = findViewById(R.id.spinnerCity); spinnerCity = findViewById(R.id.spinnerCity);
radioGroupDays = findViewById(R.id.radioGroupDays); radioGroupDays = findViewById(R.id.radioGroupDays);
radioGroupUnits = findViewById(R.id.radioGroupUnits); radioGroupUnits = findViewById(R.id.radioGroupUnits);
checkBoxRemember = findViewById(R.id.checkBoxRemember);
buttonSave = findViewById(R.id.buttonSave); buttonSave = findViewById(R.id.buttonSave);
// Podesi spinner // Podesi spinner
@ -59,13 +59,22 @@ public class SettingsActivity extends BaseCompatActivity {
units = "imperial"; units = "imperial";
} }
SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE); boolean remember = checkBoxRemember.isChecked();
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(); finish();
} }
}); });

View File

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

View File

@ -0,0 +1,54 @@
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

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

View File

@ -1,54 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,15 +0,0 @@
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

@ -1,25 +0,0 @@
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,16 +0,0 @@
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,56 +0,0 @@
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

@ -1,77 +0,0 @@
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

@ -0,0 +1,83 @@
<?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,143 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools" android:padding="16dp">
android:layout_width="match_parent"
android:layout_height="match_parent">
<include <LinearLayout
android:id="@+id/include_toolbar" android:orientation="vertical"
layout="@layout/component_toolbar" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content">
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"
>
<LinearLayout <TextView
android:id="@+id/headerInfo" android:id="@+id/textViewDetailsTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:text="Weather Details"
app:layout_constraintEnd_toEndOf="parent" android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent" android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"> android:paddingBottom="12dp" />
<FrameLayout <!-- Ovde ćemo kasnije dodati detalje o pritisku, vlazi itd. -->
android:layout_width="wrap_content" </LinearLayout>
android:layout_height="wrap_content"> </ScrollView>
<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,18 +7,46 @@
android:background="@color/white" android:background="@color/white"
tools:context=".MainActivity"> tools:context=".MainActivity">
<include <!-- AppBar layout for using Toolbar as AppBar -->
android:id="@+id/include_toolbar" <com.google.android.material.appbar.AppBarLayout
layout="@layout/component_toolbar" /> 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>
<!-- TextView of the Activity --> <!-- TextView of the Activity -->
<androidx.recyclerview.widget.RecyclerView <ListView
android:id="@+id/weatherList" android:id="@+id/listView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:divider="@android:color/darker_gray" android:divider="@android:color/darker_gray"
android:dividerHeight="1dp" android:dividerHeight="1dp"
app:layout_constraintTop_toBottomOf="@id/include_toolbar" app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>

View File

@ -101,6 +101,13 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:background="#0F9D58" /> 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 --> <!-- DUGME -->
<Button <Button

View File

@ -1,33 +0,0 @@
<?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,12 +1,4 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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: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" <TextView android:id="@+id/txtDate"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="DateTime" android:text="DateTime"
@ -22,10 +14,4 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Description" android:text="Description"
android:textSize="14sp"/> android:textSize="14sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CCCCCC"
android:layout_marginTop="12dp"/>
</LinearLayout> </LinearLayout>

View File

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