diff --git a/android/app/build.gradle b/android/app/build.gradle
index 697c783..5de6c95 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -43,15 +43,17 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'com.google.android.material:material:1.0.0'
- implementation 'com.google.firebase:firebase-core:16.0.4'
+ implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.google.firebase:firebase-messaging:17.3.4'
- implementation 'com.google.android.gms:play-services-ads:17.1.0'
+ implementation 'com.google.android.gms:play-services-ads:17.1.2'
implementation 'com.android.billingclient:billing:1.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
implementation "com.github.DeweyReed:UltimateMusicPicker:2.0.0"
implementation 'com.github.duanhong169:colorpicker:1.1.5'
+
+ implementation 'net.danlew:android.joda:2.9.9.2'
}
apply plugin: 'com.google.gms.google-services'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 46aa758..e75e26e 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,43 +1,59 @@
-
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java
index f327e08..a6548fe 100644
--- a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.widget.Toast;
import com.android.billingclient.api.BillingClient;
+import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
@@ -98,3 +99,9 @@ public class SCNApp extends Application implements LifecycleObserver
isBackground = false;
}
}
+
+//TODO: Collapse on click again
+//TODO: Share button on expand
+//TODO: Delete button on expand
+//TODO: Config for collapsed line count
+//TODO: Sometimes ads but promode
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/LogLevel.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/LogLevel.java
new file mode 100644
index 0000000..a2b7d00
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/LogLevel.java
@@ -0,0 +1,58 @@
+package com.blackforestbytes.simplecloudnotifier.model;
+
+
+import android.graphics.Color;
+
+public enum LogLevel
+{
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR;
+
+ public String toUIString()
+ {
+ switch (this)
+ {
+ case DEBUG: return "Debug";
+ case INFO: return "Info";
+ case WARN: return "Warning";
+ case ERROR: return "Error";
+ default: return "???";
+ }
+ }
+
+ public int getColor()
+ {
+ switch (this)
+ {
+ case DEBUG: return Color.GRAY;
+ case WARN: return Color.rgb(171, 145, 68);
+ case INFO: return Color.BLACK;
+ case ERROR: return Color.RED;
+ default: return Color.MAGENTA;
+ }
+ }
+
+ public int asInt()
+ {
+ switch (this)
+ {
+ case DEBUG: return 0;
+ case WARN: return 1;
+ case INFO: return 2;
+ case ERROR: return 3;
+ default: return 999;
+ }
+ }
+
+ public static LogLevel fromInt(int i)
+ {
+ if (i == 0) return LogLevel.DEBUG;
+ if (i == 1) return LogLevel.WARN;
+ if (i == 2) return LogLevel.INFO;
+ if (i == 3) return LogLevel.ERROR;
+
+ return LogLevel.ERROR; // ????
+ }
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/QueryLog.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/QueryLog.java
new file mode 100644
index 0000000..7394f02
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/QueryLog.java
@@ -0,0 +1,69 @@
+package com.blackforestbytes.simplecloudnotifier.model;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.blackforestbytes.simplecloudnotifier.SCNApp;
+import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QueryLog
+{
+ private final static int MAX_HISTORY_SIZE = 64;
+
+ private static QueryLog _instance;
+ public static QueryLog instance() { if (_instance == null) synchronized (QueryLog.class) { if (_instance == null) _instance = new QueryLog(); } return _instance; }
+
+ private QueryLog(){ load(); }
+
+ private final List history = new ArrayList<>();
+
+ public synchronized void add(SingleQuery r)
+ {
+ history.add(r);
+ while (history.size() > MAX_HISTORY_SIZE) history.remove(0);
+
+ save();
+ }
+
+ public synchronized List get()
+ {
+ List r = new ArrayList<>(history);
+ CollectionHelper.sort_inplace(r, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
+ return r;
+ }
+
+ public synchronized void save()
+ {
+ SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
+ SharedPreferences.Editor e = sharedPref.edit();
+
+ e.clear();
+
+ e.putInt("history_count", history.size());
+
+ for (int i = 0; i < history.size(); i++) history.get(i).save(e, "message["+(i+1000)+"]");
+
+ e.apply();
+ }
+
+ public synchronized void load()
+ {
+ try
+ {
+ Context c = SCNApp.getContext();
+ SharedPreferences sharedPref = c.getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
+ int count = sharedPref.getInt("history_count", 0);
+ for (int i=0; i < count; i++) history.add(SingleQuery.load(sharedPref, "message["+(i+1000)+"]"));
+
+ CollectionHelper.sort_inplace(history, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
+ }
+ catch (Exception e)
+ {
+ Log.e("SC:QL:Load", e.toString());
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/ServerCommunication.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/ServerCommunication.java
index 6779fdb..f11119a 100644
--- a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/ServerCommunication.java
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/ServerCommunication.java
@@ -4,12 +4,11 @@ import android.util.Log;
import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
-import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple5;
-import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to0;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func5to0;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.FBMService;
+import org.joda.time.Instant;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -48,20 +47,20 @@ public class ServerCommunication
@Override
public void onFailure(Call call, IOException e)
{
- Log.e("SC:register", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("register", call, null, Str.Empty, true, e);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
}
@Override
public void onResponse(Call call, Response response)
{
+ String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -81,11 +80,12 @@ public class ServerCommunication
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
+
+ handleSuccess("register", call, response, r);
}
catch (Exception e)
{
- Log.e("SC:register", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("register", call, response, r, false, e);
}
finally
{
@@ -96,8 +96,7 @@ public class ServerCommunication
}
catch (Exception e)
{
- Log.e("SC:register", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("register", null, null, Str.Empty, false, e);
}
}
@@ -114,20 +113,20 @@ public class ServerCommunication
@Override
public void onFailure(Call call, IOException e)
{
- Log.e("SC:update_1", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
}
@Override
public void onResponse(Call call, Response response)
{
+ String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -147,10 +146,12 @@ public class ServerCommunication
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
+
+ handleSuccess("update<1>", call, response, r);
}
catch (Exception e)
{
- Log.e("SC:update_1", e.toString());
+ handleError("update<1>", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
}
finally
@@ -162,8 +163,7 @@ public class ServerCommunication
}
catch (Exception e)
{
- Log.e("SC:update_1", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("update<1>", null, null, Str.Empty, false, e);
}
}
@@ -177,19 +177,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
- public void onFailure(Call call, IOException e) {
- Log.e("SC:update_2", e.toString());
+ public void onFailure(Call call, IOException e)
+ {
+ handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
}
@Override
- public void onResponse(Call call, Response response) {
- try (ResponseBody responseBody = response.body()) {
+ public void onResponse(Call call, Response response)
+ {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -207,10 +211,16 @@ public class ServerCommunication
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
- } catch (Exception e) {
- Log.e("SC:update_2", e.toString());
+
+ handleSuccess("update<2>", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("update<2>", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
- } finally {
+ }
+ finally
+ {
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
@@ -220,8 +230,7 @@ public class ServerCommunication
}
catch (Exception e)
{
- Log.e("SC:update_2", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("update<2>", null, null, Str.Empty, false, e);
}
}
@@ -236,21 +245,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
- Log.e("SC:info", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("info", call, null, Str.Empty, true, e);
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
}
@Override
- public void onResponse(Call call, Response response) {
- try (ResponseBody responseBody = response.body()) {
+ public void onResponse(Call call, Response response)
+ {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -290,21 +301,23 @@ public class ServerCommunication
if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
- } catch (Exception e) {
- Log.e("SC:info", e.toString());
+ handleSuccess("info", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("info", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
- } finally {
- SCNApp.runOnUiThread(() -> {
- if (loader != null) loader.setVisibility(View.GONE);
- });
+ }
+ finally
+ {
+ SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
}
});
}
catch (Exception e)
{
- Log.e("SC:info", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("info", null, null, Str.Empty, false, e);
}
}
@@ -319,21 +332,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
- Log.e("SC:requery", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("requery", call, null, Str.Empty, true, e);
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
}
@Override
- public void onResponse(Call call, Response response) {
- try (ResponseBody responseBody = response.body()) {
+ public void onResponse(Call call, Response response)
+ {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -359,10 +374,15 @@ public class ServerCommunication
FBMService.recieveData(time, title, content, prio, scn_id, true);
}
- } catch (Exception e) {
- Log.e("SC:info", e.toString());
+ handleSuccess("requery", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("requery", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
- } finally {
+ }
+ finally
+ {
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
@@ -372,8 +392,7 @@ public class ServerCommunication
}
catch (Exception e)
{
- Log.e("SC:requery", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("requery", null, null, Str.Empty, false, e);
}
}
@@ -387,21 +406,25 @@ public class ServerCommunication
.url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
.build();
- client.newCall(request).enqueue(new Callback() {
+ client.newCall(request).enqueue(new Callback()
+ {
@Override
- public void onFailure(Call call, IOException e) {
- Log.e("SC:upgrade", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ public void onFailure(Call call, IOException e)
+ {
+ handleError("upgrade", call, null, Str.Empty, true, e);
}
@Override
- public void onResponse(Call call, Response response) {
- try (ResponseBody responseBody = response.body()) {
+ public void onResponse(Call call, Response response)
+ {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -418,10 +441,15 @@ public class ServerCommunication
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
- } catch (Exception e) {
- Log.e("SC:upgrade", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
- } finally {
+
+ handleSuccess("upgrade", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("upgrade", call, response, r, false, e);
+ }
+ finally
+ {
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
}
@@ -429,8 +457,7 @@ public class ServerCommunication
}
catch (Exception e)
{
- e.printStackTrace();
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("upgrade", null, null, Str.Empty, false, e);
}
}
@@ -442,37 +469,43 @@ public class ServerCommunication
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
.build();
- client.newCall(request).enqueue(new Callback() {
+ client.newCall(request).enqueue(new Callback()
+ {
@Override
- public void onFailure(Call call, IOException e) {
- Log.e("SC:ack", e.toString());
+ public void onFailure(Call call, IOException e)
+ {
+ handleError("ack", call, null, Str.Empty, true, e);
}
@Override
public void onResponse(Call call, Response response)
{
- try (ResponseBody responseBody = response.body()) {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success")) SCNApp.showToast(json_str(json, "message"), 4000);
- } catch (Exception e) {
- Log.e("SC:ack", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleSuccess("ack", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("ack", call, response, r, false, e);
}
}
});
}
catch (Exception e)
{
- Log.e("SC:ack", e.toString());
+ handleError("ack", null, null, Str.Empty, false, e);
}
}
@@ -487,21 +520,21 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
- Log.e("SC:expand", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
- SCNApp.runOnUiThread(() -> {
- if (loader != null) loader.setVisibility(View.GONE);
- });
+ handleError("expand", call, null, Str.Empty, true, e);
+ SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
@Override
- public void onResponse(Call call, Response response) {
- try (ResponseBody responseBody = response.body()) {
+ public void onResponse(Call call, Response response)
+ {
+ String r = Str.Empty;
+ try (ResponseBody responseBody = response.body())
+ {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
- String r = responseBody.string();
+ r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -522,21 +555,22 @@ public class ServerCommunication
okResult.invoke(title, content, prio, time, scn_id);
- } catch (Exception e) {
- Log.e("SC:expand", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
- } finally {
- SCNApp.runOnUiThread(() -> {
- if (loader != null) loader.setVisibility(View.GONE);
- });
+ handleSuccess("expand", call, response, r);
+ }
+ catch (Exception e)
+ {
+ handleError("expand", call, response, r, false, e);
+ }
+ finally
+ {
+ SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
}
});
}
catch (Exception e)
{
- Log.e("SC:expand", e.toString());
- SCNApp.showToast("Communication with server failed", 4000);
+ handleError("expand", null, null, Str.Empty, false, e);
}
}
@@ -564,4 +598,57 @@ public class ServerCommunication
{
return o.getString(key);
}
+
+ private static void handleSuccess(String source, Call call, Response resp, String respBody)
+ {
+ Log.d("SC:"+source, respBody);
+
+ try
+ {
+ Instant i = Instant.now();
+ String s = source;
+ String u = call.request().url().toString();
+ int rc = resp.code();
+ String r = respBody;
+ LogLevel l = LogLevel.INFO;
+
+ SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "SUCCESS");
+ QueryLog.instance().add(q);
+ }
+ catch (Exception e2)
+ {
+ Log.e("SC:HandleSuccess", e2.toString());
+ }
+ }
+
+ private static void handleError(String source, Call call, Response resp, String respBody, boolean isio, Exception e)
+ {
+ Log.e("SC:"+source, e.toString());
+
+ if (isio)
+ {
+ SCNApp.showToast("Can't connect to server", 3000);
+ }
+ else
+ {
+ SCNApp.showToast("Communication with server failed", 4000);
+ }
+
+ try
+ {
+ Instant i = Instant.now();
+ String s = source;
+ String u = (call==null)?Str.Empty:call.request().url().toString();
+ int rc = (resp==null)?-1:resp.code();
+ String r = respBody;
+ LogLevel l = isio?LogLevel.WARN:LogLevel.ERROR;
+
+ SingleQuery q = new SingleQuery(l, i, s, u, r, rc, e.toString());
+ QueryLog.instance().add(q);
+ }
+ catch (Exception e2)
+ {
+ Log.e("SC:HandleError", e2.toString());
+ }
+ }
}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/SingleQuery.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/SingleQuery.java
new file mode 100644
index 0000000..3561cf4
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/model/SingleQuery.java
@@ -0,0 +1,82 @@
+package com.blackforestbytes.simplecloudnotifier.model;
+
+import android.content.SharedPreferences;
+import android.os.BaseBundle;
+import android.os.Bundle;
+
+import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
+
+import org.joda.time.Instant;
+
+public class SingleQuery
+{
+ public final Instant Timestamp;
+
+ public final LogLevel Level;
+ public final String Name;
+ public final String URL;
+ public final String Response;
+ public final int ResponseCode;
+ public final String ExceptionString;
+
+ public SingleQuery(LogLevel l, Instant i, String n, String u, String r, int rc, String e)
+ {
+ Level=l;
+ Timestamp=i;
+ Name=n;
+ URL=u;
+ Response=r;
+ ResponseCode=rc;
+ ExceptionString=e;
+ }
+
+ public void save(SharedPreferences.Editor e, String base)
+ {
+ e.putInt(base+".Level", Level.asInt());
+ e.putLong(base+".Timestamp", Timestamp.getMillis());
+ e.putString(base+".Name", Name);
+ e.putString(base+".URL", URL);
+ e.putString(base+".Response", Response);
+ e.putInt(base+".ResponseCode", ResponseCode);
+ e.putString(base+".ExceptionString", ExceptionString);
+ }
+
+ public void save(BaseBundle e, String base)
+ {
+ e.putInt(base+".Level", Level.asInt());
+ e.putLong(base+".Timestamp", Timestamp.getMillis());
+ e.putString(base+".Name", Name);
+ e.putString(base+".URL", URL);
+ e.putString(base+".Response", Response);
+ e.putInt(base+".ResponseCode", ResponseCode);
+ e.putString(base+".ExceptionString", ExceptionString);
+ }
+
+ public static SingleQuery load(SharedPreferences e, String base)
+ {
+ return new SingleQuery
+ (
+ LogLevel.fromInt(e.getInt(base+".Level", 0)),
+ new Instant(e.getLong(base+".Timestamp", 0)),
+ e.getString(base+".Name", Str.Empty),
+ e.getString(base+".URL", Str.Empty),
+ e.getString(base+".Response", Str.Empty),
+ e.getInt(base+".ResponseCode", -1),
+ e.getString(base+".ExceptionString", Str.Empty)
+ );
+ }
+
+ public static SingleQuery load(BaseBundle e, String base)
+ {
+ return new SingleQuery
+ (
+ LogLevel.fromInt(e.getInt(base+".Level", 0)),
+ new Instant(e.getLong(base+".Timestamp", 0)),
+ e.getString(base+".Name", Str.Empty),
+ e.getString(base+".URL", Str.Empty),
+ e.getString(base+".Response", Str.Empty),
+ e.getInt(base+".ResponseCode", -1),
+ e.getString(base+".ExceptionString", Str.Empty)
+ );
+ }
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/util/MaxHeightScrollView.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/util/MaxHeightScrollView.java
new file mode 100644
index 0000000..e217478
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/util/MaxHeightScrollView.java
@@ -0,0 +1,56 @@
+package com.blackforestbytes.simplecloudnotifier.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ScrollView;
+
+import com.blackforestbytes.simplecloudnotifier.R;
+
+public class MaxHeightScrollView extends ScrollView
+{
+ public int maxHeight = Integer.MAX_VALUE;//dp
+
+ public MaxHeightScrollView(Context context)
+ {
+ super(context);
+ }
+
+ public MaxHeightScrollView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
+ try {
+ maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr)
+ {
+ super(context, attrs, defStyleAttr);
+
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
+ try {
+ maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+ {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(getResources(), maxHeight), MeasureSpec.AT_MOST);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ private int dpToPx(Resources res, int dp)
+ {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
+ }
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/MainActivity.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/MainActivity.java
index 01f61a3..5314cd7 100644
--- a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/MainActivity.java
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/MainActivity.java
@@ -1,14 +1,21 @@
package com.blackforestbytes.simplecloudnotifier.view;
+import android.content.Intent;
+import android.icu.text.SymbolTable;
import android.os.Bundle;
+import android.view.View;
import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
+import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
+import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
import com.google.android.material.tabs.TabLayout;
import androidx.appcompat.app.AppCompatActivity;
@@ -24,6 +31,8 @@ public class MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
{
+ QueryLog.instance();
+
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@@ -33,6 +42,7 @@ public class MainActivity extends AppCompatActivity
layoutRoot = findViewById(R.id.layoutRoot);
Toolbar toolbar = findViewById(R.id.toolbar);
+ toolbar.setOnClickListener(this::onToolbackClicked);
setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.pager);
@@ -81,4 +91,16 @@ public class MainActivity extends AppCompatActivity
CMessageList.inst().fullSave();
IABService.inst().destroy();
}
+
+ private int clickCount = 0;
+ private long lastClick = 0;
+ private void onToolbackClicked(View v)
+ {
+ long now = System.currentTimeMillis();
+ if (now - lastClick > 200) clickCount=0;
+ clickCount++;
+ lastClick = now;
+
+ if (clickCount == 4) startActivity(new Intent(this, QueryLogActivity.class));
+ }
}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogActivity.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogActivity.java
new file mode 100644
index 0000000..782e112
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogActivity.java
@@ -0,0 +1,44 @@
+package com.blackforestbytes.simplecloudnotifier.view.debug;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
+import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.widget.ListView;
+
+import com.blackforestbytes.simplecloudnotifier.R;
+
+public class QueryLogActivity extends AppCompatActivity
+{
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_querylog);
+
+ ListView lvMain = findViewById(R.id.lvQueryList);
+ SingleQuery[] arr = QueryLog.instance().get().toArray(new SingleQuery[0]);
+ QueryLogAdapter a = new QueryLogAdapter(this, arr);
+ lvMain.setAdapter(a);
+
+ lvMain.setOnItemClickListener((parent, view, position, id) ->
+ {
+ if (position >= 0 && position < arr.length)
+ {
+ Intent i = new Intent(QueryLogActivity.this, SingleQueryLogActivity.class);
+ Bundle b = new Bundle();
+ arr[position].save(b, "data");
+ i.putExtra("query", b);
+ startActivity(i);
+ }
+ });
+
+
+ }
+
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogAdapter.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogAdapter.java
new file mode 100644
index 0000000..9459d70
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/QueryLogAdapter.java
@@ -0,0 +1,66 @@
+package com.blackforestbytes.simplecloudnotifier.view.debug;
+
+import android.content.Context;
+import android.graphics.Color;
+import androidx.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.blackforestbytes.simplecloudnotifier.R;
+import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
+
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+public class QueryLogAdapter extends ArrayAdapter
+{
+ public static DateTimeFormatter UI_FULLTIME_FORMATTER = DateTimeFormat.forPattern("HH:mm:ss");
+
+ public QueryLogAdapter(@NonNull Context context, @NonNull SingleQuery[] objects)
+ {
+ super(context, R.layout.adapter_querylog, objects);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, @NonNull ViewGroup parent)
+ {
+ View v = convertView;
+
+ if (v == null) {
+ LayoutInflater vi;
+ vi = LayoutInflater.from(getContext());
+ v = vi.inflate(R.layout.adapter_querylog, parent, false);
+ }
+
+ SingleQuery p = getItem(position);
+
+ if (p != null)
+ {
+ TextView tt1 = v.findViewById(R.id.list_item_debuglogrow_time);
+ if (tt1 != null) tt1.setText(p.Timestamp.toString(UI_FULLTIME_FORMATTER));
+ if (tt1 != null) tt1.setTextColor(Color.BLACK);
+
+ TextView tt2 = v.findViewById(R.id.list_item_debuglogrow_level);
+ if (tt2 != null) tt2.setText(p.Level.toUIString());
+ if (tt2 != null) tt2.setTextColor(Color.BLACK);
+
+ TextView tt3 = v.findViewById(R.id.list_item_debuglogrow_info);
+ if (tt3 != null) tt3.setText("");
+ if (tt3 != null) tt3.setTextColor(Color.BLUE);
+
+ TextView tt4 = v.findViewById(R.id.list_item_debuglogrow_id);
+ if (tt4 != null) tt4.setText(p.Name);
+ if (tt4 != null) tt4.setTextColor(p.Level.getColor());
+
+ TextView tt5 = v.findViewById(R.id.list_item_debuglogrow_message);
+ if (tt5 != null) tt5.setText(p.ExceptionString.length()> 40 ? p.ExceptionString.substring(0, 40-3)+"..." : p.ExceptionString);
+ if (tt5 != null) tt5.setTextColor(p.Level.getColor());
+ }
+
+ return v;
+ }
+}
diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/SingleQueryLogActivity.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/SingleQueryLogActivity.java
new file mode 100644
index 0000000..8e77467
--- /dev/null
+++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/view/debug/SingleQueryLogActivity.java
@@ -0,0 +1,38 @@
+package com.blackforestbytes.simplecloudnotifier.view.debug;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.blackforestbytes.simplecloudnotifier.R;
+import com.blackforestbytes.simplecloudnotifier.lib.string.CompactJsonFormatter;
+import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
+
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.util.Objects;
+
+public class SingleQueryLogActivity extends AppCompatActivity
+{
+ @Override
+ @SuppressLint("SetTextI18n")
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_singlequerylog);
+
+ SingleQuery q = SingleQuery.load(getIntent().getBundleExtra("query"), "data");
+
+ this.findViewById(R.id.tvQL_Timestamp).setText(q.Timestamp.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")));
+ this.findViewById(R.id.tvQL_Level).setText(q.Level.toUIString());
+ this.findViewById(R.id.tvQL_Level).setTextColor(q.Level.getColor());
+ this.findViewById(R.id.tvQL_Name).setText(q.Name);
+ this.findViewById(R.id.tvQL_URL).setText(q.URL.replace("?", "\r\n?").replace("&", "\r\n&"));
+ this.findViewById(R.id.tvQL_Response).setText(CompactJsonFormatter.formatJSON(q.Response, 999));
+ this.findViewById(R.id.tvQL_ResponseCode).setText(Integer.toString(q.ResponseCode));
+ this.findViewById(R.id.tvQL_ExceptionString).setText(q.ExceptionString);
+ }
+}
diff --git a/android/app/src/main/res/drawable-mdpi/simple_black_border.xml b/android/app/src/main/res/drawable-mdpi/simple_black_border.xml
new file mode 100644
index 0000000..78ad158
--- /dev/null
+++ b/android/app/src/main/res/drawable-mdpi/simple_black_border.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
index 40d5509..71dc8ea 100644
--- a/android/app/src/main/res/layout/activity_main.xml
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -2,10 +2,8 @@
+ android:layout_height="match_parent">
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/activity_singlequerylog.xml b/android/app/src/main/res/layout/activity_singlequerylog.xml
new file mode 100644
index 0000000..2216e5c
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_singlequerylog.xml
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/adapter_querylog.xml b/android/app/src/main/res/layout/adapter_querylog.xml
new file mode 100644
index 0000000..fb951d7
--- /dev/null
+++ b/android/app/src/main/res/layout/adapter_querylog.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..d1c31cd
--- /dev/null
+++ b/android/app/src/main/res/values/attrs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index 939fe4d..40f1022 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -6,4 +6,5 @@
10dp
30dp
90dp
+ 16dp
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 8a1c359..f250e22 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -35,4 +35,5 @@
Volume icon
Play test sound
DELETE
+ QueryLogActivity
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 2bcf132..96e5894 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -10,4 +10,8 @@
- #FF4081
+
+
+
+