19 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
50 changed files with 414 additions and 75 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

@@ -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

@@ -163,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)
@@ -192,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 ->
{ {
@@ -227,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();
@@ -329,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();
@@ -343,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");
@@ -397,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();
@@ -452,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();
@@ -471,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,9 +40,17 @@ 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"));
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); recieveData(time, title, content, prio, scn_id, false);
} }
}
catch (Exception e) catch (Exception e)
{ {
Log.e("FB:Err", e.toString()); Log.e("FB:Err", e.toString());

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,15 +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.setCategory(Notification.CATEGORY_MESSAGE);
mBuilder.setStyle(new NotificationCompat.InboxStyle()); mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
mBuilder.setGroup(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);
@@ -208,12 +209,15 @@ public class NotificationService
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);
@@ -245,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

@@ -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);
}); });
@@ -129,6 +131,7 @@ public class AccountFragment extends Fragment
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);
ImageView ivQuota = v.findViewById(R.id.ic_img_quota);
ImageButton btnQR = v.findViewById(R.id.btnQR); 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;
@@ -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

@@ -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;
@@ -216,7 +217,7 @@ 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(); }); prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
@@ -261,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())

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>

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>

View File

@@ -1,3 +1,3 @@
#Sat Nov 17 14:27:38 CET 2018 #Sun Nov 18 03:24:23 CET 2018
VERSION_NAME=0.0.9 VERSION_NAME=0.0.13
VERSION_CODE=9 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

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

@@ -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

@@ -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 -->

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>

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) > 2048) 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;
@@ -117,7 +122,8 @@ try
'data' => 'data' =>
[ [
'title' => $message, 'title' => $message,
'body' => $content, 'body' => str_limit($content, 1900),
'trimmed' => (strlen($content) > 1900),
'priority' => $priority, 'priority' => $priority,
'timestamp' => time(), 'timestamp' => time(),
'usr_msg_id' => $usrmsgid, 'usr_msg_id' => $usrmsgid,
@@ -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,