20 Commits

Author SHA1 Message Date
b95ddcc811 backup/export und import 2020-01-05 22:59:57 +01:00
90ba3c1134 fix api urls 2020-01-05 22:33:58 +01:00
05174958b2 Added php example 2019-06-30 21:46:28 +02:00
jenkins
24be9b2013 [Jenkins] Increment version 2018-12-14 22:09:01 +01:00
29ce4b727c More fixes for paid mode 2018-12-14 22:07:43 +01:00
jenkins
f178019ffe [Jenkins] Increment version 2018-12-14 19:32:22 +01:00
0a1b948042 Stupid bug -.- 2018-12-14 19:23:36 +01:00
jenkins
74cbfb235e [Jenkins] Increment version 2018-12-14 18:38:12 +01:00
63c4104a89 Add recieved messages to ComLog 2018-12-14 18:35:51 +01:00
2597287b1e Fixed wrong pro-mode reset 2018-12-14 18:30:03 +01:00
jenkins
316156f0f0 [Jenkins] Increment version 2018-12-11 13:55:38 +01:00
5286e869cc config for preview-line-count 2018-12-11 13:53:47 +01:00
d6dcf28d89 Share+Delete Button 2018-12-11 13:22:39 +01:00
1d983b9ac0 Collapse message on click-again 2018-12-11 12:31:44 +01:00
e525221010 Show Querylog via hidden shit 2018-12-11 12:25:10 +01:00
4cde4703f2 fixed exception in requery.php 2018-12-11 10:40:30 +01:00
4a07e58c16 fix expand + requery 2018-11-25 21:03:05 +01:00
b12356575a Description text correction 2018-11-25 18:11:42 +01:00
77f571de7d Send timestamp with request 2018-11-25 18:02:25 +01:00
jenkins
f5eef7563b [Jenkins] Increment version 2018-11-23 19:40:31 +01:00
48 changed files with 1775 additions and 298 deletions

View File

@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="launcherLegacy">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetType" value="IMAGE" />
<entry key="cropped" value="true" />
<entry key="iconShape" value="NONE" />
<entry key="imageAsset" value="F:\Eigene Dateien\Dropbox\Programming\Java\AndroidStudioProjects\SimpleCloudNotifier\data\icon_512_nobox.png" />
<entry key="outputName" value="ic_notification_full" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="notification">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetType" value="IMAGE" />
<entry key="imageAsset" value="F:\Eigene Dateien\Dropbox\Programming\Java\AndroidStudioProjects\SimpleCloudNotifier\data\icon_512_transparent.png" />
<entry key="outputName" value="ic_notification_white" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="outputIconType" value="LAUNCHER_LEGACY" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_garbage" />
<entry key="sourceFile" value="C:\Users\Mike\Downloads\garbage.svg" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@@ -1,29 +1,113 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<Objective-C-extensions> <codeStyleSettings language="XML">
<file> <arrangement>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" /> <rules>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" /> <section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" /> <rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" /> <match>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" /> <AND>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" /> <NAME>xmlns:android</NAME>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" /> <XML_ATTRIBUTE />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" /> <XML_NAMESPACE>^$</XML_NAMESPACE>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" /> </AND>
</file> </match>
<class> </rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" /> </section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" /> <section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" /> <rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" /> <match>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" /> <AND>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" /> <NAME>xmlns:.*</NAME>
</class> <XML_ATTRIBUTE />
<extensions> <XML_NAMESPACE>^$</XML_NAMESPACE>
<pair source="cpp" header="h" fileNamingConvention="NONE" /> </AND>
<pair source="c" header="h" fileNamingConvention="NONE" /> </match>
</extensions> <order>BY_NAME</order>
</Objective-C-extensions> </rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

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,6 +1,5 @@
<?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">
@@ -10,34 +9,60 @@
<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" /> <provider
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" /> android:name="androidx.core.content.FileProvider"
<meta-data android:name="com.google.android.gms.ads.AD_MANAGER_APP" android:value="true"/> android:authorities="com.blackforestbytes.simplecloudnotifier.fileprovider"
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3320562328966175~7579972005"/> android:grantUriPermissions="true"
android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
<service android:name=".service.FBMService" android:exported="false">
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/icon" />
<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">
<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,6 @@ public class SCNApp extends Application implements LifecycleObserver
isBackground = false; isBackground = false;
} }
} }
//TODO: Config for collapsed line count
//TODO: Sometimes ads but promode

View File

