32 Commits

Author SHA1 Message Date
4655d688c9 README 2018-11-18 17:19:15 +01:00
8263c0ad95 phone image 2018-11-18 17:12:44 +01:00
a701afd09b OWA tracking 2018-11-18 03:34:13 +01:00
jenkins
1e02d8c01f [Jenkins] Increment version 2018-11-18 03:24:46 +01:00
e4651375aa fix qr url 2018-11-18 03:23:22 +01:00
jenkins
afce4c6391 [Jenkins] Increment version 2018-11-18 00:15:21 +01:00
e95d0cb010 added disable warning 2018-11-18 00:13:36 +01:00
f717355519 use "messages" everywhere instead of "notifications" 2018-11-18 00:11:30 +01:00
3368e514ca red quota icon if OOQ 2018-11-18 00:02:00 +01:00
9dca27177f fixed js logic for errors 2018-11-17 23:59:57 +01:00
c63274f7a9 show subtext on android-o 2018-11-17 23:48:39 +01:00
6c2d2d2345 fixed requery only getting first message 2018-11-17 22:51:02 +01:00
a6fbaa192f config example 2018-11-17 19:28:44 +01:00
jenkins
a01f156535 [Jenkins] Increment version 2018-11-17 19:00:13 +01:00
3b021c09dc notification icons 2018-11-17 18:58:18 +01:00
287176c881 screenshots 2018-11-17 18:21:53 +01:00
84eeb5a002 red high-prio icons 2018-11-17 18:21:25 +01:00
b892532023 send big content over FCM 2018-11-17 17:59:43 +01:00
jenkins
56cdc52bb4 [Jenkins] Increment version 2018-11-17 17:19:59 +01:00
9ef6b1dd91 swipe-to-delete setting 2018-11-17 17:18:15 +01:00
6f7585323b fixed notification being overwritten 2018-11-17 17:06:34 +01:00
92135e64a3 css 2018-11-17 17:00:14 +01:00
28efeea30b message delete problems 2018-11-17 16:45:42 +01:00
a8a074907b fixed update when in message-list 2018-11-17 16:02:31 +01:00
6c29ec9820 max-size = 2048 chars (FCM restrictions) 2018-11-17 15:44:44 +01:00
jenkins
8ec23144ca [Jenkins] Increment version 2018-11-17 14:28:04 +01:00
b703853b28 send.bash example 2018-11-17 14:26:29 +01:00
6f7529fc9b fixed duplicate-recieve 2018-11-17 14:01:51 +01:00
27b45098f0 ups 2018-11-17 13:46:23 +01:00
21f66d99cd fixed timestamp format in requery 2018-11-17 13:44:42 +01:00
1745051e6e fixed info.php return value 2018-11-17 12:51:20 +01:00
jenkins
b395e054e6 [Jenkins] Increment version 2018-11-17 03:25:54 +01:00
59 changed files with 769 additions and 172 deletions

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
SimpleCloudNotifier [![Get it in Google Play](data/README/badge_google.png)](https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier)
===================
> SimpleCloudNotifier is an app to display messages that you can send to your phone with simple POST requests.
>
> After you start the app it generates a UserID and a UserSecret.
> Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
> (see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
>
>
> Use it to
> - send yourself automated messages from cron jobs
> - notify yourself when long-running scripts finish
> - send server error messages directly to your phone
> - integrate with other online services
>
> The possibilities are endless*
>
> \* Disclaimer: Developer does not actually guarantee endless possibilities
![](store/screenshot_1.png) ![](store/screenshot_2.png) ![](store/screenshot_3.png)

View File

@@ -3,6 +3,59 @@
<component name="WizardSettings"> <component name="WizardSettings">
<option name="children"> <option name="children">
<map> <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"> <entry key="vectorWizard">
<value> <value>
<PersistentState> <PersistentState>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -99,14 +99,4 @@ public class SCNApp extends Application implements LifecycleObserver
} }
} }
/* //TODO TabLayout indicator does not corretly animate when directly clicking on tabs
==TODO==
[ ] - test notification channels
[ ] - startup time
[ ] - periodically get non-ack (option - even when not in-app)
[ ] - publish (+ HN post ?)
*/

View File

