34 Commits

Author SHA1 Message Date
90f4270b74 expand on click 2018-11-17 03:24:10 +01:00
f68d75c226 swipe to delete 2018-11-17 03:09:18 +01:00
9cf5133469 get nonack on info-call 2018-11-17 02:09:43 +01:00
75929aad48 rename php files 2018-11-17 01:30:41 +01:00
dca149ec4e move api to sub-dir 2018-11-17 01:29:12 +01:00
75a7b97d24 audio playback on notification stream 2018-11-17 01:21:53 +01:00
eb62873fb6 Android O repeat sound 2018-11-17 00:52:44 +01:00
f8effe7d1e no LED with android O 2018-11-17 00:24:22 +01:00
87a3d34315 scroll to newest message 2018-11-17 00:07:41 +01:00
0f38ad6e5c default_quota=50 2018-11-16 23:53:47 +01:00
0ec6ab71b2 fixed layout wrap in settings 2018-11-16 23:51:46 +01:00
b42204c87d fixed NPE on message recieve if app is not active 2018-11-16 23:28:48 +01:00
53cb259ec1 stupid sound shit 2018-11-13 18:11:17 +01:00
0150cc9e8e force POST in send.php 2018-11-13 16:23:04 +01:00
20214473f1 html styling 2018-11-13 16:06:22 +01:00
jenkins
586ac07ef3 [Jenkins] Increment version 2018-11-12 18:27:08 +01:00
66f82f26d5 channel bullshit 2018-11-12 18:25:41 +01:00
6cb2aa00fb intermed 2018-11-12 17:23:19 +01:00
96a236803e simplify settings layout 2018-11-12 16:29:43 +01:00
9d24044eb3 center pro message 2018-11-12 16:11:31 +01:00
90248bcb54 confirm-dialog 2018-11-12 16:04:59 +01:00
a3b2a1b14f only one account per fcm_token (2) 2018-11-12 16:01:06 +01:00
b21b159b95 fix user_key reset in promode 2018-11-12 15:58:59 +01:00
93ec261dc0 ack messages on recieve 2018-11-12 15:51:06 +01:00
ae246e9219 favicon 2018-11-12 15:28:07 +01:00
3f18fdd35a only one account per fcm_token 2018-11-12 15:22:21 +01:00
faf5207478 fixed wrong sql key 2018-11-12 15:11:26 +01:00
71f003dd66 fix error in send.php 2018-11-12 15:07:48 +01:00
3f85ab514e prevent purchase re-use 2018-11-12 14:57:54 +01:00
9eb5a6b1b9 fix NPE 2018-11-12 14:57:44 +01:00
8e26cd6078 fixed order verify 2018-11-12 14:49:09 +01:00
36b9263730 fix json_decode problems 2018-11-12 14:24:11 +01:00
3d29fecaec gracefully handle user_not_found 2018-11-12 14:23:50 +01:00
jenkins
92ac05f1e3 [Jenkins] Increment version 2018-11-12 13:06:38 +01:00
52 changed files with 1947 additions and 733 deletions

View File

@@ -14,8 +14,8 @@
<option name="values"> <option name="values">
<map> <map>
<entry key="assetSourceType" value="FILE" /> <entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="priority_low" /> <entry key="outputName" value="ic_garbage" />
<entry key="sourceFile" value="C:\Users\Mike\Downloads\Low Priority-595b40b75ba036ed117d9842.svg" /> <entry key="sourceFile" value="C:\Users\Mike\Downloads\garbage.svg" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>

View File

@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
<application <application
@@ -35,6 +36,8 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".service.BroadcastReceiverService" android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@@ -19,7 +19,7 @@ import androidx.lifecycle.ProcessLifecycleOwner;
public class SCNApp extends Application implements LifecycleObserver public class SCNApp extends Application implements LifecycleObserver
{ {
private static SCNApp instance; private static SCNApp instance;
private static WeakReference<MainActivity> mainActivity; private static WeakReference<MainActivity> mainActivity = new WeakReference<>(null);
public static final boolean LOCAL_DEBUG = BuildConfig.DEBUG; public static final boolean LOCAL_DEBUG = BuildConfig.DEBUG;
public static final boolean DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0"); public static final boolean DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0");
@@ -102,29 +102,11 @@ public class SCNApp extends Application implements LifecycleObserver
/* /*
==TODO== ==TODO==
[X] - Pro mode
[X] - no ads
[X] - more quota
[X] - restore pro mode
[X] - send pro state to server
[X] - prevent duplicate-send
[X] - send custom msg-id in API
[X] - prevent second ack on same msg-id
[X] - more in-depth API doc on website (?)
[X] - perhaps response codes in api (?)
[ ] - test notification channels [ ] - test notification channels
[ ] - startup time
[ ] - periodically get non-ack (option - even when not in-app)
[ ] - publish (+ HN post ?) [ ] - publish (+ HN post ?)
[ ] - Use for mscom server errrors
[ ] - Use for bfb server errors
[ ] - Use for transmission state
[ ] - Message on connnection lost (seperate process - resend until succ)
[ ] - Message on connnection regained
[ ] - Message on seed-count changed
*/ */

View File

@@ -0,0 +1,29 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
import android.widget.SeekBar;
public final class FI
{
private FI() throws InstantiationException { throw new InstantiationException(); }
public static SeekBar.OnSeekBarChangeListener SeekBarChanged(Func3to0<SeekBar, Integer, Boolean> action)
{
return new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
action.invoke(seekBar, progress, fromUser);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
}
}

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import java.util.TimeZone;
public class CMessage public class CMessage
{ {
public final long SCN_ID;
public final long Timestamp; public final long Timestamp;
public final String Title; public final String Title;
public final String Content; public final String Content;
@@ -21,8 +22,9 @@ public class CMessage
_format.setTimeZone(TimeZone.getDefault()); _format.setTimeZone(TimeZone.getDefault());
} }
public CMessage(long t, String mt, String mc, PriorityEnum p) public CMessage(long id, long t, String mt, String mc, PriorityEnum p)
{ {
SCN_ID = id;
Timestamp = t; Timestamp = t;
Title = mt; Title = mt;
Content = mc; Content = mc;

View File

@@ -3,15 +3,19 @@ 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.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.HashSet;
import java.util.Set;
public class CMessageList public class CMessageList
{ {
public ArrayList<CMessage> Messages; public ArrayList<CMessage> Messages;
public Set<String> AllAcks;
private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>(); private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>();
@@ -29,6 +33,7 @@ public class CMessageList
private CMessageList() private CMessageList()
{ {
Messages = new ArrayList<>(); Messages = new ArrayList<>();
AllAcks = new HashSet<>();
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);
@@ -38,14 +43,17 @@ public class CMessageList
String title = sharedPref.getString("message["+i+"].title", ""); String title = sharedPref.getString("message["+i+"].title", "");
String content = sharedPref.getString("message["+i+"].content", ""); String content = sharedPref.getString("message["+i+"].content", "");
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1)); PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
Messages.add(new CMessage(time, title, content, prio)); Messages.add(new CMessage(scnid, time, title, content, prio));
}
} }
public CMessage add(final long time, final String title, final String content, final PriorityEnum pe) AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
}
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
{ {
CMessage msg = new CMessage(time, title, content, pe); CMessage msg = new CMessage(scnid, time, title, content, pe);
boolean run = SCNApp.runOnUiThread(() -> boolean run = SCNApp.runOnUiThread(() ->
{ {
@@ -55,6 +63,7 @@ public class CMessageList
SharedPreferences.Editor e = sharedPref.edit(); SharedPreferences.Editor e = sharedPref.edit();
Messages.add(msg); Messages.add(msg);
AllAcks.add(Long.toHexString(msg.SCN_ID));
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0); while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
@@ -63,6 +72,9 @@ public class CMessageList
e.putString("message["+count+"].title", title); e.putString("message["+count+"].title", title);
e.putString("message["+count+"].content", content); e.putString("message["+count+"].content", content);
e.putInt( "message["+count+"].priority", pe.ID); e.putInt( "message["+count+"].priority", pe.ID);
e.putLong( "message["+count+"].scnid", scnid);
e.putStringSet("acks", AllAcks);
e.apply(); e.apply();
@@ -71,13 +83,15 @@ public class CMessageList
MessageAdapter a = ref.get(); MessageAdapter a = ref.get();
if (a == null) continue; if (a == null) continue;
a.customNotifyItemInserted(count); a.customNotifyItemInserted(count);
a.scrollToTop();
} }
CleanUpListener(); CleanUpListener();
}); });
if (!run) if (!run)
{ {
Messages.add(new CMessage(time, title, content, pe)); Messages.add(new CMessage(scnid, time, title, content, pe));
AllAcks.add(Long.toHexString(msg.SCN_ID));
fullSave(); fullSave();
} }
@@ -113,8 +127,11 @@ public class CMessageList
e.putString("message["+i+"].title", Messages.get(i).Title); e.putString("message["+i+"].title", Messages.get(i).Title);
e.putString("message["+i+"].content", Messages.get(i).Content); e.putString("message["+i+"].content", Messages.get(i).Content);
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID); 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.apply();
} }
@@ -124,6 +141,11 @@ public class CMessageList
return Messages.get(pos); return Messages.get(pos);
} }
public CMessage tryGetFromBack(int pos)
{
return tryGet(Messages.size() - pos - 1);
}
public int size() public int size()
{ {
return Messages.size(); return Messages.size();
@@ -142,4 +164,21 @@ public class CMessageList
if (_listener.get(i).get() == null) _listener.remove(i); if (_listener.get(i).get() == null) _listener.remove(i);
} }
} }
public boolean isAck(long id)
{
return AllAcks.contains(Long.toHexString(id));
}
public void remove(int index)
{
Messages.remove(index);
fullSave();
}
public void insert(int index, CMessage item)
{
Messages.add(index, item);
fullSave();
}
} }

View File

@@ -1,17 +1,34 @@
package com.blackforestbytes.simplecloudnotifier.model; package com.blackforestbytes.simplecloudnotifier.model;
import android.graphics.Color; import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
public class NotificationSettings public class NotificationSettings
{ {
public boolean EnableSound = false; public boolean EnableSound;
public String SoundName = ""; public String SoundName;
public String SoundSource = Uri.EMPTY.toString(); public String SoundSource;
public boolean RepeatSound = false; public boolean RepeatSound;
public boolean EnableLED = false; public boolean ForceVolume;
public int LEDColor = Color.BLUE; public int ForceVolumeValue;
public boolean EnableVibration = false; public boolean EnableLED;
public int LEDColor;
public boolean EnableVibration;
public NotificationSettings(PriorityEnum p)
{
EnableSound = (p == PriorityEnum.HIGH);
SoundName = "Default";
SoundSource = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
RepeatSound = false;
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
LEDColor = Color.BLUE;
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
ForceVolume = false;
ForceVolumeValue = 50;
}
} }

View File

