7 Commits

41 changed files with 3076 additions and 89 deletions

12
.idea/dictionaries/weatherstation.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<component name="ProjectDictionaryState">
<dictionary name="weatherstation">
<words>
<w>appid</w>
<w>dateandtime</w>
<w>openweathermap</w>
<w>ssid</w>
<w>weseng</w>
<w>wifiweatherstation</w>
</words>
</dictionary>
</component>

35
.idea/misc.xml generated
View File

@@ -1,5 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" />
<option name="myNullables">
<value>
<list size="10">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>

View File

@@ -4,10 +4,10 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "de.weseng.wifiweatherstation"
minSdkVersion 16
minSdkVersion 26
targetSdkVersion 28
versionCode 20190302
versionName "2019.3.2"
versionCode 20190328
versionName "2019.3.28"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -18,12 +18,18 @@ android {
}
}
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-annotations:28.0.0'
testImplementation 'junit:junit:4.12'
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'
}

View File

@@ -2,10 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.weseng.wifiweatherstation">
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- <uses-permission android:name="android.permission.READ_PROFILE" /> -->
<!-- <uses-permission android:name="android.permission.READ_CONTACTS" /> --> <!-- for email completions -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- or ACCESS_FINE_LOCATION for SSID -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- or ACCESS_FINE_LOCATION for SSID -->
<uses-permission android:name="android.permission.INTERNET" />
<application
@@ -17,19 +20,37 @@
android:theme="@style/DarkTheme"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<activity
android:name=".LoginActivity"
android:label="@string/app_name"
android:noHistory="true"> <!-- android:label="@string/title_activity_login" -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SettingsActivity"
android:parentActivityName=".MainActivity">
<!-- The meta-data tag is required if you support API level 15 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
<activity
android:name=".MainNativeActivity"
android:label="@string/app_name"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name=".DetailActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:parentActivityName=".MainActivity" />
</application>
</manifest>

View File

@@ -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<Drawable> 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<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<>(d);
}
return d;
}
}

View File

@@ -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<String> dataList1 = intent.getStringArrayListExtra("temperature");
ArrayList<String> 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<String> dataList1, String label1,
ArrayList<String> dataList2, String label2) {
int n;
n = dataList1.size();
ArrayList<Entry> 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<Entry> 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<ILineDataSet> 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);
}
}

View File

@@ -0,0 +1,63 @@
package de.weseng.wifiweatherstation;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
public class HttpHandler {
private static final String TAG = HttpHandler.class.getSimpleName();
public HttpHandler() {
}
public String makeServiceCall(String reqUrl) {
String response = null;
try {
URL url = new URL(reqUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// read the response
InputStream in = new BufferedInputStream(conn.getInputStream());
response = convertStreamToString(in);
} catch (MalformedURLException e) {
Log.e(TAG, "MalformedURLException: " + e.getMessage());
} catch (ProtocolException e) {
Log.e(TAG, "ProtocolException: " + e.getMessage());
} catch (IOException e) {
Log.e(TAG, "IOException: " + e.getMessage());
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
}
return response;
}
private String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
try {
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,363 @@
package de.weseng.wifiweatherstation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.app.Activity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import static android.Manifest.permission.READ_CONTACTS;
import static de.weseng.wifiweatherstation.SettingsActivity.PREFS_NAME;
/**
* A login screen that offers login via email/password.
*/
public class LoginActivity extends Activity implements LoaderCallbacks<Cursor> {
/**
* Id to identity READ_CONTACTS permission request.
*/
private static final int REQUEST_READ_CONTACTS = 0;
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean nativeView = settings.getBoolean("nativeView", true);
if(nativeView){
Intent intent = new Intent(this, MainNativeActivity.class);
startActivity(intent);
} else {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
//
// BEGIN Template Login Activity
//
// setContentView(R.layout.activity_login);
// // Set up the login form.
// mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
// populateAutoComplete();
//
// mPasswordView = (EditText) findViewById(R.id.password);
// mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
// @Override
// public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
// if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
// attemptLogin();
// return true;
// }
// return false;
// }
// });
//
// Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
// mEmailSignInButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View view) {
// attemptLogin();
// }
// });
//
// mLoginFormView = findViewById(R.id.login_form);
// mProgressView = findViewById(R.id.login_progress);
}
private void populateAutoComplete() {
if (!mayRequestContacts()) {
return;
}
getLoaderManager().initLoader(0, null, this);
}
private boolean mayRequestContacts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
// TODO: alert the user with a Snackbar/AlertDialog giving them the permission rationale
// To use the Snackbar from the design support library, ensure that the activity extends
// AppCompatActivity and uses the Theme.AppCompat theme.
} else {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
return false;
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete();
}
}
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}
private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("@");
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}
// TODO: register the new account here.
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
}

View File

