Show Querylog via hidden shit

This commit is contained in:
Mike Schwörer 2018-12-11 12:25:10 +01:00
parent 4cde4703f2
commit e525221010
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
21 changed files with 970 additions and 103 deletions

View File

@ -43,15 +43,17 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'com.google.android.material:material:1.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.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.android.billingclient:billing:1.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.github.kenglxn.QRGen:android:2.5.0' implementation 'com.github.kenglxn.QRGen:android:2.5.0'
implementation "com.github.DeweyReed:UltimateMusicPicker:2.0.0" implementation "com.github.DeweyReed:UltimateMusicPicker:2.0.0"
implementation 'com.github.duanhong169:colorpicker:1.1.5' implementation 'com.github.duanhong169:colorpicker:1.1.5'
implementation 'net.danlew:android.joda:2.9.9.2'
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -1,43 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.blackforestbytes.simplecloudnotifier"> package="com.blackforestbytes.simplecloudnotifier">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
<application <application
android:name=".SCNApp"
android:allowBackup="false" android:allowBackup="false"
android:name="SCNApp"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".view.MainActivity"> <activity android:name=".view.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/icon" /> <meta-data
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" /> android:name="com.google.firebase.messaging.default_notification_icon"
<meta-data android:name="com.google.android.gms.ads.AD_MANAGER_APP" android:value="true"/> android:resource="@drawable/icon" />
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3320562328966175~7579972005"/> <meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
<meta-data
android:name="com.google.android.gms.ads.AD_MANAGER_APP"
android:value="true" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3320562328966175~7579972005" />
<service android:name=".service.FBMService" android:exported="false"> <service
android:name=".service.FBMService"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".service.BroadcastReceiverService" android:exported="false" /> <receiver
android:name=".service.BroadcastReceiverService"
android:exported="false" />
<activity
android:name=".view.debug.QueryLogActivity"
android:label="@string/title_activity_query_log"
android:theme="@style/AppTheme" />
<activity android:name=".view.debug.SingleQueryLogActivity"></activity>
</application> </application>
</manifest> </manifest>

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.widget.Toast; import android.widget.Toast;
import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment; import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity; import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter; import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
@ -98,3 +99,9 @@ public class SCNApp extends Application implements LifecycleObserver
isBackground = false; 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

View File

@ -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; // ????
}
}

View File

@ -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<SingleQuery> 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<SingleQuery> get()
{
List<SingleQuery> 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());
}
}
}

View File