@@ -8,6 +8,7 @@ import android.view.View;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.IABService; import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceId;
@@ -47,9 +48,9 @@ public class SCNSettings
public boolean Enabled = true; public boolean Enabled = true;
public int LocalCacheSize = 500; public int LocalCacheSize = 500;
public final NotificationSettings PriorityLow = new NotificationSettings(); public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW);
public final NotificationSettings PriorityNorm = new NotificationSettings(); public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL);
public final NotificationSettings PriorityHigh = new NotificationSettings(); public final NotificationSettings PriorityHigh = new NotificationSettings(PriorityEnum.HIGH);
// ------------------------------------------------------------ // ------------------------------------------------------------
@@ -77,6 +78,8 @@ public class SCNSettings
PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName); PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName);
PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource); PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource);
PriorityLow.LEDColor = sharedPref.getInt( "priority_low:led_color", PriorityLow.LEDColor); PriorityLow.LEDColor = sharedPref.getInt( "priority_low:led_color", PriorityLow.LEDColor);
PriorityLow.ForceVolume = sharedPref.getBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
PriorityLow.ForceVolumeValue = sharedPref.getInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
PriorityNorm.EnableLED = sharedPref.getBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED); PriorityNorm.EnableLED = sharedPref.getBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
PriorityNorm.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound); PriorityNorm.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
@@ -85,6 +88,8 @@ public class SCNSettings
PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName); PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName);
PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource); PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource);
PriorityNorm.LEDColor = sharedPref.getInt( "priority_norm:led_color", PriorityNorm.LEDColor); PriorityNorm.LEDColor = sharedPref.getInt( "priority_norm:led_color", PriorityNorm.LEDColor);
PriorityNorm.ForceVolume = sharedPref.getBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
PriorityNorm.ForceVolumeValue = sharedPref.getInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
PriorityHigh.EnableLED = sharedPref.getBoolean("priority_high:enabled_led", PriorityHigh.EnableLED); PriorityHigh.EnableLED = sharedPref.getBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
PriorityHigh.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound); PriorityHigh.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
@@ -93,6 +98,8 @@ public class SCNSettings
PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName); PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName);
PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource); PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource);
PriorityHigh.LEDColor = sharedPref.getInt( "priority_high:led_color", PriorityHigh.LEDColor); PriorityHigh.LEDColor = sharedPref.getInt( "priority_high:led_color", PriorityHigh.LEDColor);
PriorityHigh.ForceVolume = sharedPref.getBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
PriorityHigh.ForceVolumeValue = sharedPref.getInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
} }
public void save() public void save()
@@ -117,6 +124,8 @@ public class SCNSettings
e.putString( "priority_low:sound_name", PriorityLow.SoundName); e.putString( "priority_low:sound_name", PriorityLow.SoundName);
e.putString( "priority_low:sound_source", PriorityLow.SoundSource); e.putString( "priority_low:sound_source", PriorityLow.SoundSource);
e.putInt( "priority_low:led_color", PriorityLow.LEDColor); e.putInt( "priority_low:led_color", PriorityLow.LEDColor);
e.putBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
e.putInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
e.putBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED); e.putBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
e.putBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound); e.putBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
@@ -125,6 +134,8 @@ public class SCNSettings
e.putString( "priority_norm:sound_name", PriorityNorm.SoundName); e.putString( "priority_norm:sound_name", PriorityNorm.SoundName);
e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource); e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource);
e.putInt( "priority_norm:led_color", PriorityNorm.LEDColor); e.putInt( "priority_norm:led_color", PriorityNorm.LEDColor);
e.putBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
e.putInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
e.putBoolean("priority_high:enabled_led", PriorityHigh.EnableLED); e.putBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
e.putBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound); e.putBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
@@ -133,6 +144,8 @@ public class SCNSettings
e.putString( "priority_high:sound_name", PriorityHigh.SoundName); e.putString( "priority_high:sound_name", PriorityHigh.SoundName);
e.putString( "priority_high:sound_source", PriorityHigh.SoundSource); e.putString( "priority_high:sound_source", PriorityHigh.SoundSource);
e.putInt( "priority_high:led_color", PriorityHigh.LEDColor); e.putInt( "priority_high:led_color", PriorityHigh.LEDColor);
e.putBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
e.putInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
e.apply(); e.apply();
} }
@@ -154,7 +167,7 @@ public class SCNSettings
{ {
fcm_token_local = token; fcm_token_local = token;
save(); save();
if (!fcm_token_local.equals(fcm_token_server)) ServerCommunication.update(user_id, user_key, fcm_token_local, loader); if (!fcm_token_local.equals(fcm_token_server)) ServerCommunication.updateFCMToken(user_id, user_key, fcm_token_local, loader);
} }
else else
{ {
@@ -186,7 +199,7 @@ public class SCNSettings
{ {
if (!isConnected()) return; if (!isConnected()) return;
ServerCommunication.update(user_id, user_key, loader); ServerCommunication.resetSecret(user_id, user_key, loader);
} }
// refresh account data // refresh account data
@@ -195,10 +208,10 @@ public class SCNSettings
if (isConnected()) if (isConnected())
{ {
ServerCommunication.info(user_id, user_key, loader); ServerCommunication.info(user_id, user_key, loader);
if (promode_server != promode_local)
{ if (promode_server != promode_local) updateProState(loader);
updateProState(loader);
} if (!Str.equals(fcm_token_local, fcm_token_server)) work(a);
} }
else else
{ {

View File

@@ -4,7 +4,11 @@ import android.util.Log;
import android.view.View; import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.FBMService;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONTokener; import org.json.JSONTokener;
@@ -20,7 +24,7 @@ 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/"; public static final String BASE_URL = /*SCNApp.LOCAL_DEBUG ? "http://localhost:1010/" : */"https://scn.blackforestbytes.com/api/";
private static final OkHttpClient client = new OkHttpClient(); private static final OkHttpClient client = new OkHttpClient();
@@ -39,7 +43,7 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
e.printStackTrace(); Log.e("SC:register", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@@ -57,25 +61,25 @@ public class ServerCommunication
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) if (!json_bool(json, "success"))
{ {
SCNApp.showToast(json.getString("message"), 4000); SCNApp.showToast(json_str(json, "message"), 4000);
return; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().fcm_token_server = token; SCNSettings.inst().fcm_token_server = token;
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json.getBoolean("is_pro"); SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:register", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
@@ -87,17 +91,17 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:register", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
} }
public static void update(int id, String key, String token, View loader) public static void updateFCMToken(int id, String key, String token, View loader)
{ {
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(BASE_URL + "update.php?user_id="+id+"&user_key="+key+"&fcm_token="+token) .url(BASE_URL + "updateFCMToken.php?user_id="+id+"&user_key="+key+"&fcm_token="+token)
.build(); .build();
client.newCall(request).enqueue(new Callback() client.newCall(request).enqueue(new Callback()
@@ -105,7 +109,7 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
e.printStackTrace(); Log.e("SC:update_1", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@@ -123,25 +127,25 @@ public class ServerCommunication
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) if (!json_bool(json, "success"))
{ {
SCNApp.showToast(json.getString("message"), 4000); SCNApp.showToast(json_str(json, "message"), 4000);
return; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().fcm_token_server = token; SCNSettings.inst().fcm_token_server = token;
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json.getBoolean("is_pro"); SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:update_1", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
@@ -153,23 +157,23 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:update_1", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
} }
public static void update(int id, String key, View loader) public static void resetSecret(int id, String key, View loader)
{ {
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(BASE_URL + "update.php?user_id=" + id + "&user_key=" + key) .url(BASE_URL + "updateFCMToken.php?user_id=" + id + "&user_key=" + key)
.build(); .build();
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
e.printStackTrace(); Log.e("SC:update_2", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
@@ -185,21 +189,21 @@ public class ServerCommunication
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) { if (!json_bool(json, "success")) {
SCNApp.showToast(json.getString("message"), 4000); SCNApp.showToast(json_str(json, "message"), 4000);
return; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json.getBoolean("is_pro"); SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Log.e("SC:update_2", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} finally { } finally {
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
@@ -211,7 +215,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:update_2", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@@ -227,7 +231,7 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
e.printStackTrace(); Log.e("SC:info", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
@@ -246,20 +250,46 @@ public class ServerCommunication
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) { if (!json_bool(json, "success"))
SCNApp.showToast(json.getString("message"), 4000); {
return; SCNApp.showToast(json_str(json, "message"), 4000);
}
SCNSettings.inst().user_id = json.getInt("user_id"); int errid = json.optInt("errid", 0);
SCNSettings.inst().quota_curr = json.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); if (errid == 201 || errid == 202 || errid == 203 || errid == 204)
SCNSettings.inst().promode_server = json.getBoolean("is_pro"); {
// user not found or auth failed
SCNSettings.inst().user_id = -1;
SCNSettings.inst().user_key = "";
SCNSettings.inst().quota_curr = 0;
SCNSettings.inst().quota_max = 0;
SCNSettings.inst().promode_server = false;
SCNSettings.inst().fcm_token_server = "";
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
}
return;
}
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) {
e.printStackTrace(); Log.e("SC:info", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} finally { } finally {
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
@@ -271,7 +301,90 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); Log.e("SC:info", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
}
}
public static void requery(int id, String key, View loader)
{
try
{
Request request = new Request.Builder()
.url(BASE_URL + "requery.php?user_id=" + id + "&user_key=" + key)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("SC:requery", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
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", r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success"))
{
SCNApp.showToast(json_str(json, "message"), 4000);
return;
}
int count = json_int(json, "count");
JSONArray arr = json.getJSONArray("data");
for (int i = 0; i < count; i++)
{
JSONObject o = arr.getJSONObject(0);
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");
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) {
Log.e("SC:info", 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:requery", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@@ -289,7 +402,7 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
e.printStackTrace(); Log.e("SC:upgrade", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
@@ -305,21 +418,20 @@ public class ServerCommunication
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) { if (!json_bool(json, "success")) {
SCNApp.showToast(json.getString("message"), 4000); SCNApp.showToast(json_str(json, "message"), 4000);
return; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Log.e("SC:upgrade", e.toString());
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} finally { } finally {
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
@@ -334,4 +446,70 @@ public class ServerCommunication
} }
} }
public static void ack(int id, String key, CMessage msg)
{
try
{
Request request = new Request.Builder()
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg.SCN_ID)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("SC:ack", e.toString());
}
@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", r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success")) SCNApp.showToast(json_str(json, "message"), 4000);
} catch (Exception e) {
Log.e("SC:ack", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
}
}
});
}
catch (Exception e)
{
Log.e("SC:ack", e.toString());
}
}
private static boolean json_bool(JSONObject o, String key) throws JSONException
{
Object v = o.get(key);
if (v instanceof Integer) return ((int)v) != 0;
if (v instanceof Boolean) return ((boolean)v);
if (v instanceof String) return !Str.equals(((String)v), "0") && !Str.equals(((String)v), "false");
return o.getBoolean(key);
}
private static int json_int(JSONObject o, String key) throws JSONException
{
return o.getInt(key);
}
private static long json_lng(JSONObject o, String key) throws JSONException
{
return o.getLong(key);
}
private static String json_str(JSONObject o, String key) throws JSONException
{
return o.getString(key);
}
} }

View File

@@ -0,0 +1,42 @@
package com.blackforestbytes.simplecloudnotifier.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
public class BroadcastReceiverService extends BroadcastReceiver
{
public static final int NOTIF_SHOW_MAIN = 10021;
public static final int NOTIF_STOP_SOUND = 10022;
public static final String ID_KEY = "com.blackforestbytes.simplecloudnotifier.BroadcastID";
@Override
public void onReceive(Context context, Intent intent)
{
if (intent == null) return;
Bundle extras = intent.getExtras();
if (extras == null) return;
int notificationId = extras.getInt(ID_KEY, 0);
if (notificationId == 0) return;
else if (notificationId == NOTIF_SHOW_MAIN) showMain(context);
else if (notificationId == NOTIF_STOP_SOUND) stopNotificationSound();
else return;
}
private void stopNotificationSound()
{
SoundService.stop();
}
private void showMain(Context ctxt)
{
SoundService.stop();
Intent intent = new Intent(ctxt, MainActivity.class);
ctxt.startActivity(intent);
}
}

View File

@@ -8,6 +8,7 @@ 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;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.RemoteMessage;
@@ -36,9 +37,27 @@ public class FBMService extends FirebaseMessagingService
String title = remoteMessage.getData().get("title"); String title = remoteMessage.getData().get("title");
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"));
CMessage msg = CMessageList.inst().add(time, title, content, prio); recieveData(time, title, content, prio, scn_id, false);
}
catch (Exception e)
{
Log.e("FB:Err", e.toString());
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
}
}
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))
{
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
return;
}
if (SCNApp.isBackground()) if (SCNApp.isBackground())
{ {
@@ -48,11 +67,7 @@ public class FBMService extends FirebaseMessagingService
{ {
NotificationService.inst().showForeground(msg); NotificationService.inst().showForeground(msg);
} }
}
catch (Exception e) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
{
Log.e("FB:Err", e.toString());
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
}
} }
} }

View File