@@ -2,17 +2,9 @@ package de.weseng.wifiweatherstation;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
@@ -25,13 +17,9 @@ import android.webkit.WebViewClient;
import android.widget.Toast;
import java.io.InputStream;
import java.util.List;
public class MainActivity extends AppCompatActivity {
public static final String PREFS_NAME = "Settings";
WebView myWebView;
public static String MESSAGE = "de.weseng.wifiweatherstation.MESSAGE";
@SuppressLint("SetJavaScriptEnabled")
@Override
@@ -62,11 +50,6 @@ public class MainActivity extends AppCompatActivity {
myWebView.setBackgroundColor(Color.TRANSPARENT);
myWebView.setPadding(0, 0, 0, 0);
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
String ssidLocal = "\"" + settings.getString("ssidLocal", getString(R.string.ssid)) + "\"";
MESSAGE = "";
myWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
@@ -75,16 +58,19 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onPageFinished(WebView view, String url) {
// Dismiss the progress dialog
pd.dismiss();
// Inject CSS when page is done loading
injectCSS();
pd.dismiss();
//String webUrl = myWebView.getUrl();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
/* will open a new web page in webview*/
// loadUrl needs: AndroidManifest.xml <application android:usesCleartextTraffic="true"
view.loadUrl(url);
return true;
}
@@ -99,45 +85,16 @@ public class MainActivity extends AppCompatActivity {
});
/* set URL site */
if (savedInstanceState == null) {
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
//MESSAGE = MESSAGE.concat("\nmWifi: " + mWifi.toString());
//MESSAGE = MESSAGE.concat("\nmWifi.isConnected(): " + mWifi.isConnected()); // is: true
//MESSAGE = MESSAGE.concat("\nmWifi.getExtraInfo(): " + mWifi.getExtraInfo()); // is: null
if (mWifi.isConnected()) {
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
//MESSAGE = MESSAGE.concat("\nwifiInfo: " + wifiInfo.toString());
if (wifiInfo.getSupplicantState() == SupplicantState.COMPLETED) {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifiManager.getConnectionInfo();
String ssid = findSSIDForWifiInfo(wifiManager, info);
MESSAGE = MESSAGE.concat("\nSSID: " + ssid);
// loadUrl needs: AndroidManifest.xml <application android:usesCleartextTraffic="true"
//if (ssidIntern.contains(ssid)) {
if (ssid.equals(ssidLocal)) {
MESSAGE = MESSAGE.concat("\nconnection: internal");
//Toast.makeText(getApplicationContext(), "intern", Toast.LENGTH_LONG).show();
myWebView.loadUrl(getString(R.string.url_local));
} else {
MESSAGE = MESSAGE.concat("\nconnection: external");
//Toast.makeText(getApplicationContext(), "extern", Toast.LENGTH_LONG).show();
myWebView.loadUrl(getString(R.string.url_global));
}
}
// loadUrl needs: AndroidManifest.xml <application android:usesCleartextTraffic="true"
if (SettingsActivity.isLocal(getApplicationContext())) {
//Toast.makeText(getApplicationContext(), "intern", Toast.LENGTH_LONG).show();
myWebView.loadUrl(getString(R.string.url_local));
} else {
//Toast.makeText(getApplicationContext(), "extern", Toast.LENGTH_LONG).show();
myWebView.loadUrl(getString(R.string.url_global));
}
}
}
public String findSSIDForWifiInfo(WifiManager wifiManager, WifiInfo wifiInfo) {
List<WifiConfiguration> listOfConfigurations = wifiManager.getConfiguredNetworks();
for (int index = 0; index < listOfConfigurations.size(); index++) {
WifiConfiguration configuration = listOfConfigurations.get(index);
if (configuration.networkId == wifiInfo.getNetworkId()) {
return configuration.SSID;
}
}
return null;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
@@ -181,7 +138,6 @@ public class MainActivity extends AppCompatActivity {
myWebView.restoreState(savedInstanceState);
}
// Inject CSS method: read style.css from assets folder
// Append stylesheet to document head
private void injectCSS() {
@@ -208,9 +164,8 @@ public class MainActivity extends AppCompatActivity {
/** Called when the user taps the Send button */
public void settings() {
Intent intentSettings = new Intent(this, SettingsActivity.class);
intentSettings.putExtra(MESSAGE, MESSAGE);
//intentSettings.putExtra(MESSAGE, MESSAGE);
startActivity(intentSettings);
}
}

View File

@@ -0,0 +1,948 @@
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;
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;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.Toast;
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<AsyncTask<String, Void, Void>> endlessTasks = new ArrayList<>();
ArrayList<String> dataListStringTemp1 = new ArrayList<>();
ArrayList<String> dataListStringTemp2 = new ArrayList<>();
ArrayList<String> dataListStringTemp3 = new ArrayList<>();
ArrayList<String> dataListStringHumidity1 = new ArrayList<>();
ArrayList<String> dataListStringHumidity2 = new ArrayList<>();
ArrayList<String> dataListStringHumidity3 = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_native);
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
String urlLocal = settings.getString("urlLocal", getString(R.string.url_local));
String urlGlobal = settings.getString("urlGlobal", getString(R.string.url_global));
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(), "external", Toast.LENGTH_LONG).show();
if (urlGlobal != null && urlGlobal.length() > 0)
urlPre = urlGlobal.substring(urlGlobal.length() - 1).equals("/") ? urlGlobal : urlGlobal + "/";
}
executeAsyncTask(new GetData(R.id.textViewTemperatureValue1, R.id.textViewHumidityValue1),
urlPre + "api.php?host=192.168.1.71&last");
executeAsyncTask(new GetData(R.id.textViewTemperatureValue2, R.id.textViewHumidityValue2),
urlPre + "api.php?host=192.168.1.72&last");
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, 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, 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, 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");
}
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause() called");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop() called");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() called");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart() called");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume() called");
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if (keyCode == KeyEvent.KEYCODE_BACK) {
for(int i=0; i<endlessTasks.size(); i++){
endlessTasks.get(i).cancel(false); // cancel gently
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
}
// If it wasn't the Back key, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
@SuppressLint("ObsoleteSdkInt")
@SafeVarargs
@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
else
asyncTask.execute(params);
}
/**
* Async task class to get json by making HTTP call
*/
@SuppressLint("StaticFieldLeak")
private class GetData extends AsyncTask<String, Void, Void> {
String TAG = getClass().getSimpleName();
private ProgressBar pBar;
ArrayList<HashMap<String, String>> dataList = new ArrayList<>();
int v1, v2;
int idMin1, idMax1, idDelta1;
int idMin2, idMax2, idDelta2;
/*
* 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;
ArrayList<String> dataListString1 = new ArrayList<>();
ArrayList<String> dataListString2 = new ArrayList<>();
GetData(int v1, int v2) {
this.v1 = v1;
this.v2 = v2;
pBarLockCount += 1;
}
GetData(int v1, int v2, int min1, int max1, int delta1, int min2, int max2, int delta2) {
this(v1, v2);
idMin1 = min1;
idMax1 = max1;
idDelta1 = delta1;
idMin2 = min2;
idMax2 = max2;
idDelta2 = delta2;
}
/*
* Constructor to run endless
*/
GetData(int v1, int v2, int min1, int max1, int delta1, int min2, int max2, int delta2, boolean endless) {
this(v1, v2, min1, max1, delta1, min2, max2, delta2);
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<String> dataListString1, ArrayList<String> dataListString2) {
this(v1, v2, min1, max1, delta1, min2, max2, delta2, endless);
this.dataListString1 = dataListString1;
this.dataListString2 = dataListString2;
}
@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
String url = params[0];
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 {
//JSONObject jsonObj = new JSONObject(jsonStr);
// Getting JSON Array node
//JSONArray data = jsonObj.getJSONArray("contacts");
JSONArray data = new JSONArray(jsonStr);
dataList.clear();
// looping through all data
for (int i = 0; i < data.length(); i++) {
JSONObject c = data.getJSONObject(i);
String dateandtime = c.getString("dateandtime");
String sensor = c.getString("sensor");
String temperature = c.getString("temperature");
String humidity = c.getString("humidity");
// tmp hash map for single datum
HashMap<String, String> datum = new HashMap<>();
// adding each child node to HashMap key => value
datum.put("dateandtime", dateandtime);
datum.put("sensor", sensor);
datum.put("temperature", temperature);
datum.put("humidity", humidity);
// 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();
}
});
}
}
/**
* Updating parsed JSON data into Views
*/
void updateViews() {
int n = dataList.size();
if (n > 0) {
HashMap<String, String> hm = dataList.get(n - 1);
String temp, humidity;
float tempF = Float.NaN, humidityF = Float.NaN;
temp = hm.get("temperature");
if (temp != null) tempF = Float.parseFloat(temp);
temp = String.format(Locale.getDefault(), "%.1f", tempF);
humidity = hm.get("humidity");
if (humidity != null) humidityF = Float.parseFloat(humidity);
humidity = String.format(Locale.getDefault(), "%.1f", humidityF);
Color textColor;
textColor = valueToColor(tempF, 17, 19, 23, 25, blue, green, red);
TextView tv1 = findViewById(v1);
tv1.setText(temp);
tv1.setTextColor(textColor.toArgb());
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.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);
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);
tv.setText(String.format(Locale.getDefault(), "%.1f", min1));
tv = findViewById(idMax1);
tv.setText(String.format(Locale.getDefault(), "%.1f", max1));
tv = findViewById(idDelta1);
tv.setText(String.format(Locale.getDefault(), "%.1f", max1 - min1));
tv = findViewById(idMin2);
tv.setText(String.format(Locale.getDefault(), "%.1f", min2));
tv = findViewById(idMax2);
tv.setText(String.format(Locale.getDefault(), "%.1f", max2));
tv = findViewById(idDelta2);
tv.setText(String.format(Locale.getDefault(), "%.1f", max2 - min2));
}
}
}
/*
* 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();
}
}
/**
* Async task class to get json by making HTTP call
*/
@SuppressLint("StaticFieldLeak")
private class OpenWeatherMap extends AsyncTask<String, Void, Void> {
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<HashMap<String, String>> 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<String, Object> 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<String, String> 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<String, String> 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(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(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(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(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.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.settings:
settings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Called when the user taps the Settings button
*/
public void settings() {
Intent intentSettings = new Intent(this, SettingsActivity.class);
//intentSettings.putExtra(MESSAGE, MESSAGE);
startActivity(intentSettings);
}
int interpolate(int pBegin, int pEnd, int pStep, int pMax) {
if (pBegin < pEnd) {
return (int) ((pEnd - pBegin) * ((float) pStep / pMax)) + pBegin;
} else {
return (int) ((pBegin - pEnd) * (1 - ((float) pStep / pMax))) + pEnd;
}
}
int interpolateColor(int theColorBegin, int theColorEnd,
int theNumStep, int theNumSteps) {
int theR0 = (theColorBegin & 0xff0000) >> 16;
int theG0 = (theColorBegin & 0x00ff00) >> 8;
int theB0 = (theColorBegin & 0x0000ff);
//int theB0 = (theColorBegin & 0x0000ff) >> 0;
int theR1 = (theColorEnd & 0xff0000) >> 16;
int theG1 = (theColorEnd & 0x00ff00) >> 8;
int theB1 = (theColorEnd & 0x0000ff);
//int theB1 = (theColorEnd & 0x0000ff) >> 0;
int theR = interpolate(theR0, theR1, theNumStep, theNumSteps);
int theG = interpolate(theG0, theG1, theNumStep, theNumSteps);
int theB = interpolate(theB0, theB1, theNumStep, theNumSteps);
return (((theR << 8) | theG) << 8) | theB;
}
Color valueToColor(float value, float lowMin, float lowMax, float highMin, float highMax,
Color below, Color ideal, Color above) {
return Color.valueOf(Color.parseColor(String.format("#%06X",
valueToColorRGB(value, lowMin, lowMax, highMin, highMax, below, ideal, above))));
}
int valueToColorRGB(float value, float lowMin, float lowMax, float highMin, float highMax,
Color below, Color ideal, Color above) {
int bel = Integer.parseInt(String.format("%06X", (0xFFFFFF & below.toArgb())), 16);
int ide = Integer.parseInt(String.format("%06X", (0xFFFFFF & ideal.toArgb())), 16);
int abo = Integer.parseInt(String.format("%06X", (0xFFFFFF & above.toArgb())), 16);
return valueToColorRGB(value, lowMin, lowMax, highMin, highMax, bel, ide, abo);
}
/**
* lowMin < lowMax < highMin < highMax
*
* Example use for temperatures with color blue for too cold (below), green for ideal
* and red for too warm (above) temperatures:
* - Below lowMin the color is blue
* - Between lowMin and lowMax is color is linear interpolated between blue and green
* - Between lowMax and highMin the color is green
* - Between highMin and highMax the color is linear interpolated between green and red
* - Above highMax the color is red
*/
int valueToColorRGB(float value, float lowMin, float lowMax, float highMin, float highMax,
int below, int ideal, int above) {
int result = 0xFFFFFF; // white
int theNumSteps = 100;
int theColorBegin, theColorEnd, ratio;
if (value <= highMin && value >= lowMax)
result = ideal;
else if (value > highMax)
result = above;
else if (value > highMin) {
theColorBegin = ideal;
theColorEnd = above;
ratio = (int) ((value - highMin) / (highMax - highMin) * 100);
result = interpolateColor(theColorBegin, theColorEnd, ratio, theNumSteps);
} else if (value < lowMin)
result = below;
else if (value < lowMax) {
theColorBegin = ideal;
theColorEnd = below;
ratio = (int) ((lowMax - value) / (lowMax - lowMin) * 100);
result = interpolateColor(theColorBegin, theColorEnd, ratio, theNumSteps);
}
return result;
}
}

View File

@@ -1,16 +1,29 @@
package de.weseng.wifiweatherstation;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import java.util.List;
public class SettingsActivity extends AppCompatActivity {
public static final String PREFS_NAME = "Settings";
public static String MESSAGE;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -19,18 +32,36 @@ public class SettingsActivity extends AppCompatActivity {
// Get the Intent that started this activity and extract the string
Intent intent = getIntent();
String message = intent.getStringExtra(MainActivity.MESSAGE);
String message = intent.getStringExtra(MESSAGE);
if (message == null) {
message = "";
}
message = getString(R.string.message) + message;
if (isLocal(getApplicationContext())) {
message += "\n" + getString(R.string.message_connection_internal);
} else {
message += "\n" + getString(R.string.message_connection_external);
}
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
String ssidLocal = settings.getString("ssidLocal", getString(R.string.ssid));
String urlLocal = settings.getString("urlLocal", getString(R.string.url_local));
String urlGlobal = settings.getString("urlGlobal", getString(R.string.url_global));
boolean nativeView = settings.getBoolean("nativeView", true);
//message = message.concat("\nSSIDLocal: " + ssidLocal);
// Capture the layout's TextView and set the string as its text
TextView textView = findViewById(R.id.textView);
textView.setText(message);
final Switch switchView = findViewById(R.id.switchView);
switchView.setChecked(nativeView);
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
save("nativeView", isChecked);
}
});
final EditText editTextSsid = findViewById(R.id.editTextSsidLocal);
editTextSsid.setText(ssidLocal);
editTextSsid.setOnKeyListener(new View.OnKeyListener() {
@@ -94,15 +125,16 @@ public class SettingsActivity extends AppCompatActivity {
}
@Override
protected void onStop(){
protected void onStop() {
super.onStop();
save_all();
}
public void save_all() {
save("ssidLocal", ((EditText)findViewById(R.id.editTextSsidLocal)).getText().toString());
save("urlLocal", ((EditText)findViewById(R.id.editTextUrlLocal)).getText().toString());
save("urlGlobal", ((EditText)findViewById(R.id.editTextUrlGlobal)).getText().toString());
save("ssidLocal", ((EditText) findViewById(R.id.editTextSsidLocal)).getText().toString());
save("urlLocal", ((EditText) findViewById(R.id.editTextUrlLocal)).getText().toString());
save("urlGlobal", ((EditText) findViewById(R.id.editTextUrlGlobal)).getText().toString());
save("nativeView", ((Switch) findViewById(R.id.switchView)).isChecked());
}
public void save(String key, String value) {
@@ -115,4 +147,46 @@ public class SettingsActivity extends AppCompatActivity {
// Commit the edits!
editor.apply();
}
public void save(String key, Boolean value) {
// We need an Editor object to make preference changes.
// All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean(key, value);
// Commit the edits!
editor.apply();
}
public static String findSSIDForWifiInfo(WifiManager wifiManager, WifiInfo wifiInfo) {
List<WifiConfiguration> listOfConfigurations = wifiManager.getConfiguredNetworks();
for (int index = 0; index < listOfConfigurations.size(); index++) {
WifiConfiguration configuration = listOfConfigurations.get(index);
if (configuration.networkId == wifiInfo.getNetworkId()) {
return configuration.SSID;
}
}
return null;
}
public static boolean isLocal(@NonNull Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, 0);
String ssidLocal = "\"" + settings.getString("ssidLocal", context.getString(R.string.ssid)) + "\"";
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
NetworkInfo mWifi = connManager.getNetworkInfo(connManager.getActiveNetwork());
if (mWifi.isConnected()) {
WifiManager wifiMgr = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
if (wifiInfo.getSupplicantState() == SupplicantState.COMPLETED) {
String ssid = findSSIDForWifiInfo(wifiMgr, wifiInfo);
if (ssid != null)
return ssid.equals(ssidLocal);
}
}
return false;
}
}

View File

@@ -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<String, Object> params, String encoding) {
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (sb.length() > 0) {
sb.append('&');
}
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
List<String> baseParam = new ArrayList<>();
baseParam.add(name);
String str = buildUrlFromMap(baseParam, (Map) value, encoding);
sb.append(str);
} else if (value instanceof Collection) {
List<String> 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<String> baseParam, Map<Object, Object> map, String encoding) {
StringBuilder sb = new StringBuilder();
String token;
//Build string of first level - related with params of provided Map
for (Map.Entry<Object, Object> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append('&');
}
String name = String.valueOf(entry.getKey());
Object value = entry.getValue();
if (value instanceof Map) {
List<String> baseParam2 = new ArrayList<>(baseParam);
baseParam2.add(name);
String str = buildUrlFromMap(baseParam2, (Map) value, encoding);
sb.append(str);
} else if (value instanceof List) {
List<String> 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<String> 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<String> 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<String> 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<String> 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));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DetailActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="58dp">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin" />
<Space
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DetailActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="58dp">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".LoginActivity">
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/widget_vertical_margin"
android:visibility="gone" />
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="6"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,499 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainNativeActivity">
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_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"
app:layout_constraintTop_toTopOf="parent">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="-8dp"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:weightSum="6">
<TextView
android:id="@+id/textViewTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_span="3"
android:layout_weight="1"
android:text="@string/temperature"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewHumidity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_span="3"
android:layout_weight="1"
android:text="@string/humidity"
android:textStyle="bold" />
</TableRow>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TableLayout
android:id="@+id/tableRow1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/widget_vertical_margin"
android:paddingBottom="@dimen/widget_vertical_margin"
android:background="?android:attr/selectableItemBackground">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin">
<TextView
android:id="@+id/textViewTemperatureValue1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewTemperatureUnit1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/temperature_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/textViewHumidityValue1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewHumidityUnit1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/humidity_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:weightSum="6">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewTemperatureMin1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureMax1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureDelta1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewHumidityMin1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityMax1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityDelta1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
</TableRow>
</TableLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TableLayout
android:id="@+id/tableRow2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/widget_vertical_margin"
android:paddingBottom="@dimen/widget_vertical_margin"
android:background="?android:attr/selectableItemBackground">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin">
<TextView
android:id="@+id/textViewTemperatureValue2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewTemperatureUnit2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/temperature_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/textViewHumidityValue2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewHumidityUnit2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/humidity_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:weightSum="6">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewTemperatureMin2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureMax2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureDelta2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewHumidityMin2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityMax2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityDelta2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
</TableRow>
</TableLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TableLayout
android:id="@+id/tableRow3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/widget_vertical_margin"
android:paddingBottom="@dimen/widget_vertical_margin"
android:background="?android:attr/selectableItemBackground">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin">
<TextView
android:id="@+id/textViewTemperatureValue3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewTemperatureUnit3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/temperature_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/textViewHumidityValue3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/dummy_value"
android:textSize="@dimen/value_main_size"
android:textStyle="bold" />
<TextView
android:id="@+id/textViewHumidityUnit3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:text="@string/humidity_unit"
android:textSize="32sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:weightSum="6">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewTemperatureMin3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureMax3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewTemperatureDelta3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_span="3"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewHumidityMin3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityMax3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/dummy_value2" />
<TextView
android:id="@+id/textViewHumidityDelta3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dummy_value2" />
</LinearLayout>
</TableRow>
</TableLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/textViewWeatherOutdoor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/widget_vertical_margin"
android:layout_marginBottom="@dimen/widget_vertical_margin"
android:layout_marginStart="@dimen/widget_horizontal_margin"
android:layout_marginEnd="@dimen/widget_horizontal_margin"
android:text="@string/weather_outdoor" />
</TableLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="80dp"
android:background="?android:attr/listDivider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<!-- size 48x48 -->
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- size 16x16 -->
<ProgressBar
android:id="@+id/progressBarSmall"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginBottom="32dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@@ -10,21 +10,35 @@
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text=""
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="@string/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Switch
android:id="@+id/switchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/widget_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:text="@string/web_or_native"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView" />
<android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayoutSsidLocal"
android:layout_width="395dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_marginTop="@dimen/widget_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
app:hintAnimationEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView">
app:layout_constraintTop_toBottomOf="@+id/switchView">
<android.support.design.widget.TextInputEditText
android:id="@+id/editTextSsidLocal"
@@ -36,9 +50,11 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayoutUrlLocal"
android:layout_width="395dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="@dimen/widget_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
app:hintAnimationEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -54,9 +70,11 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayoutUrlGlobal"
android:layout_width="395dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="@dimen/widget_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
app:hintAnimationEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -1,7 +1,49 @@
<resources>
<string name="app_name">WiFi-Wetter-Station</string>
<string name="settings">Einstellungen</string>
<string name="loading">Bitte warten, die Ansicht wird geladen…</string>
<!-- Strings related to native view -->
<string name="temperature">Temperatur</string>
<string name="humidity">Feuchtigkeit</string>
<string name="weather_outdoor"><![CDATA[<b>Wetterdaten für %s</b> %s
<br/>{icon} %s (%s %%)
<br/>Temperatur: %s °C — %s° / %s° gefühlt: %s°
<br/>Feuchtigkeit: %s %%
<br/>Druck: %s hPa
<br/>Wind: %s km/h %s
<br/>Sonnenaufgang / -untergang: %s / %s]]></string>
<string name="weather_outdoor_rain"><![CDATA[<b>Wetterdaten für %s</b> %s
<br/>{icon} %s, Bewölkung: %s %%
<br/>Temperatur: %s °C — %s° / %s° gefühlt: %s°
<br/>Feuchtigkeit: %s %%
<br/>Druck: %s hPa
<br/>Wind: %s km/h %s
<br/>Sonnenaufgang / -untergang: %s / %s]]></string>
<string name="weather_outdoor_rain_and_gust"><![CDATA[<b>Wetterdaten für %s</b> %s
<br/>{icon} %s, Bewölkung: %s %%
<br/>Temperatur: %s °C — %s° / %s° gefühlt: %s°
<br/>Feuchtigkeit: %s %%
<br/>Druck: %s hPa
<br/>Wind: %s km/h %s Windböe %s km/h
<br/>Sonnenaufgang / -untergang: %s / %s]]></string>
<string name="weather_outdoor_gust"><![CDATA[<b>Wetterdaten für %s</b> %s
<br/>{icon} %s (%s %%)
<br/>Temperatur: %s °C — %s° / %s° gefühlt: %s°
<br/>Feuchtigkeit: %s %%
<br/>Druck: %s hPa
<br/>Wind: %s km/h %s Windböe %s km/h
<br/>Sonnenaufgang / -untergang: %s / %s]]></string>
<string name="weather_outdoor_rainfall"><![CDATA[<b>Wetterdaten für %s</b> %s
<br/>{icon} %s, Niederschlagsmenge: %s mm, Bewölkung: %s %%
<br/>Temperatur: %s °C — %s° / %s° gefühlt: %s°
<br/>Feuchtigkeit: %s %%
<br/>Druck: %s hPa
<br/>Wind: %s km/h %s
<br/>Sonnenaufgang / -untergang: %s / %s]]></string>
<!-- Strings related to settings -->
<string name="settings">Einstellungen</string>
<string name="message_connection_internal">Verbindung: intern</string>
<string name="message_connection_external">Verbindung: extern</string>
<string name="web_or_native">Web oder native Ansicht</string>
<string name="hint_url_local">Lokale URL</string>
<string name="hint_url_global">Globale URL</string>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="widget_horizontal_margin">8dp</dimen>
<dimen name="widget_vertical_margin">8dp</dimen>
<dimen name="value_main_size">40sp</dimen>
</resources>

View File

@@ -1,11 +1,75 @@
<resources>
<string name="app_name">WiFi Weather Station</string>
<string name="settings">Settings</string>
<string name="loading">Please wait, the view is loading…</string>
<!-- Strings related to native view -->
<string name="temperature">Temperature</string>
<string name="temperature_unit" translatable="false">°C</string>
<string name="humidity">Humidity</string>
<string name="humidity_unit" translatable="false">%</string>
<string name="dummy_value" translatable="false">__._</string>
<string name="dummy_value2" translatable="false">o</string>
<!-- name, dt; description, clouds; temp, tempMin, tempMax, apparent; humidity; pressure; speed, deg; sunrise, sunset -->
<string name="weather_outdoor"><![CDATA[<b>Weather data for %s</b> %s
<br/>{icon} %s (%s %%)
<br/>Temperature: %s °C — %s° / %s° feels: %s°
<br/>Humidity: %s %%
<br/>Pressure: %s hPa
<br/>Wind: %s km/h %s
<br/>Sunrise / sunset: %s / %s]]></string>
<!-- print clouds before clouds value if the description is about raining, so no misunderstanding applies -->
<!-- name, dt; description, clouds; temp, tempMin, tempMax, apparent; humidity; pressure; speed, deg; sunrise, sunset -->
<string name="weather_outdoor_rain"><![CDATA[<b>Weather data for %s</b> %s
<br/>{icon} %s, clouds: %s %%
<br/>Temperature: %s °C — %s° / %s° feels: %s°
<br/>Humidity: %s %%
<br/>Pressure: %s hPa
<br/>Wind: %s km/h %s
<br/>Sunrise / sunset: %s / %s]]></string>
<string name="weather_outdoor_rain_and_gust"><![CDATA[<b>Weather data for %s</b> %s
<br/>{icon} %s, clouds: %s %%
<br/>Temperature: %s °C — %s° / %s° feels: %s°
<br/>Humidity: %s %%
<br/>Pressure: %s hPa
<br/>Wind: %s km/h %s gust: %s km/h
<br/>Sunrise / sunset: %s / %s]]></string>
<string name="weather_outdoor_gust"><![CDATA[<b>Weather data for %s</b> %s
<br/>{icon} %s (%s %%)
<br/>Temperature: %s °C — %s° / %s° feels: %s°
<br/>Humidity: %s %%
<br/>Pressure: %s hPa
<br/>Wind: %s km/h %s gust: %s km/h
<br/>Sunrise / sunset: %s / %s]]></string>
<!-- name, dt; description, rain, clouds; temp, tempMin, tempMax, apparent; humidity; pressure; speed, deg; sunrise, sunset -->
<string name="weather_outdoor_rainfall"><![CDATA[<b>Weather data for %s</b> %s
<br/>{icon} %s, rainfall: %s mm, clouds: %s %%
<br/>Temperature: %s °C — %s° / %s° feels: %s°
<br/>Humidity: %s %%
<br/>Pressure: %s hPa
<br/>Wind: %s km/h %s
<br/>Sunrise / sunset: %s / %s]]></string>
<!-- Strings related to settings -->
<string name="settings">Settings</string>
<string name="message" translatable="false">de.weseng.wifiweatherstation.MESSAGE</string>
<string name="message_connection_internal">connection: internal</string>
<string name="message_connection_external">connection: external</string>
<string name="web_or_native">Web or native view</string>
<string name="ssid" translatable="false">NETGEAR26-5G-2</string>
<string name="url_local" translatable="false">http://192.168.1.5/site/wifi-weather-station/</string>
<string name="url_global" translatable="false">http://inetsrv.no-ip.org/site/wifi-weather-station/</string>
<string name="hint_ssid" translatable="false">SSID</string>
<string name="url_local" translatable="false">http://192.168.1.5/site/wifi-weather-station/</string>
<string name="hint_url_local">Local URL</string>
<string name="url_global" translatable="false">http://inetsrv.no-ip.org/site/wifi-weather-station/</string>
<string name="hint_url_global">Global URL</string>
<!-- Strings related to login -->
<string name="title_activity_login">Sign in</string>
<string name="prompt_email">Email</string>
<string name="prompt_password">Password (optional)</string>
<string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
<string name="error_invalid_email">This email address is invalid</string>
<string name="error_invalid_password">This password is too short</string>
<string name="error_incorrect_password">This password is incorrect</string>
<string name="error_field_required">This field is required</string>
<string name="permission_rationale">"Contacts permissions are needed for providing email
completions."
</string>
</resources>

View File

@@ -0,0 +1,78 @@
package de.weseng.wifiweatherstation;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class MainNativeActivityTest {
@Test
public void valueToColorRGB_below() {
assertEquals(0x719dc3, new MainNativeActivity().valueToColorRGB(
10, 17, 19, 23, 25,
0x719dc3, 0xff00ff00, 0xffd1655d));
}
@Test
public void valueToColorRGB_ideal() {
assertEquals(0xff00ff00, new MainNativeActivity().valueToColorRGB(
20, 17, 19, 23, 25,
0x719DC3, 0xff00ff00, 0xffd1655d));
}
@Test
public void valueToColorRGB_above() {
assertEquals(0xffd1655d, new MainNativeActivity().valueToColorRGB(
30, 17, 19, 23, 25,
0x719DC3, 0xff00ff00, 0xffd1655d));
}
@Test
public void interpolate_0_255_0() {
assertEquals(0, new MainNativeActivity().interpolate(
0, 255, 0, 100));
}
@Test
public void interpolate_0_255_50() {
assertEquals(127, new MainNativeActivity().interpolate(
0, 255, 50, 100));
}
@Test
public void interpolate_0_255_100() {
assertEquals(255, new MainNativeActivity().interpolate(
0, 255, 100, 100));
}
@Test
public void interpolateColor_00ff00_ff0000_0() {
MainNativeActivity a = new MainNativeActivity();
assertEquals(0xFFFFFF & 0xff00ff00,
a.interpolateColor(0xff00ff00, 0xffff0000, 0, 100));
}
@Test
public void interpolateColor_00ff00_ff0000_50() {
MainNativeActivity a = new MainNativeActivity();
assertEquals(0xFFFFFF & 0xff7f7f00,
a.interpolateColor(0xff00ff00, 0xffff0000, 50, 100));
}
@Test
public void interpolateColor_00ff00_ff0000_100() {
MainNativeActivity a = new MainNativeActivity();
assertEquals(0xFFFFFF & 0xffff0000,
a.interpolateColor(0xff00ff00, 0xffff0000, 100, 100));
}
@Test
public void valueToColorRGB_between_ideal_and_above() {
MainNativeActivity a = new MainNativeActivity();
assertEquals(a.interpolateColor(0xff00ff00, 0xffd1655d, 50, 100),
a.valueToColorRGB(
24, 17, 19, 23, 25,
0x719DC3, 0xff00ff00, 0xffd1655d));
}
}

View File

@@ -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<String, Object> 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<String, Object> params = new LinkedHashMap<>();
params.put("a", "1");
params.put("b", "2");
Map<String, Object> 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<String, Object> params = new LinkedHashMap<>();
params.put("a", "1");
params.put("b", "2");
Map<String, Object> cParamsLevel1 = new LinkedHashMap<>();
cParamsLevel1.put("cL1-1", "cLevel1-1val");
cParamsLevel1.put("cL1-2", "cLevel1-2val");
Map<String, Object> 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<String, Object> params = new LinkedHashMap<>();
params.put("a", "1");
params.put("b", "2");
List<Object> 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<String, Object> params = new LinkedHashMap<>();
params.put("a", "1");
params.put("b", "2");
List<Object> cParamsLevel1 = new ArrayList<>();
cParamsLevel1.add("cL1-val1");
cParamsLevel1.add("cL12-val2");
List<Object> 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<String, Object> params = new LinkedHashMap<>();
//flat
params.put("a", "1");
params.put("b", "2");
//Map level 1
Map<String, Object> cParamsLevel1 = new LinkedHashMap<>();
cParamsLevel1.put("cL1-1", "cLevel1-1val");
cParamsLevel1.put("cL1-2", "cLevel1-2val");
//Map level 2
Map<String, Object> 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<Object> dParamsLevel1 = new ArrayList<>();
dParamsLevel1.add("dL1-val1");
dParamsLevel1.add("dL12-val2");
//List level 2
List<Object> 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"));
}
}

View File

@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.android.tools.build:gradle:3.3.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files