@@ -9,6 +9,8 @@ import java.util.TimeZone;
public class CMessage public class CMessage
{ {
public boolean IsExpandedInAdapter = false;
public final long SCN_ID; public final long SCN_ID;
public final long Timestamp; public final long Timestamp;
public final String Title; public final String Title;

View File

@@ -35,6 +35,11 @@ public class CMessageList
} }
private CMessageList() private CMessageList()
{
reloadPrefs();
}
public void reloadPrefs()
{ {
synchronized (msg_lock) synchronized (msg_lock)
{ {

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 = 192;
private static QueryLog _instance;
public static QueryLog inst() { if (_instance == null) synchronized (QueryLog.class) { if (_instance == null) _instance = new QueryLog(); } return _instance; }
private QueryLog(){ reloadPrefs(); }
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 reloadPrefs()
{
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

@@ -6,8 +6,8 @@ import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.IABService; import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceId;
@@ -52,7 +52,8 @@ public class SCNSettings
public boolean Enabled = true; public boolean Enabled = true;
public int LocalCacheSize = 500; public int LocalCacheSize = 500;
public boolean EnableDeleteSwipe = true; public boolean EnableDeleteSwipe = false;
public int PreviewLineCount = 6;
public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW); public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW);
public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL); public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL);
@@ -61,6 +62,11 @@ public class SCNSettings
// ------------------------------------------------------------ // ------------------------------------------------------------
public SCNSettings() public SCNSettings()
{
reloadPrefs();
}
public void reloadPrefs()
{ {
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
@@ -77,6 +83,7 @@ public class SCNSettings
Enabled = sharedPref.getBoolean("app_enabled", Enabled); Enabled = sharedPref.getBoolean("app_enabled", Enabled);
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize); LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
EnableDeleteSwipe = sharedPref.getBoolean("do_del_swipe", EnableDeleteSwipe); EnableDeleteSwipe = sharedPref.getBoolean("do_del_swipe", EnableDeleteSwipe);
PreviewLineCount = sharedPref.getInt("preview_line_count", PreviewLineCount);
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED); PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound); PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
@@ -120,10 +127,14 @@ public class SCNSettings
e.putString( "user_key", user_key); e.putString( "user_key", user_key);
e.putString( "fcm_token_local", fcm_token_local); e.putString( "fcm_token_local", fcm_token_local);
e.putString( "fcm_token_server", fcm_token_server); e.putString( "fcm_token_server", fcm_token_server);
e.putBoolean("promode_local", promode_local);
e.putBoolean("promode_server", promode_server);
e.putString( "promode_token", promode_token);
e.putBoolean("app_enabled", Enabled); e.putBoolean("app_enabled", Enabled);
e.putInt( "local_cache_size", LocalCacheSize); e.putInt( "local_cache_size", LocalCacheSize);
e.putBoolean("do_del_swipe", EnableDeleteSwipe); e.putBoolean("do_del_swipe", EnableDeleteSwipe);
e.putInt( "preview_line_count", PreviewLineCount);
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED); e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound); e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
@@ -240,14 +251,17 @@ public class SCNSettings
public void updateProState(View loader) public void updateProState(View loader)
{ {
Purchase purch = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE); Tuple3<Boolean, Boolean, String> state = IABService.inst().getPurchaseCachedExtended(IABService.IAB_PRO_MODE);
boolean promode_real = (purch != null); if (!state.Item2) return; // not initialized
boolean promode_real = state.Item1;
if (promode_real != promode_local || promode_real != promode_server) if (promode_real != promode_local || promode_real != promode_server)
{ {
promode_local = promode_real; promode_local = promode_real;
promode_token = promode_real ? state.Item3 : "";
save();
promode_token = promode_real ? purch.getPurchaseToken() : "";
updateProStateOnServer(loader); updateProStateOnServer(loader);
} }
} }

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);
} }
} }
@@ -106,7 +105,7 @@ public class ServerCommunication
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(BASE_URL + "updateFCMToken.php?user_id="+id+"&user_key="+key+"&fcm_token="+token) .url(BASE_URL + "update.php?user_id="+id+"&user_key="+key+"&fcm_token="+token)
.build(); .build();
client.newCall(request).enqueue(new Callback() client.newCall(request).enqueue(new Callback()
@@ -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);
} }
} }
@@ -172,24 +172,28 @@ public class ServerCommunication
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(BASE_URL + "updateFCMToken.php?user_id=" + id + "&user_key=" + key) .url(BASE_URL + "update.php?user_id=" + id + "&user_key=" + key)
.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: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.inst().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.inst().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

@@ -4,16 +4,21 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple4; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple5;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.LogLevel;
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum; import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication; import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.RemoteMessage;
import org.joda.time.Instant;
import org.json.JSONObject;
public class FBMService extends FirebaseMessagingService public class FBMService extends FirebaseMessagingService
{ {
@Override @Override
@@ -42,6 +47,10 @@ public class FBMService extends FirebaseMessagingService
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id")); long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed")); boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
SingleQuery q = new SingleQuery(LogLevel.INFO, Instant.now(), "FBM<recieve>", Str.Empty, new JSONObject(remoteMessage.getData()).toString(), 0, "SUCCESS");
QueryLog.inst().add(q);
if (trimmed) if (trimmed)
{ {
ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false)); ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false));

View File