@@ -6,26 +6,31 @@ 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.Color;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.widget.Toast; import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.NotificationSettings; import com.blackforestbytes.simplecloudnotifier.model.NotificationSettings;
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum; import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity; import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
public class NotificationService public class NotificationService
{ {
private final static String CHANNEL_ID_LOW = "CHAN_BFB_SCN_MESSAGES_LOW"; private final static String CHANNEL_P0_ID = "CHAN_BFB_SCN_MESSAGES_P0";
private final static String CHANNEL_ID_NORM = "CHAN_BFB_SCN_MESSAGES_NORM"; private final static String CHANNEL_P1_ID = "CHAN_BFB_SCN_MESSAGES_P1";
private final static String CHANNEL_ID_HIGH = "CHAN_BFB_SCN_MESSAGES_HIGH"; private final static String CHANNEL_P2_ID = "CHAN_BFB_SCN_MESSAGES_P2";
private final static Object _lock = new Object(); private final static Object _lock = new Object();
private static NotificationService _inst = null; private static NotificationService _inst = null;
@@ -40,10 +45,10 @@ public class NotificationService
private NotificationService() private NotificationService()
{ {
updateChannels(); createChannels();
} }
public void updateChannels() private void createChannels()
{ {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
@@ -51,81 +56,201 @@ public class NotificationService
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class); NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
if (notifman == null) return; if (notifman == null) return;
NotificationChannel channelLow = notifman.getNotificationChannel(CHANNEL_ID_LOW); {
if (channelLow == null) notifman.createNotificationChannel(channelLow = new NotificationChannel(CHANNEL_ID_LOW, "Push notifications (low priority)", NotificationManager.IMPORTANCE_LOW)); NotificationChannel channel0 = notifman.getNotificationChannel(CHANNEL_P0_ID);
NotificationChannel channelNorm = notifman.getNotificationChannel(CHANNEL_ID_NORM); if (channel0 == null)
if (channelNorm == null) notifman.createNotificationChannel(channelNorm = new NotificationChannel(CHANNEL_ID_NORM, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT)); {
NotificationChannel channelHigh = notifman.getNotificationChannel(CHANNEL_ID_HIGH); channel0 = new NotificationChannel(CHANNEL_P0_ID, "Push notifications (low priority)", NotificationManager.IMPORTANCE_DEFAULT);
if (channelHigh == null) notifman.createNotificationChannel(channelHigh = new NotificationChannel(CHANNEL_ID_HIGH, "Push notifications (high priority)", NotificationManager.IMPORTANCE_HIGH)); channel0.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel0.setSound(null, null);
channelLow.setDescription("Messages from the API with priority set to low"); channel0.setVibrationPattern(null);
channelLow.enableLights(SCNSettings.inst().PriorityLow.EnableLED); channel0.setLightColor(Color.BLUE);
channelLow.setLightColor(SCNSettings.inst().PriorityLow.LEDColor); notifman.createNotificationChannel(channel0);
channelLow.enableVibration(SCNSettings.inst().PriorityLow.EnableVibration); }
channelLow.setVibrationPattern(new long[]{200}); }
{
channelNorm.setDescription("Messages from the API with priority set to normal"); NotificationChannel channel1 = notifman.getNotificationChannel(CHANNEL_P1_ID);
channelNorm.enableLights(SCNSettings.inst().PriorityNorm.EnableLED); if (channel1 == null)
channelNorm.setLightColor(SCNSettings.inst().PriorityNorm.LEDColor); {
channelNorm.enableVibration(SCNSettings.inst().PriorityNorm.EnableVibration); channel1 = new NotificationChannel(CHANNEL_P1_ID, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT);
channelNorm.setVibrationPattern(new long[]{200}); channel1.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel1.setSound(null, null);
channelHigh.setDescription("Messages from the API with priority set to high"); channel1.setVibrationPattern(null);
channelHigh.enableLights(SCNSettings.inst().PriorityHigh.EnableLED); channel1.setLightColor(Color.BLUE);
channelHigh.setLightColor(SCNSettings.inst().PriorityHigh.LEDColor); notifman.createNotificationChannel(channel1);
channelHigh.enableVibration(SCNSettings.inst().PriorityHigh.EnableVibration); }
channelHigh.setVibrationPattern(new long[]{200}); }
channelLow.setBypassDnd(true); {
channelLow.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); NotificationChannel channel2 = notifman.getNotificationChannel(CHANNEL_P2_ID);
if (channel2 == null)
{
channel2 = new NotificationChannel(CHANNEL_P1_ID, "Push notifications (high priority)", NotificationManager.IMPORTANCE_DEFAULT);
channel2.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel2.setSound(null, null);
channel2.setVibrationPattern(null);
channel2.setLightColor(Color.BLUE);
notifman.createNotificationChannel(channel2);
}
}
} }
public void showForeground(CMessage msg) public void showForeground(CMessage msg)
{ {
SCNApp.showToast("Message recieved: " + msg.Title, Toast.LENGTH_LONG); SCNApp.showToast("Message recieved: " + msg.Title, Toast.LENGTH_LONG);
try
{
NotificationSettings ns = SCNSettings.inst().PriorityNorm;
switch (msg.Priority)
{
case LOW: ns = SCNSettings.inst().PriorityLow; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
}
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, false);
if (ns.EnableVibration)
{
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(1500, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
v.vibrate(1500);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
} }
public void showBackground(CMessage msg) public void showBackground(CMessage msg)
{ {
Context ctxt = SCNApp.getContext(); Context ctxt = SCNApp.getContext();
String channel = CHANNEL_ID_NORM;
NotificationSettings ns = SCNSettings.inst().PriorityNorm; NotificationSettings ns = SCNSettings.inst().PriorityNorm;
switch (msg.Priority) switch (msg.Priority)
{ {
case LOW: ns = SCNSettings.inst().PriorityLow; channel = CHANNEL_ID_LOW; break; case LOW: ns = SCNSettings.inst().PriorityLow; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; channel = CHANNEL_ID_NORM; break; case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; channel = CHANNEL_ID_HIGH; break; case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
} }
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, channel); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
{
// old
showBackground_old(msg, ctxt, ns, msg.Priority);
}
else
{
// new
showBackground_new(msg, ctxt, ns, msg.Priority);
}
}
private String getChannel(PriorityEnum p)
{
switch (p)
{
case LOW: return CHANNEL_P0_ID;
case NORMAL: return CHANNEL_P1_ID;
case HIGH: return CHANNEL_P2_ID;
default: return CHANNEL_P0_ID;
}
}
private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_bfb); mBuilder.setSmallIcon(R.drawable.ic_bfb);
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); mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true); mBuilder.setAutoCancel(true);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
{
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);
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH); if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{200}); if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500); if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
}
if (ns.EnableSound && !ns.SoundSource.isEmpty()) if (ns.EnableSound && !ns.SoundSource.isEmpty() && !ns.RepeatSound) mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_NOTIFICATION);
{
mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_ALARM);
}
Intent intent = new Intent(ctxt, MainActivity.class); Intent intent = new Intent(ctxt, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0); PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
mBuilder.setContentIntent(pi); mBuilder.setContentIntent(pi);
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound)
{
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
PendingIntent pi_stop = PendingIntent.getBroadcast(SCNApp.getContext().getApplicationContext(), BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
mBuilder.setDeleteIntent(pi_stop);
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
}
Notification n = mBuilder.build(); Notification n = mBuilder.build();
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound) n.flags |= Notification.FLAG_INSISTENT;
if (mNotificationManager != null) mNotificationManager.notify(0, n); if (mNotificationManager != null) mNotificationManager.notify(0, n);
} }
@RequiresApi(api = Build.VERSION_CODES.O)
private void showBackground_new(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_bfb);
mBuilder.setContentTitle(msg.Title);
mBuilder.setContentText(msg.Content);
mBuilder.setShowWhen(true);
mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true);
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
Intent intnt_click = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
intnt_click.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_SHOW_MAIN);
PendingIntent pi = PendingIntent.getBroadcast(ctxt, 0, intnt_click, 0);
mBuilder.setContentIntent(pi);
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager == null) return;
if (ns.EnableSound && !Str.isNullOrWhitespace(ns.SoundSource))
{
if (ns.RepeatSound)
{
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
PendingIntent pi_stop = PendingIntent.getBroadcast(ctxt, BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
mBuilder.setDeleteIntent(pi_stop);
}
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
}
Notification n = mBuilder.build();
n.flags |= Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(0, n);
if (ns.EnableVibration)
{
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(VibrationEffect.createOneShot(1500, VibrationEffect.DEFAULT_AMPLITUDE));
}
//if (ns.EnableLED) { } // no LED in Android-O -- configure via Channel
}
} }

View File

@@ -0,0 +1,56 @@
package com.blackforestbytes.simplecloudnotifier.service;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.util.Log;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import java.io.IOException;
public class SoundService
{
private static MediaPlayer mpLast = null;
public static void play(boolean enableSound, String soundSource, boolean forceVolume, int forceVolumeValue, boolean loop)
{
if (!enableSound) return;
if (Str.isNullOrWhitespace(soundSource)) return;
stop();
if (forceVolume)
{
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (forceVolumeValue / 100.0)), 0);
}
try
{
MediaPlayer player = new MediaPlayer();
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
player.setDataSource(SCNApp.getContext(), Uri.parse(soundSource));
player.setLooping(loop);
player.setOnCompletionListener( mp -> { mp.stop(); mp.release(); });
player.setOnSeekCompleteListener(mp -> { mp.stop(); mp.release(); });
player.prepare();
player.start();
mpLast = player;
}
catch (IOException e)
{
Log.e("Sound::play", e.toString());
}
}
public static void stop()
{
if (mpLast != null && mpLast.isPlaying()) { mpLast.stop(); mpLast.release(); mpLast = null; }
}
}

View File