@@ -24,6 +24,11 @@ public final class CollectionHelper
return output; return output;
} }
public static <T> void sort_inplace(List<T> input, Comparator<T> comparator)
{
Collections.sort(input, comparator);
}
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper) public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
{ {
return sort(input, mapper, 1); return sort(input, mapper, 1);

View File

@@ -0,0 +1,19 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple5<T1, T2, T3, T4, T5>
{
public final T1 Item1;
public final T2 Item2;
public final T3 Item3;
public final T4 Item4;
public final T5 Item5;
public Tuple5(T1 i1, T2 i2, T3 i3, T4 i4, T5 i5)
{
Item1 = i1;
Item2 = i2;
Item3 = i3;
Item4 = i4;
Item5 = i5;
}
}

View File

@@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func5to0<TInput1, TInput2, TInput3, TInput4, TInput5> {
void invoke(TInput1 value1, TInput2 value2, TInput3 value3, TInput4 value4, TInput5 value5);
}

View File

@@ -3,17 +3,21 @@ package com.blackforestbytes.simplecloudnotifier.model;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter; import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class CMessageList public class CMessageList
{ {
private final Object msg_lock = new Object();
public ArrayList<CMessage> Messages; public ArrayList<CMessage> Messages;
public Set<String> AllAcks; public Set<String> AllAcks;
@@ -32,23 +36,26 @@ public class CMessageList
private CMessageList() private CMessageList()
{ {
Messages = new ArrayList<>(); synchronized (msg_lock)
AllAcks = new HashSet<>();
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0);
for (int i=0; i < count; i++)
{ {
long time = sharedPref.getLong("message["+i+"].timestamp", 0); Messages = new ArrayList<>();
String title = sharedPref.getString("message["+i+"].title", ""); AllAcks = new HashSet<>();
String content = sharedPref.getString("message["+i+"].content", "");
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
Messages.add(new CMessage(scnid, time, title, content, prio)); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0);
for (int i=0; i < count; i++)
{
long time = sharedPref.getLong("message["+i+"].timestamp", 0);
String title = sharedPref.getString("message["+i+"].title", "");
String content = sharedPref.getString("message["+i+"].content", "");
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
Messages.add(new CMessage(scnid, time, title, content, prio));
}
AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
} }
AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
} }
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe) public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
@@ -60,29 +67,44 @@ public class CMessageList
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0); int count = sharedPref.getInt("message_count", 0);
SharedPreferences.Editor e = sharedPref.edit(); synchronized (msg_lock)
{
Messages.add(msg);
AllAcks.add(Long.toHexString(msg.SCN_ID));
Messages.add(msg); while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
AllAcks.add(Long.toHexString(msg.SCN_ID)); }
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0); if (Messages.size()>1 && Messages.get(Messages.size()-2).Timestamp < msg.Timestamp)
{
// quick save
e.putInt( "message_count", count+1); SharedPreferences.Editor e = sharedPref.edit();
e.putLong( "message["+count+"].timestamp", time);
e.putString("message["+count+"].title", title);
e.putString("message["+count+"].content", content);
e.putInt( "message["+count+"].priority", pe.ID);
e.putLong( "message["+count+"].scnid", scnid);
e.putStringSet("acks", AllAcks); e.putInt( "message_count", count+1);
e.putLong( "message["+count+"].timestamp", time);
e.putString("message["+count+"].title", title);
e.putString("message["+count+"].content", content);
e.putInt( "message["+count+"].priority", pe.ID);
e.putLong( "message["+count+"].scnid", scnid);
e.putStringSet("acks", AllAcks);
e.apply();
}
else
{
// full save
fullSave(); // does sort in here
}
e.apply();
for (WeakReference<MessageAdapter> ref : _listener) for (WeakReference<MessageAdapter> ref : _listener)
{ {
MessageAdapter a = ref.get(); MessageAdapter a = ref.get();
if (a == null) continue; if (a == null) continue;
a.customNotifyItemInserted(count); a.customNotifyDataSetChanged();
a.scrollToTop(); a.scrollToTop();
} }
CleanUpListener(); CleanUpListener();
@@ -90,9 +112,12 @@ public class CMessageList
if (!run) if (!run)
{ {
Messages.add(new CMessage(scnid, time, title, content, pe)); synchronized (msg_lock)
AllAcks.add(Long.toHexString(msg.SCN_ID)); {
fullSave(); Messages.add(new CMessage(scnid, time, title, content, pe));
AllAcks.add(Long.toHexString(msg.SCN_ID));
}
fullSave(); // does sort in here
} }
return msg; return msg;
@@ -114,31 +139,39 @@ public class CMessageList
public void fullSave() public void fullSave()
{ {
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE); synchronized (msg_lock)
SharedPreferences.Editor e = sharedPref.edit();
e.clear();
e.putInt("message_count", Messages.size());
for (int i = 0; i < Messages.size(); i++)
{ {
e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp); CollectionHelper.sort_inplace(Messages, (a,b) -> Long.compare(a.Timestamp, b.Timestamp));
e.putString("message["+i+"].title", Messages.get(i).Title);
e.putString("message["+i+"].content", Messages.get(i).Content); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID); SharedPreferences.Editor e = sharedPref.edit();
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
e.clear();
e.putInt("message_count", Messages.size());
for (int i = 0; i < Messages.size(); i++)
{
e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp);
e.putString("message["+i+"].title", Messages.get(i).Title);
e.putString("message["+i+"].content", Messages.get(i).Content);
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID);
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
}
e.putStringSet("acks", AllAcks);
e.apply();
} }
e.putStringSet("acks", AllAcks);
e.apply();
} }
public CMessage tryGet(int pos) public CMessage tryGet(int pos)
{ {
if (pos < 0 || pos >= Messages.size()) return null; synchronized (msg_lock)
return Messages.get(pos); {
if (pos < 0 || pos >= Messages.size()) return null;
return Messages.get(pos);
}
} }
public CMessage tryGetFromBack(int pos) public CMessage tryGetFromBack(int pos)
@@ -148,7 +181,10 @@ public class CMessageList
public int size() public int size()
{ {
return Messages.size(); synchronized (msg_lock)
{
return Messages.size();
}
} }
public void register(MessageAdapter adp) public void register(MessageAdapter adp)
@@ -167,18 +203,30 @@ public class CMessageList
public boolean isAck(long id) public boolean isAck(long id)
{ {
return AllAcks.contains(Long.toHexString(id)); synchronized (msg_lock)
{
return AllAcks.contains(Long.toHexString(id));
}
} }
public void remove(int index) public CMessage removeFromBack(int pos)
{ {
Messages.remove(index); CMessage r;
fullSave(); synchronized (msg_lock)
{
int index = Messages.size() - pos - 1;
r = Messages.remove(index);
}
fullSave(); // does sort in here
return r;
} }
public void insert(int index, CMessage item) public void insert(int index, CMessage item)
{ {
Messages.add(index, item); synchronized (msg_lock)
fullSave(); {
Messages.add(index, item);
}
fullSave(); // does sort in here
} }
} }

View File

