before Lab

This commit is contained in:
Philipp Wo 2019-05-10 17:02:49 +02:00
parent 13d13338d1
commit 71057c27bd
15 changed files with 679 additions and 38 deletions

View File

@ -2,6 +2,9 @@
xmlns:tools="http://schemas.android.com/tools"
package="at.fhj.airkoality">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@ -11,7 +14,8 @@
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".ui.activity.MainActivity">
<activity android:name=".ui.activity.MainActivity"></activity>
<activity android:name=".ui.activity.MeasurementActivity">
</activity>
@ -19,7 +23,7 @@
<activity android:name=".ui.activity.SplashActivity"
android:noHistory="true"
android:theme="@style/NoToolbar"
android:theme="@style/NoActionBar"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View File

@ -3,11 +3,14 @@ package at.fhj.airkoality.db.room;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.TypeConverters;
import android.content.Context;
import at.fhj.airkoality.model.LatestMeasurements;
import at.fhj.airkoality.model.Location;
@Database(entities = {Location.class}, version = 1,exportSchema = false)
@Database(entities = {Location.class, LatestMeasurements.class}, version = 1,exportSchema = false)
@TypeConverters({Converters.class})
public abstract class AirKoalityDB extends RoomDatabase {
private static AirKoalityDB instance;
@ -15,6 +18,8 @@ public abstract class AirKoalityDB extends RoomDatabase {
public abstract LocationDAO locationDAO();
public static AirKoalityDB getDatabase(Context context){
if (instance == null) {
instance = Room.databaseBuilder(context, AirKoalityDB.class, "airkoality").allowMainThreadQueries().build();
@ -23,4 +28,5 @@ public abstract class AirKoalityDB extends RoomDatabase {
}
public abstract LatestMeasurementsDAO latestMeasurementsDAO();
}

View File

@ -0,0 +1,38 @@
package at.fhj.airkoality.db.room;
import android.arch.persistence.room.TypeConverter;
import java.util.ArrayList;
import at.fhj.airkoality.model.Measurement;
public class Converters {
@TypeConverter
public static ArrayList<Measurement> fromStringToMeasurements(String value) {
ArrayList<Measurement> measurementsList = new ArrayList<>();
String[] measurements = value.split("\\|");
for (String s : measurements) {
String[] parts = s.split(";");
measurementsList.add(new Measurement(parts[0], Double.valueOf(parts[1]), parts[2]));
}
return measurementsList;
}
@TypeConverter
public static String fromMeasurementsToString(ArrayList<Measurement> measurements) {
String measurementsString = "";
if(measurements != null) {
for (int i = 0; i < measurements.size(); i++) {
measurementsString += measurements.get(i).getParameter() + ";" + measurements.get(i).getValue() + ";" + measurements.get(i).getUnit();
if (i != measurements.size() - 1) {
measurementsString += "|";
}
}
}
return measurementsString;
}
}

View File

@ -0,0 +1,17 @@
package at.fhj.airkoality.db.room;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import at.fhj.airkoality.model.LatestMeasurements;
@Dao
public interface LatestMeasurementsDAO {
@Query("SELECT * FROM latest_measurements WHERE locationName = :locationName")
LatestMeasurements getForLocation(String locationName);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void add(LatestMeasurements latestMeasurements);
}

View File

@ -16,7 +16,8 @@ public interface LocationDAO {
List<Location> getAll();
@Query("SELECT * FROM location WHERE location = :locationName")
Location getLocationWithName(String locationName);
Location getLocationWithName(String locationName);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addLocation(Location location);
@ -24,5 +25,6 @@ public interface LocationDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addAll(List<Location> locations);
@Query("DELETE FROM location")
void clear();
}

View File

@ -0,0 +1,34 @@
package at.fhj.airkoality.model;
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
import java.util.ArrayList;
@Entity(tableName = "latest_measurements")
public class LatestMeasurements {
@PrimaryKey
@NonNull
private String locationName;
@ColumnInfo(name = "measurements")
private ArrayList<Measurement> measurements;
@NonNull
public String getLocationName() {
return locationName;
}
public void setLocationName(@NonNull String locationName) {
this.locationName = locationName;
}
public ArrayList<Measurement> getMeasurements() {
return measurements;
}
public void setMeasurements(ArrayList<Measurement> measurements) {
this.measurements = measurements;
}
}

View File

@ -0,0 +1,37 @@
package at.fhj.airkoality.model;
public class Measurement {
private String parameter;
private double value;
private String unit;
public Measurement(String parameter, double value, String unit) {
this.parameter = parameter;
this.value = value;
this.unit = unit;
}
public String getParameter() {
return parameter;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
}

View File

@ -1,5 +1,7 @@
package at.fhj.airkoality.ui.activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -12,19 +14,35 @@ import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import at.fhj.airkoality.R;
import at.fhj.airkoality.db.room.AirKoalityDB;
import at.fhj.airkoality.model.Location;
import at.fhj.airkoality.network.HttpsGetTask;
import at.fhj.airkoality.network.RequestCallback;
import at.fhj.airkoality.ui.fragment.LocationListFragment;
import at.fhj.airkoality.ui.fragment.MapFragment;
public class MainActivity extends AppCompatActivity {
public class MainActivity extends AppCompatActivity implements RequestCallback {
private Fragment locationListFragment;
private Fragment mapFragment;
private LocationListFragment locationListFragment;
private MapFragment mapFragment;
private static final String LOCATION_LIST = "location_list";
private static final String MAP = "map";
private static final String FRAGMENT_PREF_KEY = "last_fragment";
private ProgressDialog progressDialog;
private AirKoalityDB database;
private BottomNavigationView bottomNavigationView;
@ -41,6 +59,9 @@ public class MainActivity extends AppCompatActivity {
bottomNavigationView = findViewById(R.id.bnvMain);
progressDialog = new ProgressDialog(this);
database = AirKoalityDB.getDatabase(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@ -50,11 +71,9 @@ public class MainActivity extends AppCompatActivity {
transaction.commit();
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
bottomNavigationView.setOnNavigationItemSelectedListener(menuItem -> {
switch (menuItem.getItemId()){
switch (menuItem.getItemId()){
case R.id.action_location:
setSelectedFragment(LOCATION_LIST);
sharedPreferences.edit()
@ -69,14 +88,22 @@ public class MainActivity extends AppCompatActivity {
.commit();
break;
}
return false;
}
return true;
});
setSelectedFragment(sharedPreferences.getString("last_fragment", LOCATION_LIST));
fetchLocations();
}
private void fetchLocations(){
HttpsGetTask httpsGetTask = new HttpsGetTask(this);
httpsGetTask.execute("https://api.openaq.org/v1/locations?country=AT&limit=200");
}
private void setSelectedFragment(String fragmentName){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
switch (fragmentName) {
@ -92,4 +119,67 @@ public class MainActivity extends AppCompatActivity {
transaction.commit();
}
@Override
public void onRequestStart() {
progressDialog.setMessage("Fetching...");
progressDialog.show();
}
@Override
public void onResult(String result) {
new Thread(()->{
List<Location> locations;
//parse result
if(result == null){
System.out.println("no locations");
}else {
//write to db
try {
locations = parseLocations(result);
database.locationDAO().clear();
database.locationDAO().addAll(locations);
}catch (JSONException e){
e.printStackTrace();
}
}
//notify fragments
dismissProgressDialog();
updateFragments();
}).start();
}
private void updateFragments() {
runOnUiThread(() -> {
locationListFragment.refresh();
mapFragment.refresh();
});
}
private void dismissProgressDialog() {
runOnUiThread(()-> progressDialog.dismiss());
}
private List<Location> parseLocations(String json) throws JSONException {
List<Location> locations = new ArrayList<>();
JSONObject jsonObject = new JSONObject(json);
JSONArray results = jsonObject.getJSONArray("results");
for (int i = 0; i < results.length(); i++){
JSONObject locationResult = results.getJSONObject(i);
String locationName = locationResult.getString("location");
String city = locationResult.getString("city");
String country = locationResult.getString("country");
Location location = new Location(locationName, city, country);
locations.add(location);
}
return locations;
}
}

View File

@ -0,0 +1,208 @@
package at.fhj.airkoality.ui.activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import at.fhj.airkoality.R;
import at.fhj.airkoality.db.room.AirKoalityDB;
import at.fhj.airkoality.model.LatestMeasurements;
import at.fhj.airkoality.model.Measurement;
import at.fhj.airkoality.network.HttpsGetTask;
import at.fhj.airkoality.network.RequestCallback;
public class MeasurementActivity extends AppCompatActivity implements RequestCallback {
private AirKoalityDB database;
private String locationName;
private TextView tvLocation;
private TextView tvBCvalue;
private TextView tvCOvalue;
private TextView tvO3value;
private TextView tvPM10value;
private TextView tvPM25value;
private TextView tvSO2value;
private TextView tvNO2value;
private NetworkStateReceiver networkStateReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_measurement);
tvLocation = findViewById(R.id.tvLocation);
tvBCvalue = findViewById(R.id.tvBCvalue);
tvCOvalue = findViewById(R.id.tvCOvalue);
tvO3value = findViewById(R.id.tvO3value);
tvPM10value = findViewById(R.id.tvPM10value);
tvPM25value = findViewById(R.id.tvPM25value);
tvSO2value = findViewById(R.id.tvSO2value);
tvNO2value = findViewById(R.id.tvNO2value);
//TODO: get locationname from intent
locationName = getIntent().getStringExtra("location_name");
tvLocation.setText(locationName);
database = AirKoalityDB.getDatabase(this);
networkStateReceiver = new NetworkStateReceiver();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
setTitle(R.string.latest_measurements);
fetchLatestMeasurements(locationName);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(networkStateReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(networkStateReceiver);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
private void fetchLatestMeasurements(String locationName) {
HttpsGetTask httpsGetTask = new HttpsGetTask(this);
httpsGetTask.execute("https://api.openaq.org/v1/latest?location=" + locationName);
}
@Override
public void onRequestStart() {
}
public void updateUI(LatestMeasurements latestMeasurements) {
if (latestMeasurements != null) {
runOnUiThread(() -> {
for (Measurement measurement : latestMeasurements.getMeasurements()) {
String valueString = "" + measurement.getValue() + " " + measurement.getUnit();
switch (measurement.getParameter().toLowerCase()) {
case "bc":
tvBCvalue.setText(valueString);
break;
case "co":
tvCOvalue.setText(valueString);
break;
case "no2":
tvNO2value.setText(valueString);
break;
case "o3":
tvO3value.setText(valueString);
break;
case "pm10":
tvPM10value.setText(valueString);
break;
case "pm25":
tvPM25value.setText(valueString);
break;
case "so2":
tvSO2value.setText(valueString);
break;
}
}
});
}
}
@Override
public void onResult(String result) {
new Thread(() -> {
LatestMeasurements latestMeasurements;
if (result == null) {
latestMeasurements = database.latestMeasurementsDAO().getForLocation(locationName);
} else {
latestMeasurements = parseLatestMeasurements(result);
if (latestMeasurements != null) {
database.latestMeasurementsDAO().add(latestMeasurements);
}
}
updateUI(latestMeasurements);
}).start();
}
private LatestMeasurements parseLatestMeasurements(String json) {
LatestMeasurements latestMeasurements = null;
try {
JSONObject jsonObject = new JSONObject(json);
JSONArray results = jsonObject.getJSONArray("results");
JSONObject result = results.getJSONObject(0);
JSONArray measurementsArray = result.getJSONArray("measurements");
latestMeasurements = new LatestMeasurements();
latestMeasurements.setLocationName(result.getString("location"));
ArrayList<Measurement> measurements = new ArrayList<>();
for (int i = 0; i < measurementsArray.length(); i++) {
JSONObject measurementResult = measurementsArray.getJSONObject(i);
String parameter = measurementResult.getString("parameter");
double value = measurementResult.getDouble("value");
String unit = measurementResult.getString("unit");
measurements.add(new Measurement(parameter, value, unit));
}
latestMeasurements.setMeasurements(measurements);
} catch (JSONException e) {
e.printStackTrace();
}
return latestMeasurements;
}
class NetworkStateReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if(isConnected(context)) {
fetchLatestMeasurements(locationName);
}
}
private boolean isConnected(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
}
}

View File

@ -1,5 +1,6 @@
package at.fhj.airkoality.ui.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -17,6 +18,8 @@ import java.util.List;
import at.fhj.airkoality.R;
import at.fhj.airkoality.db.room.AirKoalityDB;
import at.fhj.airkoality.model.Location;
import at.fhj.airkoality.model.Measurement;
import at.fhj.airkoality.ui.activity.MeasurementActivity;
import at.fhj.airkoality.ui.adapter.LocationListAdapter;
public class LocationListFragment extends Fragment implements LocationListAdapter.ItemClickListener {
@ -31,32 +34,27 @@ public class LocationListFragment extends Fragment implements LocationListAdapte
locationList = view.findViewById(R.id.rvLocationList);
List<Location> locations = new ArrayList<>();
locations.add(new Location("Daheim", "Graz" , "Österreich"));
locations.add(new Location("Daheim2", "Graz" , "Österreich"));
locations.add(new Location("Daheim3", "Graz" , "Österreich"));
locations.add(new Location("Daheim4", "Graz" , "Österreich"));
locations.add(new Location("Daheim5", "Graz" , "Österreich"));
locations.add(new Location("Daheim6", "Graz" , "Österreich"));
locations.add(new Location("Daheim7", "Graz" , "Österreich"));
locations.add(new Location("Daheim8", "Graz" , "Österreich"));
AirKoalityDB.getDatabase(getContext()).locationDAO().addAll(locations);
LocationListAdapter adapter = new LocationListAdapter(AirKoalityDB.getDatabase(getContext()).locationDAO().getAll(),this);
locationList.setAdapter(adapter);
locationList.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
private void fetchLocations() {
LocationListAdapter adapter = new LocationListAdapter(AirKoalityDB.getDatabase(getContext()).locationDAO().getAll(),this);
locationList.setAdapter(adapter);
}
public void refresh(){
fetchLocations();
}
@Override
public void onItemClicked(Location location, int position) {
Toast.makeText(getContext(), "Clicked " + location, Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getContext(), MeasurementActivity.class);
intent.putExtra("location_name", location.getLocation());
startActivity(intent);
}
}

View File

@ -21,4 +21,8 @@ public class MapFragment extends Fragment {
return view;
}
public void refresh(){
}
}

View File

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="20dp"
android:text=""
android:textColor="@color/primaryLightColor"
android:textSize="25sp"
android:textStyle="bold" />
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_margin="10dp"
android:layout_weight="1"
app:cardBackgroundColor="@color/secondaryDarkColor">
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="BC" />
<TextView
android:id="@+id/tvBCvalue"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="CO" />
<TextView
android:id="@+id/tvCOvalue"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="O3" />
<TextView
android:id="@+id/tvO3value"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="PM10" />
<TextView
android:id="@+id/tvPM10value"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="PM25" />
<TextView
android:id="@+id/tvPM25value"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="SO2" />
<TextView
android:id="@+id/tvSO2value"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
<TableRow
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp">
<TextView
style="@style/MeasurementLabel"
android:layout_width="0dp"
android:layout_height="@dimen/measurement_label_size"
android:layout_weight="1"
android:text="NO2" />
<TextView
android:id="@+id/tvNO2value"
style="@style/MeasurementValueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_weight="1"
android:text="-" />
</TableRow>
</TableLayout>
</android.support.v7.widget.CardView>
</LinearLayout>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="measurement_label_size">50dp</dimen>
</resources>

View File

@ -6,4 +6,5 @@
<string name="location_label">Location:</string>
<string name="city_label">City:</string>
<string name="country_label">Country:</string>
<string name="latest_measurements">Latest measurements:</string>
</resources>

View File

@ -1,20 +1,28 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
<item name="colorAccent">@color/secondaryColor</item>
</style>
<!-- Base application theme. -->
<style name="NoToolbar" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<style name="NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
<item name="colorAccent">@color/secondaryColor</item>
</style>
<style name="MeasurementValueText">
<item name="android:textSize">18sp</item>
<item name="android:layout_marginLeft">30dp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/secondaryTextColor</item>
</style>
<style name="MeasurementLabel">
<item name="android:textColor">@color/primaryLightColor</item>
<item name="android:textSize">20sp</item>
<item name="android:gravity">right|center_vertical</item>
</style>
</resources>