@@ -0,0 +1,77 @@
package com.blackforestbytes.simplecloudnotifier.util;
import android.graphics.Canvas;
import android.view.View;
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class MessageAdapterTouchHelper extends ItemTouchHelper.SimpleCallback
{
private MessageAdapterTouchHelperListener listener;
public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener)
{
super(dragDirs, swipeDirs);
this.listener = listener;
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target)
{
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
{
if (viewHolder != null)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
@Override
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().clearView(foregroundView);
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction)
{
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection)
{
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
public interface MessageAdapterTouchHelperListener
{
void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
}
}

View File

@@ -1,8 +1,10 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -21,6 +23,7 @@ import net.glxn.qrgen.android.QRCode;
import net.glxn.qrgen.core.image.ImageType; import net.glxn.qrgen.core.image.ImageType;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static android.content.Context.CLIPBOARD_SERVICE; import static android.content.Context.CLIPBOARD_SERVICE;
@@ -55,15 +58,47 @@ public class AccountFragment extends Fragment
v.findViewById(R.id.btnAccountReset).setOnClickListener(cv -> v.findViewById(R.id.btnAccountReset).setOnClickListener(cv ->
{ {
Activity a = getActivity();
if (a == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(a);
builder.setTitle("Confirm");
builder.setMessage("Reset account key?");
builder.setPositiveButton("YES", (dialog, which) -> {
View lpnl = v.findViewById(R.id.loadingPanel); View lpnl = v.findViewById(R.id.loadingPanel);
lpnl.setVisibility(View.VISIBLE); lpnl.setVisibility(View.VISIBLE);
SCNSettings.inst().reset(lpnl); SCNSettings.inst().reset(lpnl);
dialog.dismiss();
});
builder.setNegativeButton("NO", (dialog, which) -> dialog.dismiss());
AlertDialog alert = builder.create();
alert.show();
}); });
v.findViewById(R.id.btnClearLocalStorage).setOnClickListener(cv -> v.findViewById(R.id.btnClearLocalStorage).setOnClickListener(cv ->
{ {
Activity a = getActivity();
if (a == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(a);
builder.setTitle("Confirm");
builder.setMessage("Clear local messages?");
builder.setPositiveButton("YES", (dialog, which) -> {
CMessageList.inst().clear(); CMessageList.inst().clear();
SCNApp.showToast("Notifications cleared", 1000); SCNApp.showToast("Notifications cleared", 1000);
dialog.dismiss();
});
builder.setNegativeButton("NO", (dialog, which) -> dialog.dismiss());
AlertDialog alert = builder.create();
alert.show();
}); });
v.findViewById(R.id.btnQR).setOnClickListener(cv -> v.findViewById(R.id.btnQR).setOnClickListener(cv ->

View File

@@ -1,6 +1,7 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.os.Bundle; import android.os.Bundle;
import android.widget.RelativeLayout;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
@@ -18,6 +19,7 @@ import androidx.viewpager.widget.ViewPager;
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
{ {
public TabAdapter adpTabs; public TabAdapter adpTabs;
public RelativeLayout layoutRoot;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
@@ -28,6 +30,8 @@ public class MainActivity extends AppCompatActivity
NotificationService.inst(); NotificationService.inst();
CMessageList.inst(); CMessageList.inst();
layoutRoot = findViewById(R.id.layoutRoot);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@@ -39,6 +43,22 @@ public class MainActivity extends AppCompatActivity
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setupWithViewPager(viewPager); tabLayout.setupWithViewPager(viewPager);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /* */ }
@Override
public void onPageSelected(int position)
{
if (position != 2) adpTabs.tab3.onViewpagerHide();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
SCNApp.register(this); SCNApp.register(this);
IABService.startup(this); IABService.startup(this);
SCNSettings.inst().work(this); SCNSettings.inst().work(this);

View File

@@ -4,22 +4,36 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
public class MessageAdapter extends RecyclerView.Adapter public class MessageAdapter extends RecyclerView.Adapter
{ {
private final View vNoElements; private final View vNoElements;
private final LinearLayoutManager manLayout;
private final RecyclerView viewRecycler;
public MessageAdapter(View noElementsView) private WeakHashMap<MessagePresenter, Boolean> viewHolders = new WeakHashMap<>();
public MessageAdapter(View noElementsView, LinearLayoutManager layout, RecyclerView recycler)
{ {
vNoElements = noElementsView; vNoElements = noElementsView;
manLayout = layout;
viewRecycler = recycler;
CMessageList.inst().register(this); CMessageList.inst().register(this);
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE); vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
@@ -36,9 +50,18 @@ public class MessageAdapter extends RecyclerView.Adapter
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
{ {
CMessage msg = CMessageList.inst().tryGet(position); CMessage msg = CMessageList.inst().tryGetFromBack(position);
MessagePresenter view = (MessagePresenter) holder; MessagePresenter view = (MessagePresenter) holder;
view.setMessage(msg); view.setMessage(msg);
viewHolders.put(view, true);
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder)
{
if (holder instanceof MessagePresenter) viewHolders.remove(holder);
} }
@Override @Override
@@ -59,13 +82,33 @@ public class MessageAdapter extends RecyclerView.Adapter
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE); vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
} }
private class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener public void scrollToTop()
{
manLayout.smoothScrollToPosition(viewRecycler, null, 0);
}
public void removeItem(int position)
{
CMessageList.inst().remove(position);
notifyItemRemoved(position);
}
public void restoreItem(CMessage item, int position)
{
CMessageList.inst().insert(position, item);
notifyItemInserted(position);
}
public class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
{ {
private TextView tvTimestamp; private TextView tvTimestamp;
private TextView tvTitle; private TextView tvTitle;
private TextView tvMessage; private TextView tvMessage;
private ImageView ivPriority; private ImageView ivPriority;
public RelativeLayout viewForeground;
public RelativeLayout viewBackground;
private CMessage data; private CMessage data;
MessagePresenter(View itemView) MessagePresenter(View itemView)
@@ -75,7 +118,15 @@ public class MessageAdapter extends RecyclerView.Adapter
tvTitle = itemView.findViewById(R.id.tvTitle); tvTitle = itemView.findViewById(R.id.tvTitle);
tvMessage = itemView.findViewById(R.id.tvMessage); tvMessage = itemView.findViewById(R.id.tvMessage);
ivPriority = itemView.findViewById(R.id.ivPriority); ivPriority = itemView.findViewById(R.id.ivPriority);
viewForeground = itemView.findViewById(R.id.layoutFront);
viewBackground = itemView.findViewById(R.id.layoutBack);
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
tvTimestamp.setOnClickListener(this);
tvTitle.setOnClickListener(this);
tvMessage.setOnClickListener(this);
ivPriority.setOnClickListener(this);
viewForeground.setOnClickListener(this);
} }
void setMessage(CMessage msg) void setMessage(CMessage msg)
@@ -105,7 +156,16 @@ public class MessageAdapter extends RecyclerView.Adapter
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
//SCNApp.showToast(data.Title, Toast.LENGTH_LONG); for (MessagePresenter holder : MessageAdapter.this.viewHolders.keySet())
{
if (holder == null) continue;
if (holder == this) continue;
if (holder.tvMessage == null) continue;
if (holder.tvMessage.getMaxLines() == 6) continue;
holder.tvMessage.setMaxLines(6);
}
tvMessage.setMaxLines(9999);
} }
} }
} }

View File

@@ -1,24 +1,32 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.graphics.Color;
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 com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.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.google.android.gms.ads.doubleclick.PublisherAdRequest; import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
import com.google.android.gms.ads.doubleclick.PublisherAdView; import com.google.android.gms.ads.doubleclick.PublisherAdView;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
public class NotificationsFragment extends Fragment public class NotificationsFragment extends Fragment implements MessageAdapterTouchHelper.MessageAdapterTouchHelperListener
{ {
private PublisherAdView adView; private PublisherAdView adView;
private MessageAdapter adpMessages;
public NotificationsFragment() public NotificationsFragment()
{ {
@@ -31,8 +39,12 @@ public class NotificationsFragment extends Fragment
View v = inflater.inflate(R.layout.fragment_notifications, container, false); View v = inflater.inflate(R.layout.fragment_notifications, container, false);
RecyclerView rvMessages = v.findViewById(R.id.rvMessages); RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true)); LinearLayoutManager lman = new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, false);
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements))); rvMessages.setLayoutManager(lman);
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages);
adView = v.findViewById(R.id.adBanner); adView = v.findViewById(R.id.adBanner);
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build(); PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
@@ -45,6 +57,25 @@ public class NotificationsFragment extends Fragment
public void updateProState() public void updateProState()
{ {
adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE); if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position)
{
if (viewHolder instanceof MessageAdapter.MessagePresenter)
{
final CMessage deletedItem = CMessageList.inst().tryGet(viewHolder.getAdapterPosition());
final int deletedIndex = viewHolder.getAdapterPosition();
String name = deletedItem.Title;
adpMessages.removeItem(viewHolder.getAdapterPosition());
Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", view -> adpMessages.restoreItem(deletedItem, deletedIndex));
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
} }
} }

View File

