Completed 5, 6, 7 tasks

This commit is contained in:
Stepan 2025-07-05 14:35:00 +02:00
parent e65180b143
commit 39447cc237
25 changed files with 722 additions and 130 deletions

View File

@ -15,6 +15,12 @@
android:supportsRtl="true"
android:theme="@style/Theme.AirQuality"
tools:targetApi="31">
<activity
android:name=".SearchCityActivity"
android:exported="false" />
<activity
android:name=".FavoritesActivity"
android:exported="false" />
<activity
android:name=".DetailsActivity"
android:exported="false" />

View File

@ -5,12 +5,6 @@ import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;
@ -18,16 +12,10 @@ 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.FormattedTemperature;
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 {
@ -54,7 +42,7 @@ public class DetailsActivity extends ToolbarCompatActivity {
}
protected void updateData() {
FormattedTemp formattedTemp = weather3h.formattedTemp(this);
FormattedTemperature formattedTemperature = weather3h.formattedTemp(this);
if(checkInternet())
{
@ -70,10 +58,10 @@ public class DetailsActivity extends ToolbarCompatActivity {
binding.iconCode.setText(weather3h.getIconCode());
}
binding.cityText.setText(city.name);
binding.cityText.setText(city.toString());
binding.datetimeText.setText(weather3h.getDateTime());
binding.tempWeatherText.setText(formattedTemp.tempText + " | " + weather3h.getDescription());
binding.tempText.setText(formattedTemp.tempMinText + " ~ " + formattedTemp.tempMaxText);
binding.tempWeatherText.setText(formattedTemperature.tempText + " | " + weather3h.getDescription());
binding.tempText.setText(formattedTemperature.tempMinText + " ~ " + formattedTemperature.tempMaxText);
binding.pressureText.setText(weather3h.getPressure() + " hPa");
binding.humidityText.setText(weather3h.getHumidity() + "%");
binding.windSpeedText.setText(weather3h.getWindSpeed() + " m/s");
@ -81,6 +69,11 @@ public class DetailsActivity extends ToolbarCompatActivity {
@Override
protected void onRefreshOptionToolbar() {
if(!checkInternet()) {
Toast.makeText(this, "You can't load data from internet on offline mode", Toast.LENGTH_SHORT).show();
return;
}
String units = PreferencesManager.getUnits(this);
RequestQueue queue = Volley.newRequestQueue(this);

View File

@ -0,0 +1,81 @@
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;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.airquality.databinding.ActivityFavoritesBinding;
import com.example.airquality.types.City;
import com.example.airquality.utils.BaseCompatActivity;
import com.example.airquality.utils.CityAdapter;
import com.example.airquality.utils.PreferencesManager;
import com.example.airquality.utils.Weather3hAdapter;
import java.util.Arrays;
import java.util.List;
public class FavoritesActivity extends BaseCompatActivity {
private ActivityFavoritesBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityFavoritesBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
binding.cityFavoriteList.setLayoutManager(new LinearLayoutManager(this));
}
public void onSearchActivityBtn(View view) {
Intent intent = new Intent(this, SearchCityActivity.class);
startActivity(intent);
}
@Override
protected void onResume() {
super.onResume();
loadFavoriteList();
}
private City[] getFavoriteList()
{
// Get from db
City[] cities = new City[] {
new City("Belgrade", "RS", 44.7866, 20.4489, "2025-07-04 12:30"),
new City("Novi Sad", "RS", 45.2671, 19.8335, "2025-07-03 09:15"),
new City("Niš", "RS", 43.3209, 21.8958, "2025-07-02 18:45"),
new City("Subotica", "RS", 46.1000, 19.6667, "2025-07-01 14:10"),
new City("Kragujevac", "RS", 44.0128, 20.9110, "2025-06-30 07:50")
};
return cities;
}
private void setEnteredDateTime(City city) {
// Search one item by city.name and change for him enteredAt to now()
}
private void loadFavoriteList() {
List<City> cities = Arrays.asList(getFavoriteList());
CityAdapter adapter = new CityAdapter(this, cities) {
@Override
public void onSelectCity(City city) {
PreferencesManager.setCity(FavoritesActivity.this, city);
setEnteredDateTime(city);
finish();
}
};
binding.cityFavoriteList.setAdapter(adapter);
}
}

View File

@ -1,23 +1,15 @@
package com.example.airquality;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuInflater;
import android.widget.ListView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
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 com.example.airquality.api.ForecastAPIRequest;
import com.example.airquality.types.City;
@ -25,30 +17,15 @@ 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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends ToolbarCompatActivity {
List<Weather3h> prognoza;
private RecyclerView recyclerView;
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);
@ -61,13 +38,26 @@ public class MainActivity extends ToolbarCompatActivity {
initToolbar();
}
@Override
protected void onResume() {
super.onResume();
loadForecast();
}
private void saveOffline(Weather3h[] weathers) {
// Save to db
}
private Weather3h[] loadOffline() {
// Load from db
return new Weather3h[] { };
}
@Override
protected void onRefreshOptionToolbar() {
if(!checkInternet()) {
Toast.makeText(MainActivity.this, "You can't load data from internet on offline mode", Toast.LENGTH_SHORT).show();
return;
}
loadForecast(true);
}
@ -79,10 +69,18 @@ public class MainActivity extends ToolbarCompatActivity {
int days = PreferencesManager.getDays(this);
String units = PreferencesManager.getUnits(this);
setToolbarTitle(city.name);
if (city == null) {
Toast.makeText(this, "Unknown city: " + city.name, Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Unknown city", Toast.LENGTH_SHORT).show();
return;
}
setToolbarTitle(city.toString());
// If no internet
if(!checkInternet()) {
displayRecycler(loadOffline(), city, days);
Toast.makeText(MainActivity.this, "You are on offline mode", Toast.LENGTH_SHORT).show();
return;
}
@ -91,14 +89,12 @@ public class MainActivity extends ToolbarCompatActivity {
ForecastAPIRequest forecastAPI = new ForecastAPIRequest(this, city, units) {
@Override
public void onSuccess(Weather3h[] weatherList) {
Weather3h[] arr = Arrays.copyOfRange(weatherList, 0, days);
prognoza = Arrays.asList(arr);
Weather3hAdapter adapter = new Weather3hAdapter(MainActivity.this, prognoza, city);
recyclerView.setAdapter(adapter);
displayRecycler(weatherList, city, days);
if(isUpdated)
Toast.makeText(MainActivity.this, "List Updated!", Toast.LENGTH_SHORT).show();
saveOffline(weatherList);
}
@Override
@ -109,4 +105,16 @@ public class MainActivity extends ToolbarCompatActivity {
queue.add(forecastAPI.getRequest());
}
private void displayRecycler(Weather3h[] weathers, City city, int days) {
if(weathers.length > 0) {
Weather3h[] weatherDaysRange = Arrays.copyOfRange(weathers, 0, days * 8);
prognoza = Arrays.asList(weatherDaysRange);
}
else {
prognoza.clear();
}
Weather3hAdapter adapter = new Weather3hAdapter(this, prognoza, city);
recyclerView.setAdapter(adapter);
}
}

View File

@ -0,0 +1,82 @@
package com.example.airquality;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;
import com.example.airquality.api.CityAPIRequest;
import com.example.airquality.databinding.ActivitySearchCityBinding;
import com.example.airquality.types.City;
import com.example.airquality.utils.BaseCompatActivity;
import com.example.airquality.utils.CityAdapter;
import com.example.airquality.utils.PreferencesManager;
import com.example.airquality.utils.Weather3hAdapter;
import java.util.Arrays;
public class SearchCityActivity extends BaseCompatActivity {
private ActivitySearchCityBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivitySearchCityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.citySearchResult.setLayoutManager(new LinearLayoutManager(this));
}
public void onSearchBtn(View view) {
loadCities();
}
private void addToFavorite(City city) {
// Add to db
}
private void loadCities() {
String searchQuery = binding.searchEditText.getText().toString();
// If no internet
if(!checkInternet()) {
Toast.makeText(this, "You are on offline mode", Toast.LENGTH_SHORT).show();
return;
}
RequestQueue queue = Volley.newRequestQueue(this);
CityAPIRequest cityAPIRequest = new CityAPIRequest(this, searchQuery) {
@Override
public void onSuccess(City[] objectList) {
CityAdapter adapter = new CityAdapter(SearchCityActivity.this, Arrays.asList(objectList), "Add") {
@Override
public void onSelectCity(City city) {
addToFavorite(city);
finish();
}
};
binding.citySearchResult.setAdapter(adapter);
}
@Override
public void onError(VolleyError error) {
Toast.makeText(SearchCityActivity.this, "Failed to load weather data", Toast.LENGTH_SHORT).show();
}
};
queue.add(cityAPIRequest.getRequest());
}
}

View File

@ -7,47 +7,35 @@ import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.airquality.types.City;
import com.example.airquality.utils.BaseCompatActivity;
import com.example.airquality.utils.PreferencesManager;
public class SettingsActivity extends BaseCompatActivity {
Spinner spinnerCity;
RadioGroup radioGroupDays, radioGroupUnits;
Button buttonSave;
String[] cities = {"Subotica", "Novi Sad", "Beograd", "San Francisko", "Sidnej"};
TextView cityNameText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
spinnerCity = findViewById(R.id.spinnerCity);
radioGroupDays = findViewById(R.id.radioGroupDays);
radioGroupUnits = findViewById(R.id.radioGroupUnits);
buttonSave = findViewById(R.id.buttonSave);
// Podesi spinner
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, cities);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCity.setAdapter(adapter);
cityNameText = findViewById(R.id.cityName);
buttonSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String selectedCity = spinnerCity.getSelectedItem().toString();
int days = 7; // podrazumevano
int days = 1; // podrazumevano
if (radioGroupDays.getCheckedRadioButtonId() == R.id.radio3days) {
days = 3;
} else if (radioGroupDays.getCheckedRadioButtonId() == R.id.radio5days) {
@ -59,15 +47,22 @@ public class SettingsActivity extends BaseCompatActivity {
units = "imperial";
}
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();
PreferencesManager.savePreferences(SettingsActivity.this, days, units);
finish();
}
});
}
public void onFavoriteBtn(View view) {
Intent intent = new Intent(this, FavoritesActivity.class);
startActivity(intent);
}
protected void onResume() {
super.onResume();
City currentCity = PreferencesManager.getCity(this);
cityNameText.setText(currentCity.toString());
}
}

View File

@ -0,0 +1,59 @@
package com.example.airquality.api;
import android.annotation.SuppressLint;
import android.content.Context;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.example.airquality.types.City;
import com.example.airquality.utils.CityParser;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONArray;
import org.json.JSONObject;
public class CityAPIRequest extends JsonAPIRequest<City, JsonArrayRequest> {
@SuppressLint("DefaultLocale")
public CityAPIRequest(Context context, String query) {
super(context);
this.url = String.format(
"https://api.openweathermap.org/geo/1.0/direct?q=%s&limit=10&appid=%s",
query, getKey()
);
}
@Override
public JsonArrayRequest getRequest() {
return new JsonArrayRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
onSuccess(CityParser.parse(response));
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
onError(error);
}
}
);
}
@Override
public void onSuccess(City[] objectList) {
}
@Override
public void onError(VolleyError error) {
}
}