@@ -2,6 +2,7 @@ package com.blackforestbytes.simplecloudnotifier.service;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
@@ -11,13 +12,18 @@ import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.PurchasesUpdatedListener;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple2;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0; import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity; import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -45,12 +51,21 @@ public class IABService implements PurchasesUpdatedListener
} }
} }
public enum SimplePurchaseState { YES, NO, UNINITIALIZED }
private BillingClient client; private BillingClient client;
private boolean isServiceConnected; private boolean isServiceConnected;
private final List<Purchase> purchases = new ArrayList<>(); private final List<Purchase> purchases = new ArrayList<>();
private boolean _isInitialized = false;
private Map<String, Boolean> _localCache= new HashMap<>();
public IABService(Context c) public IABService(Context c)
{ {
_isInitialized = false;
loadCache();
client = BillingClient client = BillingClient
.newBuilder(c) .newBuilder(c)
.setListener(this) .setListener(this)
@@ -59,6 +74,50 @@ public class IABService implements PurchasesUpdatedListener
startServiceConnection(this::queryPurchases, false); startServiceConnection(this::queryPurchases, false);
} }
public void reloadPrefs()
{
loadCache();
}
private void loadCache()
{
_localCache.clear();
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
int count = sharedPref.getInt("c", 0);
for (int i=0; i < count; i++)
{
String k = sharedPref.getString("["+i+"]->key", null);
boolean v = sharedPref.getBoolean("["+i+"]->value", false);
if (k==null)continue;
_localCache.put(k, v);
}
}
private void saveCache()
{
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
SharedPreferences.Editor editor= sharedPref.edit();
editor.putInt("c", _localCache.size());
int i = 0;
for (Map.Entry<String, Boolean> e : _localCache.entrySet())
{
editor.putString("["+i+"]->key", e.getKey());
editor.putBoolean("["+i+"]->value", e.getValue());
i++;
}
editor.apply();
}
@SuppressWarnings("ConstantConditions")
private synchronized void updateCache(String k, boolean v)
{
if (_localCache.containsKey(k) && _localCache.get(k)==v) return;
_localCache.put(k, v);
saveCache();
}
public void queryPurchases() public void queryPurchases()
{ {
Func0to0 queryToExecute = () -> Func0to0 queryToExecute = () ->
@@ -71,10 +130,12 @@ public class IABService implements PurchasesUpdatedListener
{ {
for (Purchase p : purchasesResult.getPurchasesList()) for (Purchase p : purchasesResult.getPurchasesList())
{ {
handlePurchase(p); handlePurchase(p, false);
} }
boolean newProMode = getPurchaseCached(IAB_PRO_MODE) != null; _isInitialized = true;
boolean newProMode = getPurchaseCachedSimple(IAB_PRO_MODE);
if (newProMode != SCNSettings.inst().promode_local) if (newProMode != SCNSettings.inst().promode_local)
{ {
refreshProModeListener(); refreshProModeListener();
@@ -102,7 +163,8 @@ public class IABService implements PurchasesUpdatedListener
}, true); }, true);
} }
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest) { private void executeServiceRequest(Func0to0 runnable, final boolean userRequest)
{
if (isServiceConnected) if (isServiceConnected)
{ {
runnable.invoke(); runnable.invoke();
@@ -130,28 +192,31 @@ public class IABService implements PurchasesUpdatedListener
{ {
for (Purchase purchase : purchases) for (Purchase purchase : purchases)
{ {
handlePurchase(purchase); handlePurchase(purchase, true);
} }
} }
else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null) else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null)
{ {
for (Purchase purchase : purchases) for (Purchase purchase : purchases)
{ {
handlePurchase(purchase); handlePurchase(purchase, true);
} }
} }
} }
private void handlePurchase(Purchase purchase) private void handlePurchase(Purchase purchase, boolean triggerUpdate)
{ {
Log.d(TAG, "Got a verified purchase: " + purchase); Log.d(TAG, "Got a verified purchase: " + purchase);
purchases.add(purchase); purchases.add(purchase);
refreshProModeListener(); if (triggerUpdate) refreshProModeListener();
updateCache(purchase.getSku(), true);
} }
private void refreshProModeListener() { private void refreshProModeListener()
{
MainActivity ma = SCNApp.getMainActivity(); MainActivity ma = SCNApp.getMainActivity();
if (ma != null) ma.adpTabs.tab3.updateProState(); if (ma != null) ma.adpTabs.tab3.updateProState();
if (ma != null) ma.adpTabs.tab1.updateProState(); if (ma != null) ma.adpTabs.tab1.updateProState();
@@ -183,13 +248,31 @@ public class IABService implements PurchasesUpdatedListener
}); });
} }
public Purchase getPurchaseCached(String id) public boolean getPurchaseCachedSimple(String id)
{ {
for (Purchase p : purchases) return getPurchaseCachedExtended(id).Item1;
{
if (Str.equals(p.getSku(), id)) return p;
} }
return null; @SuppressWarnings("ConstantConditions")
public Tuple3<Boolean, Boolean, String> getPurchaseCachedExtended(String id)
{
// <state, initialized, token>
if (!_isInitialized)
{
if (_localCache.containsKey(id) && _localCache.get(id)) return new Tuple3<>(true, false, Str.Empty);
}
for (Purchase p : purchases)
{
if (Str.equals(p.getSku(), id))
{
updateCache(id, true);
return new Tuple3<>(true, true, p.getPurchaseToken());
}
}
updateCache(id, false);
return new Tuple3<>(false, true, 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

@@ -0,0 +1,25 @@
package com.blackforestbytes.simplecloudnotifier.util;
import android.text.Editable;
import android.text.TextWatcher;
public abstract class TextChangedListener<T> implements TextWatcher {
private T target;
public TextChangedListener(T target) {
this.target = target;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
this.onTextChanged(target, s);
}
public abstract void onTextChanged(T target, Editable s);
}

View File

@@ -1,14 +1,23 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
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;
@@ -16,6 +25,12 @@ import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.Map;
import java.util.Set;
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
{ {
public TabAdapter adpTabs; public TabAdapter adpTabs;
@@ -24,6 +39,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
QueryLog.inst();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
@@ -33,6 +50,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 +99,126 @@ 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));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1991 && resultCode == RESULT_OK)
{
Uri uri = data.getData(); //The uri with the location of the file
Context ctxt = this;
try
{
ObjectInputStream stream = new ObjectInputStream(getContentResolver().openInputStream(uri));
Map<String, ?> d1 = (Map<String, ?>)stream.readObject();
Map<String, ?> d2 = (Map<String, ?>)stream.readObject();
Map<String, ?> d3 = (Map<String, ?>)stream.readObject();
Map<String, ?> d4 = (Map<String, ?>)stream.readObject();
stream.close();
runOnUiThread(() ->
{
SharedPreferences.Editor e1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).edit();
e1.clear();
for (Map.Entry<String, ?> entry : d1.entrySet())
{
if (entry.getValue() instanceof String) e1.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e1.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e1.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e1.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e1.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e1.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e2.clear();
for (Map.Entry<String, ?> entry : d2.entrySet())
{
if (entry.getValue() instanceof String) e2.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e2.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e2.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e2.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e2.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e2.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e2.clear();
for (Map.Entry<String, ?> entry : d3.entrySet())
{
if (entry.getValue() instanceof String) e3.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e3.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e3.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e3.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e3.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e3.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e4.clear();
for (Map.Entry<String, ?> entry : d4.entrySet())
{
if (entry.getValue() instanceof String) e4.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e4.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e4.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e4.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e4.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e4.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e1.apply();
e2.apply();
e3.apply();
e4.apply();
SCNSettings.inst().reloadPrefs();
IABService.inst().reloadPrefs();
CMessageList.inst().reloadPrefs();
QueryLog.inst().reloadPrefs();
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.pager);
PagerAdapter adapter = adpTabs = new TabAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
SCNSettings.inst().work(this);
SCNApp.showToast("Backup imported", Toast.LENGTH_LONG);
finish();
});
}
catch (Exception e)
{
Log.e("Import:Err", e.toString());
SCNApp.showToast("Import failed", Toast.LENGTH_LONG);
}
}
}
} }

View File

@@ -1,5 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -9,8 +10,11 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.google.android.material.button.MaterialButton;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Collections; import java.util.Collections;
@@ -53,7 +57,7 @@ public class MessageAdapter extends RecyclerView.Adapter
{ {
CMessage msg = CMessageList.inst().tryGetFromBack(position); CMessage msg = CMessageList.inst().tryGetFromBack(position);
MessagePresenter view = (MessagePresenter) holder; MessagePresenter view = (MessagePresenter) holder;
view.setMessage(msg); view.setMessage(msg, position);
viewHolders.put(view, true); viewHolders.put(view, true);
} }
@@ -110,7 +114,11 @@ public class MessageAdapter extends RecyclerView.Adapter
public RelativeLayout viewForeground; public RelativeLayout viewForeground;
public RelativeLayout viewBackground; public RelativeLayout viewBackground;
public MaterialButton btnShare;
public MaterialButton btnDelete;
private CMessage data; private CMessage data;
private int datapos;
MessagePresenter(View itemView) MessagePresenter(View itemView)
{ {
@@ -121,6 +129,8 @@ public class MessageAdapter extends RecyclerView.Adapter
ivPriority = itemView.findViewById(R.id.ivPriority); ivPriority = itemView.findViewById(R.id.ivPriority);
viewForeground = itemView.findViewById(R.id.layoutFront); viewForeground = itemView.findViewById(R.id.layoutFront);
viewBackground = itemView.findViewById(R.id.layoutBack); viewBackground = itemView.findViewById(R.id.layoutBack);
btnShare = itemView.findViewById(R.id.btnShare);
btnDelete = itemView.findViewById(R.id.btnDelete);
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
tvTimestamp.setOnClickListener(this); tvTimestamp.setOnClickListener(this);
@@ -128,9 +138,22 @@ public class MessageAdapter extends RecyclerView.Adapter
tvMessage.setOnClickListener(this); tvMessage.setOnClickListener(this);
ivPriority.setOnClickListener(this); ivPriority.setOnClickListener(this);
viewForeground.setOnClickListener(this); viewForeground.setOnClickListener(this);
btnShare.setOnClickListener(v ->
{
if (data == null) return;
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, data.Title);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, data.Content);
SCNApp.getMainActivity().startActivity(Intent.createChooser(sharingIntent, "Share message"));
});
btnDelete.setOnClickListener(v -> { if (data != null) SCNApp.getMainActivity().adpTabs.tab1.deleteMessage(datapos); });
} }
void setMessage(CMessage msg) void setMessage(CMessage msg, int pos)
{ {
tvTimestamp.setText(msg.formatTimestamp()); tvTimestamp.setText(msg.formatTimestamp());
tvTitle.setText(msg.Title); tvTitle.setText(msg.Title);
@@ -155,21 +178,49 @@ public class MessageAdapter extends RecyclerView.Adapter
} }
data = msg; data = msg;
datapos = pos;
if (msg.IsExpandedInAdapter) expand(true); else collapse(true);
}
private void expand(boolean force)
{
if (data != null && data.IsExpandedInAdapter && !force) return;
if (data != null) data.IsExpandedInAdapter = true;
if (tvMessage != null) tvMessage.setMaxLines(9999);
if (btnDelete != null) btnDelete.setVisibility(View.VISIBLE);
if (btnShare != null) btnShare.setVisibility(View.VISIBLE);
}
private int norm(int i) { return (i<=0)?0:((i>9999)?9999:i); }
private void collapse(boolean force)
{
if (data != null && !data.IsExpandedInAdapter && !force) return;
if (data != null) data.IsExpandedInAdapter = false;
if (tvMessage != null) tvMessage.setMaxLines(norm(SCNSettings.inst().PreviewLineCount));
if (btnDelete != null) btnDelete.setVisibility(View.GONE);
if (btnShare != null) btnShare.setVisibility(View.GONE);
} }
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
if (data.IsExpandedInAdapter)
{
collapse(false);
return;
}
for (MessagePresenter holder : MessageAdapter.this.viewHolders.keySet()) for (MessagePresenter holder : MessageAdapter.this.viewHolders.keySet())
{ {
if (holder == null) continue; if (holder == null) continue;
if (holder == this) continue; if (holder == this) continue;
if (holder.tvMessage == null) continue; holder.collapse(false);
if (holder.tvMessage.getMaxLines() == 6) continue;
holder.tvMessage.setMaxLines(6);
} }
tvMessage.setMaxLines(9999); expand(false);
} }
} }
} }

