diff --git a/.idea/dictionaries/weatherstation.xml b/.idea/dictionaries/weatherstation.xml new file mode 100644 index 0000000..fce3e16 --- /dev/null +++ b/.idea/dictionaries/weatherstation.xml @@ -0,0 +1,12 @@ + + + + appid + dateandtime + openweathermap + ssid + weseng + wifiweatherstation + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a7509..85d6e17 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,40 @@ + + + + diff --git a/app/build.gradle b/app/build.gradle index fa756fd..ba0cbfb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId "de.weseng.wifiweatherstation" minSdkVersion 26 targetSdkVersion 28 - versionCode 20190310 - versionName "2019.3.10" + versionCode 20190317 + versionName "2019.3.17" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -18,6 +18,10 @@ android { } } +repositories { + maven { url 'https://jitpack.io' } +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' @@ -27,4 +31,5 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.android.support:design:28.0.0' + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0-alpha' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2241dd7..458aecc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,11 +2,10 @@ - - - + + @@ -24,7 +23,7 @@ + android:noHistory="true"> @@ -33,7 +32,8 @@ + android:label="@string/app_name" + android:configChanges="orientation|screenSize"> @@ -43,6 +43,11 @@ + + + + + diff --git a/app/src/main/java/de/weseng/wifiweatherstation/CenteredImageSpan.java b/app/src/main/java/de/weseng/wifiweatherstation/CenteredImageSpan.java new file mode 100644 index 0000000..8d5bcd6 --- /dev/null +++ b/app/src/main/java/de/weseng/wifiweatherstation/CenteredImageSpan.java @@ -0,0 +1,79 @@ +package de.weseng.wifiweatherstation; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.text.style.ImageSpan; + +import java.lang.ref.WeakReference; + +public class CenteredImageSpan extends ImageSpan { + private WeakReference mDrawableRef; + + CenteredImageSpan(Context context, final int drawableRes) { + super(context, drawableRes); + } + + @Override + public int getSize(@NonNull Paint paint, CharSequence text, + int start, int end, + Paint.FontMetricsInt fm) { + Drawable d = getCachedDrawable(); + Rect rect = d.getBounds(); + + float drawableHeight = rect.height(); + if (fm != null) { + Paint.FontMetricsInt pfm = paint.getFontMetricsInt(); + float fontHeight = pfm.descent - pfm.ascent; + int paintOversizeHalf = (int) (drawableHeight/2-fontHeight*1.25); + + // keep it the same as paint's fm plus some space + fm.ascent = pfm.ascent - paintOversizeHalf; + fm.descent = pfm.descent + paintOversizeHalf; + fm.top = pfm.top; + fm.bottom = pfm.bottom; + } + + return rect.right; + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, + int start, int end, float x, + int top, int y, int bottom, @NonNull Paint paint) { + Drawable b = getCachedDrawable(); + canvas.save(); + + int drawableHeight = b.getIntrinsicHeight(); + int fontAscent = paint.getFontMetricsInt().ascent; + int fontDescent = paint.getFontMetricsInt().descent; + + Paint.FontMetricsInt pfm = paint.getFontMetricsInt(); + int fontHeightRelativeCorrection = (int) ((pfm.descent - pfm.ascent)/5.0); + + int transY = bottom - b.getBounds().bottom + // align bottom to bottom + (drawableHeight - fontDescent + fontAscent) / 2 - fontHeightRelativeCorrection; // align center to center + canvas.translate(x, transY); + b.draw(canvas); + canvas.restore(); + } + + // Redefined locally because it is a private member from DynamicDrawableSpan + private Drawable getCachedDrawable() { + WeakReference wr = mDrawableRef; + Drawable d = null; + + if (wr != null) + d = wr.get(); + + if (d == null) { + d = getDrawable(); + mDrawableRef = new WeakReference<>(d); + } + + return d; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/weseng/wifiweatherstation/DetailActivity.java b/app/src/main/java/de/weseng/wifiweatherstation/DetailActivity.java new file mode 100644 index 0000000..044e192 --- /dev/null +++ b/app/src/main/java/de/weseng/wifiweatherstation/DetailActivity.java @@ -0,0 +1,287 @@ +package de.weseng.wifiweatherstation; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; + +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.IFillFormatter; +import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider; +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; +import com.github.mikephil.charting.utils.ColorTemplate; + +import java.util.ArrayList; + +public class DetailActivity extends AppCompatActivity { + private final String TAG = getClass().getSimpleName(); + private LineChart chart; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_detail); + + Intent intent = getIntent(); + ArrayList dataList1 = intent.getStringArrayListExtra("temperature"); + ArrayList dataList2 = intent.getStringArrayListExtra("humidity"); + Log.d(TAG, "dataList1: " + dataList1); + Log.d(TAG, "dataList2: " + dataList2); + + chart = findViewById(R.id.chart1); + + // background color for the whole view + //chart.setBackgroundColor(Color.TRANSPARENT); + + // background color for the chart area + //chart.setDrawGridBackground(true); + //chart.setGridBackgroundColor(Color.TRANSPARENT); + + // disable description text + chart.getDescription().setEnabled(false); + + // enable touch gestures, default true + //chart.setTouchEnabled(false); + + // enable dragging (default true) and scaling (default true) + //chart.setDragEnabled(false); + //chart.setScaleEnabled(false); + //chart.setScaleXEnabled(false); + chart.setScaleYEnabled(false); + + // force pinch zoom along both axis (default false) + //chart.setPinchZoom(true); + + // animates the rendering of the chart + chart.animateX(1000); + //chart.animateY(1500); + //chart.animateXY(1500, 1500); + + { // x axis + + XAxis xAxis = chart.getXAxis(); + //xAxis.setTypeface(tfLight); + //xAxis.setTextSize(11f); + xAxis.setDrawLabels(false); + //xAxis.setTextColor(Color.LTGRAY); + xAxis.setDrawGridLines(false); + xAxis.setDrawAxisLine(false); + } + + { // y axis + + //YAxis yAxis = chart.getAxisLeft(); + + // disable dual axis (only use LEFT axis) + //chart.getAxisRight().setEnabled(false); + + //leftAxis.setTypeface(tfLight); + //yAxis.setTextColor(ColorTemplate.getHoloBlue()); + //yAxis.setAxisMinimum(12f); + //yAxis.setAxisMaximum(30f); + //yAxis.setDrawGridLines(true); + + // horizontal grid lines + //yAxis.enableGridDashedLine(10f, 10f, 0f); + + //yAxis.setGranularityEnabled(true); + } + + + { // y axis left + + YAxis leftAxis = chart.getAxisLeft(); + leftAxis.setTypeface(Typeface.DEFAULT_BOLD); + leftAxis.setTextColor(MainNativeActivity.red.toArgb()); + leftAxis.setAxisMinimum(12); + leftAxis.setAxisMaximum(30); + leftAxis.setDrawGridLines(true); + leftAxis.setGranularityEnabled(true); + } + + { // y axis right + YAxis rightAxis = chart.getAxisRight(); + rightAxis.setTypeface(Typeface.DEFAULT_BOLD); + rightAxis.setTextColor(ColorTemplate.getHoloBlue()); + rightAxis.setAxisMinimum(20); + rightAxis.setAxisMaximum(80); + //rightAxis.setDrawGridLines(false); + //rightAxis.setDrawZeroLine(false); + //rightAxis.setGranularityEnabled(false); + } + + setData(dataList1, getString(R.string.temperature), dataList2, getString(R.string.humidity)); + + { // ### Legend ### + + // get the legend (only possible after setting data) + Legend l = chart.getLegend(); + + // draw legend entries as lines, default square + l.setForm(Legend.LegendForm.LINE); + + l.setTypeface(Typeface.DEFAULT_BOLD); + //l.setTextSize(11f); + l.setTextColor(Color.LTGRAY); + + // position, default: bottom left + //l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); + //l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); + + // default horizontal + //l.setOrientation(Legend.LegendOrientation.HORIZONTAL); + + // default false + //l.setDrawInside(true); + + //l.setYOffset(11f); + } + + } + + private void setData(ArrayList dataList1, String label1, + ArrayList dataList2, String label2) { + int n; + + n = dataList1.size(); + ArrayList values1 = new ArrayList<>(); + if (n > 0) { + for (int i = 0; i < n; i++) { + float val = Float.parseFloat(dataList1.get(i)); + values1.add(new Entry(i, val)); + } + } + + n = dataList2.size(); + ArrayList values2 = new ArrayList<>(); + if (n > 0) { + for (int i = 0; i < n; i++) { + float val = Float.parseFloat(dataList2.get(i)); + values2.add(new Entry(i, val)); + } + } + + + // create a DataSet and give it a type + LineDataSet set1 = new LineDataSet(values1, label1); + set1.setAxisDependency(YAxis.AxisDependency.LEFT); + + { // ### Lines ### + + //set1.setColor(Color.BLACK); + set1.setColor(MainNativeActivity.red.toArgb()); + //set1.setColor(ColorTemplate.getHoloBlue()); + + // line thickness (min = 0.2f, max = 10f, default 1f) + //set1.setLineWidth(1f); + + // draw dashed line, see also legend + //set1.enableDashedLine(10f, 5f, 0f); + + // set interpolation + //set1.setMode(LineDataSet.Mode.STEPPED); + //set1.setMode(LineDataSet.Mode.LINEAR); + //set1.setMode(LineDataSet.Mode.CUBIC_BEZIER); + //set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); + } + + { // ### Points ### + + // points, circles over the data point, def: true + //set1.setDrawCircles(false); + + //set1.setCircleColor(Color.BLACK); + set1.setCircleColor(MainNativeActivity.red.toArgb()); + + // point size (default radius = 4f, min = 1f) + //set1.setCircleRadius(3f); + + // icons over the data point, def: false + //set1.setDrawIcons(false); + + // false to draw points as solid circles (default true) + set1.setDrawCircleHole(false); + } + + { // ### Values ### + + // values over the data point, def: true + set1.setDrawValues(false); + + // text size of values + //set1.setValueTextSize(9f); + } + + { // ### Legend ### + + //set1.setFormLineWidth(1f); + //set1.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f)); + //set1.setFormSize(15.f); + } + + // draw selection line as dashed + set1.setHighLightColor(Color.LTGRAY); + //set1.enableDashedHighlightLine(10f, 5f, 0f); + + { // ### Filled area ### + + // set the filled area + //set1.setDrawFilled(true); + set1.setFillFormatter(new IFillFormatter() { + @Override + public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) { + return chart.getAxisLeft().getAxisMinimum(); + } + }); + + // set color of filled area + set1.setFillColor(MainNativeActivity.red.toArgb()); + //if (Utils.getSDKInt() >= 18) { + // // drawables only supported on api level 18 and above + // Drawable drawable = ContextCompat.getDrawable(this, R.drawable.fade_red); + // set1.setFillDrawable(drawable); + //} else { + // set1.setFillColor(Color.BLACK); + //} + } + + + LineDataSet set2 = new LineDataSet(values2, label2); + set2.setAxisDependency(YAxis.AxisDependency.RIGHT); + set2.setColor(ColorTemplate.getHoloBlue()); + set2.setCircleColor(ColorTemplate.getHoloBlue()); + //set2.setCircleColor(MainNativeActivity.red.toArgb()); + set2.setDrawCircleHole(false); + set2.setDrawValues(false); + set2.setHighLightColor(Color.LTGRAY); + //set2.setDrawFilled(true); + set2.setFillFormatter(new IFillFormatter() { + @Override + public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) { + return chart.getAxisRight().getAxisMinimum(); + } + }); + + + ArrayList dataSets = new ArrayList<>(); + dataSets.add(set1); // add the data sets + dataSets.add(set2); // add the data sets + + // create a data object with the data sets + LineData data = new LineData(dataSets); + + //data.setValueTextColor(Color.WHITE); + //data.setValueTextSize(9f); + + // set data + chart.setData(data); + } +} diff --git a/app/src/main/java/de/weseng/wifiweatherstation/MainNativeActivity.java b/app/src/main/java/de/weseng/wifiweatherstation/MainNativeActivity.java index 3a5e57e..ec11d8f 100644 --- a/app/src/main/java/de/weseng/wifiweatherstation/MainNativeActivity.java +++ b/app/src/main/java/de/weseng/wifiweatherstation/MainNativeActivity.java @@ -1,6 +1,8 @@ package de.weseng.wifiweatherstation; +import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; @@ -8,6 +10,9 @@ import android.os.AsyncTask; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -22,20 +27,38 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import static de.weseng.wifiweatherstation.SettingsActivity.PREFS_NAME; +import static java.lang.Math.pow; public class MainNativeActivity extends AppCompatActivity { private final String TAG = getClass().getSimpleName(); + + static Color green = Color.valueOf(0x71ff5f); + static Color red = Color.valueOf(0xffd1655d); + static Color blue = Color.valueOf(Color.rgb(113, 157, 195)); + private int pBarLockCount = 0; // In Android 4.4 (API 19) AsyncTask MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; (maximum number of threads) ArrayList> endlessTasks = new ArrayList<>(); + ArrayList dataListStringTemp1 = new ArrayList<>(); + ArrayList dataListStringTemp2 = new ArrayList<>(); + ArrayList dataListStringTemp3 = new ArrayList<>(); + ArrayList dataListStringHumidity1 = new ArrayList<>(); + ArrayList dataListStringHumidity2 = new ArrayList<>(); + ArrayList dataListStringHumidity3 = new ArrayList<>(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -45,36 +68,48 @@ public class MainNativeActivity extends AppCompatActivity { String urlLocal = settings.getString("urlLocal", getString(R.string.url_local)); String urlGlobal = settings.getString("urlGlobal", getString(R.string.url_global)); - if (savedInstanceState == null) { - TableLayout tr1 = findViewById(R.id.tableRow1); - tr1.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // TODO: do your logic here - } - }); - TableLayout tr2 = findViewById(R.id.tableRow2); - tr2.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // TODO: do your logic here - } - }); - TableLayout tr3 = findViewById(R.id.tableRow3); - tr3.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // TODO: do your logic here - } - }); + TableLayout tr1 = findViewById(R.id.tableRow1); + tr1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(MainNativeActivity.this, DetailActivity.class); + intent.putExtra("temperature", dataListStringTemp1); + intent.putExtra("humidity", dataListStringHumidity1); + startActivity(intent); + } + }); + TableLayout tr2 = findViewById(R.id.tableRow2); + tr2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(MainNativeActivity.this, DetailActivity.class); + intent.putExtra("temperature", dataListStringTemp2); + intent.putExtra("humidity", dataListStringHumidity2); + startActivity(intent); + } + }); + TableLayout tr3 = findViewById(R.id.tableRow3); + tr3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(MainNativeActivity.this, DetailActivity.class); + intent.putExtra("temperature", dataListStringTemp3); + intent.putExtra("humidity", dataListStringHumidity3); + startActivity(intent); + } + }); + TextView tv = findViewById(R.id.textViewWeatherOutdoor); + tv.setText(Html.fromHtml(getString(R.string.weather_outdoor), Html.FROM_HTML_MODE_COMPACT)); + + if (savedInstanceState == null) { String urlPre = ""; if (SettingsActivity.isLocal(getApplicationContext())) { //Toast.makeText(getApplicationContext(), "intern", Toast.LENGTH_LONG).show(); if (urlLocal != null && urlLocal.length() > 0) urlPre = urlLocal.substring(urlLocal.length() - 1).equals("/") ? urlLocal : urlLocal + "/"; } else { - //Toast.makeText(getApplicationContext(), "extern", Toast.LENGTH_LONG).show(); + //Toast.makeText(getApplicationContext(), "external", Toast.LENGTH_LONG).show(); if (urlGlobal != null && urlGlobal.length() > 0) urlPre = urlGlobal.substring(urlGlobal.length() - 1).equals("/") ? urlGlobal : urlGlobal + "/"; } @@ -85,18 +120,20 @@ public class MainNativeActivity extends AppCompatActivity { executeAsyncTask(new GetData(R.id.textViewTemperatureValue3, R.id.textViewHumidityValue3), urlPre + "api.php?host=192.168.1.73&last"); + executeAsyncTask(new OpenWeatherMap(R.id.textViewWeatherOutdoor), urlPre + "openweathermap/api.php"); + endlessTasks.add(new GetData(R.id.textViewTemperatureValue1, R.id.textViewHumidityValue1, R.id.textViewTemperatureMin1, R.id.textViewTemperatureMax1, R.id.textViewTemperatureDelta1, R.id.textViewHumidityMin1, R.id.textViewHumidityMax1, R.id.textViewHumidityDelta1, - true)); + true, dataListStringTemp1, dataListStringHumidity1)); endlessTasks.add(new GetData(R.id.textViewTemperatureValue2, R.id.textViewHumidityValue2, R.id.textViewTemperatureMin2, R.id.textViewTemperatureMax2, R.id.textViewTemperatureDelta2, R.id.textViewHumidityMin2, R.id.textViewHumidityMax2, R.id.textViewHumidityDelta2, - true)); + true, dataListStringTemp2, dataListStringHumidity2)); endlessTasks.add(new GetData(R.id.textViewTemperatureValue3, R.id.textViewHumidityValue3, R.id.textViewTemperatureMin3, R.id.textViewTemperatureMax3, R.id.textViewTemperatureDelta3, R.id.textViewHumidityMin3, R.id.textViewHumidityMax3, R.id.textViewHumidityDelta3, - true)); + true, dataListStringTemp3, dataListStringHumidity3)); executeAsyncTask(endlessTasks.get(0), urlPre + "api.php?host=192.168.1.71&day"); executeAsyncTask(endlessTasks.get(1), urlPre + "api.php?host=192.168.1.72&day"); executeAsyncTask(endlessTasks.get(2), urlPre + "api.php?host=192.168.1.73&day"); @@ -148,6 +185,8 @@ public class MainNativeActivity extends AppCompatActivity { return super.onKeyDown(keyCode, event); } + @SuppressLint("ObsoleteSdkInt") + @SafeVarargs @TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11 public static void executeAsyncTask(AsyncTask asyncTask, T... params) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) @@ -159,6 +198,7 @@ public class MainNativeActivity extends AppCompatActivity { /** * Async task class to get json by making HTTP call */ + @SuppressLint("StaticFieldLeak") private class GetData extends AsyncTask { String TAG = getClass().getSimpleName(); private ProgressBar pBar; @@ -178,6 +218,9 @@ public class MainNativeActivity extends AppCompatActivity { private final Condition tryAgain = lock.newCondition(); private volatile boolean finished = false; + ArrayList dataListString1 = new ArrayList<>(); + ArrayList dataListString2 = new ArrayList<>(); + GetData(int v1, int v2) { this.v1 = v1; this.v2 = v2; @@ -202,6 +245,12 @@ public class MainNativeActivity extends AppCompatActivity { this.endless = endless; pBarLockCount -= 1; // do not count for endless tasks, pBarSmall is used } + GetData(int v1, int v2, int min1, int max1, int delta1, int min2, int max2, int delta2, boolean endless, + ArrayList dataListString1, ArrayList dataListString2) { + this(v1, v2, min1, max1, delta1, min2, max2, delta2, endless); + this.dataListString1 = dataListString1; + this.dataListString2 = dataListString2; + } @Override protected void onPreExecute() { @@ -228,7 +277,7 @@ public class MainNativeActivity extends AppCompatActivity { lock.lock(); do { getJsonOverHttpData(params); - publishProgress(); + publishProgress(); // goes to onProgressUpdate tryAgain.awaitUninterruptibly(); //terminateTask(); } while(!finished); @@ -250,7 +299,7 @@ public class MainNativeActivity extends AppCompatActivity { } void getJsonOverHttpData(String... params) { - // URL to get contacts JSON + // URL to get data JSON String url = params[0]; HttpHandler sh = new HttpHandler(); @@ -268,6 +317,7 @@ public class MainNativeActivity extends AppCompatActivity { //JSONArray data = jsonObj.getJSONArray("contacts"); JSONArray data = new JSONArray(jsonStr); + dataList.clear(); // looping through all data for (int i = 0; i < data.length(); i++) { @@ -324,43 +374,51 @@ public class MainNativeActivity extends AppCompatActivity { if (n > 0) { HashMap hm = dataList.get(n - 1); - float temp = Float.parseFloat(hm.get("temperature")); - String temperature = String.format(Locale.getDefault(), "%.1f", temp); + String temp, humidity; + float tempF = Float.NaN, humidityF = Float.NaN; - float humi = Float.parseFloat(hm.get("humidity")); - String humidity = String.format(Locale.getDefault(), "%.1f", humi); + temp = hm.get("temperature"); + if (temp != null) tempF = Float.parseFloat(temp); + temp = String.format(Locale.getDefault(), "%.1f", tempF); - Color green = Color.valueOf(0x71ff5f); - Color red = Color.valueOf(0xffd1655d); - Color blue = Color.valueOf(Color.rgb(113, 157, 195)); + humidity = hm.get("humidity"); + if (humidity != null) humidityF = Float.parseFloat(humidity); + humidity = String.format(Locale.getDefault(), "%.1f", humidityF); Color textColor; - textColor = valueToColor(temp, 17, 19, 23, 25, blue, green, red); + textColor = valueToColor(tempF, 17, 19, 23, 25, blue, green, red); TextView tv1 = findViewById(v1); - tv1.setText(temperature); + tv1.setText(temp); tv1.setTextColor(textColor.toArgb()); - textColor = valueToColor(humi, 25, 40, 60, 75, red, green, blue); + textColor = valueToColor(humidityF, 25, 40, 60, 75, red, green, blue); TextView tv2 = findViewById(v2); tv2.setText(humidity); tv2.setTextColor(textColor.toArgb()); if (idMin1 != 0) { - float min1 = 100, max1 = 0, val1; - float min2 = 100, max2 = 0, val2; + float min1 = 100, max1 = 0, val1 = Float.NaN; + float min2 = 100, max2 = 0, val2 = Float.NaN; + dataListString1.clear(); + dataListString2.clear(); + String valString1, valString2; for (int i = 0; i < n; i++) { hm = dataList.get(i); - val1 = Float.parseFloat(hm.get("temperature")); - if (val1 < min1) - min1 = val1; - if (val1 > max1) - max1 = val1; - val2 = Float.parseFloat(hm.get("humidity")); - if (val2 < min2) - min2 = val2; - if (val2 > max2) - max2 = val2; + valString1 = hm.get("temperature"); + if (valString1 != null) + val1 = Float.parseFloat(valString1); + valString2 = hm.get("humidity"); + if (valString2 != null) + val2 = Float.parseFloat(valString2); + if (val2 <= 100 && val2 >= 0) { + dataListString1.add(valString1); + dataListString2.add(valString2); + if (val1 < min1) min1 = val1; + if (val1 > max1) max1 = val1; + if (val2 < min2) min2 = val2; + if (val2 > max2) max2 = val2; + } } TextView tv; tv = findViewById(idMin1); @@ -418,6 +476,369 @@ public class MainNativeActivity extends AppCompatActivity { } + /** + * Async task class to get json by making HTTP call + */ + @SuppressLint("StaticFieldLeak") + private class OpenWeatherMap extends AsyncTask { + String TAG = getClass().getSimpleName(); + private ProgressBar pBar; + + // OpenWeatherMap API + String url = "http://api.openweathermap.org/data/2.5/weather"; + + // APPID (API key) + String APPID = "f66f296d1ced9a6121998493b84fbaeb"; + + // Location id + int id = 2850872; + + // Units + String units = "metric"; + + // Language + String lang = "de"; + + ArrayList> dataList = new ArrayList<>(); + + int v; + + /* + * Properties to run endless + */ + boolean endless = false; + private ProgressBar pBarSmall; + private final ReentrantLock lock = new ReentrantLock(); + private final Condition tryAgain = lock.newCondition(); + private volatile boolean finished = false; + + OpenWeatherMap(int v) { + this.v = v; + pBarLockCount += 1; + } + + /* + * Constructor to run endless + */ +// OpenWeatherMap(int v, boolean endless) { +// this(v); +// this.endless = endless; +// pBarLockCount -= 1; // do not count for endless tasks, pBarSmall is used +// } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + // Showing progress bar + pBar = findViewById(R.id.progressBar); + if (pBar.getVisibility() == View.INVISIBLE) { + pBar.setVisibility(View.VISIBLE); + } + if (endless) { + // Showing progress bar + pBarSmall = findViewById(R.id.progressBarSmall); + if (pBarSmall.getVisibility() == View.INVISIBLE) { + pBarSmall.setVisibility(View.VISIBLE); + } + } + } + + @Override + protected Void doInBackground(String... params) { + if(!endless) + getJsonOverHttpData(params); + else{ + lock.lock(); + do { + getJsonOverHttpData(params); + publishProgress(); // goes to onProgressUpdate + tryAgain.awaitUninterruptibly(); + //terminateTask(); + } while(!finished); + lock.unlock(); + } + + return null; + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + pBarLockCount -= 1; + // Dismiss the progress bar + if (pBar.getVisibility() == View.VISIBLE && pBarLockCount == 0) { + pBar.setVisibility(View.GONE); + } + updateViews(); + } + + void getJsonOverHttpData(String... params) { + // URL to get data JSON + if (params.length > 0) + url = params[0]; // overwrite openweathermap url with given url + else { + Map query = new LinkedHashMap<>(); + query.put("APPID", APPID); + query.put("id", id); + query.put("units", units); + query.put("lang", lang); + Log.d(TAG, url + "?" + URLBuilder.httpBuildQuery(query, "UTF-8")); + } + + HttpHandler sh = new HttpHandler(); + + // Making a request to url and getting response + String jsonStr = sh.makeServiceCall(url); + + Log.d(TAG, "Response from url: " + jsonStr); + + if (jsonStr != null) { + try { + dataList.clear(); + + JSONObject jsonObj = new JSONObject(jsonStr); + String name = jsonObj.getString("name"); // City name + String dt = jsonObj.getString("dt"); // Time of data calculation, unix, UTC + + JSONObject weather = jsonObj.getJSONArray("weather").getJSONObject(0); + String weatherId = weather.getString("id"); // Weather condition id + String weatherDescription = weather.getString("description"); // Weather condition within the group + String weatherIcon = weather.getString("icon"); // Weather icon id + + JSONObject main = jsonObj.getJSONObject("main"); + String mainTemp = main.getString("temp"); // Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + String mainTempMin = main.getString("temp_min"); // Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + String mainTempMax = main.getString("temp_max"); // Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + String mainHumidity = main.getString("humidity"); // Humidity, % + String mainPressure = main.getString("pressure"); // Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa + + JSONObject sys = jsonObj.getJSONObject("sys"); + String sunrise = sys.getString("sunrise"); // Sunrise time, unix, UTC + String sunset = sys.getString("sunset"); // Sunset time, unix, UTC + + JSONObject wind = jsonObj.getJSONObject("wind"); + String windSpeed = wind.getString("speed"); // Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. + String windDeg = wind.getString("deg"); // Wind direction, degrees (meteorological) + String windGust = wind.optString("gust"); + + JSONObject clouds = jsonObj.getJSONObject("clouds"); + String cloudsAll = clouds.getString("all"); // Cloudiness, % + + // tmp hash map for single datum + HashMap datum = new HashMap<>(); + + // adding each child node to HashMap key => value + datum.put("name", name); + datum.put("dt", dt); + datum.put("weather.id", weatherId); + datum.put("description", weatherDescription); + datum.put("icon", weatherIcon); + datum.put("temp", mainTemp); + datum.put("temp_min", mainTempMin); + datum.put("temp_max", mainTempMax); + datum.put("humidity", mainHumidity); + datum.put("pressure", mainPressure); + datum.put("speed", windSpeed); + datum.put("deg", windDeg); + datum.put("gust", windGust); + datum.put("clouds", cloudsAll); + datum.put("sunrise", sunrise); + datum.put("sunset", sunset); + + // adding datum to data list + dataList.add(datum); + } catch (final JSONException e) { + Log.e(TAG, "Json parsing error: " + e.getMessage()); + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), + "Json parsing error: " + e.getMessage(), + Toast.LENGTH_LONG) + .show(); + } + }); + } + } else { + Log.e(TAG, "Couldn't get json from server."); + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), + "Couldn't get json from server. Check LogCat for possible errors!", + Toast.LENGTH_LONG) + .show(); + } + }); + } + } + + String degToCompass(int deg) { + int val = (int) ((deg / 22.5) + .5); + String[] arr = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", + "WNW", "NW", "NNW"}; + return arr[(val % 16)]; + } + + float mps2kmph(float speed) { + return speed*3.6f; + } + + // temp in °C + // speed (wind) in m/s + float apparentTemperature(float temp, float speed, float humidity) { + speed = mps2kmph(speed); + float temp_a, temp2, humidity2; + // Windchill + // http://en.wikipedia.org/wiki/Wind_chill + if (temp <= 10 && speed >= 4) { + temp_a = 13.12f + 0.6215f * temp - 11.37f * (float) pow(speed, 0.16f) + + 0.3965f * temp * (float) pow(speed, 0.16f); + } + // Heat index + // http://de.wikipedia.org/wiki/Hitzeindex + else if(temp >= 26.7 && humidity >= 40) { + temp2 = (float) pow(temp, 2); + humidity2 = (float) pow(humidity, 2); + temp_a = -8.784695f + 1.61139411f * temp + 2.338549f * humidity + + -0.14611605f * temp * humidity + -1.2308094e-2f * temp2 + + -1.6424828e-2f * humidity2 + 2.211732e-3f * temp2 * humidity + + 7.2546e-4f * temp * humidity2 + -3.582e-6f * temp2 * humidity2; + } else{ + temp_a = temp; + } + return temp_a; + } + + + /** + * Updating parsed JSON data into Views + */ + void updateViews() { + int n = dataList.size(); + if (n > 0) { + HashMap hm = dataList.get(n - 1); + DateTimeFormatter formatter; + float tempF = Float.NaN, humidityF = Float.NaN; + float speedF = Float.NaN; + + String name = hm.get("name"); + formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String dt = hm.get("dt"); + if (dt != null) dt = Instant.ofEpochSecond(Long.parseLong(dt)).atZone(ZoneId.systemDefault()).format(formatter); + String weatherId = hm.get("weather.id"); + String description = hm.get("description"); + String icon = hm.get("icon"); + + String temp = hm.get("temp"); + if (temp != null) tempF = Float.parseFloat(temp); + temp = String.format(Locale.getDefault(), "%.1f", tempF); + String tempMin = hm.get("temp_min"); + if (tempMin != null) tempMin = String.format(Locale.getDefault(), "%.1f", Float.parseFloat(tempMin)); + String tempMax = hm.get("temp_max"); + if (tempMax != null) tempMax = String.format(Locale.getDefault(), "%.1f", Float.parseFloat(tempMax)); + String humidity = hm.get("humidity"); + if (humidity != null) humidityF = Float.parseFloat(humidity); + humidity = String.format(Locale.getDefault(), "%.0f", humidityF); + + String pressure = hm.get("pressure"); + String speed = hm.get("speed"); + if (speed != null) speedF = Float.parseFloat(speed); + speed = String.format(Locale.getDefault(), "%.1f", mps2kmph(speedF)); + String deg = hm.get("deg"); + int degI = Integer.MIN_VALUE; + if (deg != null) degI = Integer.parseInt(deg); + deg = degToCompass(degI); + String gust = hm.get("gust"); + assert gust != null; + if (!gust.equals("")) { + float gustF = Float.parseFloat(gust); + gust = String.format(Locale.getDefault(), "%.1f", mps2kmph(gustF)); + } + String clouds = hm.get("clouds"); + formatter = DateTimeFormatter.ofPattern("HH:mm"); + String sunrise = hm.get("sunrise"); + if (sunrise != null) sunrise = Instant.ofEpochSecond(Long.parseLong(sunrise)).atZone(ZoneId.systemDefault()).format(formatter); + String sunset = hm.get("sunset"); + if (sunset != null) sunset = Instant.ofEpochSecond(Long.parseLong(sunset)).atZone(ZoneId.systemDefault()).format(formatter); + + String apparent = String.format(Locale.getDefault(), "%.1f", apparentTemperature(tempF, speedF, humidityF)); + + TextView tv = findViewById(v); + if (weatherId != null && weatherId.startsWith("5")){ // 5xx rain + if (!gust.equals("")) + tv.setText(Html.fromHtml(String.format(getString(R.string.weather_outdoor_rain_and_gust), + name, dt, description, clouds, + temp, tempMin, tempMax, apparent, + humidity, pressure, speed, deg, gust, + sunrise, sunset), Html.FROM_HTML_MODE_COMPACT)); + else + tv.setText(Html.fromHtml(String.format(getString(R.string.weather_outdoor_rain), + name, dt, description, clouds, + temp, tempMin, tempMax, apparent, + humidity, pressure, speed, deg, + sunrise, sunset), Html.FROM_HTML_MODE_COMPACT)); + } else if (!gust.equals("")) + tv.setText(Html.fromHtml(String.format(getString(R.string.weather_outdoor_gust), + name, dt, description, clouds, + temp, tempMin, tempMax, apparent, + humidity, pressure, speed, deg, gust, + sunrise, sunset), Html.FROM_HTML_MODE_COMPACT)); + else tv.setText(Html.fromHtml(String.format(getString(R.string.weather_outdoor), + name, dt, description, clouds, + temp, tempMin, tempMax, apparent, + humidity, pressure, speed, deg, + sunrise, sunset), Html.FROM_HTML_MODE_COMPACT)); + + // insert image + SpannableStringBuilder ssb = new SpannableStringBuilder(tv.getText()); + Context context = getApplicationContext(); + int id = context.getResources().getIdentifier("openweathermap_" + icon, "drawable", context.getPackageName()); + String weather = tv.getText().toString(); + ssb.setSpan(new CenteredImageSpan(context, id), weather.indexOf("{icon}"), weather.indexOf("{icon}")+6, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + tv.setText(ssb, TextView.BufferType.SPANNABLE); + } + } + + /* + * Functions to run endless + */ + + @Override + protected void onProgressUpdate(Void... result) { + //super.onProgressUpdate(result); + // Treat this like onPostExecute(), do something with result + if(endless) { + updateViews(); + runAgain(); + } + } + + @Override + protected void onCancelled() { + // Make sure we clean up if the task is killed + if(endless) + terminateTask(); + } + + void runAgain() { + // Call this to request data from the server again + lock.lock(); + try { + tryAgain.signal(); + } finally { + lock.unlock(); + } + } + + void terminateTask() { + // The task will only finish when we call this method + finished = true; + //lock.unlock(); + } + + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. diff --git a/app/src/main/java/de/weseng/wifiweatherstation/URLBuilder.java b/app/src/main/java/de/weseng/wifiweatherstation/URLBuilder.java new file mode 100644 index 0000000..ded41c8 --- /dev/null +++ b/app/src/main/java/de/weseng/wifiweatherstation/URLBuilder.java @@ -0,0 +1,168 @@ +package de.weseng.wifiweatherstation; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.*; + +/** + * Class: URLBuilder + * + * Utility that helps to build URL String + * + * Test functions in URLBuilderTest.java + * + * Source + * - User: Gilad Tiram + * - Date: 6/12/13 + * - Time: 4:02 PM + */ +class URLBuilder { + + /** + * Build URL string from Map of params. Nested Map and Collection is also supported + * + * @param params Map of params for constructing the URL Query String + * @param encoding encoding type. If not set the "UTF-8" is selected by default + * @return String of type key=value&...key=value + */ + static String httpBuildQuery(Map params, String encoding) { + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (sb.length() > 0) { + sb.append('&'); + } + + String name = entry.getKey(); + Object value = entry.getValue(); + + + if (value instanceof Map) { + List baseParam = new ArrayList<>(); + baseParam.add(name); + String str = buildUrlFromMap(baseParam, (Map) value, encoding); + sb.append(str); + + } else if (value instanceof Collection) { + List baseParam = new ArrayList<>(); + baseParam.add(name); + String str = buildUrlFromCollection(baseParam, (Collection) value, encoding); + sb.append(str); + + } else { + sb.append(encodeParam(name)); + sb.append("="); + sb.append(encodeParam(value)); + } + + + } + return sb.toString(); + } + + private static String buildUrlFromMap(List baseParam, Map map, String encoding) { + StringBuilder sb = new StringBuilder(); + String token; + + //Build string of first level - related with params of provided Map + for (Map.Entry entry : map.entrySet()) { + + if (sb.length() > 0) { + sb.append('&'); + } + + String name = String.valueOf(entry.getKey()); + Object value = entry.getValue(); + if (value instanceof Map) { + List baseParam2 = new ArrayList<>(baseParam); + baseParam2.add(name); + String str = buildUrlFromMap(baseParam2, (Map) value, encoding); + sb.append(str); + + } else if (value instanceof List) { + List baseParam2 = new ArrayList<>(baseParam); + baseParam2.add(name); + String str = buildUrlFromCollection(baseParam2, (List) value, encoding); + sb.append(str); + } else { + token = getBaseParamString(baseParam) + "[" + name + "]=" + encodeParam(value); + sb.append(token); + } + } + + return sb.toString(); + } + + private static String buildUrlFromCollection(List baseParam, Collection coll, String encoding) { + StringBuilder sb = new StringBuilder(); + String token; + if (!(coll instanceof List)) { + coll = new ArrayList(coll); + } + List arrColl = (List) coll; + + //Build string of first level - related with params of provided Map + for (int i = 0; i < arrColl.size(); i++) { + + if (sb.length() > 0) { + sb.append('&'); + } + + Object value = arrColl.get(i); + if (value instanceof Map) { + List baseParam2 = new ArrayList<>(baseParam); + baseParam2.add(String.valueOf(i)); + String str = buildUrlFromMap(baseParam2, (Map) value, encoding); + sb.append(str); + + } else if (value instanceof List) { + List baseParam2 = new ArrayList<>(baseParam); + baseParam2.add(String.valueOf(i)); + String str = buildUrlFromCollection(baseParam2, (List) value, encoding); + sb.append(str); + } else { + token = getBaseParamString(baseParam) + "[" + i + "]=" + encodeParam(value); + sb.append(token); + } + } + + return sb.toString(); + } + + + private static String getBaseParamString(List baseParam) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < baseParam.size(); i++) { + String s = baseParam.get(i); + if (i == 0) { + sb.append(s); + } else { + sb.append("[" + s + "]"); + } + } + return sb.toString(); + } + + /** + * Check if String is either empty or null + * + * @param str string to check + * @return true if string is empty. Else return false + */ + private static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + + private static String encodeParam(Object param) { + try { + // @throws java.io.UnsupportedEncodingException if encoding is not supported + return URLEncoder.encode(String.valueOf(param), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return URLEncoder.encode(String.valueOf(param)); + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/openweathermap_01d.png b/app/src/main/res/drawable/openweathermap_01d.png new file mode 100644 index 0000000..7d2f792 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_01d.png differ diff --git a/app/src/main/res/drawable/openweathermap_01n.png b/app/src/main/res/drawable/openweathermap_01n.png new file mode 100644 index 0000000..19d0c2e Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_01n.png differ diff --git a/app/src/main/res/drawable/openweathermap_02d.png b/app/src/main/res/drawable/openweathermap_02d.png new file mode 100644 index 0000000..d86a99c Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_02d.png differ diff --git a/app/src/main/res/drawable/openweathermap_02n.png b/app/src/main/res/drawable/openweathermap_02n.png new file mode 100644 index 0000000..780c35c Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_02n.png differ diff --git a/app/src/main/res/drawable/openweathermap_03d.png b/app/src/main/res/drawable/openweathermap_03d.png new file mode 100644 index 0000000..8a4a8e9 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_03d.png differ diff --git a/app/src/main/res/drawable/openweathermap_03n.png b/app/src/main/res/drawable/openweathermap_03n.png new file mode 100644 index 0000000..8a4a8e9 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_03n.png differ diff --git a/app/src/main/res/drawable/openweathermap_04d.png b/app/src/main/res/drawable/openweathermap_04d.png new file mode 100644 index 0000000..4677b85 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_04d.png differ diff --git a/app/src/main/res/drawable/openweathermap_04n.png b/app/src/main/res/drawable/openweathermap_04n.png new file mode 100644 index 0000000..4677b85 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_04n.png differ diff --git a/app/src/main/res/drawable/openweathermap_09d.png b/app/src/main/res/drawable/openweathermap_09d.png new file mode 100644 index 0000000..b7cb53b Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_09d.png differ diff --git a/app/src/main/res/drawable/openweathermap_09n.png b/app/src/main/res/drawable/openweathermap_09n.png new file mode 100644 index 0000000..b7cb53b Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_09n.png differ diff --git a/app/src/main/res/drawable/openweathermap_10d.png b/app/src/main/res/drawable/openweathermap_10d.png new file mode 100644 index 0000000..b1e1f83 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_10d.png differ diff --git a/app/src/main/res/drawable/openweathermap_10n.png b/app/src/main/res/drawable/openweathermap_10n.png new file mode 100644 index 0000000..b09628e Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_10n.png differ diff --git a/app/src/main/res/drawable/openweathermap_11d.png b/app/src/main/res/drawable/openweathermap_11d.png new file mode 100644 index 0000000..f3ce654 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_11d.png differ diff --git a/app/src/main/res/drawable/openweathermap_11n.png b/app/src/main/res/drawable/openweathermap_11n.png new file mode 100644 index 0000000..f3ce654 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_11n.png differ diff --git a/app/src/main/res/drawable/openweathermap_13d.png b/app/src/main/res/drawable/openweathermap_13d.png new file mode 100644 index 0000000..7b64a6c Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_13d.png differ diff --git a/app/src/main/res/drawable/openweathermap_13n.png b/app/src/main/res/drawable/openweathermap_13n.png new file mode 100644 index 0000000..7b64a6c Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_13n.png differ diff --git a/app/src/main/res/drawable/openweathermap_50d.png b/app/src/main/res/drawable/openweathermap_50d.png new file mode 100644 index 0000000..18afb34 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_50d.png differ diff --git a/app/src/main/res/drawable/openweathermap_50n.png b/app/src/main/res/drawable/openweathermap_50n.png new file mode 100644 index 0000000..18afb34 Binary files /dev/null and b/app/src/main/res/drawable/openweathermap_50n.png differ diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml new file mode 100644 index 0000000..25b2c83 --- /dev/null +++ b/app/src/main/res/layout/activity_detail.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_native.xml b/app/src/main/res/layout/activity_main_native.xml index 4a3d9d7..fc87044 100644 --- a/app/src/main/res/layout/activity_main_native.xml +++ b/app/src/main/res/layout/activity_main_native.xml @@ -10,8 +10,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:layout_marginStart="@dimen/widget_horizontal_margin" + android:layout_marginEnd="@dimen/widget_horizontal_margin" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" @@ -21,6 +21,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="-8dp" + android:layout_marginStart="@dimen/widget_horizontal_margin" android:weightSum="6"> + android:layout_height="match_parent" + android:layout_marginStart="@dimen/widget_horizontal_margin" + android:layout_marginEnd="@dimen/widget_horizontal_margin"> + android:layout_height="match_parent" + android:layout_marginStart="@dimen/widget_horizontal_margin" + android:layout_marginEnd="@dimen/widget_horizontal_margin"> + android:layout_height="match_parent" + android:layout_marginStart="@dimen/widget_horizontal_margin" + android:layout_marginEnd="@dimen/widget_horizontal_margin"> + + + + Temperatur Feuchtigkeit + Wetterdaten für %s %s +
{icon} %s (%s %%) +
Temperatur: %s °C — %s° / %s° gefühlt: %s° +
Feuchtigkeit: %s %% +
Druck: %s hPa +
Wind: %s km/h %s +
Sonnenaufgang / -untergang: %s / %s]]>
+ Wetterdaten für %s %s +
{icon} %s, Bewölkung: %s %% +
Temperatur: %s °C — %s° / %s° gefühlt: %s° +
Feuchtigkeit: %s %% +
Druck: %s hPa +
Wind: %s km/h %s +
Sonnenaufgang / -untergang: %s / %s]]>
+ Wetterdaten für %s %s +
{icon} %s, Bewölkung: %s %% +
Temperatur: %s °C — %s° / %s° gefühlt: %s° +
Feuchtigkeit: %s %% +
Druck: %s hPa +
Wind: %s km/h %s Windböe %s km/h +
Sonnenaufgang / -untergang: %s / %s]]>
+ Wetterdaten für %s %s +
{icon} %s (%s %%) +
Temperatur: %s °C — %s° / %s° gefühlt: %s° +
Feuchtigkeit: %s %% +
Druck: %s hPa +
Wind: %s km/h %s Windböe %s km/h +
Sonnenaufgang / -untergang: %s / %s]]>
+ Wetterdaten für %s %s +
{icon} %s, Niederschlagsmenge: %s mm, Bewölkung: %s %% +
Temperatur: %s °C — %s° / %s° gefühlt: %s° +
Feuchtigkeit: %s %% +
Druck: %s hPa +
Wind: %s km/h %s +
Sonnenaufgang / -untergang: %s / %s]]>
Einstellungen Verbindung: intern diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4b2373..968f13b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,45 @@ % __._ o + + Weather data for %s %s +
{icon} %s (%s %%) +
Temperature: %s °C — %s° / %s° feels: %s° +
Humidity: %s %% +
Pressure: %s hPa +
Wind: %s km/h %s +
Sunrise / sunset: %s / %s]]>
+ + + Weather data for %s %s +
{icon} %s, clouds: %s %% +
Temperature: %s °C — %s° / %s° feels: %s° +
Humidity: %s %% +
Pressure: %s hPa +
Wind: %s km/h %s +
Sunrise / sunset: %s / %s]]>
+ Weather data for %s %s +
{icon} %s, clouds: %s %% +
Temperature: %s °C — %s° / %s° feels: %s° +
Humidity: %s %% +
Pressure: %s hPa +
Wind: %s km/h %s gust: %s km/h +
Sunrise / sunset: %s / %s]]>
+ Weather data for %s %s +
{icon} %s (%s %%) +
Temperature: %s °C — %s° / %s° feels: %s° +
Humidity: %s %% +
Pressure: %s hPa +
Wind: %s km/h %s gust: %s km/h +
Sunrise / sunset: %s / %s]]>
+ + Weather data for %s %s +
{icon} %s, rainfall: %s mm, clouds: %s %% +
Temperature: %s °C — %s° / %s° feels: %s° +
Humidity: %s %% +
Pressure: %s hPa +
Wind: %s km/h %s +
Sunrise / sunset: %s / %s]]>
Settings de.weseng.wifiweatherstation.MESSAGE diff --git a/app/src/test/java/de/weseng/wifiweatherstation/URLBuilderTest.java b/app/src/test/java/de/weseng/wifiweatherstation/URLBuilderTest.java new file mode 100644 index 0000000..d8b62cb --- /dev/null +++ b/app/src/test/java/de/weseng/wifiweatherstation/URLBuilderTest.java @@ -0,0 +1,137 @@ +package de.weseng.wifiweatherstation; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static de.weseng.wifiweatherstation.URLBuilder.httpBuildQuery; +import static org.junit.Assert.assertEquals; + +public class URLBuilderTest { + + @Test + public void basicTest() { + Map params = new LinkedHashMap<>(); + params.put("a", "1"); + params.put("b", "2"); + params.put("c", "3"); + + assertEquals("a=1&b=2&c=3", + httpBuildQuery(params, "UTF-8")); + } + + @Test + public void testWithMap() { + Map params = new LinkedHashMap<>(); + params.put("a", "1"); + params.put("b", "2"); + + Map cParams = new LinkedHashMap<>(); + cParams.put("c1", "c1val"); + cParams.put("c2", "c2val"); + params.put("c", cParams); + + assertEquals("a=1&b=2&c[c1]=c1val&c[c2]=c2val", + httpBuildQuery(params, "UTF-8")); + } + + @Test + public void testWithNestedMap() { + Map params = new LinkedHashMap<>(); + params.put("a", "1"); + params.put("b", "2"); + + Map cParamsLevel1 = new LinkedHashMap<>(); + cParamsLevel1.put("cL1-1", "cLevel1-1val"); + cParamsLevel1.put("cL1-2", "cLevel1-2val"); + + Map cParamsLevel2 = new LinkedHashMap<>(); + cParamsLevel2.put("cL2-1", "cLevel2-1val"); + cParamsLevel2.put("cL2-2", "cLevel2-2val"); + cParamsLevel1.put("cL1-3", cParamsLevel2); + + params.put("c", cParamsLevel1); + + assertEquals("a=1&b=2&c[cL1-1]=cLevel1-1val&c[cL1-2]=cLevel1-2val&c[cL1-3][cL2-1]=cLevel2-1val&c[cL1-3][cL2-2]=cLevel2-2val", + httpBuildQuery(params, "UTF-8")); + } + + @Test + public void testWithList() { + Map params = new LinkedHashMap<>(); + params.put("a", "1"); + params.put("b", "2"); + + List cParams = new ArrayList<>(); + cParams.add("c1val"); + cParams.add("c2val"); + params.put("c", cParams); + + assertEquals("a=1&b=2&c[0]=c1val&c[1]=c2val", + httpBuildQuery(params, "UTF-8")); + } + + @Test + public void testWithNestedList() { + Map params = new LinkedHashMap<>(); + params.put("a", "1"); + params.put("b", "2"); + + List cParamsLevel1 = new ArrayList<>(); + cParamsLevel1.add("cL1-val1"); + cParamsLevel1.add("cL12-val2"); + + List cParamsLevel2 = new ArrayList<>(); + cParamsLevel2.add("cL2-val1"); + cParamsLevel2.add("cL2-val2"); + cParamsLevel1.add(cParamsLevel2); + + params.put("c", cParamsLevel1); + + assertEquals("a=1&b=2&c[0]=cL1-val1&c[1]=cL12-val2&c[2][0]=cL2-val1&c[2][1]=cL2-val2", + httpBuildQuery(params, "UTF-8")); + } + + @Test + public void testCompound() { + Map params = new LinkedHashMap<>(); + + //flat + params.put("a", "1"); + params.put("b", "2"); + + //Map level 1 + Map cParamsLevel1 = new LinkedHashMap<>(); + cParamsLevel1.put("cL1-1", "cLevel1-1val"); + cParamsLevel1.put("cL1-2", "cLevel1-2val"); + + //Map level 2 + Map cParamsLevel2 = new LinkedHashMap<>(); + cParamsLevel2.put("cL2-1", "cLevel2-1val"); + cParamsLevel2.put("cL2-2", "cLevel2-2val"); + cParamsLevel1.put("cL1-3", cParamsLevel2); + + params.put("c", cParamsLevel1); + + //List level 1 + List dParamsLevel1 = new ArrayList<>(); + dParamsLevel1.add("dL1-val1"); + dParamsLevel1.add("dL12-val2"); + + //List level 2 + List dParamsLevel2 = new ArrayList<>(); + dParamsLevel2.add("dL2-val1"); + dParamsLevel2.add("dL2-val2"); + dParamsLevel1.add(dParamsLevel2); + + params.put("d", dParamsLevel1); + + assertEquals("a=1&b=2&c[cL1-1]=cLevel1-1val&c[cL1-2]=cLevel1-2val&c[cL1-3][cL2-1]=cLevel2-1val&c[cL1-3][cL2-2]=cLevel2-2val&d[0]=dL1-val1&d[1]=dL12-val2&d[2][0]=dL2-val1&d[2][1]=dL2-val2", + httpBuildQuery(params, "UTF-8")); + + } + +} \ No newline at end of file