From 605280c5db6e590419c23cabdfb584ad5a9fb948 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Sun, 15 Nov 2020 00:11:19 +0100 Subject: [PATCH 1/5] Upgrade gradle and gradle-plugin --- build.gradle | 2 +- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index be2c04b..a37f23b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:4.1.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f817df6..c721741 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Feb 28 13:50:08 CET 2020 +#Sun Nov 15 00:09:31 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip From 5c0a68a0faf990eeb4d780e9aa8e97eba8b6ab8f Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Mon, 23 Nov 2020 00:04:14 +0100 Subject: [PATCH 2/5] Add some code comments I hope this makes it a bit easier to read :) --- app/src/main/java/ch/fixme/status/Main.java | 40 +++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/ch/fixme/status/Main.java b/app/src/main/java/ch/fixme/status/Main.java index 4581ac0..cba4bda 100644 --- a/app/src/main/java/ch/fixme/status/Main.java +++ b/app/src/main/java/ch/fixme/status/Main.java @@ -32,8 +32,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.LinearLayout; @@ -42,6 +40,9 @@ import android.widget.SectionIndexer; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + import com.woozzu.android.util.StringMatcher; import com.woozzu.android.widget.IndexableListView; @@ -59,8 +60,6 @@ import java.util.Set; import java.util.regex.Pattern; -import androidx.annotation.UiThread; - public class Main extends Activity { // API: https://spaceapi.io/ @@ -79,9 +78,13 @@ public class Main extends Activity { private static final String MAP_SEARCH = "geo:0,0?q="; private static final String MAP_COORD = "geo:%s,%s?z=23&q=%s&"; + // Shared preferences private SharedPreferences mPrefs; + // Hashmap with the endpoint URL as key and the endpoint JSON string as value private HashMap mResultHs; + // Contains directory endpoint JSON data as string public String mResultDir; + // The endpoint URL of the currently showing space private String mApiUrl; private boolean finishApi = false; private boolean finishDir = false; @@ -97,14 +100,22 @@ public class Main extends Activity { @UiThread public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Load layout setContentView(R.layout.main); + + // Hide views until loaded setViewVisibility(false); + + // Load shared prefs mPrefs = PreferenceManager.getDefaultSharedPreferences(Main.this); + + // Load data mResultHs = new HashMap<>(); if (checkNetwork()) { - Log.d(TAG, "onCreate() intent="+ getIntent().toString()); + Log.d(TAG, "onCreate() intent=" + getIntent().toString()); setCache(); - getHsList(savedInstanceState); + getHsList(); showHsInfo(getIntent()); } else { showError(getString(R.string.error_title) + getString(R.string.error_network_title), @@ -296,7 +307,7 @@ private AlertDialog createHsDialog() { } } - private void getHsList(Bundle savedInstanceState) { + private void getHsList() { final Bundle data = (Bundle) getLastNonConfigurationInstance(); if (data == null) { Log.d(TAG, "getHsList(fresh data)"); @@ -310,14 +321,14 @@ private void getHsList(Bundle savedInstanceState) { } } - private void showHsInfo(Intent intent) { + private void showHsInfo(@Nullable Intent intent) { final Bundle data = (Bundle) getLastNonConfigurationInstance(); - // Get hackerspace api url - if(data != null && data.containsKey(STATE_URL)) { + + // Get space endpoint URL + if (data != null && data.containsKey(STATE_URL)) { Log.d(TAG, "showHsInfo(uri from state)"); mApiUrl = data.getString(STATE_URL); - } else if (intent != null - && intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { + } else if (intent != null && intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { Log.d(TAG, "showHsInfo(uri from widget intent)"); mApiUrl = mPrefs.getString( PREF_API_URL_WIDGET @@ -332,8 +343,9 @@ private void showHsInfo(Intent intent) { Log.d(TAG, "showHsInfo(uri from prefs)"); mApiUrl = mPrefs.getString(Prefs.KEY_API_URL, ParseGeneric.API_DEFAULT); } - // Get Data - if(data != null && data.containsKey(STATE_HS)) { + + // Now that we have the URL, fetch the data + if (data != null && data.containsKey(STATE_HS)) { Log.d(TAG, "showHsInfo(data from state)"); finishApi = true; mResultHs = (HashMap) data.getSerializable(STATE_HS); From 501d9a6e34b0565c987b68b285cccc24f29c87ef Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Mon, 23 Nov 2020 23:52:31 +0100 Subject: [PATCH 3/5] Replace parser classes with spaceapi-kt based parsing Besides replacing the JSON / HashMap based parser, the whole layout setup code was also refactored. A few helpers were added to make the code a bit cleaner. The widget can be added but currently doesn't seem to work on Android 11. However, as far as I know, it did not work previously either. Probably some APIs changed. --- app/build.gradle | 6 + app/src/main/java/ch/fixme/status/Main.java | 519 +++++++++++------- .../main/java/ch/fixme/status/Parse12.java | 150 ----- .../main/java/ch/fixme/status/Parse13.java | 214 -------- .../java/ch/fixme/status/ParseGeneric.java | 82 --- app/src/main/java/ch/fixme/status/Utils.java | 26 + app/src/main/java/ch/fixme/status/Widget.java | 66 +-- app/src/main/res/layout/entry_sensor.xml | 54 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 29 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values/strings.xml | 36 +- .../test/java/ch/fixme/status/UtilsTest.java | 19 + build.gradle | 2 + 15 files changed, 466 insertions(+), 743 deletions(-) delete mode 100644 app/src/main/java/ch/fixme/status/Parse12.java delete mode 100644 app/src/main/java/ch/fixme/status/Parse13.java delete mode 100644 app/src/main/java/ch/fixme/status/ParseGeneric.java create mode 100644 app/src/main/java/ch/fixme/status/Utils.java create mode 100644 app/src/test/java/ch/fixme/status/UtilsTest.java diff --git a/app/build.gradle b/app/build.gradle index 45dc106..408a497 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,4 +26,10 @@ android { dependencies { implementation "androidx.annotation:annotation:1.1.0" + + // SpaceAPI Library + implementation "spaceapi-community:spaceapi-kt:0.2.1" + + // JUnit for testing + testImplementation 'junit:junit:4.13.1' } diff --git a/app/src/main/java/ch/fixme/status/Main.java b/app/src/main/java/ch/fixme/status/Main.java index cba4bda..d01d804 100644 --- a/app/src/main/java/ch/fixme/status/Main.java +++ b/app/src/main/java/ch/fixme/status/Main.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist) + * Copyright (C) 2020 Danilo Bargen * Licensed under GNU's GPL 3, see README */ @@ -19,7 +20,6 @@ import android.graphics.drawable.AnimationDrawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.Uri; import android.net.http.HttpResponseCache; import android.os.AsyncTask; import android.os.Bundle; @@ -40,7 +40,9 @@ import android.widget.SectionIndexer; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.annotation.UiThread; import com.woozzu.android.util.StringMatcher; @@ -55,16 +57,28 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.Set; import java.util.regex.Pattern; +import io.spaceapi.SpaceApiParser; +import io.spaceapi.types.AccountBalance; +import io.spaceapi.types.Barometer; +import io.spaceapi.types.BeverageSupply; +import io.spaceapi.types.DoorLocked; +import io.spaceapi.types.Humidity; +import io.spaceapi.types.MemberCount; +import io.spaceapi.types.NetworkConnection; +import io.spaceapi.types.PeoplePresent; +import io.spaceapi.types.PowerConsumption; +import io.spaceapi.types.Status; +import io.spaceapi.types.Temperature; + public class Main extends Activity { + protected static final String TAG = "MyHackerspace"; // API: https://spaceapi.io/ - protected static final String TAG = "MyHackerspace"; + static final String API_DEFAULT = "https://fixme.ch/status.json"; + protected static final String PREF_API_URL_WIDGET = "api_url_widget_"; protected static final String PREF_LAST_WIDGET = "last_widget_"; protected static final String PREF_FORCE_WIDGET = "force_widget_"; @@ -73,10 +87,8 @@ public class Main extends Activity { protected static final String STATE_URL = "url"; private static final int DIALOG_LOADING = 0; private static final int DIALOG_LIST = 1; - private static final String TWITTER = "https://twitter.com/"; - private static final String FOURSQUARE = "https://foursquare.com/v/"; private static final String MAP_SEARCH = "geo:0,0?q="; - private static final String MAP_COORD = "geo:%s,%s?z=23&q=%s&"; + private static final String MAP_COORD = "geo:%s,%s?z=23&q="; // Shared preferences private SharedPreferences mPrefs; @@ -292,7 +304,7 @@ private AlertDialog createHsDialog() { edit.putString(Prefs.KEY_API_URL, url); getApiTask = new GetApiTask(); getApiTask.execute(url); - edit.commit(); + edit.apply(); setIntent(null); dismissDialog(DIALOG_LIST); Log.i(TAG, "Item clicked=" + url + " (" + position + ")"); @@ -307,6 +319,9 @@ private AlertDialog createHsDialog() { } } + /** + * Fetch the directory and update the `mResultDir` variable. + */ private void getHsList() { final Bundle data = (Bundle) getLastNonConfigurationInstance(); if (data == null) { @@ -321,6 +336,9 @@ private void getHsList() { } } + /** + * Fetch the endpoint and update the `mApiUrl` and `mResultHs` variables. + */ private void showHsInfo(@Nullable Intent intent) { final Bundle data = (Bundle) getLastNonConfigurationInstance(); @@ -335,13 +353,13 @@ private void showHsInfo(@Nullable Intent intent) { + intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID), - ParseGeneric.API_DEFAULT); + API_DEFAULT); } else if (intent != null && intent.hasExtra(STATE_HS)) { Log.d(TAG, "showHsInfo(uri from intent)"); mApiUrl = intent.getStringExtra(STATE_HS); } else { Log.d(TAG, "showHsInfo(uri from prefs)"); - mApiUrl = mPrefs.getString(Prefs.KEY_API_URL, ParseGeneric.API_DEFAULT); + mApiUrl = mPrefs.getString(Prefs.KEY_API_URL, API_DEFAULT); } // Now that we have the URL, fetch the data @@ -552,24 +570,89 @@ private TextView addEntry(LayoutInflater inflater, LinearLayout vg, String value vg.addView(tv); return tv; } - private TextView addTitle(LayoutInflater inflater, LinearLayout vg, int value) { + + /** + * Shortcut function. If the value is not null, add it as an entry. + */ + private @Nullable TextView addEntryIfValueNotNull( + @NonNull LayoutInflater inflater, + @NonNull LinearLayout vg, + @Nullable @StringRes int title, + @Nullable Object value + ) { + if (value != null) { + return addEntry(inflater, vg, getString(title) + ": " + value.toString()); + } + return null; + } + + private TextView addTitle(LayoutInflater inflater, LinearLayout vg, @StringRes int value) { return addTitle(inflater, vg, getString(value)); } - private TextView addTitle(LayoutInflater inflater, LinearLayout vg, String value) { - TextView title = (TextView) inflater.inflate(R.layout.title, null); + private TextView addTitle(LayoutInflater inflater, LinearLayout vg, @NonNull String value) { + final TextView title = (TextView) inflater.inflate(R.layout.title, null); title.setText(value); vg.addView(title); inflater.inflate(R.layout.separator, vg); return title; } + private TextView addSubtitle(LayoutInflater inflater, LinearLayout vg, @StringRes int value) { + return addSubtitle(inflater, vg, getString(value)); + } + + private TextView addSubtitle(LayoutInflater inflater, LinearLayout vg, @NonNull String value) { + final TextView title = (TextView) inflater.inflate(R.layout.subtitle, null); + title.setText(value); + vg.addView(title); + return title; + } + + /** + * Add a sensor with the "sensor" layout. + */ + private void addSensor( + @NonNull LayoutInflater inflater, + @NonNull LinearLayout vg, + @NonNull String value, + @Nullable String details1, + @Nullable String details2 + ) { + final RelativeLayout rl = (RelativeLayout) inflater.inflate(R.layout.entry_sensor, null); + final TextView viewValue = rl.findViewById(R.id.entry_value); + final TextView viewDetails1 = rl.findViewById(R.id.entry_details1); + final TextView viewDetails2 = rl.findViewById(R.id.entry_details2); + viewValue.setText(value); + if (details1 != null) { + viewDetails1.setText(details1); + } else { + viewDetails1.setVisibility(View.GONE); + } + if (details2 != null) { + viewDetails2.setText(details2); + } else { + viewDetails2.setVisibility(View.GONE); + } + vg.addView(rl); + } + + /** + * Parse the endpoint JSON and populate UI with parsed information. + */ private void populateDataHs() { try { Log.i(TAG, "populateDataHs()=" + mApiUrl); setViewVisibility(false); - HashMap data = new ParseGeneric(mResultHs.get(mApiUrl)) - .getData(); + + // Look up endpoint JSOn + final String endpointJson = mResultHs.get(mApiUrl); + if (endpointJson == null) { + throw new IllegalStateException("Endpoint JSON not found"); + } + + // Parse the JSON string using the `spaceapi-kt` library. + final Status data = SpaceApiParser.parseString(endpointJson); // Initialize views LayoutInflater iftr = getLayoutInflater(); @@ -579,234 +662,260 @@ private void populateDataHs() { scroll.addView(vg); // Mandatory fields - ((TextView) findViewById(R.id.space_name)).setText((String) data - .get(ParseGeneric.API_NAME)); - ((TextView) findViewById(R.id.space_url)).setText((String) data - .get(ParseGeneric.API_URL)); + ((TextView) findViewById(R.id.space_name)).setText(data.space); + ((TextView) findViewById(R.id.space_url)).setText(data.url.toString()); getImageTask = new GetImage(R.id.space_image); - getImageTask.execute((String) data.get(ParseGeneric.API_LOGO)); + getImageTask.execute(data.logo); // Status text String status_txt; - if (data.get(ParseGeneric.API_STATUS) == null) { + if (data.state == null) { status_txt = getString(R.string.status_unknown); ((TextView) findViewById(R.id.status_txt)) - .setCompoundDrawablesWithIntrinsicBounds( - android.R.drawable.presence_invisible, 0, 0, 0); - } else if ((Boolean) data.get(ParseGeneric.API_STATUS)) { + .setCompoundDrawablesWithIntrinsicBounds( + android.R.drawable.presence_invisible, 0, 0, 0); + } else if (Boolean.TRUE.equals(data.state.open)) { status_txt = getString(R.string.status_open); ((TextView) findViewById(R.id.status_txt)) - .setCompoundDrawablesWithIntrinsicBounds( - android.R.drawable.presence_online, 0, 0, 0); + .setCompoundDrawablesWithIntrinsicBounds( + android.R.drawable.presence_online, 0, 0, 0); } else { status_txt = getString(R.string.status_closed); ((TextView) findViewById(R.id.status_txt)) - .setCompoundDrawablesWithIntrinsicBounds( - android.R.drawable.presence_busy, 0, 0, 0); + .setCompoundDrawablesWithIntrinsicBounds( + android.R.drawable.presence_busy, 0, 0, 0); } - if (data.containsKey(ParseGeneric.API_STATUS_TXT)) { - status_txt += ": " - + data.get(ParseGeneric.API_STATUS_TXT); + if (data.state.message != null) { + status_txt += ": " + data.state.message; } ((TextView) findViewById(R.id.status_txt)).setText(status_txt); // Status last change - if (data.containsKey(ParseGeneric.API_LASTCHANGE)) { - addEntry(iftr, vg, getString(R.string.api_lastchange) + " " - + data.get(ParseGeneric.API_LASTCHANGE)); - } - - // Status duration - if (data.containsKey(ParseGeneric.API_EXT_DURATION) - && data.get(ParseGeneric.API_STATUS) != null - && (Boolean) data.get(ParseGeneric.API_STATUS)) { - addEntry(iftr, vg, getString(R.string.api_duration) + " " - + data.get(ParseGeneric.API_EXT_DURATION) - + getString(R.string.api_duration_hours)); + if (data.state != null && data.state.lastchange != null) { + // TODO: Do we need to properly format the date? Maybe using relative time? + addEntry(iftr, vg, getString(R.string.api_lastchange) + " " + data.state.lastchange); } // Location - Pattern ptn = Pattern.compile("^.*$", Pattern.DOTALL); - if (data.containsKey(ParseGeneric.API_ADDRESS) - || data.containsKey(ParseGeneric.API_LON)) { - - addTitle(iftr, vg, R.string.api_location); - - // Address - if (data.containsKey(ParseGeneric.API_ADDRESS)) { - TextView tv = addEntry(iftr, vg, (String) data.get(ParseGeneric.API_ADDRESS)); - Linkify.addLinks(tv, ptn, MAP_SEARCH); - } - // Lon/Lat - if (data.containsKey(ParseGeneric.API_LON) - && data.containsKey(ParseGeneric.API_LAT)) { - TextView tv = addEntry(iftr, vg, data.get(ParseGeneric.API_LON) + ", " - + data.get(ParseGeneric.API_LAT)); - String addr = (data.containsKey(ParseGeneric.API_ADDRESS)) ? (String) data - .get(ParseGeneric.API_ADDRESS) : getString(R.string.empty); - Linkify.addLinks(tv, ptn, String.format(MAP_COORD, - data.get(ParseGeneric.API_LAT), data.get(ParseGeneric.API_LON), addr)); - } + addTitle(iftr, vg, R.string.api_location); + TextView latLonTv = addEntry(iftr, vg, data.location.lon + ", " + data.location.lat); + Linkify.addLinks( + latLonTv, + Pattern.compile("^.*$", Pattern.DOTALL), + String.format(MAP_COORD, data.location.lat, data.location.lon) + ); + + // Postal address + if (data.location.address != null) { + addTitle(iftr, vg, R.string.api_postal_addr); + addEntry(iftr, vg, data.location.address); } // Contact - if (data.containsKey(ParseGeneric.API_PHONE) - || data.containsKey(ParseGeneric.API_TWITTER) - || data.containsKey(ParseGeneric.API_IRC) - || data.containsKey(ParseGeneric.API_EMAIL) - || data.containsKey(ParseGeneric.API_ML)) { - - addTitle(iftr, vg, R.string.api_contact); - - if (data.containsKey(ParseGeneric.API_PHONE)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_PHONE)); - } - if (data.containsKey(ParseGeneric.API_SIP)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_SIP)); - } - if (data.containsKey(ParseGeneric.API_TWITTER)) { - addEntry(iftr, vg, TWITTER - + ((String) data.get(ParseGeneric.API_TWITTER)).replace("@", "")); + addTitle(iftr, vg, R.string.api_contact); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_phone, data.contact.phone); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_sip, data.contact.sip); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_email, data.contact.email); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_ml, data.contact.ml); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_irc, data.contact.irc); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_twitter, data.contact.twitter); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_identica, data.contact.identica); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_mastodon, data.contact.identica); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_facebook, data.contact.facebook); // Eeeew! + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_foursquare, data.contact.foursquare); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_xmpp, data.contact.xmpp); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_gopher, data.contact.gopher); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_matrix, data.contact.matrix); + addEntryIfValueNotNull(iftr, vg, R.string.api_contact_mumble, data.contact.mumble); + + // Sensors: Space + if (data.sensors != null && ( + data.sensors.people_now_present.length > 0 + || data.sensors.door_locked.length > 0 + || data.sensors.beverage_supply.length > 0 + || data.sensors.power_consumption.length > 0 + || data.sensors.network_connections.length > 0 + /*|| data.sensors.network_traffic.length > 0*/ + )) { + addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_space)); + + // People present + if (data.sensors.people_now_present.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_people_now_present); + for (PeoplePresent entry : data.sensors.people_now_present) { + addSensor( + iftr, + vg, + String.format("%d", entry.value), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_IDENTICA)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_IDENTICA)); + + // Door status + if (data.sensors.door_locked.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_door_locked); + for (DoorLocked entry : data.sensors.door_locked) { + addSensor( + iftr, + vg, + getString(entry.value ? R.string.api_sensor_door_locked_yes : R.string.api_sensor_door_locked_no), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_FOURSQUARE)) { - addEntry(iftr, vg, FOURSQUARE + data.get(ParseGeneric.API_FOURSQUARE)); + + // Beverage supply + if (data.sensors.beverage_supply.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_beverage_supply); + for (BeverageSupply entry : data.sensors.beverage_supply) { + addSensor( + iftr, + vg, + String.format("%.1f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_IRC)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_IRC)); + + // Power consumption + if (data.sensors.power_consumption.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_power_consumption); + for (PowerConsumption entry : data.sensors.power_consumption) { + addSensor( + iftr, + vg, + String.format("%.1f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_EMAIL)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_EMAIL)); + + // Network connections + if (data.sensors.network_connections.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_network_connections); + for (NetworkConnection entry : data.sensors.network_connections) { + String details = Utils.joinStrings(" / ", entry.location, entry.name); + if (details != null && entry.type != null) { + details += " (" + entry.type + ")"; + } else if (entry.type != null) { + details = entry.type; + } + addSensor( + iftr, + vg, + String.format("%d", entry.value), + details, + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_JABBER)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_JABBER)); + } + + // Sensors: Environment + if (data.sensors != null && ( + data.sensors.temperature.length > 0 + || data.sensors.humidity.length > 0 + || data.sensors.barometer.length > 0 + || data.sensors.wind.length > 0 + )) { + addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_environment)); + + // Temperature + if (data.sensors.temperature.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_temperature); + for (Temperature entry : data.sensors.temperature) { + addSensor( + iftr, + vg, + String.format("%.1f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - if (data.containsKey(ParseGeneric.API_ML)) { - addEntry(iftr, vg, (String) data.get(ParseGeneric.API_ML)); + + // Humidity + if (data.sensors.humidity.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_humidity); + for (Humidity entry : data.sensors.humidity) { + addSensor( + iftr, + vg, + String.format("%.1f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); + } } - } - // Sensors - if (data.containsKey(ParseGeneric.API_SENSORS)) { - - addTitle(iftr, vg, R.string.api_sensors); - - HashMap>> sensors = - (HashMap>>) - data.get(ParseGeneric.API_SENSORS); - Set names = sensors.keySet(); - for (String name : names) { - - // Subtitle - String name_title = name.toLowerCase(Locale.getDefault()).replace("_", " "); - name_title = name_title.substring(0, 1).toUpperCase(Locale.getDefault()) - + name_title.substring(1, name_title.length()); - TextView subtitle = (TextView) iftr.inflate( - R.layout.subtitle, null); - subtitle.setText(name_title); - vg.addView(subtitle); - - // Sensors data - ArrayList> sensorsData = sensors - .get(name); - for (HashMap elem : sensorsData) { - RelativeLayout rl = (RelativeLayout) iftr.inflate( - R.layout.entry_sensor, null); - if (elem.containsKey(ParseGeneric.API_SENSOR_VALUE)) { - ((TextView) rl.findViewById(R.id.entry_value)) - .setText(elem.get(ParseGeneric.API_SENSOR_VALUE)); - } else { - rl.findViewById(R.id.entry_value).setVisibility( - View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_UNIT)) { - ((TextView) rl.findViewById(R.id.entry_unit)) - .setText(elem.get(ParseGeneric.API_SENSOR_UNIT)); - } else { - rl.findViewById(R.id.entry_unit).setVisibility( - View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_NAME)) { - ((TextView) rl.findViewById(R.id.entry_name)) - .setText(elem.get(ParseGeneric.API_SENSOR_NAME)); - } else { - rl.findViewById(R.id.entry_name).setVisibility( - View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_LOCATION)) { - ((TextView) rl.findViewById(R.id.entry_location)) - .setText(elem - .get(ParseGeneric.API_SENSOR_LOCATION)); - } else { - rl.findViewById(R.id.entry_location).setVisibility( - View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_DESCRIPTION)) { - ((TextView) rl.findViewById(R.id.entry_description)) - .setText(elem - .get(ParseGeneric.API_SENSOR_DESCRIPTION)); - } else { - rl.findViewById(R.id.entry_description) - .setVisibility(View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_PROPERTIES)) { - ((TextView) rl.findViewById(R.id.entry_properties)) - .setText(elem - .get(ParseGeneric.API_SENSOR_PROPERTIES)); - } else { - rl.findViewById(R.id.entry_properties) - .setVisibility(View.GONE); - } - if (elem.containsKey(ParseGeneric.API_SENSOR_MACHINES)) { - ((TextView) rl.findViewById(R.id.entry_other)) - .setText(elem - .get(ParseGeneric.API_SENSOR_MACHINES)); - } else if (elem.containsKey(ParseGeneric.API_SENSOR_NAMES)) { - ((TextView) rl.findViewById(R.id.entry_other)) - .setText(elem.get(ParseGeneric.API_SENSOR_NAMES)); - } else { - rl.findViewById(R.id.entry_other).setVisibility( - View.GONE); - } - vg.addView(rl); + // Air pressure + if (data.sensors.barometer.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_barometer); + for (Barometer entry : data.sensors.barometer) { + addSensor( + iftr, + vg, + String.format("%.1f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); } } + + // Missing: Wind (not used by any space right now) + // and Radiation (I don't know how to interpret and visualize the measurements in a meaningful way) } - // Stream and cam - if (data.containsKey(ParseGeneric.API_STREAM) - || data.containsKey(ParseGeneric.API_CAM)) { - - addTitle(iftr, vg, R.string.api_stream); - - // Stream - if (data.containsKey(ParseGeneric.API_STREAM)) { - HashMap stream = (HashMap) data - .get(ParseGeneric.API_STREAM); - for (Entry entry : stream.entrySet()) { - final String type = entry.getKey(); - final String url = entry.getValue(); - TextView tv = addEntry(iftr, vg, url); - tv.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse(url), type); - startActivity(i); - } - }); + // Sensors: Organization + if (data.sensors != null && ( + data.sensors.total_member_count.length > 0 + || data.sensors.account_balance.length > 0 + )) { + addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_organization)); + + // Total Member Count + if (data.sensors.total_member_count.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_total_member_count); + for (MemberCount entry : data.sensors.total_member_count) { + addSensor( + iftr, + vg, + String.format("%d", entry.value), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); } } - // Cam - if (data.containsKey(ParseGeneric.API_CAM)) { - ArrayList cams = (ArrayList) data - .get(ParseGeneric.API_CAM); - for (String value : cams) { - addEntry(iftr, vg, value); + + // Account balance + if (data.sensors.account_balance.length > 0) { + addSubtitle(iftr, vg, R.string.api_sensor_account_balance); + for (AccountBalance entry : data.sensors.account_balance) { + addSensor( + iftr, + vg, + String.format("%.2f %s", entry.value, entry.unit), + Utils.joinStrings(" / ", entry.location, entry.name), + entry.description + ); } } } + + // Webcams + if (data.cam.length > 0) { + addTitle(iftr, vg, R.string.api_webcams); + for (String url : data.cam) { + final TextView tv = addEntry(iftr, vg, url); + Linkify.addLinks(tv, Pattern.compile("^.*$", Pattern.DOTALL), null); + } + } + setViewVisibility(true); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/ch/fixme/status/Parse12.java b/app/src/main/java/ch/fixme/status/Parse12.java deleted file mode 100644 index 1a6f3b7..0000000 --- a/app/src/main/java/ch/fixme/status/Parse12.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist) - * Licensed under GNU's GPL 3, see README - */ -package ch.fixme.status; - -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.sql.Date; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; - -public class Parse12 extends ParseGeneric { - - public Parse12(JSONObject jsonObject) throws JSONException { - super(jsonObject); - } - - protected HashMap parse() throws JSONException { - - // Mandatory fields - mResult.put(API_STATUS, mApi.getBoolean(API_STATUS)); - mResult.put(API_NAME, mApi.getString(API_NAME)); - mResult.put(API_URL, mApi.getString(API_URL)); - mResult.put(API_LOGO, mApi.getString(API_LOGO)); - - // Status icons - JSONObject icons = mApi.getJSONObject(API_ICON); - mResult.put(API_ICON + API_ICON_OPEN, icons.getString(API_ICON_OPEN)); - mResult.put(API_ICON + API_ICON_CLOSED, - icons.getString(API_ICON_CLOSED)); - - // Status text - if (!mApi.isNull(API_STATUS_TXT)) { - mResult.put(API_STATUS_TXT, mApi.getString(API_STATUS_TXT)); - } - - // Last change date - if (!mApi.isNull(API_LASTCHANGE)) { - Date date = new Date(mApi.getLong(API_LASTCHANGE) * 1000); - DateFormat formatter = SimpleDateFormat.getDateTimeInstance(); - mResult.put(API_LASTCHANGE, formatter.format(date)); - } - - // Location - if (!mApi.isNull(API_LON) && !mApi.isNull(API_LAT)) { - mResult.put(API_LON, mApi.getString(API_LON)); - mResult.put(API_LAT, mApi.getString(API_LAT)); - } - if (!mApi.isNull(API_ADDRESS)) { - mResult.put(API_ADDRESS, mApi.getString(API_ADDRESS)); - } - - // Contact - if (!mApi.isNull(API_CONTACT)) { - JSONObject contact = mApi.getJSONObject(API_CONTACT); - - // Phone - if (!contact.isNull(API_PHONE)) { - mResult.put(API_PHONE, contact.getString(API_PHONE)); - } - // Twitter - if (!contact.isNull(API_TWITTER)) { - mResult.put(API_TWITTER, contact.getString(API_TWITTER)); - } - // IRC - if (!contact.isNull(API_IRC)) { - mResult.put(API_IRC, contact.getString(API_IRC)); - } - // Email - if (!contact.isNull(API_EMAIL)) { - mResult.put(API_EMAIL, contact.getString(API_EMAIL)); - } - // Mailing-List - if (!contact.isNull(API_ML)) { - mResult.put(API_ML, contact.getString(API_ML)); - } - } - - // Sensors - if (!mApi.isNull(API_SENSORS)) { - JSONArray sensors = mApi.getJSONArray(API_SENSORS); - JSONObject elem; - HashMap>> result = new HashMap<>(sensors.length()); - for (int i = 0; i < sensors.length(); i++) { - elem = (JSONObject) sensors.get(i); - try { - for (int j = 0; j < elem.length(); j++) { - ArrayList> elem_value = new ArrayList<>(); - String name = (String) elem.names().get(j); - JSONObject obj = elem.getJSONObject(name); - for (int k = 0; k < obj.length(); k++) { - String name2 = (String) obj.names().get(k); - HashMap elem_value_map = new HashMap<>(); - elem_value_map.put(API_SENSOR_NAME, name2); - elem_value_map.put(API_SENSOR_VALUE, obj.getString(name2)); - elem_value.add(elem_value_map); - } - result.put(name, elem_value); - } - } catch (Exception e) { - Log.e(Main.TAG, e.getLocalizedMessage()); - e.printStackTrace(); - ArrayList> elem_value = new ArrayList<>(); - HashMap elem_value_map = new HashMap<>(); - elem_value_map.put(API_SENSOR_VALUE, elem.toString()); - elem_value.add(elem_value_map); - result.put((String) elem.names().get(0), elem_value); - } - } - mResult.put(API_SENSORS, result); - } - - if (!mApi.isNull(API_STREAM) || !mApi.isNull(API_CAM)) { - // Stream - if (!mApi.isNull(API_STREAM)) { - JSONObject stream = mApi.optJSONObject(API_STREAM); - if (stream != null) { - HashMap streamMap = new HashMap<>(stream.length()); - JSONArray names = stream.names(); - for (int i = 0; i < stream.length(); i++) { - final String type = names.getString(i); - final String url = stream.getString(type); - streamMap.put(type, url); - } - mResult.put(API_STREAM, streamMap); - } - } - // Cam - if (!mApi.isNull(API_CAM)) { - JSONArray cam = mApi.optJSONArray(API_CAM); - if (cam != null) { - HashMap camMap = new HashMap<>(cam.length()); - for (int i = 0; i < cam.length(); i++) { - camMap.put("http", cam.getString(i)); - } - mResult.put(API_CAM, camMap); - } - } - } - - return mResult; - } -} diff --git a/app/src/main/java/ch/fixme/status/Parse13.java b/app/src/main/java/ch/fixme/status/Parse13.java deleted file mode 100644 index a1480e6..0000000 --- a/app/src/main/java/ch/fixme/status/Parse13.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist) - * Licensed under GNU's GPL 3, see README - */ -package ch.fixme.status; - -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.sql.Date; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; - -public class Parse13 extends ParseGeneric { - - public Parse13(JSONObject jsonObject) throws JSONException { - super(jsonObject); - } - - protected HashMap parse() throws JSONException { - - // Mandatory fields - JSONObject state = mApi.getJSONObject(API_STATE); - if (!state.isNull(API_STATUS)){ - mResult.put(API_STATUS, state.getBoolean(API_STATUS)); - } else { - mResult.put(API_STATUS, null); - } - mResult.put(API_NAME, mApi.getString(API_NAME)); - mResult.put(API_URL, mApi.getString(API_URL)); - mResult.put(API_LOGO, mApi.getString(API_LOGO)); - - // Status icons - if (!state.isNull(API_ICON)) { - JSONObject icon = state.getJSONObject(API_ICON); - mResult.put(API_ICON + API_ICON_OPEN, icon.getString(API_ICON_OPEN)); - mResult.put(API_ICON + API_ICON_CLOSED, - icon.getString(API_ICON_CLOSED)); - } - - // Status text - if (!state.isNull(API_STATE_MESSAGE)) { - mResult.put(API_STATUS_TXT, state.getString(API_STATE_MESSAGE)); - } - - // Last change date - if (!state.isNull(API_LASTCHANGE)) { - Date date = new Date(state.getLong(API_LASTCHANGE) * 1000); - DateFormat formatter = SimpleDateFormat.getDateTimeInstance(); - mResult.put(API_LASTCHANGE, formatter.format(date)); - } - - // Duration (FIXME addition) - if (!state.isNull(API_EXT_DURATION)) { - mResult.put(API_EXT_DURATION, state.getString(API_EXT_DURATION)); - } - - // Location (Mandatory) - if (!mApi.isNull(API_LOCATION)) { - JSONObject loc = mApi.getJSONObject(API_LOCATION); - if (!loc.isNull(API_LON) && !loc.isNull(API_LAT)) { - mResult.put(API_LON, loc.getString(API_LON)); - mResult.put(API_LAT, loc.getString(API_LAT)); - } - if (!loc.isNull(API_ADDRESS)) { - mResult.put(API_ADDRESS, loc.getString(API_ADDRESS)); - } - } - - // Contact - if (!mApi.isNull(API_CONTACT)) { - JSONObject contact = mApi.getJSONObject(API_CONTACT); - - // Phone - if (!contact.isNull(API_PHONE)) { - mResult.put(API_PHONE, contact.getString(API_PHONE)); - } - // SIP - if (!contact.isNull(API_SIP)) { - mResult.put(API_SIP, contact.getString(API_SIP)); - } - // Twitter - if (!contact.isNull(API_TWITTER)) { - mResult.put(API_TWITTER, contact.getString(API_TWITTER)); - } - // Identica - if (!contact.isNull(API_IDENTICA)) { - mResult.put(API_IDENTICA, contact.getString(API_IDENTICA)); - } - // Foursquare - if (!contact.isNull(API_FOURSQUARE)) { - mResult.put(API_FOURSQUARE, contact.getString(API_FOURSQUARE)); - } - // IRC - if (!contact.isNull(API_IRC)) { - mResult.put(API_IRC, contact.getString(API_IRC)); - } - // Jabber - if (!contact.isNull(API_JABBER)) { - mResult.put(API_JABBER, contact.getString(API_JABBER)); - } - // Email - if (!contact.isNull(API_EMAIL)) { - mResult.put(API_EMAIL, contact.getString(API_EMAIL)); - } - // Mailing-List - if (!contact.isNull(API_ML)) { - mResult.put(API_ML, contact.getString(API_ML)); - } - } - - // Sensors - if (!mApi.isNull(API_SENSORS)) { - JSONObject sensors = mApi.getJSONObject(API_SENSORS); - JSONArray names = sensors.names(); - JSONArray elem; - ArrayList> elem_value; - HashMap>> result = new HashMap>>( - sensors.length()); - for (int i = 0; i < names.length(); i++) { - String sensor_name = names.getString(i); - if (sensor_name.startsWith(API_EXT) || sensor_name.startsWith(API_RADIATION)) { - continue; - } - elem = sensors.getJSONArray(sensor_name); - elem_value = new ArrayList<>(elem.length()); - for (int j = 0; j < elem.length(); j++) { - HashMap elem_value_map = new HashMap<>(); - try { - JSONObject obj = (JSONObject) elem.get(j); - if (!obj.isNull(API_SENSOR_VALUE) - && !"".equals(obj.getString(API_SENSOR_VALUE))) { - elem_value_map.put(API_SENSOR_VALUE, obj.getString(API_SENSOR_VALUE)); - } - if (!obj.isNull(API_SENSOR_UNIT) - && !"".equals(obj.getString(API_SENSOR_UNIT))) { - elem_value_map.put(API_SENSOR_UNIT, obj.getString(API_SENSOR_UNIT)); - } - if (!obj.isNull(API_SENSOR_NAME) - && !"".equals(obj.getString(API_SENSOR_NAME))) { - elem_value_map.put(API_SENSOR_NAME, obj.getString(API_SENSOR_NAME)); - } - if (!obj.isNull(API_SENSOR_LOCATION) - && !"".equals(obj.getString(API_SENSOR_LOCATION))) { - elem_value_map.put(API_SENSOR_LOCATION, obj.getString(API_SENSOR_LOCATION)); - } - if (!obj.isNull(API_SENSOR_DESCRIPTION) - && !"".equals(obj.getString(API_SENSOR_DESCRIPTION))) { - elem_value_map.put(API_SENSOR_DESCRIPTION, obj.getString(API_SENSOR_DESCRIPTION)); - } - if (!obj.isNull(API_SENSOR_MACHINES) && obj.getJSONArray(API_SENSOR_MACHINES).length() > 0) { - elem_value_map.put(API_SENSOR_MACHINES, obj.get(API_SENSOR_MACHINES).toString()); - } - if (!obj.isNull(API_SENSOR_NAMES) && obj.getJSONArray(API_SENSOR_NAMES).length() > 0) { - elem_value_map.put(API_SENSOR_NAMES, obj.get(API_SENSOR_NAMES).toString()); - } - if (!obj.isNull(API_SENSOR_PROPERTIES)) { - - JSONObject obj2 = obj.getJSONObject(API_SENSOR_PROPERTIES); - String prop = ""; - for (int k = 0; k < obj2.length(); k++) { - String name = (String) obj2.names().get(k); - JSONObject obj3 = obj2.getJSONObject(name); - prop += name + ": " + obj3.getString(API_SENSOR_VALUE) + " " + obj3.getString(API_SENSOR_UNIT) + ", "; - } - elem_value_map.put(API_SENSOR_PROPERTIES, prop.substring(0, prop.length() - 2)); - } - } catch (Exception e) { - Log.e(Main.TAG, e.getMessage()); - elem_value_map.put(API_SENSOR_VALUE, elem.get(j).toString()); - } - elem_value.add(elem_value_map); - } - result.put(sensor_name, elem_value); - } - mResult.put(API_SENSORS, result); - } - - // Stream - if (!mApi.isNull(API_STREAM)) { - JSONObject stream = mApi.optJSONObject(API_STREAM); - if (stream != null) { - HashMap streamMap = new HashMap<>( - stream.length()); - JSONArray names = stream.names(); - for (int i = 0; i < stream.length(); i++) { - final String type = names.getString(i); - final String url = stream.getString(type); - streamMap.put(type, url); - } - mResult.put(API_STREAM, streamMap); - } - } - - // Cam - if (!mApi.isNull(API_CAM)) { - JSONArray cam = mApi.optJSONArray(API_CAM); - if (cam != null) { - ArrayList camList = new ArrayList<>(cam.length()); - for (int i = 0; i < cam.length(); i++) { - camList.add(cam.getString(i)); - } - mResult.put(API_CAM, camList); - } - } - - return mResult; - } -} diff --git a/app/src/main/java/ch/fixme/status/ParseGeneric.java b/app/src/main/java/ch/fixme/status/ParseGeneric.java deleted file mode 100644 index bc21ebf..0000000 --- a/app/src/main/java/ch/fixme/status/ParseGeneric.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist) - * Licensed under GNU's GPL 3, see README - */ -package ch.fixme.status; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; - -public class ParseGeneric { - protected HashMap mResult = new HashMap<>(); - protected JSONObject mApi; - - protected static final String API_NAME = "space"; - protected static final String API_LON = "lon"; - protected static final String API_LOCATION = "location"; - protected static final String API_LAT = "lat"; - protected static final String API_URL = "url"; - protected static final String API_LEVEL = "api"; - protected static final String API_STATE = "state"; - protected static final String API_STATE_MESSAGE = "message"; - protected static final String API_STATUS_TXT = "status"; - protected static final String API_EXT_DURATION = "ext_duration"; - protected static final String API_ADDRESS = "address"; - protected static final String API_CONTACT = "contact"; - protected static final String API_EMAIL = "email"; - protected static final String API_IRC = "irc"; - protected static final String API_JABBER = "jabber"; - protected static final String API_PHONE = "phone"; - protected static final String API_SIP = "sip"; - protected static final String API_TWITTER = "twitter"; - protected static final String API_IDENTICA = "identica"; - protected static final String API_FOURSQUARE = "foursquare"; - protected static final String API_ML = "ml"; - protected static final String API_STREAM = "stream"; - protected static final String API_CAM = "cam"; - protected static final String API_SENSORS = "sensors"; - protected static final String API_EXT = "ext_"; - protected static final String API_RADIATION = "radiation"; - - // Sensors - protected static final String API_SENSOR_VALUE = "value"; - protected static final String API_SENSOR_UNIT = "unit"; - protected static final String API_SENSOR_LOCATION = "location"; - protected static final String API_SENSOR_NAME = "name"; - protected static final String API_SENSOR_DESCRIPTION = "description"; - protected static final String API_SENSOR_MACHINES = "machines"; - protected static final String API_SENSOR_NAMES = "names"; - protected static final String API_SENSOR_PROPERTIES = "properties"; - - // State - protected static final String API_DEFAULT = "https://fixme.ch/status.json"; - protected static final String API_ICON = "icon"; - protected static final String API_ICON_OPEN = "open"; - protected static final String API_ICON_CLOSED = "closed"; - protected static final String API_LOGO = "logo"; - protected static final String API_STATUS = "open"; - protected static final String API_LASTCHANGE = "lastchange"; - - public ParseGeneric(JSONObject jsonObject) { - mApi = jsonObject; - } - - public ParseGeneric(String jsonString) throws JSONException { - mApi = new JSONObject(jsonString); - } - - public HashMap getData() throws JSONException { - if ("0.13".equals(mApi.getString(ParseGeneric.API_LEVEL))) { - mResult = new Parse13(mApi).parse(); - } else if ("0.12".equals(mApi.getString(ParseGeneric.API_LEVEL))) { - mResult = new Parse12(mApi).parse(); - } else { - throw new JSONException("API LEVEL NOT SUPPORTED: " - + mApi.getString(ParseGeneric.API_LEVEL)); - } - return mResult; - } - -} diff --git a/app/src/main/java/ch/fixme/status/Utils.java b/app/src/main/java/ch/fixme/status/Utils.java new file mode 100644 index 0000000..6ad7068 --- /dev/null +++ b/app/src/main/java/ch/fixme/status/Utils.java @@ -0,0 +1,26 @@ +package ch.fixme.status; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class Utils { + /** + * Join the strings using the specified separator. + */ + public static @Nullable + String joinStrings(@NonNull String separator, String... strings) { + final StringBuilder builder = new StringBuilder(); + boolean empty = true; + for (String string : strings) { + if (string != null) { + if (empty) { + builder.append(string); + empty = false; + } else { + builder.append(separator).append(string); + } + } + } + return empty ? null : builder.toString(); + } +} diff --git a/app/src/main/java/ch/fixme/status/Widget.java b/app/src/main/java/ch/fixme/status/Widget.java index a78e849..78e6ed4 100644 --- a/app/src/main/java/ch/fixme/status/Widget.java +++ b/app/src/main/java/ch/fixme/status/Widget.java @@ -28,8 +28,14 @@ import org.json.JSONException; import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.net.URL; import java.util.HashMap; +import io.spaceapi.ParseError; +import io.spaceapi.SpaceApiParser; +import io.spaceapi.types.Status; + public class Widget extends AppWidgetProvider { static final String TAG ="MyHackerspace_Widget"; @@ -110,7 +116,7 @@ protected static void setAlarm(Context ctxt, Intent i, int widgetId, // + "s"); } - private static class GetImage extends AsyncTask { + private static class GetImage extends AsyncTask { private final int mId; private WeakReference mCtxt; @@ -124,9 +130,9 @@ public GetImage(Context ctxt, int id, String text) { } @Override - protected Bitmap doInBackground(String... url) { + protected Bitmap doInBackground(URL... url) { try { - return new Net(url[0]).getBitmap(); + return new Net(url[0].toString()).getBitmap(); } catch (Throwable e) { e.printStackTrace(); mError = e.getMessage(); @@ -228,60 +234,48 @@ protected void onCancelled() { } @Override - protected void onPostExecute(String result) { + protected void onPostExecute(String endpointJson) { final Context ctxt = mCtxt.get(); if(ctxt == null) { Log.e(TAG, "Context error (postExecute)"); return; } try { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(ctxt); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctxt); + + final io.spaceapi.types.Status data = SpaceApiParser.parseString(endpointJson); + + boolean statusBool = data.state != null && data.state.open; - HashMap api = new ParseGeneric(result) - .getData(); - boolean statusBool = (Boolean) api.get(ParseGeneric.API_STATUS); // Update only if different than last status or not forced if (prefs.contains(Main.PREF_LAST_WIDGET + mId) && prefs.getBoolean(Main.PREF_LAST_WIDGET + mId, false) == statusBool - && !prefs.getBoolean(Main.PREF_FORCE_WIDGET + mId, - false)) { + && !prefs.getBoolean(Main.PREF_FORCE_WIDGET + mId, false)) { Log.d(TAG, "Nothing to update"); return; } // Mandatory fields - String status = ParseGeneric.API_ICON - + ParseGeneric.API_ICON_CLOSED; - if (statusBool) { - status = ParseGeneric.API_ICON + ParseGeneric.API_ICON_OPEN; - } - Editor edit = prefs.edit(); + final Editor edit = prefs.edit(); edit.putBoolean(Main.PREF_LAST_WIDGET + mId, statusBool); edit.apply(); String status_text = null; - if (prefs.getBoolean(Prefs.KEY_WIDGET_TEXT, - Prefs.DEFAULT_WIDGET_TEXT)) { - if (api.containsKey(ParseGeneric.API_STATUS_TXT)) { - status_text = (String) api - .get(ParseGeneric.API_STATUS_TXT); + if (prefs.getBoolean(Prefs.KEY_WIDGET_TEXT, Prefs.DEFAULT_WIDGET_TEXT)) { + if (data.state != null && data.state.message != null) { + status_text = data.state.message; } else { - status_text = statusBool ? ctxt.getString(R.string.status_open) : - ctxt.getString(R.string.status_closed); + status_text = statusBool + ? ctxt.getString(R.string.status_open) + : ctxt.getString(R.string.status_closed); } } // Status icon or space icon - if (api.containsKey(ParseGeneric.API_ICON - + ParseGeneric.API_ICON_OPEN) - && api.containsKey(ParseGeneric.API_ICON - + ParseGeneric.API_ICON_CLOSED)) { - new GetImage(ctxt, mId, status_text).execute((String) api - .get(status)); + if (data.state != null && data.state.icon != null) { + new GetImage(ctxt, mId, status_text).execute(statusBool ? data.state.icon.open : data.state.icon.closed); } else { - new GetImage(ctxt, mId, status_text).execute((String) api - .get(ParseGeneric.API_LOGO)); + new GetImage(ctxt, mId, status_text).execute(new URL(data.logo)); } - } catch (JSONException e) { + } catch (ParseError | MalformedURLException e) { e.printStackTrace(); String msg = e.getMessage(); printMessage(ctxt, msg); @@ -304,10 +298,8 @@ protected void onHandleIntent(Intent intent) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(ctxt); if (Main.checkNetwork(ctxt) && prefs.contains(Main.PREF_API_URL_WIDGET + widgetId)) { - final String url = prefs.getString(Main.PREF_API_URL_WIDGET - + widgetId, ParseGeneric.API_DEFAULT); - Log.i(TAG, "Update widgetid " + widgetId + " with url " - + url); + final String url = prefs.getString(Main.PREF_API_URL_WIDGET + widgetId, Main.API_DEFAULT); + Log.i(TAG, "Update widgetid " + widgetId + " with url " + url); new Handler(Looper.getMainLooper()) .post(() -> new GetApiTask(ctxt, widgetId).execute(url)); } diff --git a/app/src/main/res/layout/entry_sensor.xml b/app/src/main/res/layout/entry_sensor.xml index fea2769..d7df73e 100644 --- a/app/src/main/res/layout/entry_sensor.xml +++ b/app/src/main/res/layout/entry_sensor.xml @@ -1,6 +1,6 @@ + android:text="Value" /> - - - - + android:text="More details" /> diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 2b32228..bc38228 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -51,7 +51,7 @@ time(r) Lokation Kontakt - Videostrøm + Webkameraer Sensorer diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e863ae4..b6adb89 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,8 +13,8 @@ Licensed under GNU's GPL 3, see README OK - Öffnen - Abgeschlossen + Offen + Geschlossen Unbekannt Logo des Hackerspaces @@ -53,8 +53,31 @@ Licensed under GNU's GPL 3, see README Dauer: Stunde(n) Ort + Postanschrift + Koordinaten Kontakt - Webcam + Webcams Sensoren + Umwelt + Space + Organisation + Tel + E-Mail + ML + + Anwesende Personen + Türstatus + Geschlossen + Geöffnet + Getränkevorrat + Stromverbrauch + Netzwerkverbindungen + Temperatur + Luftfeuchtigkeit + Luftdruck + Wind + Radioaktivität + Mitgliederanzahl + Kontostand diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0d055bb..95a223d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -50,7 +50,7 @@ heure(s) Localisation Contact - Flux + Webcam Capteurs diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6e49e4c..714f799 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -50,7 +50,7 @@ u(u)r(en) Locatie Kontakt - Stream + Webcams Sensoren diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b98b486..215c16d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,8 +50,42 @@ Licensed under GNU's GPL 3, see README Duration: hour(s) Location + Postal Address + Coordinates Contact - Stream + WebCam + Webcams Sensors + Environment + Space + Organization + Phone + SIP + IRC + Twitter + Mastodon + Facebook + Identica + Foursquare + Email + ML + XMPP + Gopher + Matrix + Mumble + + People Present + Door Status + Locked + Unlocked + Beverage Supply + Power Consumption + Network Connections + Temperature + Humidity + Air Pressure + Wind + Radiation + Member Count + Account Balance diff --git a/app/src/test/java/ch/fixme/status/UtilsTest.java b/app/src/test/java/ch/fixme/status/UtilsTest.java new file mode 100644 index 0000000..c171fa6 --- /dev/null +++ b/app/src/test/java/ch/fixme/status/UtilsTest.java @@ -0,0 +1,19 @@ +package ch.fixme.status; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UtilsTest { + @Test + public void testJoinStrings() { + Assert.assertEquals("foo / bar", Utils.joinStrings(" / ", "foo", "bar")); + Assert.assertEquals("foo.bar.baz", Utils.joinStrings(".", "foo", "bar", "baz")); + Assert.assertEquals("foo.baz", Utils.joinStrings(".", "foo", null, "baz")); + Assert.assertEquals("foo.baz", Utils.joinStrings(".", null, "foo", null, "baz")); + Assert.assertNull(Utils.joinStrings(" / ", null, null, null)); + Assert.assertNull(Utils.joinStrings(" / ")); + } +} diff --git a/build.gradle b/build.gradle index a37f23b..6bf4924 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { jcenter() google() } + dependencies { classpath 'com.android.tools.build:gradle:4.1.0' // NOTE: Do not place your application dependencies here; they belong @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url "https://dl.bintray.com/spaceapi-community/maven" } google() } } From 99876b541111b877b0016f0115f603c7bb2e369c Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 24 Nov 2020 00:01:45 +0100 Subject: [PATCH 4/5] Improve DE translation strings Co-authored-by: Matthias Jacob --- app/src/main/res/values-de/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b6adb89..efff806 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -35,17 +35,17 @@ Licensed under GNU's GPL 3, see README Fehler: Netzwerk Netzwerk deaktiviert - \n\nÜberprüfen Sie ob die API der Version entspricht, welche angeben wurde!\n\nsiehe https://spaceapi.io/ + \n\nÜberprüfe, ob die API der Version entspricht, welche angegeben wurde!\n\nSiehe https://spaceapi.io/ SpaceAPI Endpoint URL zum globalen SpaceAPI-Directory (directory.json)\nBeispiel:\nhttps://directory.spaceapi.io/ Aktuelle Hackerspace-API Aktuelle Hackerspace-API überladen (geht verloren, wenn ein anderer Hackerspace in der Liste gewählt wird) Aktualisierungsintervall - Zeitspanne in der das Widget den tatsächlichen Status überprüft. + Zeitspanne,, in der das Widget den tatsächlichen Status überprüft. Transparentes Widget Das Hintengrundbild des Widgets wird transparent statt grau dargestellt. - Widget Statustext + Widget-Statustext Statustext unter dem Widget hinzufügen Status From 71dffceaa9c31d283fb74965be8ff4fce3bc0395 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 24 Nov 2020 00:17:36 +0100 Subject: [PATCH 5/5] Reduce severity of MissingTranslation lint to warning This allows landing PRs and releases even if not all translation files are up to date. (If a string is missing, the english string is used as fallback. That's fine, translations should be done on a best-effort basis.) --- app/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 408a497..b22449d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,10 +18,15 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } + compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 } + + lintOptions { + warning 'MissingTranslation' + } } dependencies {