View File

@@ -58,7 +58,7 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
public void updateProState() public void updateProState()
{ {
if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE); if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE) ? View.GONE : View.VISIBLE);
} }
@Override @Override
@@ -66,9 +66,15 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
{ {
if (viewHolder instanceof MessageAdapter.MessagePresenter) if (viewHolder instanceof MessageAdapter.MessagePresenter)
{ {
final int deletedIndex = viewHolder.getAdapterPosition(); deleteMessage(viewHolder.getAdapterPosition());
}
}
final CMessage deletedItem = adpMessages.removeItem(viewHolder.getAdapterPosition()); public void deleteMessage(int pos)
{
final int deletedIndex = pos;
final CMessage deletedItem = adpMessages.removeItem(pos);
String name = deletedItem.Title; String name = deletedItem.Title;
Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG); Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG);
@@ -76,7 +82,6 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
snackbar.setActionTextColor(Color.YELLOW); snackbar.setActionTextColor(Color.YELLOW);
snackbar.show(); snackbar.show();
} }
}
public void updateDeleteSwipeEnabled() public void updateDeleteSwipeEnabled()
{ {

View File

@@ -1,6 +1,9 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioManager; import android.media.AudioManager;
@@ -10,6 +13,8 @@ import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.text.Editable;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -17,6 +22,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
@@ -25,6 +31,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.BuildConfig;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.android.ThreadUtils; import com.blackforestbytes.simplecloudnotifier.lib.android.ThreadUtils;
@@ -32,12 +39,19 @@ import com.blackforestbytes.simplecloudnotifier.lib.lambda.FI;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
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.util.TextChangedListener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.Map;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import top.defaults.colorpicker.ColorPickerPopup; import top.defaults.colorpicker.ColorPickerPopup;
import xyz.aprildown.ultimatemusicpicker.MusicPickerListener; import xyz.aprildown.ultimatemusicpicker.MusicPickerListener;
@@ -51,6 +65,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private TextView prefUpgradeAccount_msg; private TextView prefUpgradeAccount_msg;
private TextView prefUpgradeAccount_info; private TextView prefUpgradeAccount_info;
private Switch prefEnableDeleteSwipe; private Switch prefEnableDeleteSwipe;
private EditText prefPreviewLineCount;
private Switch prefMsgLowEnableSound; private Switch prefMsgLowEnableSound;
private TextView prefMsgLowRingtone_value; private TextView prefMsgLowRingtone_value;
@@ -88,6 +103,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private SeekBar prefMsgHighVolume; private SeekBar prefMsgHighVolume;
private ImageView prefMsgHighVolumeTest; private ImageView prefMsgHighVolumeTest;
private Button prefBtnImport;
private Button prefBtnExport;
private int musicPickerSwitch = -1; private int musicPickerSwitch = -1;
private MediaPlayer[] mPlayers = new MediaPlayer[3]; private MediaPlayer[] mPlayers = new MediaPlayer[3];
@@ -117,6 +135,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2); prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info); prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
prefEnableDeleteSwipe = v.findViewById(R.id.prefEnableDeleteSwipe); prefEnableDeleteSwipe = v.findViewById(R.id.prefEnableDeleteSwipe);
prefPreviewLineCount = v.findViewById(R.id.prefPreviewLineCount);
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound); prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value); prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
@@ -154,11 +173,15 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume); prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume);
prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest); prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest);
prefBtnExport = v.findViewById(R.id.prefExport);
prefBtnImport = v.findViewById(R.id.prefImport);
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(v.getContext(), android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES); ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(v.getContext(), android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
prefLocalCacheSize.setAdapter(plcsa); prefLocalCacheSize.setAdapter(plcsa);
} }
@SuppressLint("SetTextI18n")
private void updateUI() private void updateUI()
{ {
SCNSettings s = SCNSettings.inst(); SCNSettings s = SCNSettings.inst();
@@ -167,6 +190,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled); if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
if (prefEnableDeleteSwipe.isChecked() != s.EnableDeleteSwipe) prefEnableDeleteSwipe.setChecked(s.EnableDeleteSwipe); if (prefEnableDeleteSwipe.isChecked() != s.EnableDeleteSwipe) prefEnableDeleteSwipe.setChecked(s.EnableDeleteSwipe);
if (!prefPreviewLineCount.getText().toString().equals(Integer.toString(s.PreviewLineCount))) prefPreviewLineCount.setText(Integer.toString(s.PreviewLineCount));
prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE); prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE); prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
@@ -220,6 +244,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); }); prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); });
prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); }); prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
prefPreviewLineCount.addTextChangedListener(new TextChangedListener<EditText>(prefPreviewLineCount) {
@Override
public void onTextChanged(EditText target, Editable ed) {
if (!ed.toString().isEmpty()) try { s.PreviewLineCount=Integer.parseInt(ed.toString()); saveAndUpdate(); } catch (Exception e) { /* */ }
}
});
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{ {
@@ -232,6 +262,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefUpgradeAccount.setOnClickListener(a -> onUpgradeAccount()); prefUpgradeAccount.setOnClickListener(a -> onUpgradeAccount());
prefBtnExport.setOnClickListener(a -> onExport());
prefBtnImport.setOnClickListener(a -> onImport());
prefMsgLowEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableSound=b; saveAndUpdate(); }); prefMsgLowEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableSound=b; saveAndUpdate(); });
prefMsgLowRingtone_container.setOnClickListener(a -> chooseRingtoneLow()); prefMsgLowRingtone_container.setOnClickListener(a -> chooseRingtoneLow());
prefMsgLowRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.RepeatSound=b; saveAndUpdate(); }); prefMsgLowRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.RepeatSound=b; saveAndUpdate(); });
@@ -263,6 +296,55 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgHighVolumeTest.setOnClickListener((v) -> { if (s.PriorityHigh.ForceVolume) playTestSound(2, prefMsgHighVolumeTest, s.PriorityHigh.SoundSource, s.PriorityHigh.ForceVolumeValue); }); prefMsgHighVolumeTest.setOnClickListener((v) -> { if (s.PriorityHigh.ForceVolume) playTestSound(2, prefMsgHighVolumeTest, s.PriorityHigh.SoundSource, s.PriorityHigh.ForceVolumeValue); });
} }
private void onExport()
{
Context ctxt = getContext();
if (ctxt == null) return;
try
{
File outputDir = ctxt.getCacheDir(); // context being the Activity pointer
File outputFile = File.createTempFile("scn_export_", ".dat", outputDir);
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(outputFile));
Map<String, ?> d1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).getAll();
Map<String, ?> d2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).getAll();
Map<String, ?> d3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).getAll();
Map<String, ?> d4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).getAll();
output.writeObject(d1);
output.writeObject(d2);
output.writeObject(d3);
output.writeObject(d4);
Intent intent = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(ctxt, "com.blackforestbytes.simplecloudnotifier.fileprovider", outputFile);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("*/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Export"));
}
catch (IOException e)
{
Log.e("Export:Err", e.toString());
SCNApp.showToast("Export failed", Toast.LENGTH_LONG);
}
}
private void onImport()
{
SCNApp.getMainActivity().setContentView(R.layout.activity_main);
Intent intent = new Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT);
((MainActivity)getActivity()).startActivityForResult(Intent.createChooser(intent, "Select a file"), 1991);
}
private void updateEnabled(boolean prev, boolean now) private void updateEnabled(boolean prev, boolean now)
{ {
if (!prev && now) if (!prev && now)
@@ -352,11 +434,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
public void updateProState() public void updateProState()
{ {
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE); boolean pmode = IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE);
if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE); if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( pmode ? View.GONE : View.VISIBLE);
if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE); if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(pmode ? View.GONE : View.VISIBLE);
if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE ); if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( pmode ? View.VISIBLE : View.GONE );
} }
private int getCacheSizeIndex(int value) private int getCacheSizeIndex(int value)

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.inst().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