@@ -15,14 +15,19 @@ import com.google.firebase.iid.FirebaseInstanceId;
public class SCNSettings public class SCNSettings
{ {
private final static Object _lock = new Object(); private final static Object _lock = new Object();
private static SCNSettings _inst = null; private static volatile SCNSettings _inst = null;
public static SCNSettings inst() public static SCNSettings inst()
{ {
synchronized (_lock) SCNSettings local = _inst;
if (local == null)
{ {
if (_inst != null) return _inst; synchronized (_lock)
return _inst = new SCNSettings(); {
local = _inst;
if (local == null) _inst = local = new SCNSettings();
}
} }
return local;
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@@ -47,6 +52,7 @@ public class SCNSettings
public boolean Enabled = true; public boolean Enabled = true;
public int LocalCacheSize = 500; public int LocalCacheSize = 500;
public boolean EnableDeleteSwipe = true;
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);
@@ -70,6 +76,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);
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);
@@ -116,6 +123,7 @@ public class SCNSettings
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("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);
@@ -155,10 +163,12 @@ public class SCNSettings
return user_id>=0 && user_key != null && !user_key.isEmpty(); return user_id>=0 && user_key != null && !user_key.isEmpty();
} }
public String createOnlineURL() public String createOnlineURL(boolean longurl)
{ {
if (!isConnected()) return ServerCommunication.BASE_URL + "index.php"; String base = longurl ? ServerCommunication.PAGE_URL_LONG : ServerCommunication.PAGE_URL_SHORT;
return ServerCommunication.BASE_URL + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
if (!isConnected()) return base;
return base + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
} }
public void setServerToken(String token, View loader) public void setServerToken(String token, View loader)
@@ -184,7 +194,7 @@ public class SCNSettings
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult -> FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
{ {
String newToken = instanceIdResult.getToken(); String newToken = instanceIdResult.getToken();
Log.e("FB::GetInstanceId", newToken); Log.d("FB::GetInstanceId", newToken);
SCNSettings.inst().setServerToken(newToken, null); SCNSettings.inst().setServerToken(newToken, null);
}).addOnCompleteListener(r -> }).addOnCompleteListener(r ->
{ {
@@ -219,7 +229,7 @@ public class SCNSettings
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult -> FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
{ {
String newToken = instanceIdResult.getToken(); String newToken = instanceIdResult.getToken();
Log.e("FB::GetInstanceId", newToken); Log.d("FB::GetInstanceId", newToken);
SCNSettings.inst().setServerToken(newToken, loader); // does register in here SCNSettings.inst().setServerToken(newToken, loader); // does register in here
}).addOnCompleteListener(r -> }).addOnCompleteListener(r ->
{ {

View File

@@ -4,6 +4,9 @@ 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.string.Str; import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.FBMService; import com.blackforestbytes.simplecloudnotifier.service.FBMService;
@@ -24,7 +27,9 @@ import okhttp3.ResponseBody;
public class ServerCommunication public class ServerCommunication
{ {
public static final String BASE_URL = /*SCNApp.LOCAL_DEBUG ? "http://localhost:1010/" : */"https://scn.blackforestbytes.com/api/"; public static final String PAGE_URL_LONG = "https://simplecloudnotifier.blackforestbytes.com/";
public static final String PAGE_URL_SHORT = "https://scn.blackforestbytes.com/";
public static final String BASE_URL = "https://scn.blackforestbytes.com/api/";
private static final OkHttpClient client = new OkHttpClient(); private static final OkHttpClient client = new OkHttpClient();
@@ -57,7 +62,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -123,7 +128,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -185,7 +190,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -246,7 +251,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -283,10 +288,7 @@ public class ServerCommunication
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
if (json_int(json, "unack_count")>0) if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
{
ServerCommunication.requery(id, key, loader);
}
} catch (Exception e) { } catch (Exception e) {
Log.e("SC:info", e.toString()); Log.e("SC:info", e.toString());
@@ -332,7 +334,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -346,7 +348,7 @@ public class ServerCommunication
JSONArray arr = json.getJSONArray("data"); JSONArray arr = json.getJSONArray("data");
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
JSONObject o = arr.getJSONObject(0); JSONObject o = arr.getJSONObject(i);
long time = json_lng(o, "timestamp"); long time = json_lng(o, "timestamp");
String title = json_str(o, "title"); String title = json_str(o, "title");
@@ -357,20 +359,6 @@ public class ServerCommunication
FBMService.recieveData(time, title, content, prio, scn_id, true); FBMService.recieveData(time, title, content, prio, scn_id, true);
} }
SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
if (!json_bool(json, "fcm_token_set")) SCNSettings.inst().fcm_token_server = "";
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
if (json_int(json, "unack_count")>0)
{
ServerCommunication.requery(id, key, loader);
}
} catch (Exception e) { } catch (Exception e) {
Log.e("SC:info", e.toString()); Log.e("SC:info", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
@@ -414,7 +402,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -446,12 +434,12 @@ public class ServerCommunication
} }
} }
public static void ack(int id, String key, CMessage msg) public static void ack(int id, String key, long msg_scn_id)
{ {
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.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() {
@@ -469,7 +457,7 @@ public class ServerCommunication
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); String r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
@@ -488,6 +476,70 @@ public class ServerCommunication
} }
} }
public static void expand(int id, String key, long scn_msg_id, View loader, Func5to0<String, String, PriorityEnum, Long, Long> okResult)
{
try
{
Request request = new Request.Builder()
.url(BASE_URL + "expand.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + scn_msg_id)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("SC:expand", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
}
@Override
public void onResponse(Call call, Response response) {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
String r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success"))
{
SCNApp.showToast(json_str(json, "message"), 4000);
return;
}
JSONObject o = json.getJSONObject("data");
long time = json_lng(o, "timestamp");
String title = json_str(o, "title");
String content = json_str(o, "body");
PriorityEnum prio = PriorityEnum.parseAPI(json_int(o, "priority"));
long scn_id = json_lng(o, "scn_msg_id");
okResult.invoke(title, content, prio, time, scn_id);
} catch (Exception e) {
Log.e("SC:expand", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
} finally {
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
}
}
});
}
catch (Exception e)
{
Log.e("SC:expand", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
}
}
private static boolean json_bool(JSONObject o, String key) throws JSONException private static boolean json_bool(JSONObject o, String key) throws JSONException
{ {
Object v = o.get(key); Object v = o.get(key);

View File

@@ -4,6 +4,8 @@ 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.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.PriorityEnum; import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
@@ -38,8 +40,16 @@ public class FBMService extends FirebaseMessagingService
String content = remoteMessage.getData().get("body"); String content = remoteMessage.getData().get("body");
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority")); PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
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"));
recieveData(time, title, content, prio, scn_id, false); 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));
}
else
{
recieveData(time, title, content, prio, scn_id, false);
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -50,15 +60,15 @@ public class FBMService extends FirebaseMessagingService
public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck) public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck)
{ {
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
if (CMessageList.inst().isAck(scn_id)) if (CMessageList.inst().isAck(scn_id))
{ {
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id); Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg); if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
return; return;
} }
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
if (SCNApp.isBackground()) if (SCNApp.isBackground())
{ {
NotificationService.inst().showBackground(msg); NotificationService.inst().showBackground(msg);
@@ -68,6 +78,6 @@ public class FBMService extends FirebaseMessagingService
NotificationService.inst().showForeground(msg); NotificationService.inst().showForeground(msg);
} }
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg); ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
} }
} }