@@ -1,9 +1,16 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.media.AudioAttributes;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -11,18 +18,24 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
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 com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.android.ThreadUtils;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.FI;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.service.IABService; import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import top.defaults.colorpicker.ColorPickerPopup; import top.defaults.colorpicker.ColorPickerPopup;
@@ -45,6 +58,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private View prefMsgLowLedColor_container; private View prefMsgLowLedColor_container;
private ImageView prefMsgLowLedColor_value; private ImageView prefMsgLowLedColor_value;
private Switch prefMsgLowEnableVibrations; private Switch prefMsgLowEnableVibrations;
private Switch prefMsgLowForceVolume;
private SeekBar prefMsgLowVolume;
private ImageView prefMsgLowVolumeTest;
private Switch prefMsgNormEnableSound; private Switch prefMsgNormEnableSound;
private TextView prefMsgNormRingtone_value; private TextView prefMsgNormRingtone_value;
@@ -54,6 +70,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private View prefMsgNormLedColor_container; private View prefMsgNormLedColor_container;
private ImageView prefMsgNormLedColor_value; private ImageView prefMsgNormLedColor_value;
private Switch prefMsgNormEnableVibrations; private Switch prefMsgNormEnableVibrations;
private Switch prefMsgNormForceVolume;
private SeekBar prefMsgNormVolume;
private ImageView prefMsgNormVolumeTest;
private Switch prefMsgHighEnableSound; private Switch prefMsgHighEnableSound;
private TextView prefMsgHighRingtone_value; private TextView prefMsgHighRingtone_value;
@@ -63,9 +82,14 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private View prefMsgHighLedColor_container; private View prefMsgHighLedColor_container;
private ImageView prefMsgHighLedColor_value; private ImageView prefMsgHighLedColor_value;
private Switch prefMsgHighEnableVibrations; private Switch prefMsgHighEnableVibrations;
private Switch prefMsgHighForceVolume;
private SeekBar prefMsgHighVolume;
private ImageView prefMsgHighVolumeTest;
private int musicPickerSwitch = -1; private int musicPickerSwitch = -1;
private MediaPlayer[] mPlayers = new MediaPlayer[3];
public SettingsFragment() public SettingsFragment()
{ {
// Required empty public constructor // Required empty public constructor
@@ -99,6 +123,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgLowLedColor_value = v.findViewById(R.id.prefMsgLowLedColor_value); prefMsgLowLedColor_value = v.findViewById(R.id.prefMsgLowLedColor_value);
prefMsgLowLedColor_container = v.findViewById(R.id.prefMsgLowLedColor_container); prefMsgLowLedColor_container = v.findViewById(R.id.prefMsgLowLedColor_container);
prefMsgLowEnableVibrations = v.findViewById(R.id.prefMsgLowEnableVibrations); prefMsgLowEnableVibrations = v.findViewById(R.id.prefMsgLowEnableVibrations);
prefMsgLowForceVolume = v.findViewById(R.id.prefMsgLowForceVolume);
prefMsgLowVolume = v.findViewById(R.id.prefMsgLowVolume);
prefMsgLowVolumeTest = v.findViewById(R.id.btnLowVolumeTest);
prefMsgNormEnableSound = v.findViewById(R.id.prefMsgNormEnableSound); prefMsgNormEnableSound = v.findViewById(R.id.prefMsgNormEnableSound);
prefMsgNormRingtone_value = v.findViewById(R.id.prefMsgNormRingtone_value); prefMsgNormRingtone_value = v.findViewById(R.id.prefMsgNormRingtone_value);
@@ -108,6 +135,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgNormLedColor_value = v.findViewById(R.id.prefMsgNormLedColor_value); prefMsgNormLedColor_value = v.findViewById(R.id.prefMsgNormLedColor_value);
prefMsgNormLedColor_container = v.findViewById(R.id.prefMsgNormLedColor_container); prefMsgNormLedColor_container = v.findViewById(R.id.prefMsgNormLedColor_container);
prefMsgNormEnableVibrations = v.findViewById(R.id.prefMsgNormEnableVibrations); prefMsgNormEnableVibrations = v.findViewById(R.id.prefMsgNormEnableVibrations);
prefMsgNormForceVolume = v.findViewById(R.id.prefMsgNormForceVolume);
prefMsgNormVolume = v.findViewById(R.id.prefMsgNormVolume);
prefMsgNormVolumeTest = v.findViewById(R.id.btnNormVolumeTest);
prefMsgHighEnableSound = v.findViewById(R.id.prefMsgHighEnableSound); prefMsgHighEnableSound = v.findViewById(R.id.prefMsgHighEnableSound);
prefMsgHighRingtone_value = v.findViewById(R.id.prefMsgHighRingtone_value); prefMsgHighRingtone_value = v.findViewById(R.id.prefMsgHighRingtone_value);
@@ -117,6 +147,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgHighLedColor_value = v.findViewById(R.id.prefMsgHighLedColor_value); prefMsgHighLedColor_value = v.findViewById(R.id.prefMsgHighLedColor_value);
prefMsgHighLedColor_container = v.findViewById(R.id.prefMsgHighLedColor_container); prefMsgHighLedColor_container = v.findViewById(R.id.prefMsgHighLedColor_container);
prefMsgHighEnableVibrations = v.findViewById(R.id.prefMsgHighEnableVibrations); prefMsgHighEnableVibrations = v.findViewById(R.id.prefMsgHighEnableVibrations);
prefMsgHighForceVolume = v.findViewById(R.id.prefMsgHighForceVolume);
prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume);
prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest);
} }
private void updateUI() private void updateUI()
@@ -142,6 +175,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
if (prefMsgLowEnableLED.isChecked() != s.PriorityLow.EnableLED) prefMsgLowEnableLED.setChecked(s.PriorityLow.EnableLED); if (prefMsgLowEnableLED.isChecked() != s.PriorityLow.EnableLED) prefMsgLowEnableLED.setChecked(s.PriorityLow.EnableLED);
prefMsgLowLedColor_value.setColorFilter(s.PriorityLow.LEDColor); prefMsgLowLedColor_value.setColorFilter(s.PriorityLow.LEDColor);
if (prefMsgLowEnableVibrations.isChecked() != s.PriorityLow.EnableVibration) prefMsgLowEnableVibrations.setChecked(s.PriorityLow.EnableVibration); if (prefMsgLowEnableVibrations.isChecked() != s.PriorityLow.EnableVibration) prefMsgLowEnableVibrations.setChecked(s.PriorityLow.EnableVibration);
if (prefMsgLowForceVolume.isChecked() != s.PriorityLow.ForceVolume) prefMsgLowForceVolume.setChecked(s.PriorityLow.ForceVolume);
if (prefMsgLowVolume.getMax() != 100) prefMsgLowVolume.setMax(100);
if (prefMsgLowVolume.getProgress() != s.PriorityLow.ForceVolumeValue) prefMsgLowVolume.setProgress(s.PriorityLow.ForceVolumeValue);
if (prefMsgLowVolume.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolume.setEnabled(s.PriorityLow.ForceVolume);
if (prefMsgLowVolumeTest.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setEnabled(s.PriorityLow.ForceVolume);
if (s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setColorFilter(null); else prefMsgLowVolumeTest.setColorFilter(Color.argb(150,200,200,200));
if (prefMsgNormEnableSound.isChecked() != s.PriorityNorm.EnableSound) prefMsgNormEnableSound.setChecked(s.PriorityNorm.EnableSound); if (prefMsgNormEnableSound.isChecked() != s.PriorityNorm.EnableSound) prefMsgNormEnableSound.setChecked(s.PriorityNorm.EnableSound);
if (!prefMsgNormRingtone_value.getText().equals(s.PriorityNorm.SoundName)) prefMsgNormRingtone_value.setText(s.PriorityNorm.SoundName); if (!prefMsgNormRingtone_value.getText().equals(s.PriorityNorm.SoundName)) prefMsgNormRingtone_value.setText(s.PriorityNorm.SoundName);
@@ -149,6 +188,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
if (prefMsgNormEnableLED.isChecked() != s.PriorityNorm.EnableLED) prefMsgNormEnableLED.setChecked(s.PriorityNorm.EnableLED); if (prefMsgNormEnableLED.isChecked() != s.PriorityNorm.EnableLED) prefMsgNormEnableLED.setChecked(s.PriorityNorm.EnableLED);
prefMsgNormLedColor_value.setColorFilter(s.PriorityNorm.LEDColor); prefMsgNormLedColor_value.setColorFilter(s.PriorityNorm.LEDColor);
if (prefMsgNormEnableVibrations.isChecked() != s.PriorityNorm.EnableVibration) prefMsgNormEnableVibrations.setChecked(s.PriorityNorm.EnableVibration); if (prefMsgNormEnableVibrations.isChecked() != s.PriorityNorm.EnableVibration) prefMsgNormEnableVibrations.setChecked(s.PriorityNorm.EnableVibration);
if (prefMsgNormForceVolume.isChecked() != s.PriorityNorm.ForceVolume) prefMsgNormForceVolume.setChecked(s.PriorityNorm.ForceVolume);
if (prefMsgNormVolume.getMax() != 100) prefMsgNormVolume.setMax(100);
if (prefMsgNormVolume.getProgress() != s.PriorityNorm.ForceVolumeValue) prefMsgNormVolume.setProgress(s.PriorityNorm.ForceVolumeValue);
if (prefMsgNormVolume.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolume.setEnabled(s.PriorityNorm.ForceVolume);
if (prefMsgNormVolumeTest.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setEnabled(s.PriorityNorm.ForceVolume);
if (s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setColorFilter(null); else prefMsgNormVolumeTest.setColorFilter(Color.argb(150,200,200,200));
if (prefMsgHighEnableSound.isChecked() != s.PriorityHigh.EnableSound) prefMsgHighEnableSound.setChecked(s.PriorityHigh.EnableSound); if (prefMsgHighEnableSound.isChecked() != s.PriorityHigh.EnableSound) prefMsgHighEnableSound.setChecked(s.PriorityHigh.EnableSound);
if (!prefMsgHighRingtone_value.getText().equals(s.PriorityHigh.SoundName)) prefMsgHighRingtone_value.setText(s.PriorityHigh.SoundName); if (!prefMsgHighRingtone_value.getText().equals(s.PriorityHigh.SoundName)) prefMsgHighRingtone_value.setText(s.PriorityHigh.SoundName);
@@ -156,6 +201,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
if (prefMsgHighEnableLED.isChecked() != s.PriorityHigh.EnableLED) prefMsgHighEnableLED.setChecked(s.PriorityHigh.EnableLED); if (prefMsgHighEnableLED.isChecked() != s.PriorityHigh.EnableLED) prefMsgHighEnableLED.setChecked(s.PriorityHigh.EnableLED);
prefMsgHighLedColor_value.setColorFilter(s.PriorityHigh.LEDColor); prefMsgHighLedColor_value.setColorFilter(s.PriorityHigh.LEDColor);
if (prefMsgHighEnableVibrations.isChecked() != s.PriorityHigh.EnableVibration) prefMsgHighEnableVibrations.setChecked(s.PriorityHigh.EnableVibration); if (prefMsgHighEnableVibrations.isChecked() != s.PriorityHigh.EnableVibration) prefMsgHighEnableVibrations.setChecked(s.PriorityHigh.EnableVibration);
if (prefMsgHighForceVolume.isChecked() != s.PriorityHigh.ForceVolume) prefMsgHighForceVolume.setChecked(s.PriorityHigh.ForceVolume);
if (prefMsgHighVolume.getMax() != 100) prefMsgHighVolume.setMax(100);
if (prefMsgHighVolume.getProgress() != s.PriorityHigh.ForceVolumeValue) prefMsgHighVolume.setProgress(s.PriorityHigh.ForceVolumeValue);
if (prefMsgHighVolume.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolume.setEnabled(s.PriorityHigh.ForceVolume);
if (prefMsgHighVolumeTest.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setEnabled(s.PriorityHigh.ForceVolume);
if (s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setColorFilter(null); else prefMsgHighVolumeTest.setColorFilter(Color.argb(150,200,200,200));
} }
private void initListener() private void initListener()
@@ -181,6 +232,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgLowEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableLED=b; saveAndUpdate(); }); prefMsgLowEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableLED=b; saveAndUpdate(); });
prefMsgLowLedColor_container.setOnClickListener(a -> chooseLEDColorLow()); prefMsgLowLedColor_container.setOnClickListener(a -> chooseLEDColorLow());
prefMsgLowEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableVibration=b; saveAndUpdate(); }); prefMsgLowEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableVibration=b; saveAndUpdate(); });
prefMsgLowForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.ForceVolume=b; saveAndUpdate(); });
prefMsgLowVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityLow.ForceVolumeValue=b; saveAndUpdate(); updateVolume(0, b); } }));
prefMsgLowVolumeTest.setOnClickListener((v) -> { if (s.PriorityLow.ForceVolume) playTestSound(0, prefMsgLowVolumeTest, s.PriorityLow.SoundSource, s.PriorityLow.ForceVolumeValue); });
prefMsgNormEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableSound=b; saveAndUpdate(); }); prefMsgNormEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableSound=b; saveAndUpdate(); });
prefMsgNormRingtone_container.setOnClickListener(a -> chooseRingtoneNorm()); prefMsgNormRingtone_container.setOnClickListener(a -> chooseRingtoneNorm());
@@ -188,6 +242,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgNormEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableLED=b; saveAndUpdate(); }); prefMsgNormEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableLED=b; saveAndUpdate(); });
prefMsgNormLedColor_container.setOnClickListener(a -> chooseLEDColorNorm()); prefMsgNormLedColor_container.setOnClickListener(a -> chooseLEDColorNorm());
prefMsgNormEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableVibration=b; saveAndUpdate(); }); prefMsgNormEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableVibration=b; saveAndUpdate(); });
prefMsgNormForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.ForceVolume=b; saveAndUpdate(); });
prefMsgNormVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityNorm.ForceVolumeValue=b; saveAndUpdate(); updateVolume(1, b); } }));
prefMsgNormVolumeTest.setOnClickListener((v) -> { if (s.PriorityNorm.ForceVolume) playTestSound(1, prefMsgNormVolumeTest, s.PriorityNorm.SoundSource, s.PriorityNorm.ForceVolumeValue); });
prefMsgHighEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableSound=b; saveAndUpdate(); }); prefMsgHighEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableSound=b; saveAndUpdate(); });
prefMsgHighRingtone_container.setOnClickListener(a -> chooseRingtoneHigh()); prefMsgHighRingtone_container.setOnClickListener(a -> chooseRingtoneHigh());
@@ -195,13 +252,78 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
prefMsgHighEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableLED=b; saveAndUpdate(); }); prefMsgHighEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableLED=b; saveAndUpdate(); });
prefMsgHighLedColor_container.setOnClickListener(a -> chooseLEDColorHigh()); prefMsgHighLedColor_container.setOnClickListener(a -> chooseLEDColorHigh());
prefMsgHighEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableVibration=b; saveAndUpdate(); }); prefMsgHighEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableVibration=b; saveAndUpdate(); });
prefMsgHighForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.ForceVolume=b; saveAndUpdate(); });
prefMsgHighVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityHigh.ForceVolumeValue=b; saveAndUpdate(); updateVolume(2, b); } }));
prefMsgHighVolumeTest.setOnClickListener((v) -> { if (s.PriorityHigh.ForceVolume) playTestSound(2, prefMsgHighVolumeTest, s.PriorityHigh.SoundSource, s.PriorityHigh.ForceVolumeValue); });
}
private void updateVolume(int idx, int volume)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
}
}
private void stopSound(final int idx, final ImageView iv)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
mPlayers[idx].stop();
mPlayers[idx].release();
iv.setImageResource(R.drawable.ic_play);
mPlayers[idx] = null;
}
}
private void playTestSound(final int idx, final ImageView iv, String src, int volume)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
mPlayers[idx].stop();
mPlayers[idx].release();
iv.setImageResource(R.drawable.ic_play);
mPlayers[idx] = null;
return;
}
if (Str.isNullOrWhitespace(src)) return;
if (volume == 0) return;
Context ctxt = getContext();
if (ctxt == null) return;
iv.setImageResource(R.drawable.ic_pause);
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
MediaPlayer player = mPlayers[idx] = new MediaPlayer();
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
try
{
player.setDataSource(ctxt, Uri.parse(src));
player.setLooping(false);
player.setOnCompletionListener( mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
player.setOnSeekCompleteListener(mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
player.prepare();
player.start();
}
catch (IOException e)
{
Log.e("SFRAG:play", e.toString());
}
} }
private void saveAndUpdate() private void saveAndUpdate()
{ {
SCNSettings.inst().save(); SCNSettings.inst().save();
updateUI(); updateUI();
NotificationService.inst().updateChannels();
} }
private void onUpgradeAccount() private void onUpgradeAccount()
@@ -213,9 +335,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
{ {
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE); Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE); if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE);
prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE); if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE);
prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE ); if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE );
} }
private int getCacheSizeIndex(int value) private int getCacheSizeIndex(int value)
@@ -233,7 +355,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker(); UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound"); ump.windowTitle("Choose notification sound");
ump.removeSilent(); ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM); ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone(); ump.ringtone();
ump.notification(); ump.notification();
ump.alarm(); ump.alarm();
@@ -248,7 +370,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker(); UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound"); ump.windowTitle("Choose notification sound");
ump.removeSilent(); ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM); ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone(); ump.ringtone();
ump.notification(); ump.notification();
ump.alarm(); ump.alarm();
@@ -263,7 +385,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker(); UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound"); ump.windowTitle("Choose notification sound");
ump.removeSilent(); ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM); ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone(); ump.ringtone();
ump.notification(); ump.notification();
ump.alarm(); ump.alarm();
@@ -356,4 +478,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
{ {
musicPickerSwitch = -1; musicPickerSwitch = -1;
} }
public void onViewpagerHide()
{
stopSound(0, prefMsgLowVolumeTest);
stopSound(1, prefMsgNormVolumeTest);
stopSound(2, prefMsgHighVolumeTest);
}
} }