@@ -0,0 +1,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12sp"
android:height="12sp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -0,0 +1,16 @@
<vector
android:height="12sp"
android:width="12sp"
android:viewportHeight="53"
android:viewportWidth="53"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FFFFFF"
android:pathData="M42.943,6H33.5V3c0,-1.654 -1.346,-3 -3,-3h-8c-1.654,0 -3,1.346 -3,3v3h-9.443C8.096,6 6.5,7.596 6.5,9.557V14h2h36h2V9.557C46.5,7.596 44.904,6 42.943,6zM31.5,6h-10V3c0,-0.552 0.449,-1 1,-1h8c0.551,0 1,0.448 1,1V6z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M8.5,49.271C8.5,51.327 10.173,53 12.229,53h28.541c2.057,0 3.729,-1.673 3.729,-3.729V16h-36V49.271z"/>
</vector>

View File

@@ -1,31 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-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"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
app:titleTextColor="@color/colorOnPrimary"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="6dp" android:elevation="6dp"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize" />
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout" android:id="@+id/tab_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/toolbar" android:layout_below="@+id/toolbar"
app:titleTextColor="@color/colorOnPrimary"
app:tabTextColor="@color/colorOnPrimary"
app:tabSelectedTextColor="@color/colorSecondary"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="6dp" android:elevation="6dp"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize" />
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<androidx.viewpager.widget.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/pager" android:id="@+id/pager"

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