View File

@@ -6,6 +6,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
@@ -165,12 +166,15 @@ public class NotificationService
private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio) private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{ {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio)); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_bfb); mBuilder.setSmallIcon(R.drawable.ic_notification_white);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
mBuilder.setContentTitle(msg.Title); mBuilder.setContentTitle(msg.Title);
mBuilder.setContentText(msg.Content); mBuilder.setContentText(msg.Content);
mBuilder.setShowWhen(true); mBuilder.setShowWhen(true);
mBuilder.setWhen(msg.Timestamp * 1000); mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true); mBuilder.setAutoCancel(true);
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT); if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
@@ -198,19 +202,22 @@ public class NotificationService
Notification n = mBuilder.build(); Notification n = mBuilder.build();
if (mNotificationManager != null) mNotificationManager.notify(0, n); if (mNotificationManager != null) mNotificationManager.notify((int)msg.SCN_ID, n);
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
private void showBackground_new(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio) private void showBackground_new(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{ {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio)); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_bfb); mBuilder.setSmallIcon(R.drawable.ic_notification_white);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
mBuilder.setContentTitle(msg.Title); mBuilder.setContentTitle(msg.Title);
mBuilder.setContentText(msg.Content); mBuilder.setContentText(msg.Content);
mBuilder.setShowWhen(true); mBuilder.setShowWhen(true);
mBuilder.setWhen(msg.Timestamp * 1000); mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true); mBuilder.setAutoCancel(true);
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500); if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
@@ -242,7 +249,7 @@ public class NotificationService
Notification n = mBuilder.build(); Notification n = mBuilder.build();
n.flags |= Notification.FLAG_AUTO_CANCEL; n.flags |= Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(0, n); mNotificationManager.notify((int)msg.SCN_ID, n);
if (ns.EnableVibration) if (ns.EnableVibration)
{ {

View File

@@ -3,6 +3,7 @@ package com.blackforestbytes.simplecloudnotifier.util;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.view.View; import android.view.View;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter; import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -13,10 +14,21 @@ public class MessageAdapterTouchHelper extends ItemTouchHelper.SimpleCallback
{ {
private MessageAdapterTouchHelperListener listener; private MessageAdapterTouchHelperListener listener;
private int dir = 0;
public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener) public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener)
{ {
super(dragDirs, swipeDirs); super(dragDirs, swipeDirs);
this.dir = swipeDirs;
this.listener = listener; this.listener = listener;
updateEnabled();
}
public void updateEnabled()
{
int sdir = SCNSettings.inst().EnableDeleteSwipe ? ItemTouchHelper.LEFT : 0;
if (dir == sdir) return;
setDefaultSwipeDirs(dir = sdir);
} }
@Override @Override

View File

@@ -6,12 +6,14 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
@@ -91,7 +93,7 @@ public class AccountFragment extends Fragment
builder.setPositiveButton("YES", (dialog, which) -> { builder.setPositiveButton("YES", (dialog, which) -> {
CMessageList.inst().clear(); CMessageList.inst().clear();
SCNApp.showToast("Notifications cleared", 1000); SCNApp.showToast("Messages cleared", 1000);
dialog.dismiss(); dialog.dismiss();
}); });
@@ -103,7 +105,7 @@ public class AccountFragment extends Fragment
v.findViewById(R.id.btnQR).setOnClickListener(cv -> v.findViewById(R.id.btnQR).setOnClickListener(cv ->
{ {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL())); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL(true)));
startActivity(browserIntent); startActivity(browserIntent);
}); });
@@ -126,10 +128,11 @@ public class AccountFragment extends Fragment
public void updateUI(View v) public void updateUI(View v)
{ {
if (v == null) return; if (v == null) return;
TextView tvUserID = v.findViewById(R.id.tvUserID); TextView tvUserID = v.findViewById(R.id.tvUserID);
TextView tvUserKey = v.findViewById(R.id.tvUserKey); TextView tvUserKey = v.findViewById(R.id.tvUserKey);
TextView tvQuota = v.findViewById(R.id.tvQuota); TextView tvQuota = v.findViewById(R.id.tvQuota);
ImageButton btnQR = v.findViewById(R.id.btnQR); ImageView ivQuota = v.findViewById(R.id.ic_img_quota);
ImageButton btnQR = v.findViewById(R.id.btnQR);
SCNSettings s = SCNSettings.inst(); SCNSettings s = SCNSettings.inst();
@@ -138,7 +141,8 @@ public class AccountFragment extends Fragment
tvUserID.setText(String.valueOf(s.user_id)); tvUserID.setText(String.valueOf(s.user_id));
tvUserKey.setText(s.user_key); tvUserKey.setText(s.user_key);
tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max)); tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max));
btnQR.setImageBitmap(QRCode.from(s.createOnlineURL()).to(ImageType.PNG).withSize(512, 512).bitmap()); btnQR.setImageBitmap(QRCode.from(s.createOnlineURL(false)).to(ImageType.PNG).withSize(512, 512).bitmap());
ivQuota.setColorFilter(s.quota_curr>=s.quota_max ? Color.rgb(200, 0, 0) : Color.rgb(128, 128, 128));
} }
else else
{ {
@@ -146,6 +150,7 @@ public class AccountFragment extends Fragment
tvUserKey.setText(R.string.str_not_connected); tvUserKey.setText(R.string.str_not_connected);
tvQuota.setText(R.string.str_not_connected); tvQuota.setText(R.string.str_not_connected);
btnQR.setImageResource(R.drawable.qr_default); btnQR.setImageResource(R.drawable.qr_default);
ivQuota.setColorFilter(0x80_80_80);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -57,7 +58,6 @@ public class MessageAdapter extends RecyclerView.Adapter
viewHolders.put(view, true); viewHolders.put(view, true);
} }
@Override @Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder)
{ {
@@ -87,16 +87,17 @@ public class MessageAdapter extends RecyclerView.Adapter
manLayout.smoothScrollToPosition(viewRecycler, null, 0); manLayout.smoothScrollToPosition(viewRecycler, null, 0);
} }
public void removeItem(int position) public CMessage removeItem(int position)
{ {
CMessageList.inst().remove(position); CMessage i = CMessageList.inst().removeFromBack(position);
notifyItemRemoved(position); notifyDataSetChanged();
return i;
} }
public void restoreItem(CMessage item, int position) public void restoreItem(CMessage item, int position)
{ {
CMessageList.inst().insert(position, item); CMessageList.inst().insert(position, item);
notifyItemInserted(position); notifyDataSetChanged();
} }
public class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener public class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
@@ -140,13 +141,16 @@ public class MessageAdapter extends RecyclerView.Adapter
case LOW: case LOW:
ivPriority.setVisibility(View.VISIBLE); ivPriority.setVisibility(View.VISIBLE);
ivPriority.setImageResource(R.drawable.priority_low); ivPriority.setImageResource(R.drawable.priority_low);
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
break; break;
case NORMAL: case NORMAL:
ivPriority.setVisibility(View.GONE); ivPriority.setVisibility(View.GONE);
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
break; break;
case HIGH: case HIGH:
ivPriority.setVisibility(View.VISIBLE); ivPriority.setVisibility(View.VISIBLE);
ivPriority.setImageResource(R.drawable.priority_high); ivPriority.setImageResource(R.drawable.priority_high);
ivPriority.setColorFilter(Color.rgb(200, 0, 0));
break; break;
} }