View File

@@ -0,0 +1,16 @@
<vector
android:height="24dp"
android:width="24dp"
android:viewportHeight="438.536"
android:viewportWidth="438.536"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF3F51B5"
android:pathData="M164.453,0H18.276C13.324,0 9.041,1.807 5.425,5.424C1.808,9.04 0.001,13.322 0.001,18.271v401.991c0,4.948 1.807,9.233 5.424,12.847c3.619,3.617 7.902,5.428 12.851,5.428h146.181c4.949,0 9.231,-1.811 12.847,-5.428c3.617,-3.613 5.424,-7.898 5.424,-12.847V18.271c0,-4.952 -1.807,-9.231 -5.428,-12.847C173.685,1.807 169.402,0 164.453,0z"/>
<path
android:fillColor="#FF3F51B5"
android:pathData="M433.113,5.424C429.496,1.807 425.215,0 420.267,0H274.086c-4.949,0 -9.237,1.807 -12.847,5.424c-3.621,3.615 -5.432,7.898 -5.432,12.847v401.991c0,4.948 1.811,9.233 5.432,12.847c3.609,3.617 7.897,5.428 12.847,5.428h146.181c4.948,0 9.229,-1.811 12.847,-5.428c3.614,-3.613 5.421,-7.898 5.421,-12.847V18.271C438.534,13.319 436.73,9.04 433.113,5.424z"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector
android:height="24dp"
android:viewportHeight="41.999"
android:viewportWidth="41.999"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF3F51B5"
android:pathData="M36.068,20.176l-29,-20C6.761,-0.035 6.363,-0.057 6.035,0.114C5.706,0.287 5.5,0.627 5.5,0.999v40c0,0.372 0.206,0.713 0.535,0.886c0.146,0.076 0.306,0.114 0.465,0.114c0.199,0 0.397,-0.06 0.568,-0.177l29,-20c0.271,-0.187 0.432,-0.494 0.432,-0.823S36.338,20.363 36.068,20.176z"/>
</vector>

View File

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

View File

@@ -0,0 +1,12 @@
<vector
android:height="24dp"
android:viewportHeight="459"
android:viewportWidth="459"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF757575"
android:pathData="M0,153v153h102l127.5,127.5v-408L102,153H0zM344.25,229.5c0,-45.9 -25.5,-84.15 -63.75,-102v204C318.75,313.65 344.25,275.4 344.25,229.5zM280.5,5.1v53.55C354.45,81.6 408,147.899 408,229.5S354.45,377.4 280.5,400.35V453.9C382.5,430.949 459,339.15 459,229.5C459,119.85 382.5,28.049 280.5,5.1z"/>
</vector>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout
android:id="@+id/layoutRoot"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -4,7 +4,6 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="TooManyViews"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".view.SettingsFragment"> tools:context=".view.SettingsFragment">
@@ -33,23 +32,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout <Switch
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefAppEnabled" android:id="@+id/prefAppEnabled"
android:text="@string/str_enabled" android:text="@string/str_enabled"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:minHeight="48dp" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -67,7 +57,7 @@
<TextView <TextView
android:id="@+id/tvLocalCacheSize" android:id="@+id/tvLocalCacheSize"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/str_localcachesize" android:text="@string/str_localcachesize"
android:textColor="#000" android:textColor="#000"
@@ -102,6 +92,7 @@
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center|center"
android:minHeight="48dp"> android:minHeight="48dp">
<Button <Button
@@ -151,23 +142,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgLowEnableSound" android:id="@+id/prefMsgLowEnableSound"
android:text="@string/str_msg_enablesound" android:text="@string/str_msg_enablesound"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -176,20 +158,15 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout <LinearLayout
android:id="@+id/prefMsgLowRingtone_container" android:id="@+id/prefMsgLowRingtone_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintTop_toTopOf="parent"> android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView <TextView
android:id="@+id/tvMsgLowRingtone" android:id="@+id/tvMsgLowRingtone"
@@ -206,9 +183,8 @@
android:spinnerMode="dialog" android:spinnerMode="dialog"
android:text="Whatever" android:text="Whatever"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -217,22 +193,67 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgLowRepeatSound" android:id="@+id/prefMsgLowRepeatSound"
android:text="@string/str_repeatnotificationsound" android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnLowVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgLowVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/btnLowVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnLowVolume"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnLowVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@@ -242,23 +263,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgLowEnableLED" android:id="@+id/prefMsgLowEnableLED"
android:text="@string/str_enable_led" android:text="@string/str_enable_led"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -308,23 +320,15 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgLowEnableVibrations" android:id="@+id/prefMsgLowEnableVibrations"
android:text="@string/str_enable_vibration" android:text="@string/str_enable_vibration"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>
@@ -349,23 +353,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgNormEnableSound" android:id="@+id/prefMsgNormEnableSound"
android:text="@string/str_msg_enablesound" android:text="@string/str_msg_enablesound"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -374,20 +369,15 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout <LinearLayout
android:id="@+id/prefMsgNormRingtone_container" android:id="@+id/prefMsgNormRingtone_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintTop_toTopOf="parent"> android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView <TextView
android:id="@+id/tvMsgNormRingtone" android:id="@+id/tvMsgNormRingtone"
@@ -406,6 +396,58 @@
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgNormForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnNormVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgNormVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnNormVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnNormVolume"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnNormVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@@ -415,23 +457,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgNormRepeatSound" android:id="@+id/prefMsgNormRepeatSound"
android:text="@string/str_repeatnotificationsound" android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -440,23 +473,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgNormEnableLED" android:id="@+id/prefMsgNormEnableLED"
android:text="@string/str_enable_led" android:text="@string/str_enable_led"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -506,23 +530,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgNormEnableVibrations" android:id="@+id/prefMsgNormEnableVibrations"
android:text="@string/str_enable_vibration" android:text="@string/str_enable_vibration"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>
@@ -547,23 +562,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgHighEnableSound" android:id="@+id/prefMsgHighEnableSound"
android:text="@string/str_msg_enablesound" android:text="@string/str_msg_enablesound"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -572,18 +578,15 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout <LinearLayout
android:id="@+id/prefMsgHighRingtone_container" android:id="@+id/prefMsgHighRingtone_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@@ -604,6 +607,51 @@
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
<Switch
android:id="@+id/prefMsgHighForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnHighVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgHighVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnHighVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnHighVolume"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnHighVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@@ -613,23 +661,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgHighRepeatSound" android:id="@+id/prefMsgHighRepeatSound"
android:text="@string/str_repeatnotificationsound" android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -638,23 +677,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgHighEnableLED" android:id="@+id/prefMsgHighEnableLED"
android:text="@string/str_enable_led" android:text="@string/str_enable_led"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -704,23 +734,14 @@
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:background="#c0c0c0"/> android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch <Switch
android:id="@+id/prefMsgHighEnableVibrations" android:id="@+id/prefMsgHighEnableVibrations"
android:text="@string/str_enable_vibration" android:text="@string/str_enable_vibration"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="4dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="4dp"
app:layout_constraintTop_toTopOf="parent" /> android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@@ -1,9 +1,43 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:id="@+id/layoutBack"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/card_margin"
android:background="@color/bg_row_background">
<ImageView
android:id="@+id/delete_icon"
android:layout_width="@dimen/ic_delete"
android:layout_height="@dimen/ic_delete"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:src="@drawable/ic_trash"
android:contentDescription="@string/delete" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:layout_toLeftOf="@id/delete_icon"
android:text="@string/delete"
android:textColor="#fff"
android:textSize="13dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layoutFront"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_view" android:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -55,8 +89,8 @@
app:layout_constraintRight_toLeftOf="@+id/ivPriority" app:layout_constraintRight_toLeftOf="@+id/ivPriority"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_margin="4sp" android:layout_margin="4sp"
android:ellipsize="none" android:ellipsize="end"
android:maxLines="32" android:maxLines="6"
android:scrollHorizontally="false" android:scrollHorizontally="false"
android:text="asdasd asdasd asdasd a" /> android:text="asdasd asdasd asdasd a" />
@@ -78,4 +112,6 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </RelativeLayout>
</FrameLayout>

View File

@@ -7,4 +7,6 @@
<color name="colorHeaderForeground">#FFFFFF</color> <color name="colorHeaderForeground">#FFFFFF</color>
<color name="colorBlack">#000</color> <color name="colorBlack">#000</color>
<color name="bg_row_background">#fa315b</color>
</resources> </resources>

View File

@@ -2,4 +2,8 @@
<resources> <resources>
<dimen name="card_margin">5dp</dimen> <dimen name="card_margin">5dp</dimen>
<dimen name="card_album_radius">0dp</dimen> <dimen name="card_album_radius">0dp</dimen>
<dimen name="padd_10">10dp</dimen>
<dimen name="ic_delete">30dp</dimen>
<dimen name="thumbnail">90dp</dimen>
</resources> </resources>

View File

@@ -18,16 +18,20 @@
<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 notifications locally</string>
<string name="str_header_prio0">Notifications (low priority)</string> <string name="str_header_prio0">Notifications (priority 0 - Low)</string>
<string name="str_header_prio1">Notifications (normal priority)</string> <string name="str_header_prio1">Notifications (priority 1 - Normal)</string>
<string name="str_header_prio2">Notifications (high priority)</string> <string name="str_header_prio2">Notifications (priority 2 - High)</string>
<string name="str_msg_enablesound">Enable notification sound</string> <string name="str_msg_enablesound">Enable notification sound</string>
<string name="str_notificationsound">Notification sound</string> <string name="str_notificationsound">Notification sound</string>
<string name="str_repeatnotificationsound">Repeat notification sound</string> <string name="str_repeatnotificationsound">Repeat notification sound</string>
<string name="str_forcevolume">Automatically set the volume</string>
<string name="str_enable_led">Enable notification light</string> <string name="str_enable_led">Enable notification light</string>
<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_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="play_test_sound">Play test sound</string>
<string name="delete">DELETE</string>
</resources> </resources>

View File

@@ -1,3 +1,3 @@
#Sun Nov 11 19:37:57 CET 2018 #Mon Nov 12 18:26:41 CET 2018
VERSION_NAME=0.0.5 VERSION_NAME=0.0.7
VERSION_CODE=5 VERSION_CODE=7

BIN
data/icon_web.pdn Normal file

Binary file not shown.

View File

@@ -8,13 +8,21 @@
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">--> <!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="icon" type="image/png" href="/favicon.ico"/>
</head> </head>
<body> <body>
<div id="mainpnl">
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <div id="copyinfo">
<a tabindex="-1" href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl">
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p> <p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
<pre>curl \ <pre>curl \
@@ -32,12 +40,8 @@
--data "title={message_title}" \ --data "title={message_title}" \
https://scn.blackforestbytes.com/send.php</pre> https://scn.blackforestbytes.com/send.php</pre>
<a href="/index_more.php" class="button bordered tertiary" style="float: right; min-width: 100px; text-align: center">More</a> <a href="/api_more.php" class="button bordered tertiary" style="float: right; min-width: 100px; text-align: center">More</a>
</div> </div>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>

2
web/api/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
config.php
.verify_accesstoken

46
web/api/ack.php Normal file
View File

@@ -0,0 +1,46 @@
<?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 ack 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']));
$stmt = $pdo->prepare('UPDATE messages SET ack=1 WHERE scn_message_id=:smid AND sender_user_id=:uid');
$stmt->execute(['smid' => $scn_msg_id, 'uid' => $user_id]);
api_return(200,
[
'success' => true,
'prev_ack' => $datas[0]['ack'],
'new_ack' => 1,
'message' => 'ok'
]);