@@ -198,19 +198,27 @@
app:layout_constraintBottom_toTopOf="@+id/btnAccountReset" app:layout_constraintBottom_toTopOf="@+id/btnAccountReset"
app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" /> app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/btnAccountReset" android:id="@+id/btnAccountReset"
app:cornerRadius="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#fa315b"
android:text="@string/str_reset_account" android:text="@string/str_reset_account"
app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" /> app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/btnClearLocalStorage" android:id="@+id/btnClearLocalStorage"
app:cornerRadius="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Clear Messages" android:text="Clear Messages"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#607D8B"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -95,6 +95,46 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="48dp" /> android:minHeight="48dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<TextView
android:id="@+id/tvPreviewLineCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/str_previewlinecount"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefPreviewLineCount"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:minWidth="64dp"
android:id="@+id/prefPreviewLineCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:importantForAutofill="no"
tools:ignore="LabelFor,UnusedAttribute" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
@@ -111,8 +151,10 @@
android:gravity="center|center" android:gravity="center|center"
android:minHeight="48dp"> android:minHeight="48dp">
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/prefUpgradeAccount" android:id="@+id/prefUpgradeAccount"
app:cornerRadius="0dp"
android:backgroundTint="#4CAF50"
android:text="@string/str_upgrade_account" android:text="@string/str_upgrade_account"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@@ -763,6 +805,24 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/prefExport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cornerRadius="0dp"
android:backgroundTint="#444444"
android:text="@string/export_settings" />
<com.google.android.material.button.MaterialButton
android:id="@+id/prefImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cornerRadius="0dp"
android:backgroundTint="#666666"
android:text="@string/import_settings" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -108,6 +108,43 @@
android:paddingTop="3dp" android:paddingTop="3dp"
android:contentDescription="@string/desc_priority_icon" /> android:contentDescription="@string/desc_priority_icon" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnShare"
style="@style/Widget.MaterialComponents.Button.Icon"
app:cornerRadius="0dp"
app:icon="@drawable/ic_share_small"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_marginEnd="8sp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:textSize="12sp"
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
app:layout_constraintRight_toLeftOf="@id/btnDelete"
app:layout_constraintBottom_toBottomOf="parent"
android:backgroundTint="#03A9F4"
android:text="Share" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDelete"
style="@style/Widget.MaterialComponents.Button.Icon"
app:cornerRadius="0dp"
app:icon="@drawable/ic_trash_small"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:textSize="12sp"
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:backgroundTint="#F44336"
android:text="Delete"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

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,6 +6,9 @@
<color name="colorHeader">#3F51B5</color> <color name="colorHeader">#3F51B5</color>
<color name="colorHeaderForeground">#FFFFFF</color> <color name="colorHeaderForeground">#FFFFFF</color>
<color name="colorOnPrimary">#ecf0f1</color>
<color name="colorSecondary">#FF5722</color>
<color name="colorBlack">#000</color> <color name="colorBlack">#000</color>
<color name="bg_row_background">#fa315b</color> <color name="bg_row_background">#fa315b</color>

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