@ -4,12 +4,11 @@ import android.util.Log;
import android.view.View; import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp; 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.lambda.Func5to0;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.FBMService; import com.blackforestbytes.simplecloudnotifier.service.FBMService;
import org.joda.time.Instant;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -48,20 +47,20 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
Log.e("SC:register", e.toString()); handleError("register", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@Override @Override
public void onResponse(Call call, Response response) public void onResponse(Call call, Response response)
{ {
String r = Str.Empty;
try (ResponseBody responseBody = response.body()) try (ResponseBody responseBody = response.body())
{ {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -81,11 +80,12 @@ public class ServerCommunication
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
handleSuccess("register", call, response, r);
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:register", e.toString()); handleError("register", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
{ {
@ -96,8 +96,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:register", e.toString()); handleError("register", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -114,20 +113,20 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
Log.e("SC:update_1", e.toString()); handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@Override @Override
public void onResponse(Call call, Response response) public void onResponse(Call call, Response response)
{ {
String r = Str.Empty;
try (ResponseBody responseBody = response.body()) try (ResponseBody responseBody = response.body())
{ {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -147,10 +146,12 @@ public class ServerCommunication
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
handleSuccess("update<1>", call, response, r);
} }
catch (Exception e) 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); SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
@ -162,8 +163,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:update_1", e.toString()); handleError("update<1>", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -177,19 +177,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e)
Log.e("SC:update_2", e.toString()); {
handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -207,10 +211,16 @@ public class ServerCommunication
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); 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); SCNApp.showToast("Communication with server failed", 4000);
} finally { }
finally
{
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
@ -220,8 +230,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:update_2", e.toString()); handleError("update<2>", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -236,21 +245,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
Log.e("SC:info", e.toString()); handleError("info", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); 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); if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
} catch (Exception e) { handleSuccess("info", call, response, r);
Log.e("SC:info", e.toString()); }
catch (Exception e)
{
handleError("info", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} finally { }
SCNApp.runOnUiThread(() -> { finally
if (loader != null) loader.setVisibility(View.GONE); {
}); SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
} }
} }
}); });
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:info", e.toString()); handleError("info", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -319,21 +332,23 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
Log.e("SC:requery", e.toString()); handleError("requery", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -359,10 +374,15 @@ public class ServerCommunication
FBMService.recieveData(time, title, content, prio, scn_id, true); FBMService.recieveData(time, title, content, prio, scn_id, true);
} }
} catch (Exception e) { handleSuccess("requery", call, response, r);
Log.e("SC:info", e.toString()); }
catch (Exception e)
{
handleError("requery", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} finally { }
finally
{
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
@ -372,8 +392,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:requery", e.toString()); handleError("requery", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -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")) .url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
.build(); .build();
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback()
{
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e)
Log.e("SC:upgrade", e.toString()); {
SCNApp.showToast("Communication with server failed", 4000); handleError("upgrade", call, null, Str.Empty, true, e);
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -418,10 +441,15 @@ public class ServerCommunication
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
} catch (Exception e) {
Log.e("SC:upgrade", e.toString()); handleSuccess("upgrade", call, response, r);
SCNApp.showToast("Communication with server failed", 4000); }
} finally { catch (Exception e)
{
handleError("upgrade", call, response, r, false, e);
}
finally
{
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
} }
} }
@ -429,8 +457,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("upgrade", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -442,37 +469,43 @@ public class ServerCommunication
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id) .url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
.build(); .build();
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback()
{
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e)
Log.e("SC:ack", e.toString()); {
handleError("ack", call, null, Str.Empty, true, e);
} }
@Override @Override
public void onResponse(Call call, Response response) public void onResponse(Call call, Response response)
{ {
try (ResponseBody responseBody = response.body()) { String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success")) SCNApp.showToast(json_str(json, "message"), 4000); if (!json_bool(json, "success")) SCNApp.showToast(json_str(json, "message"), 4000);
} catch (Exception e) { handleSuccess("ack", call, response, r);
Log.e("SC:ack", e.toString()); }
SCNApp.showToast("Communication with server failed", 4000); catch (Exception e)
{
handleError("ack", call, response, r, false, e);
} }
} }
}); });
} }
catch (Exception 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() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
Log.e("SC:expand", e.toString()); handleError("expand", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No 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); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@ -522,21 +555,22 @@ public class ServerCommunication
okResult.invoke(title, content, prio, time, scn_id); okResult.invoke(title, content, prio, time, scn_id);
} catch (Exception e) { handleSuccess("expand", call, response, r);
Log.e("SC:expand", e.toString()); }
SCNApp.showToast("Communication with server failed", 4000); catch (Exception e)
} finally { {
SCNApp.runOnUiThread(() -> { handleError("expand", call, response, r, false, e);
if (loader != null) loader.setVisibility(View.GONE); }
}); finally
{
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
} }
} }
}); });
} }
catch (Exception e) catch (Exception e)
{ {
Log.e("SC:expand", e.toString()); handleError("expand", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@ -564,4 +598,57 @@ public class ServerCommunication
{ {
return o.getString(key); 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());
}
}
} }

View File

@ -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)
);
}
}

View File

@ -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());
}
}

View File

@ -1,14 +1,21 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Intent;
import android.icu.text.SymbolTable;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.service.IABService; import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService; import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -24,6 +31,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
QueryLog.instance();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
@ -33,6 +42,7 @@ public class MainActivity extends AppCompatActivity
layoutRoot = findViewById(R.id.layoutRoot); layoutRoot = findViewById(R.id.layoutRoot);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setOnClickListener(this::onToolbackClicked);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.pager); ViewPager viewPager = findViewById(R.id.pager);
@ -81,4 +91,16 @@ public class MainActivity extends AppCompatActivity
CMessageList.inst().fullSave(); CMessageList.inst().fullSave();
IABService.inst().destroy(); 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));
}
} }