View File

@@ -9,7 +9,6 @@ import android.view.ViewGroup;
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.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
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.MessageAdapterTouchHelper; import com.blackforestbytes.simplecloudnotifier.util.MessageAdapterTouchHelper;
@@ -28,6 +27,8 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
private PublisherAdView adView; private PublisherAdView adView;
private MessageAdapter adpMessages; private MessageAdapter adpMessages;
public MessageAdapterTouchHelper touchHelper;
public NotificationsFragment() public NotificationsFragment()
{ {
// Required empty public constructor // Required empty public constructor
@@ -43,7 +44,7 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
rvMessages.setLayoutManager(lman); rvMessages.setLayoutManager(lman);
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages)); rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this); ItemTouchHelper.SimpleCallback itemTouchHelperCallback = touchHelper = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages); new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages);
adView = v.findViewById(R.id.adBanner); adView = v.findViewById(R.id.adBanner);
@@ -65,13 +66,11 @@ public class NotificationsFragment extends Fragment implements MessageAdapterTou
{ {
if (viewHolder instanceof MessageAdapter.MessagePresenter) if (viewHolder instanceof MessageAdapter.MessagePresenter)
{ {
final CMessage deletedItem = CMessageList.inst().tryGet(viewHolder.getAdapterPosition());
final int deletedIndex = viewHolder.getAdapterPosition(); final int deletedIndex = viewHolder.getAdapterPosition();
final CMessage deletedItem = adpMessages.removeItem(viewHolder.getAdapterPosition());
String name = deletedItem.Title; String name = deletedItem.Title;
adpMessages.removeItem(viewHolder.getAdapterPosition());
Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG); Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", view -> adpMessages.restoreItem(deletedItem, deletedIndex)); snackbar.setAction("UNDO", view -> adpMessages.restoreItem(deletedItem, deletedIndex));
snackbar.setActionTextColor(Color.YELLOW); snackbar.setActionTextColor(Color.YELLOW);

View File

@@ -22,6 +22,7 @@ import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Switch; import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
@@ -49,6 +50,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private Button prefUpgradeAccount; private Button prefUpgradeAccount;
private TextView prefUpgradeAccount_msg; private TextView prefUpgradeAccount_msg;
private TextView prefUpgradeAccount_info; private TextView prefUpgradeAccount_info;
private Switch prefEnableDeleteSwipe;
private Switch prefMsgLowEnableSound; private Switch prefMsgLowEnableSound;
private TextView prefMsgLowRingtone_value; private TextView prefMsgLowRingtone_value;
@@ -114,6 +116,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount); prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
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);
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);
@@ -159,6 +162,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
if (c == null) return; if (c == null) return;
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);
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);
@@ -213,7 +217,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
{ {
SCNSettings s = SCNSettings.inst(); SCNSettings s = SCNSettings.inst();
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { s.Enabled=b; saveAndUpdate(); }); prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); });
prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{ {
@@ -257,6 +262,18 @@ 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 updateEnabled(boolean prev, boolean now)
{
if (!prev && now)
{
SCNApp.showToast("SimpleCloudNotifier is now enabled", Toast.LENGTH_SHORT);
}
else if (prev && !now)
{
SCNApp.showToast("SimpleCloudNotifier is now disabled\nYou won't recieve new messages.", Toast.LENGTH_LONG);
}
}
private void updateVolume(int idx, int volume) private void updateVolume(int idx, int volume)
{ {
if (mPlayers[idx] != null && mPlayers[idx].isPlaying()) if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
@@ -324,6 +341,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
{ {
SCNSettings.inst().save(); SCNSettings.inst().save();
updateUI(); updateUI();
SCNApp.getMainActivity().adpTabs.tab1.touchHelper.updateEnabled();
} }
private void onUpgradeAccount() private void onUpgradeAccount()

View File

@@ -35,7 +35,7 @@ public class TabAdapter extends FragmentStatePagerAdapter {
{ {
switch (position) switch (position)
{ {
case 0: return "Notifications"; case 0: return "Messages";
case 1: return "Account"; case 1: return "Account";
case 2: return "Settings"; case 2: return "Settings";
default: return null; default: return null;

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,15 +0,0 @@
<vector android:height="24dp" android:viewportHeight="1000"
android:viewportWidth="1000" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M500,500m-500,0a500,500 0,1 1,1000 0a500,500 0,1 1,-1000 0"
android:strokeColor="#000000" android:strokeWidth="1"/>
<path android:fillColor="#ffffff" android:pathData="M300,694L700,694L500,136L300,694"/>
<path android:fillColor="#ffffff" android:pathData="M473,559L527,559L527,774L473,774"/>
<path android:fillColor="#000000" android:pathData="M376,640L624,640L500,295L376,640"/>
<path android:fillColor="#ffffff" android:pathData="M100,730L500,730L300,172L100,730"/>
<path android:fillColor="#000000" android:pathData="M176,676L424,676L300,331L176,676"/>
<path android:fillColor="#ffffff" android:pathData="M273,595L327,595L327,810L273,810"/>
<path android:fillColor="#ffffff" android:pathData="M500,730L900,730L700,172L500,730"/>
<path android:fillColor="#000000" android:pathData="M576,676L824,676L700,331L576,676"/>
<path android:fillColor="#ffffff" android:pathData="M673,595L727,595L727,810L673,810"/>
</vector>

View File

@@ -79,6 +79,22 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:id="@+id/prefEnableDeleteSwipe"
android:text="@string/str_deleteswipe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -11,13 +11,13 @@
<string name="ic_img_fuel_desc">Icon Fuel</string> <string name="ic_img_fuel_desc">Icon Fuel</string>
<string name="str_qr_code">QR Code</string> <string name="str_qr_code">QR Code</string>
<string name="str_reset_account">Reset Account</string> <string name="str_reset_account">Reset Account</string>
<string name="no_notifications">No notifications</string> <string name="no_notifications">No messages</string>
<string name="str_not_connected">not connected</string> <string name="str_not_connected">not connected</string>
<string name="str_reload">reload</string> <string name="str_reload">reload</string>
<string name="desc_priority_icon">Priority icon</string> <string name="desc_priority_icon">Priority icon</string>
<string name="str_common_settings">Common Settings</string> <string name="str_common_settings">Common Settings</string>
<string name="str_enabled">Enabled</string> <string name="str_enabled">Enabled</string>
<string name="str_localcachesize">Remember the last x notifications locally</string> <string name="str_localcachesize">Remember the last x messages locally</string>
<string name="str_header_prio0">Notifications (priority 0 - Low)</string> <string name="str_header_prio0">Notifications (priority 0 - Low)</string>
<string name="str_header_prio1">Notifications (priority 1 - Normal)</string> <string name="str_header_prio1">Notifications (priority 1 - Normal)</string>
<string name="str_header_prio2">Notifications (priority 2 - High)</string> <string name="str_header_prio2">Notifications (priority 2 - High)</string>
@@ -29,6 +29,7 @@
<string name="str_ledcolor">Notification light color</string> <string name="str_ledcolor">Notification light color</string>
<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_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>

View File

@@ -1,3 +1,3 @@
#Mon Nov 12 18:26:41 CET 2018 #Sun Nov 18 03:24:23 CET 2018
VERSION_NAME=0.0.7 VERSION_NAME=0.0.13
VERSION_CODE=7 VERSION_CODE=13

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
data/README/badge_apple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
data/icon_512_nobox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
data/phone.pdn Normal file

Binary file not shown.

BIN
data/phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

77
examples/scn_send.sh Normal file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
#
# Call with `scn_send title`
# or `scn_send title content`
# or `scn_send title content priority`
#
#
if [ "$#" -lt 1 ]; then
echo "no title supplied via parameter"
exit 1
fi
################################################################################
# INSERT YOUR DATA HERE #
################################################################################
user_id=999
user_key="????????????????????????????????????????????????????????????????"
################################################################################
title=$1
content=""
if [ "$#" -gt 1 ]; then
content=$2
fi
priority=1
if [ "$#" -gt 2 ]; then
priority=$3
fi
usr_msg_id=$(uuidgen)
while true ; do
curlresp=$(curl -s -o /dev/null -w "%{http_code}" \
-d "user_id=$user_id" -d "user_key=$user_key" -d "title=$title" \
-d "content=$content" -d "priority=$priority" -d "msg_id=$usr_msg_id" \
https://scn.blackforestbytes.com/send.php)
if [ "$curlresp" == 200 ] ; then
echo "Successfully send"
exit 0
fi
if [ "$curlresp" == 400 ] ; then
echo "Bad request - something went wrong"
exit 1
fi
if [ "$curlresp" == 401 ] ; then
echo "Unauthorized - wrong userid/userkey"
exit 1
fi
if [ "$curlresp" == 403 ] ; then
echo "Quota exceeded - wait one hour before re-try"
sleep 3600
fi
if [ "$curlresp" == 412 ] ; then
echo "Precondition Failed - No device linked"
exit 1
fi
if [ "$curlresp" == 500 ] ; then
echo "Internal server error - waiting for better times"
sleep 60
fi
# if none of the above matched we probably hav no network ...
echo "Send failed (response code $curlresp) ... try again in 5s"
sleep 5
done

View File

@@ -1,13 +1,13 @@
SimpleCloudNotifier is a app to display messages that you can send to your phone with a simple POST request to the right URL. SimpleCloudNotifier is a app to display messages that you can send to your phone with a simple POST request to the right URL.
After you start the app it generates a userID and a private key. After you start the app it generates a UserID and a UserSecret.
Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone. Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
(see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl) (see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
Use it to Use it to
- send yourself automated messages from cron jobs - send yourself automated messages from cron jobs
- notify youreself when long-running scripts finish - notify yourself when long-running scripts finish
- send server error messages directly to your phone - send server error messages directly to your phone
- integrate with other online services - integrate with other online services

BIN
store/screenshot_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
store/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
store/screenshot_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -1,5 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<?php
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
{
require_once('/var/www/openwebanalytics/owa_php.php');
$owa = new owa_php();
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
$owa->setPageTitle('API (Short)');
$owa->trackPageView();
}
?>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs --> <link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->

View File

@@ -0,0 +1,56 @@
<?php
// insert your values here and rename to config.php
return
[
'global' =>
[
'prod' => true,
],
'database' =>
[
'host' => '?',
'database' => '?',
'user' => '?',
'password' => '?',
],
'firebase' =>
[
'type' => 'service_account',
'project_id' => '?',
'private_key_id' => '???',
'client_email' => '???.iam.gserviceaccount.com',
'client_id' => '???',
'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
'token_uri' => 'https://oauth2.googleapis.com/token',
'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
'client_x509_cert_url' => 'https://www.googleapis.com/robot/v1/metadata/x509/???f.iam.gserviceaccount.com',
'private_key' => "-----BEGIN PRIVATE KEY-----\n"
. "??????????\n"
. "-----END PRIVATE KEY-----\n",
'server_key' => '????',
],
'verify_api' =>
[
'package_name' => 'com.blackforestbytes.simplecloudnotifier',
'product_id' => '???',
'clientid' => '???.apps.googleusercontent.com',
'clientsecret' => '???',
'accesstoken' => file_exists('.verify_accesstoken') ? file_get_contents('.verify_accesstoken') : '',
'refreshtoken' => '???',
'scope' => 'https://www.googleapis.com/auth/androidpublisher',
],
'error_reporting' =>
[
'send-mail' => true,
'email-error-target' => '???@???.com',
'email-error-sender' => '???@???.com',
],
];

53
web/api/expand.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
include_once 'model.php';
$INPUT = array_merge($_GET, $_POST);
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errid'=>101, 'message' => 'Missing parameter [[user_id]]']));
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errid'=>102, 'message' => 'Missing parameter [[user_key]]']));
if (!isset($INPUT['scn_msg_id'])) die(json_encode(['success' => false, 'errid'=>103, 'message' => 'Missing parameter [[scn_msg_id]]']));
$user_id = $INPUT['user_id'];
$user_key = $INPUT['user_key'];
$scn_msg_id = $INPUT['scn_msg_id'];
//----------------------
$pdo = getDatabase();
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day, fcm_token FROM users WHERE user_id = :uid LIMIT 1');
$stmt->execute(['uid' => $user_id]);
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>201, 'message' => 'User not found']));
$data = $datas[0];
if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message' => 'User not found']));
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID not found']));
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
$stmt = $pdo->prepare('SELECT * FROM messages WHERE scn_message_id=:smid AND sender_user_id=:uid LIMIT 1');
$stmt->execute(['smid' => $scn_msg_id, 'uid' => $user_id]);
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>301, 'message' => 'Message not found']));
$msg = $datas[0];
api_return(200,
[
'success' => true,
'data' =>
[
'title' => $msg['title'],
'body' => $msg['content'],
'trimmed' => false,
'priority' => $msg['priority'],
'timestamp' => strtotime($msg['timestamp']),
'usr_msg_id' => $msg['usr_message_id'],
'scn_msg_id' => $msg['scn_message_id'],
],
'message' => 'ok'
]);

View File

@@ -30,7 +30,7 @@ if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid
$stmt = $pdo->prepare('SELECT COUNT(*) FROM messages WHERE ack=0 AND sender_user_id=:uid'); $stmt = $pdo->prepare('SELECT COUNT(*) FROM messages WHERE ack=0 AND sender_user_id=:uid');
$stmt->execute(['uid' => $user_id]); $stmt->execute(['uid' => $user_id]);
$nack_count = $stmt->fetch(PDO::FETCH_NUM); $nack_count = $stmt->fetch(PDO::FETCH_NUM)[0];
$quota = $data['quota_today']; $quota = $data['quota_today'];

View File

@@ -35,6 +35,14 @@ class Statics
public static $CFG = NULL; public static $CFG = NULL;
public static function quota_max($is_pro) { return $is_pro ? 1000 : 50; } public static function quota_max($is_pro) { return $is_pro ? 1000 : 50; }
public static function contentlen_max($is_pro) { return $is_pro ? 16384 : 2048; }
}
function str_limit($str, $len)
{
if (strlen($str)>$len) return substr($str, 0, $len-3)."...";
return $str;
} }
function getConfig() function getConfig()

View File

@@ -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' => $nack['timestamp'], 'timestamp' => strtotime($nack['timestamp']),
'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

@@ -1,5 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<?php
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
{
require_once('/var/www/openwebanalytics/owa_php.php');
$owa = new owa_php();
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
$owa->setPageTitle('API (Long)');
$owa->trackPageView();
}
?>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs --> <link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
@@ -187,6 +197,95 @@
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again. Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
</p> </p>
</div> </div>
<h2>Bash script example</h2>
<div class="section">
<p>
Depending on your use case it can be useful to create a bash script that handles things like resending messages if you have connection problems or waiting if there is no quota left.<br/>
Here is an example how such a scrippt could look like, you can put it into <code>/usr/local/sbin</code> and call it with <code>scn_send "title" "content"</code>
</p>
<pre style="color:#000000;" class="yellow-code"><span style="color:#3f7f59; font-weight:bold;">#!/usr/bin/env bash</span>
<span style="color:#3f7f59; ">#</span>
<span style="color:#3f7f59; "># Call with `scn_send title`</span>
<span style="color:#3f7f59; "># or `scn_send title content`</span>
<span style="color:#3f7f59; "># or `scn_send title content priority`</span>
<span style="color:#3f7f59; ">#</span>
<span style="color:#3f7f59; ">#</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -lt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"no title supplied via parameter"</span>
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#3f7f59; ">################################################################################</span>
<span style="color:#3f7f59; "># INSERT YOUR DATA HERE #</span>
<span style="color:#3f7f59; ">################################################################################</span>
user_id=999
user_key=<span style="color:#2a00ff; ">"????????????????????????????????????????????????????????????????"</span>
<span style="color:#3f7f59; ">################################################################################</span>
title=$1
content=<span style="color:#2a00ff; ">""</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
<span style="color:#7f0055; font-weight:bold; ">fi</span>
priority=1
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 2 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
priority=$3
<span style="color:#7f0055; font-weight:bold; ">fi</span>
usr_msg_id=$(uuidgen)
<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>
-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; ">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>)
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 200 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Successfully send"</span>
<span style="color:#7f0055; font-weight:bold; ">exit</span> 0
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 400 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Bad request - something went wrong"</span>
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 401 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Unauthorized - wrong </span><span style="color:#3f3fbf; ">userid/userkey</span><span style="color:#2a00ff; ">"</span>
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 403 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Quota exceeded - wait one hour before re-try"</span>
sleep 3600
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 412 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Precondition Failed - No device linked"</span>
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 500 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Internal server error - waiting for better times"</span>
sleep 60
<span style="color:#7f0055; font-weight:bold; ">fi</span>
<span style="color:#3f7f59; "># if none of the above matched we probably hav no network ...</span>
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Send failed (response code </span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">) ... try again in 5s"</span>
sleep 5
<span style="color:#7f0055; font-weight:bold; ">done</span>
</pre>
<p>
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
</p>
</div>
</div> </div>
</body> </body>

View File

@@ -42,6 +42,11 @@ body
border-left: .25rem solid #E53935; border-left: .25rem solid #E53935;
} }
.yellow-code
{
border-left: .25rem solid #FFCB05;
}
#mainpnl input, #mainpnl input,
#mainpnl textarea #mainpnl textarea
{ {
@@ -233,6 +238,13 @@ table.scode_table th:nth-child(2) {
margin-top: 1.75rem; margin-top: 1.75rem;
} }
.linkcaption:hover { .linkcaption:hover,
.linkcaption:focus {
text-decoration: none; text-decoration: none;
}
pre, pre span
{
font-family: Menlo, Consolas, monospace;
background: #F9F9F9;;
} }

View File

@@ -1,5 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<?php
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
{
require_once('/var/www/openwebanalytics/owa_php.php');
$owa = new owa_php();
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
$owa->setPageTitle('Index');
$owa->trackPageView();
}
?>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Simple Cloud Notifications</title> <title>Simple Cloud Notifications</title>
@@ -54,7 +64,7 @@
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div> <div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8"></textarea></div> <div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8" maxlength="2048"></textarea></div>
</div> </div>
<div class="row"> <div class="row">

View File

@@ -34,10 +34,10 @@ function send()
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
console.log('Status: ' + xhr.status); console.log('Status: ' + xhr.status);
if (xhr.status === 200) if (xhr.status === 200 || xhr.status === 401 || xhr.status === 403 || xhr.status === 412)
{ {
let resp = JSON.parse(xhr.responseText); let resp = JSON.parse(xhr.responseText);
if (!resp.success) if (!resp.success || xhr.status !== 200)
{ {
if (resp.errhighlight === 101) uid.classList.add('input-invalid'); if (resp.errhighlight === 101) uid.classList.add('input-invalid');
if (resp.errhighlight === 102) key.classList.add('input-invalid'); if (resp.errhighlight === 102) key.classList.add('input-invalid');

View File

@@ -32,8 +32,6 @@ try
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']);
if (strlen($message) > 120) api_return(400, ['success' => false, 'error' => ERR::TITLE_TOO_LONG, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)']);
if (strlen($content) > 10000) api_return(400, ['success' => false, 'error' => ERR::CONTENT_TOO_LONG, 'errhighlight' => 104, 'message' => 'Content too long (10000 characters)']);
if ($usrmsgid != null && strlen($usrmsgid) > 64) api_return(400, ['success' => false, 'error' => ERR::USR_MSG_ID_TOO_LONG, 'errhighlight' => -1, 'message' => 'MessageID too long (64 characters)']); if ($usrmsgid != null && strlen($usrmsgid) > 64) api_return(400, ['success' => false, 'error' => ERR::USR_MSG_ID_TOO_LONG, 'errhighlight' => -1, 'message' => 'MessageID too long (64 characters)']);
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -44,13 +42,20 @@ try
$stmt->execute(['uid' => $user_id]); $stmt->execute(['uid' => $user_id]);
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC); $datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($datas)<=0) die(json_encode(['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'User not found'])); if (count($datas)<=0) api_return(401, ['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'User not found']);
$data = $datas[0]; $data = $datas[0];
if ($data === null) api_return(401, ['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'User not found']); if ($data === null) api_return(401, ['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'User not found']);
if ($data['user_id'] !== (int)$user_id) api_return(401, ['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'UserID not found']); if ($data['user_id'] !== (int)$user_id) api_return(401, ['success' => false, 'error' => ERR::USER_NOT_FOUND, 'errhighlight' => 101, 'message' => 'UserID not found']);
if ($data['user_key'] !== $user_key) api_return(401, ['success' => false, 'error' => ERR::USER_AUTH_FAILED, 'errhighlight' => 102, 'message' => 'Authentification failed']); if ($data['user_key'] !== $user_key) api_return(401, ['success' => false, 'error' => ERR::USER_AUTH_FAILED, 'errhighlight' => 102, 'message' => 'Authentification failed']);
//------------------------------------------------------------------
if (strlen($message) > 120) api_return(400, ['success' => false, 'error' => ERR::TITLE_TOO_LONG, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)']);
if (strlen($content) > Statics::contentlen_max($data['is_pro'])) api_return(400, ['success' => false, 'error' => ERR::CONTENT_TOO_LONG, 'errhighlight' => 104, 'message' => 'Content too long ('.Statics::contentlen_max($data['is_pro']).' characters)']);
//------------------------------------------------------------------
$fcm = $data['fcm_token']; $fcm = $data['fcm_token'];
$new_quota = $data['quota_today'] + 1; $new_quota = $data['quota_today'] + 1;
@@ -115,14 +120,15 @@ try
// 'body' => $content, // 'body' => $content,
//], //],
'data' => 'data' =>
[ [
'title' => $message, 'title' => $message,
'body' => $content, 'body' => str_limit($content, 1900),
'priority' => $priority, 'trimmed' => (strlen($content) > 1900),
'timestamp' => time(), 'priority' => $priority,
'usr_msg_id' => $usrmsgid, 'timestamp' => time(),
'scn_msg_id' => $scn_msg_id, 'usr_msg_id' => $usrmsgid,
] 'scn_msg_id' => $scn_msg_id,
]
]); ]);
$header= $header=
[ [
@@ -148,6 +154,8 @@ try
api_return(500, ['success' => false, 'error' => ERR::FIREBASE_COM_FAILED, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()]); api_return(500, ['success' => false, 'error' => ERR::FIREBASE_COM_FAILED, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()]);
} }
//------------------------------------------------------------------
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid'); $stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
$stmt->execute(['uid' => $user_id, 'q' => $new_quota]); $stmt->execute(['uid' => $user_id, 'q' => $new_quota]);
@@ -156,6 +164,8 @@ try
$pdo->commit(); $pdo->commit();
//------------------------------------------------------------------
api_return(200, api_return(200,
[ [
'success' => true, 'success' => true,