@@ -30,9 +30,13 @@
<string name="str_enable_vibration">Enable notification vibration</string> <string name="str_enable_vibration">Enable notification vibration</string>
<string name="str_upgrade_account">Upgrade account</string> <string name="str_upgrade_account">Upgrade account</string>
<string name="str_deleteswipe">Delete messages by swiping left</string> <string name="str_deleteswipe">Delete messages by swiping left</string>
<string name="str_previewlinecount">Number of visibile lines in collapsed messages</string>
<string name="str_promode">Thank you for supporting the app and using the pro mode</string> <string name="str_promode">Thank you for supporting the app and using the pro mode</string>
<string name="str_promode_info">Increase your daily quota, remove the ad banner and support the developer (that\'s me)</string> <string name="str_promode_info">Increase your daily quota, remove the ad banner and support the developer (that\'s me)</string>
<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>
<string name="import_settings">Import settings</string>
<string name="export_settings">Export settings</string>
</resources> </resources>

View File

@@ -1,13 +1,20 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- your app branding color for the app bar --> <!-- your app branding color for the app bar -->
<item name="colorPrimary">#3F51B5</item> <item name="colorPrimary">#3F51B5</item>
<!-- darker variant for the status bar and contextual app bars --> <!-- darker variant for the status bar and contextual app bars -->
<item name="colorPrimaryDark">#303F9F</item> <item name="colorPrimaryDark">#303F9F</item>
<!-- theme UI controls like checkboxes and text fields --> <!-- theme UI controls like checkboxes and text fields -->
<item name="colorAccent">#FF4081</item> <item name="colorAccent">#FF5722</item>
<item name="colorSecondary">#FF5722</item>
</style> </style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
</resources> </resources>

View File

@@ -0,0 +1,7 @@
<paths>
<files-path path="/" name="files" />
<cache-path path="/" name="cache" />
<external-path path="/" name="external" />
<external-files-path path="/" name="external-files" />
<external-cache-path path="/" name="external-cache" />
</paths>

View File

@@ -1,3 +1,3 @@
#Mon Nov 19 18:43:09 CET 2018 #Fri Dec 14 22:08:16 CET 2018
VERSION_NAME=1.1.0 VERSION_NAME=1.6.0
VERSION_CODE=15 VERSION_CODE=20

35
examples/scn_send.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
/**
* @param string $title
* @param string $content
* @param int $priority
* @return bool
*/
function sendSCN($title, $content, $priority) {
global $config;
$data =
[
'user_id' => '', //TODO set your userid
'user_key' => '', //TODO set your userkey
'title' => $title,
'content' => $content,
'priority' => $priority,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://simplecloudnotifier.blackforestbytes.com/send.php");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
if ($result === false) return false;
$json = json_decode($result, true);
return $json['success'];
}

View File

@@ -21,6 +21,7 @@ user_key="????????????????????????????????????????????????????????????????"
title=$1 title=$1
content="" content=""
sendtime=$(date +%s)
if [ "$#" -gt 1 ]; then if [ "$#" -gt 1 ]; then
content=$2 content=$2
@@ -37,7 +38,7 @@ usr_msg_id=$(uuidgen)
while true ; do while true ; do
curlresp=$(curl -s -o /dev/null -w "%{http_code}" \ curlresp=$(curl -s -o /dev/null -w "%{http_code}" \
-d "user_id=$user_id" -d "user_key=$user_key" -d "title=$title" \ -d "user_id=$user_id" -d "user_key=$user_key" -d "title=$title" -d "timestamp=$sendtime" \
-d "content=$content" -d "priority=$priority" -d "msg_id=$usr_msg_id" \ -d "content=$content" -d "priority=$priority" -d "msg_id=$usr_msg_id" \
https://scn.blackforestbytes.com/send.php) https://scn.blackforestbytes.com/send.php)

View File

@@ -45,7 +45,7 @@ api_return(200,
'body' => $msg['content'], 'body' => $msg['content'],
'trimmed' => false, 'trimmed' => false,
'priority' => $msg['priority'], 'priority' => $msg['priority'],
'timestamp' => strtotime($msg['timestamp']), 'timestamp' => $msg['sendtime'],
'usr_msg_id' => $msg['usr_message_id'], 'usr_msg_id' => $msg['usr_message_id'],
'scn_msg_id' => $msg['scn_message_id'], 'scn_msg_id' => $msg['scn_message_id'],
], ],

View File

@@ -16,6 +16,7 @@ class ERR
const TITLE_TOO_LONG = 1202; const TITLE_TOO_LONG = 1202;
const CONTENT_TOO_LONG = 1203; const CONTENT_TOO_LONG = 1203;
const USR_MSG_ID_TOO_LONG = 1204; const USR_MSG_ID_TOO_LONG = 1204;
const TIMESTAMP_OUT_OF_RANGE = 1205;
const USER_NOT_FOUND = 1301; const USER_NOT_FOUND = 1301;
const USER_AUTH_FAILED = 1302; const USER_AUTH_FAILED = 1302;
@@ -165,6 +166,12 @@ function verifyOrderToken($tok)
{ {
// https://developers.google.com/android-publisher/api-ref/purchases/products/get // https://developers.google.com/android-publisher/api-ref/purchases/products/get
// if this does no longer work, you probably have to go through the initial OAuth process again
// 1. go to Postman do the [ https://accounts.google.com/o/oauth2/auth ] request (in browser) to get a new "code"
// 2. go to Postman do the [ Get Tokens ] request to get a new "access_token" and "access_token"
// 3. update these tokens in the server config.php
try try
{ {
$package = getConfig()['verify_api']['package_name']; $package = getConfig()['verify_api']['package_name'];

View File

@@ -28,7 +28,7 @@ if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid
//------------------- //-------------------
$stmt = $pdo->prepare('SELECT * FROM messages WHERE ack=0 AND sender_user_id=:uid ORDER BY `timestamp` DESC LIMIT 16'); $stmt = $pdo->prepare('SELECT * FROM messages WHERE ack=0 AND sender_user_id=:uid ORDER BY `timestamp_real` DESC LIMIT 16');
$stmt->execute(['uid' => $user_id]); $stmt->execute(['uid' => $user_id]);
$nonacks_sql = $stmt->fetchAll(PDO::FETCH_ASSOC); $nonacks_sql = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -41,7 +41,7 @@ foreach ($nonacks_sql as $nack)
'title' => $nack['title'], 'title' => $nack['title'],
'body' => $nack['content'], 'body' => $nack['content'],
'priority' => $nack['priority'], 'priority' => $nack['priority'],
'timestamp' => strtotime($nack['timestamp']), 'timestamp' => $nack['sendtime'],
'usr_msg_id' => $nack['usr_message_id'], 'usr_msg_id' => $nack['usr_message_id'],
'scn_msg_id' => $nack['scn_message_id'], 'scn_msg_id' => $nack['scn_message_id'],
]; ];

View File

@@ -23,12 +23,13 @@ CREATE TABLE `messages`
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT, `scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
`sender_user_id` INT(11) NOT NULL, `sender_user_id` INT(11) NOT NULL,
`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ack` BIT NOT NULL DEFAULT 0, `ack` BIT NOT NULL DEFAULT 0,
`title` VARCHAR(256) NOT NULL, `title` VARCHAR(256) NOT NULL,
`content` VARCHAR(12288) NULL, `content` VARCHAR(12288) NULL,
`priority` INT(11) NOT NULL, `priority` INT(11) NOT NULL,
`sendtime` BIGINT UNSIGNED NOT NULL,
`fcm_message_id` VARCHAR(256) NULL, `fcm_message_id` VARCHAR(256) NULL,
`usr_message_id` VARCHAR(256) NULL, `usr_message_id` VARCHAR(256) NULL,

View File

@@ -198,6 +198,22 @@ if (file_exists('/var/www/openwebanalytics/owa_php.php'))
</p> </p>
</div> </div>
<h2>Custom Time</h2>
<div class="section">
<p>
You can modify the displayed timestamp of a message by sending the <code>timestamp</code> parameter. The format must be a valid UNIX timestamp (elapsed seconds since 1970-01-01 GMT)
</p>
<p>
The custom timestamp must be within 48 hours of the current time. This parameter is only intended to supply a more precise value in case the message sending was delayed.
</p>
<pre>curl \
--data "user_id={userid}" \
--data "user_key={userkey}" \
--data "title={message_title}" \
--data "timestamp={unix_timestamp}" \
https://scn.blackforestbytes.com/send.php</pre>
</div>
<h2>Bash script example</h2> <h2>Bash script example</h2>
<div class="section"> <div class="section">
<p> <p>
@@ -227,6 +243,7 @@ user_key=<span style="color:#2a00ff; ">"????????????????????????????????????????
title=$1 title=$1
content=<span style="color:#2a00ff; ">""</span> content=<span style="color:#2a00ff; ">""</span>
sendtime=$(date +%s)
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span> <span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
content=$2 content=$2
@@ -243,7 +260,7 @@ usr_msg_id=$(uuidgen)
<span style="color:#7f0055; font-weight:bold; ">while</span> true ; <span style="color:#7f0055; font-weight:bold; ">do</span> <span style="color:#7f0055; font-weight:bold; ">while</span> true ; <span style="color:#7f0055; font-weight:bold; ">do</span>
curlresp=$(curl -s -o <span style="color:#3f3fbf; ">/dev/null</span> -w <span style="color:#2a00ff; ">"%{http_code}"</span> <span style="color:#2a00ff; ">\</span> curlresp=$(curl -s -o <span style="color:#3f3fbf; ">/dev/null</span> -w <span style="color:#2a00ff; ">"%{http_code}"</span> <span style="color:#2a00ff; ">\</span>
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_id</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_key</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_key</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">title</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$title</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_id</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_key</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_key</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">title</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$title</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">timestamp</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$sendtime</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">content</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$content</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">priority</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$priority</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">msg_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$usr_msg_id</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">content</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$content</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">priority</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$priority</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">msg_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$usr_msg_id</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
https:<span style="color:#3f3fbf; ">/</span><span style="color:#3f3fbf; ">/scn.blackforestbytes.com/send.php</span>) https:<span style="color:#3f3fbf; ">/</span><span style="color:#3f3fbf; ">/scn.blackforestbytes.com/send.php</span>)

View File

@@ -5,8 +5,6 @@ include_once 'api/model.php';
try try
{ {
//------------------------------------------------------------------
//sleep(1);
//------------------------------------------------------------------ //------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_return(400, ['success' => false, 'error' => ERR::REQ_METHOD, 'errhighlight' => -1, 'message' => 'Invalid request method (must be POST)']); if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_return(400, ['success' => false, 'error' => ERR::REQ_METHOD, 'errhighlight' => -1, 'message' => 'Invalid request method (must be POST)']);
@@ -19,16 +17,18 @@ try
//------------------------------------------------------------------ //------------------------------------------------------------------
$user_id = $INPUT['user_id']; $user_id = $INPUT['user_id'];
$user_key = $INPUT['user_key']; $user_key = $INPUT['user_key'];
$message = $INPUT['title']; $message = $INPUT['title'];
$content = isset($INPUT['content']) ? $INPUT['content'] : ''; $content = isset($INPUT['content']) ? $INPUT['content'] : '';
$priority = isset($INPUT['priority']) ? $INPUT['priority'] : '1'; $priority = isset($INPUT['priority']) ? $INPUT['priority'] : '1';
$usrmsgid = isset($INPUT['msg_id']) ? $INPUT['msg_id'] : null; $usrmsgid = isset($INPUT['msg_id']) ? $INPUT['msg_id'] : null;
$time = isset($INPUT['timestamp']) ? $INPUT['timestamp'] : time();
//------------------------------------------------------------------ //------------------------------------------------------------------
if (abs($time - time()) > 60*60*24*2) api_return(400, ['success' => false, 'error' => ERR::TIMESTAMP_OUT_OF_RANGE, 'errhighlight' => -1, 'message' => 'The timestamp mus be within 24 hours of now()']);
if ($priority !== '0' && $priority !== '1' && $priority !== '2') api_return(400, ['success' => false, 'error' => ERR::INVALID_PRIO, 'errhighlight' => 105, 'message' => 'Invalid priority']); if ($priority !== '0' && $priority !== '1' && $priority !== '2') api_return(400, ['success' => false, 'error' => ERR::INVALID_PRIO, 'errhighlight' => 105, 'message' => 'Invalid priority']);
if (strlen(trim($message)) == 0) api_return(400, ['success' => false, 'error' => ERR::NO_TITLE, 'errhighlight' => 103, 'message' => 'No title specified']); if (strlen(trim($message)) == 0) api_return(400, ['success' => false, 'error' => ERR::NO_TITLE, 'errhighlight' => 103, 'message' => 'No title specified']);
@@ -95,13 +95,14 @@ try
$pdo->beginTransaction(); $pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO messages (sender_user_id, title, content, priority, fcm_message_id, usr_message_id) VALUES (:suid, :t, :c, :p, :fmid, :umid)'); $stmt = $pdo->prepare('INSERT INTO messages (sender_user_id, title, content, priority, sendtime, fcm_message_id, usr_message_id) VALUES (:suid, :t, :c, :p, :ts, :fmid, :umid)');
$stmt->execute( $stmt->execute(
[ [
'suid' => $user_id, 'suid' => $user_id,
't' => $message, 't' => $message,
'c' => $content, 'c' => $content,
'p' => $priority, 'p' => $priority,
'ts' => $time,
'fmid' => null, 'fmid' => null,
'umid' => $usrmsgid, 'umid' => $usrmsgid,
]); ]);
@@ -125,7 +126,7 @@ try
'body' => str_limit($content, 1900), 'body' => str_limit($content, 1900),
'trimmed' => (strlen($content) > 1900), 'trimmed' => (strlen($content) > 1900),
'priority' => $priority, 'priority' => $priority,
'timestamp' => time(), 'timestamp' => $time,
'usr_msg_id' => $usrmsgid, 'usr_msg_id' => $usrmsgid,
'scn_msg_id' => $scn_msg_id, 'scn_msg_id' => $scn_msg_id,
] ]