View File

@ -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);
}
});
}
}

View File

@ -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<SingleQuery>
{
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;
}
}

View File

@ -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.<TextView>findViewById(R.id.tvQL_Timestamp).setText(q.Timestamp.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")));
this.<TextView>findViewById(R.id.tvQL_Level).setText(q.Level.toUIString());
this.<TextView>findViewById(R.id.tvQL_Level).setTextColor(q.Level.getColor());
this.<TextView>findViewById(R.id.tvQL_Name).setText(q.Name);
this.<TextView>findViewById(R.id.tvQL_URL).setText(q.URL.replace("?", "\r\n?").replace("&", "\r\n&"));
this.<TextView>findViewById(R.id.tvQL_Response).setText(CompactJsonFormatter.formatJSON(q.Response, 999));
this.<TextView>findViewById(R.id.tvQL_ResponseCode).setText(Integer.toString(q.ResponseCode));
this.<TextView>findViewById(R.id.tvQL_ExceptionString).setText(q.ExceptionString);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@android:color/white" />
<stroke android:width="1dip" android:color="#888888"/>
</shape>

View File

@ -2,10 +2,8 @@
<RelativeLayout <RelativeLayout
android:id="@+id/layoutRoot" android:id="@+id/layoutRoot"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:showIn="@layout/activity_main">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/layoutRoot"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lvQueryList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View File

@ -0,0 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:padding="4sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:textAlignment="center"
android:textStyle="bold"
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="Server Query" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="Timestamp" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:background="@drawable/simple_black_border"
android:layout_margin="2dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_Timestamp"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="Level" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:background="@drawable/simple_black_border"
android:layout_margin="2dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_Level"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="Name" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:layout_margin="2dip"
android:background="@drawable/simple_black_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_Name"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="URL" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:layout_margin="2dip"
android:background="@drawable/simple_black_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_URL"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text=""/>
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="ResponeCode" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="64"
android:layout_margin="2dip"
android:background="@drawable/simple_black_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_ResponseCode"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="Response" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:layout_margin="2dip"
android:background="@drawable/simple_black_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_Response"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_margin="2dip"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:text="Exception" />
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
app:maxHeightOverride="100"
android:layout_margin="2dip"
android:background="@drawable/simple_black_border"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<TextView
android:id="@+id/tvQL_ExceptionString"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="1dip"
android:textIsSelectable="true"
android:text="" />
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_item_imagerow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<LinearLayout
android:orientation="vertical"
android:layout_width="96dp"
android:layout_height="match_parent">
<TextView
android:id="@+id/list_item_debuglogrow_time"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/list_item_debuglogrow_level"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/list_item_debuglogrow_info"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/list_item_debuglogrow_id"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/list_item_debuglogrow_message"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeightOverride" format="integer" />
</declare-styleable>
</resources>

View File

@ -6,4 +6,5 @@
<dimen name="padd_10">10dp</dimen> <dimen name="padd_10">10dp</dimen>
<dimen name="ic_delete">30dp</dimen> <dimen name="ic_delete">30dp</dimen>
<dimen name="thumbnail">90dp</dimen> <dimen name="thumbnail">90dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources> </resources>

View File

@ -35,4 +35,5 @@
<string name="volume_icon">Volume icon</string> <string name="volume_icon">Volume icon</string>
<string name="play_test_sound">Play test sound</string> <string name="play_test_sound">Play test sound</string>
<string name="delete">DELETE</string> <string name="delete">DELETE</string>
<string name="title_activity_query_log">QueryLogActivity</string>
</resources> </resources>

View File

@ -10,4 +10,8 @@
<item name="colorAccent">#FF4081</item> <item name="colorAccent">#FF4081</item>
</style> </style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources> </resources>