View File

@@ -15,7 +15,7 @@ $user_key = $INPUT['user_key'];
$pdo = getDatabase(); $pdo = getDatabase();
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day FROM users WHERE user_id = :uid LIMIT 1'); $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]); $stmt->execute(['uid' => $user_id]);
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC); $datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -26,18 +26,26 @@ if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message'
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID 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'])); if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
$stmt = $pdo->prepare('SELECT COUNT(*) FROM messages WHERE ack=0 AND sender_user_id=:uid');
$stmt->execute(['uid' => $user_id]);
$nack_count = $stmt->fetch(PDO::FETCH_NUM);
$quota = $data['quota_today']; $quota = $data['quota_today'];
$is_pro = $data['is_pro']; $is_pro = $data['is_pro'];
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0; if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
echo json_encode( api_return(200,
[ [
'success' => true, 'success' => true,
'message' => 'ok',
'user_id' => $user_id, 'user_id' => $user_id,
'quota' => $quota, 'quota' => $quota,
'quota_max' => Statics::quota_max($is_pro), 'quota_max' => Statics::quota_max($is_pro),
'is_pro' => $is_pro, 'is_pro' => $is_pro,
'message' => 'ok' 'fcm_token_set' => ($data['fcm_token'] != null),
'unack_count' => $nack_count,
]); ]);
return 0;

View File

@@ -2,12 +2,39 @@
include('lib/httpful.phar'); include('lib/httpful.phar');
class ERR
{
const NO_ERROR = 0000;
const MISSING_UID = 1101;
const MISSING_TOK = 1102;
const MISSING_TITLE = 1103;
const INVALID_PRIO = 1104;
const REQ_METHOD = 1105;
const NO_TITLE = 1201;
const TITLE_TOO_LONG = 1202;
const CONTENT_TOO_LONG = 1203;
const USR_MSG_ID_TOO_LONG = 1204;
const USER_NOT_FOUND = 1301;
const USER_AUTH_FAILED = 1302;
const NO_DEVICE_LINKED = 1401;
const QUOTA_REACHED = 2101;
const FIREBASE_COM_FAILED = 9901;
const FIREBASE_COM_ERRORED = 9902;
const INTERNAL_EXCEPTION = 9903;
}
class Statics class Statics
{ {
public static $DB = NULL; public static $DB = NULL;
public static $CFG = NULL; public static $CFG = NULL;
public static function quota_max($is_pro) { return $is_pro ? 1000 : 100; } public static function quota_max($is_pro) { return $is_pro ? 1000 : 50; }
} }
function getConfig() function getConfig()
@@ -123,7 +150,7 @@ function sendPOST($url, $body, $header)
if ($response->code != 200) throw new Exception("Repsponse code: " . $response->code); if ($response->code != 200) throw new Exception("Repsponse code: " . $response->code);
return $response->body; return $response->raw_body;
} }
function verifyOrderToken($tok) function verifyOrderToken($tok)
@@ -136,26 +163,25 @@ function verifyOrderToken($tok)
$product = getConfig()['verify_api']['product_id']; $product = getConfig()['verify_api']['product_id'];
$acctoken = getConfig()['verify_api']['accesstoken']; $acctoken = getConfig()['verify_api']['accesstoken'];
if ($acctoken == '') $acctoken = refreshVerifyToken(); if ($acctoken == '' || $acctoken == null || $acctoken == false) $acctoken = refreshVerifyToken();
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken; $url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
$response = $builder = \Httpful\Request::get($url)->send();
$obj = json_decode($response->raw_body, true);
$json = sendPOST($url, "", []); if ($response->code != 401 && ($obj === null || $obj === false))
$obj = json_decode($json);
if ($obj === null || $obj === false)
{ {
reportError('verify-token returned NULL'); reportError('verify-token returned NULL');
return false; return false;
} }
if (isset($obj['error']) && isset($obj['error']['code']) && $obj['error']['code'] == 401) // "Invalid Credentials" -- refresh acces_token if ($response->code == 401 || isset($obj['error']) && isset($obj['error']['code']) && $obj['error']['code'] == 401) // "Invalid Credentials" -- refresh acces_token
{ {
$acctoken = refreshVerifyToken(); $acctoken = refreshVerifyToken();
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken; $url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
$json = sendPOST($url, "", []); $response = $builder = \Httpful\Request::get($url)->send();
$obj = json_decode($json); $obj = json_decode($response->raw_body, true);
if ($obj === null || $obj === false) if ($obj === null || $obj === false)
{ {
@@ -185,28 +211,34 @@ function refreshVerifyToken()
'&client_secret='.getConfig()['verify_api']['clientsecret']; '&client_secret='.getConfig()['verify_api']['clientsecret'];
$json = sendPOST($url, "", []); $json = sendPOST($url, "", []);
$obj = json_decode($json); $obj = json_decode($json, true);
file_put_contents('.verify_accesstoken', $obj['access_token']); file_put_contents('.verify_accesstoken', $obj['access_token']);
return $obj->access_token; return $obj['access_token'];
} }
/**
* @param int $http_code
* @param array $message
*/
function api_return($http_code, $message) function api_return($http_code, $message)
{ {
http_response_code($http_code); http_response_code($http_code);
echo $message; header('Content-Type: application/json');
echo json_encode($message);
die(); die();
} }
/** /**
* @param String $str * @param String $str
* @param String[] $path * @param String[] $path
* @return mixed|null
*/ */
function try_json($str, $path) function try_json($str, $path)
{ {
try try
{ {
$o = json_decode($str); $o = json_decode($str, true);
foreach ($path as $p) $o = $o[$p]; foreach ($path as $p) $o = $o[$p];
return $o; return $o;
} }
@@ -215,3 +247,15 @@ function try_json($str, $path)
return null; return null;
} }
} }
//#################################################################################################################
if (getConfig()['global']['prod']) {
ini_set('display_errors', 0);
ini_set('log_errors', 1);
} else {
error_reporting(E_STRICT);
ini_set('display_errors', 1);
}
//#################################################################################################################

View File

@@ -15,16 +15,30 @@ $user_key = generateRandomAuthKey();
$pdo = getDatabase(); $pdo = getDatabase();
$pdo->beginTransaction();
if ($ispro) if ($ispro)
{ {
if (!verifyOrderToken($pro_token)) die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified'])); if (!verifyOrderToken($pro_token))
{
$pdo->rollBack();
die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified']));
}
$stmt = $pdo->prepare('UPDATE users SET is_pro=0, pro_token=NULL WHERE user_id <> :uid AND pro_token = :ptk');
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
} }
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, is_pro, pro_token, timestamp_accessed) VALUES (:key, :token, :bpro, :spro, NOW())'); $stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, is_pro, pro_token, timestamp_accessed) VALUES (:key, :token, :bpro, :spro, NOW())');
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken, 'bpro' => $ispro, 'spro' => $ispro ? $pro_token : null]); $stmt->execute(['key' => $user_key, 'token' => $fcmtoken, 'bpro' => $ispro, 'spro' => $ispro ? $pro_token : null]);
$user_id = $pdo->lastInsertId('user_id'); $user_id = $pdo->lastInsertId('user_id');
echo json_encode( $stmt = $pdo->prepare('UPDATE users SET fcm_token=NULL WHERE user_id <> :uid AND fcm_token=:ft');
$stmt->execute(['uid' => $user_id, 'ft' => $fcm_token]);
$pdo->commit();
api_return(200,
[ [
'success' => true, 'success' => true,
'user_id' => $user_id, 'user_id' => $user_id,
@@ -34,5 +48,3 @@ echo json_encode(
'is_pro' => $ispro, 'is_pro' => $ispro,
'message' => 'New user registered' 'message' => 'New user registered'
]); ]);
return 0;

56
web/api/requery.php Normal file
View File

@@ -0,0 +1,56 @@
<?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]]']));
$user_id = $INPUT['user_id'];
$user_key = $INPUT['user_key'];
//----------------------
$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 ack=0 AND sender_user_id=:uid ORDER BY `timestamp` DESC LIMIT 16');
$stmt->execute(['uid' => $user_id]);
$nonacks_sql = $stmt->fetchAll(PDO::FETCH_ASSOC);
$nonacks = [];
foreach ($nonacks_sql as $nack)
{
$nonacks []=
[
'title' => $nack['title'],
'body' => $nack['content'],
'priority' => $nack['priority'],
'timestamp' => $nack['timestamp'],
'usr_msg_id' => $nack['usr_message_id'],
'scn_msg_id' => $nack['scn_message_id'],
];
}
api_return(200,
[
'success' => true,
'message' => 'ok',
'count' => count($nonacks),
'data' => $nonacks,
]);

View File

@@ -1,3 +1,4 @@
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` CREATE TABLE `users`
( (
`user_id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` INT(11) NOT NULL AUTO_INCREMENT,
@@ -16,19 +17,21 @@ CREATE TABLE `users`
PRIMARY KEY (`user_id`) PRIMARY KEY (`user_id`)
); );
DROP TABLE IF EXISTS `messages`;
CREATE TABLE `messages` CREATE TABLE `messages`
( (
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT, `scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
`sender_user_id` INT(11) NOT NULL, `sender_user_id` INT(11) NOT NULL,
`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ack` BIT NOT NULL DEFAULT 0,
`title` VARCHAR(256) NOT NULL, `title` VARCHAR(256) NOT NULL,
`content` VARCHAR(12288) NULL, `content` VARCHAR(12288) NULL,
`priority` INT(11) NOT NULL, `priority` INT(11) NOT NULL,
`fcn_message_id` VARCHAR(256) NOT NULL, `fcm_message_id` VARCHAR(256) NULL,
`usr_message_id` VARCHAR(256) NULL, `usr_message_id` VARCHAR(256) NULL,
PRIMARY KEY (`scn_message_id`) PRIMARY KEY (`scn_message_id`)
) );

View File

@@ -34,10 +34,12 @@ $new_userkey = generateRandomAuthKey();
if ($fcm_token === null) if ($fcm_token === null)
{ {
// only gen new user_secret
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), user_key=:at WHERE user_id = :uid'); $stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), user_key=:at WHERE user_id = :uid');
$stmt->execute(['uid' => $user_id, 'at' => $new_userkey]); $stmt->execute(['uid' => $user_id, 'at' => $new_userkey]);
echo json_encode( api_return(200,
[ [
'success' => true, 'success' => true,
'user_id' => $user_id, 'user_id' => $user_id,
@@ -47,14 +49,18 @@ if ($fcm_token === null)
'is_pro' => $is_pro, 'is_pro' => $is_pro,
'message' => 'user updated' 'message' => 'user updated'
]); ]);
return 0;
} }
else else
{ {
// update fcm and gen new user_secret
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), fcm_token=:ft, user_key=:at WHERE user_id = :uid'); $stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), fcm_token=:ft, user_key=:at WHERE user_id = :uid');
$stmt->execute(['uid' => $user_id, 'ft' => $fcm_token, 'at' => $new_userkey]); $stmt->execute(['uid' => $user_id, 'ft' => $fcm_token, 'at' => $new_userkey]);
echo json_encode( $stmt = $pdo->prepare('UPDATE users SET fcm_token=NULL WHERE user_id <> :uid AND fcm_token=:ft');
$stmt->execute(['uid' => $user_id, 'ft' => $fcm_token]);
api_return(200,
[ [
'success' => true, 'success' => true,
'user_id' => $user_id, 'user_id' => $user_id,
@@ -64,5 +70,4 @@ else
'is_pro' => $is_pro, 'is_pro' => $is_pro,
'message' => 'user updated' 'message' => 'user updated'
]); ]);
return 0;
} }

View File