View File

@ -16,7 +16,7 @@ import org.json.JSONObject;
import java.util.Arrays;
public class ForecastAPIRequest extends JsonAPIRequest {
public class ForecastAPIRequest extends JsonAPIRequest<Weather3h, JsonObjectRequest> {
@SuppressLint("DefaultLocale")
public ForecastAPIRequest(Context context, City city, String units) {
@ -49,6 +49,6 @@ public class ForecastAPIRequest extends JsonAPIRequest {
);
}
public void onSuccess(Weather3h[] weatherList) { }
public void onSuccess(Weather3h[] objectList) { }
public void onError(VolleyError error) { }
}

View File

@ -9,12 +9,13 @@ 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.types.Weather3h;
import com.example.airquality.utils.Weather3hAdapter;
import com.example.airquality.utils.Weather3hParser;
import org.json.JSONObject;
public abstract class JsonAPIRequest {
public abstract class JsonAPIRequest<T, JR> {
private Context appContext;
protected String url;
@ -28,5 +29,8 @@ public abstract class JsonAPIRequest {
{
return this.appContext.getString(R.string.weather_api_key);
}
public abstract JsonObjectRequest getRequest();
public abstract JR getRequest();
public abstract void onSuccess(T[] objectList);
public abstract void onError(VolleyError error);
}

View File

@ -1,15 +1,30 @@
package com.example.airquality.types;
import androidx.annotation.NonNull;
import java.io.Serializable;
public class City implements Serializable {
public String name;
public String country;
public double latitude;
public double longitude;
public String lastEnteredAt;
public City(String name, double latitude, double longitude) {
public City(String name, String country, double latitude, double longitude, String lastEnteredAt) {
this.name = name;
this.country = country;
this.latitude = latitude;
this.longitude = longitude;
this.lastEnteredAt = lastEnteredAt;
}
public City(String name, String country, double latitude, double longitude) {
this(name, country, latitude, longitude, "");
}
@NonNull
@Override
public String toString() {
return this.name + " (" + this.country + ")";
}
}

View File

@ -2,18 +2,14 @@ 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 class FormattedTemperature {
public String tempText;
public String tempMinText;
public String tempMaxText;
public FormattedTemp(Context context, double temp, double tempMin, double tempMax)
public FormattedTemperature(Context context, double temp, double tempMin, double tempMax)
{
String units = PreferencesManager.getUnits(context);

View File

@ -49,8 +49,8 @@ public class Weather3h implements Serializable {
return tempMax;
}
public FormattedTemp formattedTemp(Context context) {
return new FormattedTemp(context, temp, tempMin, tempMax);
public FormattedTemperature formattedTemp(Context context) {
return new FormattedTemperature(context, temp, tempMin, tempMax);
}
public int getDt() {

View File

@ -3,6 +3,7 @@ package com.example.airquality.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
@ -13,4 +14,9 @@ public class BaseCompatActivity extends AppCompatActivity {
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
public void onBackButton(View view)
{
finish();
}
}

View File

@ -0,0 +1,74 @@
package com.example.airquality.utils;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.example.airquality.R;
import com.example.airquality.types.City;
import java.util.List;
public class CityAdapter extends RecyclerView.Adapter<CityAdapter.CityViewHolder> {
private Context context;
private List<City> cityList;
private String applyBtnText;
public CityAdapter(Context context, List<City> cityList) {
this(context, cityList, "Select");
}
public CityAdapter(Context context, List<City> cityList, String applyBtnText) {
this.context = context;
this.cityList = cityList;
this.applyBtnText = applyBtnText;
}
public static class CityViewHolder extends RecyclerView.ViewHolder {
TextView cityName, cityCoords, lastEnteredTime;
Button selectBtn;
public CityViewHolder(View itemView) {
super(itemView);
cityName = itemView.findViewById(R.id.cityName);
cityCoords = itemView.findViewById(R.id.cityCoords);
selectBtn = itemView.findViewById(R.id.selectBtn);
lastEnteredTime = itemView.findViewById(R.id.lastEnteredTime);
}
}
@Override
public CityViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_city, parent, false);
return new CityViewHolder(view);
}
@Override
public void onBindViewHolder(CityViewHolder holder, int position) {
City item = cityList.get(position);
holder.cityName.setText(item.toString());
holder.cityCoords.setText(
"Long: " + String.format("%.3f", item.longitude) + ", Lat: " + String.format("%.3f", item.latitude));
holder.lastEnteredTime.setText(item.lastEnteredAt);
holder.selectBtn.setText(this.applyBtnText);
holder.selectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSelectCity(item);
}
});
}
@Override
public int getItemCount() {
return cityList.size();
}
public void onSelectCity(City city) { }
}

View File

@ -0,0 +1,34 @@
package com.example.airquality.utils;
import com.example.airquality.types.City;
import com.example.airquality.types.Weather3h;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class CityParser {
public static City[] parse(JSONArray jsonArray) {
List<City> cities = new ArrayList<>();
try {
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String cityName = jsonObject.getString("name");
double lon = jsonObject.getDouble("lon");
double lat = jsonObject.getDouble("lat");
String countryName = jsonObject.getString("country");
City city = new City(cityName, countryName, lat, lon);
cities.add(city);
}
} catch (Exception e) {
e.printStackTrace();
}
return cities.toArray(new City[0]);
}
}

View File

@ -11,7 +11,7 @@ public class PreferencesManager {
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) {
public static void savePreferences(Context context, int days, String units) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KEY_DAYS, days);
@ -20,13 +20,28 @@ public class PreferencesManager {
}
public static City getCity(Context context) {
// DAVID: Realizovati iz sqlite
return new City("Subotica", 46.1, 19.6667);
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String cityName = prefs.getString("city_name", "Subotica");
String country = prefs.getString("city_country", "RS");
double longitude = prefs.getFloat("city_longitude", 46.1f);
double latitude = prefs.getFloat("city_latitude", 19.6667f);
return new City(cityName, country, longitude, latitude);
}
public static void setCity(Context context, City city) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("city_name", city.name);
editor.putString("city_country", city.country);
editor.putFloat("city_longitude", (float)city.longitude);
editor.putFloat("city_latitude", (float)city.latitude);
editor.apply();
}
public static int getDays(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getInt(KEY_DAYS, 7); // default: 7 dana
return prefs.getInt(KEY_DAYS, 1); // default: 1 dana
}
public static String getUnits(Context context) {

View File

@ -5,17 +5,14 @@ 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.FormattedTemperature;
import com.example.airquality.types.Weather3h;
import java.util.List;
@ -31,8 +28,6 @@ public class Weather3hAdapter extends RecyclerView.Adapter<Weather3hAdapter.Weat
this.weatherList = weatherList;
this.city = city;
}
// ViewHolder описывает и хранит ссылки на элементы layout
public static class WeatherViewHolder extends RecyclerView.ViewHolder {
TextView txtDate, txtTemp, txtDesc;
@ -53,10 +48,10 @@ public class Weather3hAdapter extends RecyclerView.Adapter<Weather3hAdapter.Weat
@Override
public void onBindViewHolder(WeatherViewHolder holder, int position) {
Weather3h item = weatherList.get(position);
FormattedTemp formattedTemp = item.formattedTemp(context);
FormattedTemperature formattedTemperature = item.formattedTemp(context);
holder.txtDate.setText(item.getDateTime());
holder.txtTemp.setText(formattedTemp.tempText);
holder.txtTemp.setText(formattedTemperature.tempText);
holder.txtDesc.setText(item.getDescription());
holder.itemView.setOnClickListener(new View.OnClickListener() {

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -17,6 +17,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/include_toolbar"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@color/white"
android:padding="20dp"
>
@ -57,12 +58,9 @@
>
<TextView
style="@style/headerText"
android:id="@+id/cityText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="City name"
android:textStyle="bold"
android:textSize="20sp"/>
android:text="City name"/>
<TextView
android:id="@+id/datetimeText"

View File

@ -0,0 +1,54 @@
<?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"
android:padding="16dp"
android:background="@color/white"
tools:context=".FavoritesActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
style="@style/BackButton"
android:onClick="onBackButton" />
<TextView
style="@style/headerText"
android:text="Favorite cities" />
</LinearLayout>
<Button
android:id="@+id/newCityBtn"
style="@style/GreenButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxWidth="320dp"
android:text="Add new city"
android:layout_marginTop="20dp"
android:onClick="onSearchActivityBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cityFavoriteList"
android:layout_width="0dp"
android:layout_height="0dp"
android:dividerHeight="1dp"
app:layout_constraintTop_toBottomOf="@id/newCityBtn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,72 @@
<?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"
android:padding="16dp"
android:background="@color/white"
tools:context=".SearchCityActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
style="@style/BackButton"
android:onClick="onBackButton" />
<TextView
style="@style/headerText"
android:text="Search city" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fieldset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/linearLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/searchBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
style="@style/GreenButtonStyle"
android:id="@+id/searchBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Search"
android:onClick="onSearchBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/citySearchResult"
android:layout_width="0dp"
android:layout_height="0dp"
android:dividerHeight="1dp"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/fieldset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,6 +2,7 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:background="@color/white">
@ -10,9 +11,24 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center">
<Button
style="@style/BackButton"
android:onClick="onBackButton"
/>
<TextView
style="@style/headerText"
android:text="Settings"/>
</LinearLayout>
<!-- GRAD -->
<TextView
android:text="Choose City:"
android:text="City"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#444444"
@ -20,24 +36,37 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/spinnerCity"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:spinnerMode="dropdown"
android:layout_height="wrap_content">
android:popupBackground="@color/green_popup"/>
<TextView
android:id="@+id/cityName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="City name"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/selectCityBtn"
style="@style/GreenSmallButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select"
android:onClick="onFavoriteBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- separator -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:background="#0F9D58" />
<include layout="@layout/component_settings_separator" />
<!-- BROJ DANA -->
<TextView
android:text="Forecast Duration:"
android:text="Forecast Duration"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#444444"
@ -49,6 +78,10 @@
android:id="@+id/radioGroupDays"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton android:id="@+id/radio1days"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1 day" />
<RadioButton android:id="@+id/radio3days"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -57,22 +90,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5 days" />
<RadioButton android:id="@+id/radio7days"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="7 days" />
</RadioGroup>
<!-- separator -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:background="#0F9D58" />
<include layout="@layout/component_settings_separator" />
<!-- JEDINICE -->
<TextView
android:text="Units:"
android:text="Units"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#444444"
@ -95,19 +120,14 @@
</RadioGroup>
<!-- separator -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:background="#0F9D58" />
<include layout="@layout/component_settings_separator" />
<!-- DUGME -->
<Button
android:id="@+id/buttonSave"
style="@style/GreenButtonStyle"
android:text="Save and Return"
android:textColor="@android:color/white"
android:backgroundTint="#0F9D58"
android:layout_marginTop="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:background="#0F9D58" />

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/cityName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Subotica"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/cityCoords"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="long: 21.12, lat: 342.21"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cityName" />
<TextView
android:id="@+id/lastEnteredTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="20.12.2025 21:00:00"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cityCoords" />
<Button
android:id="@+id/selectBtn"
style="@style/GreenSmallButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,5 +5,33 @@
</style>
<style name="BackButton">
<item name="android:backgroundTint">#C5C5C5</item>
<item name="android:drawableLeft">@drawable/baseline_arrow_back</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minWidth">0dp</item>
<item name="android:minHeight">0dp</item>
<item name="android:layout_marginRight">15dp</item>
<item name="android:textSize">20sp</item>
</style>
<style name="GreenButtonStyle">
<item name="android:textColor">@android:color/white</item>
<item name="android:backgroundTint">#0F9D58</item>
</style>
<style name="GreenSmallButtonStyle">
<item name="android:textColor">@android:color/white</item>
<item name="android:backgroundTint">#0F9D58</item>
<item name="android:textSize">13sp</item>
<item name="android:minWidth">0dp</item>
<item name="android:minHeight">0dp</item>
</style>
<style name="headerText">
<item name="android:textStyle">bold</item>
<item name="android:textSize">24sp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="Theme.AirQuality" parent="Base.Theme.AirQuality" />
</resources>