@@ -45,17 +45,15 @@ if ($ispro)
$stmt = $pdo->prepare('UPDATE users SET is_pro=0, pro_token=NULL WHERE user_id <> :uid AND pro_token = :ptk'); $stmt = $pdo->prepare('UPDATE users SET is_pro=0, pro_token=NULL WHERE user_id <> :uid AND pro_token = :ptk');
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]); $stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
echo json_encode( api_return(200,
[ [
'success' => true, 'success' => true,
'user_id' => $user_id, 'user_id' => $user_id,
'user_key' => $new_userkey, 'quota' => $data['quota_today'],
'quota' => $data['quota'],
'quota_max'=> Statics::quota_max(true), 'quota_max'=> Statics::quota_max(true),
'is_pro' => true, 'is_pro' => true,
'message' => 'user updated' 'message' => 'user updated'
]); ]);
return 0;
} }
else else
{ {
@@ -64,15 +62,13 @@ else
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), is_pro=0, pro_token=NULL WHERE user_id = :uid'); $stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), is_pro=0, pro_token=NULL WHERE user_id = :uid');
$stmt->execute(['uid' => $user_id]); $stmt->execute(['uid' => $user_id]);
echo json_encode( api_return(200,
[ [
'success' => true, 'success' => true,
'user_id' => $user_id, 'user_id' => $user_id,
'user_key' => $new_userkey, 'quota' => $data['quota_today'],
'quota' => $data['quota'],
'quota_max'=> Statics::quota_max(false), 'quota_max'=> Statics::quota_max(false),
'is_pro' => false, 'is_pro' => false,
'message' => 'user updated' 'message' => 'user updated'
]); ]);
return 0;
} }

View File

@@ -8,13 +8,21 @@
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">--> <!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="icon" type="image/png" href="/favicon.ico"/>
</head> </head>
<body> <body>
<div id="mainpnl">
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <div id="copyinfo">
<a tabindex="-1" href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl">
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<h2>Introduction</h2> <h2>Introduction</h2>
<div class="section"> <div class="section">
@@ -69,7 +77,6 @@
"canonical_ids":0, "canonical_ids":0,
"results": [{"message_id":"0:10000000000000000000000000000000d"}] "results": [{"message_id":"0:10000000000000000000000000000000d"}]
}, },
"messagecount":623,
"quota":17, "quota":17,
"quota_max":100 "quota_max":100
}</pre> }</pre>
@@ -100,6 +107,10 @@
<td data-label="Statuscode">403 (Forbidden)</td> <td data-label="Statuscode">403 (Forbidden)</td>
<td data-label="Explanation">The user has exceeded its daily quota - wait 24 hours or upgrade your account</td> <td data-label="Explanation">The user has exceeded its daily quota - wait 24 hours or upgrade your account</td>
</tr> </tr>
<tr>
<td data-label="Statuscode">412 (Precondition Failed)</td>
<td data-label="Explanation">There is no device connected with this account - open the app and press the refresh button in the account tab</td>
</tr>
<tr> <tr>
<td data-label="Statuscode">500 (Internal Server Error)</td> <td data-label="Statuscode">500 (Internal Server Error)</td>
<td data-label="Explanation">There was an internal error while sending your data - try again later</td> <td data-label="Explanation">There was an internal error while sending your data - try again later</td>
@@ -176,11 +187,7 @@
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>
</div> </div>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>

View File

@@ -86,13 +86,10 @@ body
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: -999; //z-index: -999;
} display: flex;
flex-direction: column;
#copyinfo a:hover text-align: right;
{
font-family: "Courier New", monospace;
color: #00F;
} }
#copyinfo a, #copyinfo a,
@@ -102,6 +99,14 @@ body
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
color: #AAA; color: #AAA;
text-decoration: none; text-decoration: none;
display: block;
line-height: 1em;
}
#copyinfo a:hover
{
font-family: "Courier New", monospace;
color: #0288D1;
} }
#tr_link #tr_link
@@ -193,6 +198,11 @@ input::-webkit-inner-spin-button {
pointer-events: none; pointer-events: none;
} }
.fullcenterflex .card
{
pointer-events: auto;
}
a.card, a.card,
a.card:active, a.card:active,
a.card:visited, a.card:visited,

BIN
web/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -9,14 +9,22 @@
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">--> <!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="icon" type="image/png" href="/favicon.ico"/>
</head> </head>
<body> <body>
<div id="copyinfo">
<a tabindex="-1" href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<form id="mainpnl"> <form id="mainpnl">
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a> <a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a href="/index_api.php" class="button bordered" id="tr_link">API</a> <a tabindex="-1" href="/api.php" class="button bordered" id="tr_link">API</a>
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div> <div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
@@ -55,10 +63,6 @@
</div> </div>
</form> </form>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
<script src="/js/logic.js" type="text/javascript" ></script> <script src="/js/logic.js" type="text/javascript" ></script>
<script src="/js/toastify.js"></script> <script src="/js/toastify.js"></script>
</body> </body>

View File

@@ -55,7 +55,7 @@ function send()
else else
{ {
window.location.href = window.location.href =
'/index_sent.php' + '/message_sent.php' +
'?ok=' + 1 + '?ok=' + 1 +
'&message_count=' + resp.messagecount + '&message_count=' + resp.messagecount +
'&quota=' + resp.quota + '&quota=' + resp.quota +

View File

@@ -8,9 +8,15 @@
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">--> <!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="icon" type="image/png" href="/favicon.ico"/>
</head> </head>
<body> <body>
<div id="copyinfo">
<a tabindex="-1" href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl"> <div id="mainpnl">
@@ -39,16 +45,12 @@
</div> </div>
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a> <a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a href="/index.php" class="button bordered" id="tr_link">Send</a> <a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
</div> </div>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>

View File

@@ -1,6 +1,6 @@
<?php <?php
include_once 'model.php'; include_once 'api/model.php';
try try
{ {
@@ -9,11 +9,13 @@ try
//sleep(1); //sleep(1);
//------------------------------------------------------------------ //------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_return(400, ['success' => false, 'error' => ERR::REQ_METHOD, 'errhighlight' => -1, 'message' => 'Invalid request method (must be POST)']);
$INPUT = array_merge($_GET, $_POST); $INPUT = array_merge($_GET, $_POST);
if (!isset($INPUT['user_id'])) api_return(400, json_encode(['success' => false, 'error' => 1101, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]'])); if (!isset($INPUT['user_id'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_UID, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]']);
if (!isset($INPUT['user_key'])) api_return(400, json_encode(['success' => false, 'error' => 1102, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]'])); if (!isset($INPUT['user_key'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_TOK, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]']);
if (!isset($INPUT['title'])) api_return(400, json_encode(['success' => false, 'error' => 1103, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]'])); if (!isset($INPUT['title'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_TITLE, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]']);
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -27,11 +29,12 @@ try
//------------------------------------------------------------------ //------------------------------------------------------------------
if ($priority !== '0' && $priority !== '1' && $priority !== '2') api_return(400, json_encode(['success' => false, 'error' => 1104, '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, json_encode(['success' => false, 'error' => 1201, '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, json_encode(['success' => false, 'error' => 1202, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)'])); 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, json_encode(['success' => false, 'error' => 1203, 'errhighlight' => 104, 'message' => 'Content too long (10000 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)']);
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -41,18 +44,23 @@ 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' => 1301, 'errhighlight' => 101, 'message' => 'User not found'])); if (count($datas)<=0) die(json_encode(['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, json_encode(['success' => false, 'error' => 1301, '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, json_encode(['success' => false, 'error' => 1302, '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, json_encode(['success' => false, 'error' => 1303, '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']);
$fcm = $data['fcm_token']; $fcm = $data['fcm_token'];
$new_quota = $data['quota_today'] + 1; $new_quota = $data['quota_today'] + 1;
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1; if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
if ($new_quota > Statics::quota_max($data['is_pro'])) api_return(403, json_encode(['success' => false, 'error' => 2101, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.Statics::quota_max($data['is_pro']).')'])); if ($new_quota > Statics::quota_max($data['is_pro'])) api_return(403, ['success' => false, 'error' => ERR::QUOTA_REACHED, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.Statics::quota_max($data['is_pro']).')']);
if ($fcm == null || $fcm == '' || $fcm == false)
{
api_return(412, ['success' => false, 'error' => ERR::NO_DEVICE_LINKED, 'errhighlight' => -1, 'message' => 'No device linked with this account']);
}
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -63,7 +71,7 @@ try
if (count($stmt->fetchAll(PDO::FETCH_ASSOC))>0) if (count($stmt->fetchAll(PDO::FETCH_ASSOC))>0)
{ {
api_return(200, json_encode( api_return(200,
[ [
'success' => true, 'success' => true,
'message' => 'Message already sent', 'message' => 'Message already sent',
@@ -73,12 +81,28 @@ try
'quota' => $data['quota_today'], 'quota' => $data['quota_today'],
'is_pro' => $data['is_pro'], 'is_pro' => $data['is_pro'],
'quota_max' => Statics::quota_max($data['is_pro']), 'quota_max' => Statics::quota_max($data['is_pro']),
])); ]);
} }
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO messages (sender_user_id, title, content, priority, fcm_message_id, usr_message_id) VALUES (:suid, :t, :c, :p, :fmid, :umid)');
$stmt->execute(
[
'suid' => $user_id,
't' => $message,
'c' => $content,
'p' => $priority,
'fmid' => null,
'umid' => $usrmsgid,
]);
$scn_msg_id = $pdo->lastInsertId();
$url = "https://fcm.googleapis.com/fcm/send"; $url = "https://fcm.googleapis.com/fcm/send";
$payload = json_encode( $payload = json_encode(
[ [
@@ -97,6 +121,7 @@ try
'priority' => $priority, 'priority' => $priority,
'timestamp' => time(), 'timestamp' => time(),
'usr_msg_id' => $usrmsgid, 'usr_msg_id' => $usrmsgid,
'scn_msg_id' => $scn_msg_id,
] ]
]); ]);
$header= $header=
@@ -112,32 +137,30 @@ try
if (try_json($httpresult, ['success']) != 1) if (try_json($httpresult, ['success']) != 1)
{ {
reportError("FCM communication failed (success_1 <> true)\n\n".$httpresult); reportError("FCM communication failed (success_1 <> true)\n\n".$httpresult);
api_return(403, json_encode(['success' => false, 'error' => 9902, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'])); $pdo->rollBack();
api_return(500, ['success' => false, 'error' => ERR::FIREBASE_COM_ERRORED, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.']);
} }
} }
catch (Exception $e) catch (Exception $e)
{ {
reportError("FCM communication failed", $e); reportError("FCM communication failed", $e);
api_return(403, json_encode(['success' => false, 'error' => 9901, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()])); $pdo->rollBack();
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]);
$stmt = $pdo->prepare('INSERT INTO messages (sender_user_id, title, content, priority, fcn_message_id, usr_message_id) VALUES (:suid, :t, :c, :p, :fmid, :umid)'); $stmt = $pdo->prepare('UPDATE messages SET fcm_message_id=:fmid WHERE scn_message_id=:smid');
$stmt->execute( $stmt->execute([ 'fmid' => try_json($httpresult, ['results', 0, 'message_id']), 'smid' => $scn_msg_id ]);
[
'suid' => $user_id,
't' => $message,
'c' => $content,
'p' => $priority,
'fmid' => try_json($httpresult, ['results', 'message_id']),
'umid' => $usrmsgid,
]);
api_return(200, json_encode( $pdo->commit();
api_return(200,
[ [
'success' => true, 'success' => true,
'error' => ERR::NO_ERROR,
'errhighlight' => -1,
'message' => 'Message sent', 'message' => 'Message sent',
'suppress_send' => false, 'suppress_send' => false,
'response' => $httpresult, 'response' => $httpresult,
@@ -145,9 +168,12 @@ try
'quota' => $new_quota, 'quota' => $new_quota,
'is_pro' => $data['is_pro'], 'is_pro' => $data['is_pro'],
'quota_max' => Statics::quota_max($data['is_pro']), 'quota_max' => Statics::quota_max($data['is_pro']),
])); 'scn_msg_id' => $scn_msg_id,
]);
} }
catch (Exception $mex) catch (Exception $mex)
{ {
reportError("Root try-catch triggered", $mex); reportError("Root try-catch triggered", $mex);
if ($pdo->inTransaction()) $pdo->rollBack();
api_return(500, ['success' => false, 'error' => ERR::INTERNAL_EXCEPTION, 'errhighlight' => -1, 'message' => 'PHP script threw exception.'."\n\n".'Exception: ' . $e->getMessage()]);
} }