Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
b703853b28
|
|||
6f7529fc9b
|
|||
27b45098f0
|
|||
21f66d99cd
|
|||
1745051e6e
|
|||
![]() |
b395e054e6 | ||
90f4270b74
|
|||
f68d75c226
|
|||
9cf5133469
|
|||
75929aad48
|
|||
dca149ec4e
|
|||
75a7b97d24
|
|||
eb62873fb6
|
|||
f8effe7d1e
|
|||
87a3d34315
|
|||
0f38ad6e5c
|
|||
0ec6ab71b2
|
|||
b42204c87d
|
|||
53cb259ec1
|
|||
0150cc9e8e
|
|||
20214473f1
|
|||
![]() |
586ac07ef3 |
4
android/.idea/assetWizardSettings.xml
generated
4
android/.idea/assetWizardSettings.xml
generated
@@ -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>
|
||||||
|
@@ -36,6 +36,8 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name=".service.BroadcastReceiverService" android:exported="false" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@@ -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,33 +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 (?)
|
|
||||||
|
|
||||||
[X] - verify recieve
|
|
||||||
|
|
||||||
[ ] - Android O repeat sound
|
|
||||||
|
|
||||||
[ ] - 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
|
|
||||||
|
|
||||||
*/
|
*/
|
@@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -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);
|
||||||
@@ -42,6 +47,8 @@ public class CMessageList
|
|||||||
|
|
||||||
Messages.add(new CMessage(scnid, time, title, content, prio));
|
Messages.add(new CMessage(scnid, time, title, content, prio));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
|
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
|
||||||
@@ -56,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);
|
||||||
|
|
||||||
@@ -66,6 +74,8 @@ public class CMessageList
|
|||||||
e.putInt( "message["+count+"].priority", pe.ID);
|
e.putInt( "message["+count+"].priority", pe.ID);
|
||||||
e.putLong( "message["+count+"].scnid", scnid);
|
e.putLong( "message["+count+"].scnid", scnid);
|
||||||
|
|
||||||
|
e.putStringSet("acks", AllAcks);
|
||||||
|
|
||||||
e.apply();
|
e.apply();
|
||||||
|
|
||||||
for (WeakReference<MessageAdapter> ref : _listener)
|
for (WeakReference<MessageAdapter> ref : _listener)
|
||||||
@@ -73,6 +83,7 @@ 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();
|
||||||
});
|
});
|
||||||
@@ -80,6 +91,7 @@ public class CMessageList
|
|||||||
if (!run)
|
if (!run)
|
||||||
{
|
{
|
||||||
Messages.add(new CMessage(scnid, time, title, content, pe));
|
Messages.add(new CMessage(scnid, time, title, content, pe));
|
||||||
|
AllAcks.add(Long.toHexString(msg.SCN_ID));
|
||||||
fullSave();
|
fullSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +130,8 @@ public class CMessageList
|
|||||||
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
|
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.putStringSet("acks", AllAcks);
|
||||||
|
|
||||||
e.apply();
|
e.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,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();
|
||||||
@@ -145,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,9 @@ public class NotificationSettings
|
|||||||
public String SoundSource;
|
public String SoundSource;
|
||||||
public boolean RepeatSound;
|
public boolean RepeatSound;
|
||||||
|
|
||||||
|
public boolean ForceVolume;
|
||||||
|
public int ForceVolumeValue;
|
||||||
|
|
||||||
public boolean EnableLED;
|
public boolean EnableLED;
|
||||||
public int LEDColor;
|
public int LEDColor;
|
||||||
|
|
||||||
@@ -18,12 +21,14 @@ public class NotificationSettings
|
|||||||
|
|
||||||
public NotificationSettings(PriorityEnum p)
|
public NotificationSettings(PriorityEnum p)
|
||||||
{
|
{
|
||||||
EnableSound = (p == PriorityEnum.HIGH);
|
EnableSound = (p == PriorityEnum.HIGH);
|
||||||
SoundName = (p == PriorityEnum.HIGH) ? "Default" : "";
|
SoundName = "Default";
|
||||||
SoundSource = (p == PriorityEnum.HIGH) ? RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString() : Uri.EMPTY.toString();
|
SoundSource = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
|
||||||
RepeatSound = false;
|
RepeatSound = false;
|
||||||
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||||
LEDColor = Color.BLUE;
|
LEDColor = Color.BLUE;
|
||||||
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||||
|
ForceVolume = false;
|
||||||
|
ForceVolumeValue = 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -71,29 +71,35 @@ public class SCNSettings
|
|||||||
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
||||||
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||||
|
|
||||||
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||||
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||||
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||||
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||||
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);
|
||||||
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||||
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||||
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);
|
||||||
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||||
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||||
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()
|
||||||
@@ -101,39 +107,45 @@ public class SCNSettings
|
|||||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||||
SharedPreferences.Editor e = sharedPref.edit();
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
e.putInt( "quota_curr", quota_curr);
|
e.putInt( "quota_curr", quota_curr);
|
||||||
e.putInt( "quota_max", quota_max);
|
e.putInt( "quota_max", quota_max);
|
||||||
e.putInt( "user_id", user_id);
|
e.putInt( "user_id", user_id);
|
||||||
e.putString( "user_key", user_key);
|
e.putString( "user_key", user_key);
|
||||||
e.putString( "fcm_token_local", fcm_token_local);
|
e.putString( "fcm_token_local", fcm_token_local);
|
||||||
e.putString( "fcm_token_server", fcm_token_server);
|
e.putString( "fcm_token_server", fcm_token_server);
|
||||||
|
|
||||||
e.putBoolean("app_enabled", Enabled);
|
e.putBoolean("app_enabled", Enabled);
|
||||||
e.putInt( "local_cache_size", LocalCacheSize);
|
e.putInt( "local_cache_size", LocalCacheSize);
|
||||||
|
|
||||||
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||||
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||||
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||||
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||||
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);
|
||||||
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||||
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||||
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);
|
||||||
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||||
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -155,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
|
||||||
{
|
{
|
||||||
@@ -187,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
|
||||||
|
@@ -5,7 +5,9 @@ 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.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.FBMService;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
@@ -22,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();
|
||||||
|
|
||||||
@@ -94,12 +96,12 @@ public class ServerCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -160,12 +162,12 @@ public class ServerCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
@@ -280,6 +282,9 @@ public class ServerCommunication
|
|||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("SC:info", e.toString());
|
Log.e("SC:info", e.toString());
|
||||||
SCNApp.showToast("Communication with server failed", 4000);
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
@@ -298,6 +303,75 @@ public class ServerCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
|
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -355,12 +429,12 @@ public class ServerCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ack(int id, String key, CMessage msg)
|
public static void ack(int id, String key, long msg_scn_id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg.SCN_ID)
|
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback() {
|
client.newCall(request).enqueue(new Callback() {
|
||||||
@@ -370,8 +444,24 @@ public class ServerCommunication
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call call, Response response) {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -396,6 +486,11 @@ public class ServerCommunication
|
|||||||
return o.getInt(key);
|
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
|
private static String json_str(JSONObject o, String key) throws JSONException
|
||||||
{
|
{
|
||||||
return o.getString(key);
|
return o.getString(key);
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -39,18 +39,7 @@ public class FBMService extends FirebaseMessagingService
|
|||||||
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
|
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
|
||||||
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
|
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
|
||||||
|
|
||||||
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
|
recieveData(time, title, content, prio, scn_id, false);
|
||||||
|
|
||||||
if (SCNApp.isBackground())
|
|
||||||
{
|
|
||||||
NotificationService.inst().showBackground(msg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NotificationService.inst().showForeground(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -58,4 +47,27 @@ public class FBMService extends FirebaseMessagingService
|
|||||||
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
|
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)
|
||||||
|
{
|
||||||
|
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, scn_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
|
||||||
|
|
||||||
|
if (SCNApp.isBackground())
|
||||||
|
{
|
||||||
|
NotificationService.inst().showBackground(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotificationService.inst().showForeground(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -6,10 +6,8 @@ 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.media.AudioAttributes;
|
import android.graphics.Color;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.Ringtone;
|
|
||||||
import android.media.RingtoneManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
@@ -18,17 +16,21 @@ 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 = "CHAN_BFB_SCN_MESSAGES";
|
private final static String CHANNEL_P0_ID = "CHAN_BFB_SCN_MESSAGES_P0";
|
||||||
|
private final static String CHANNEL_P1_ID = "CHAN_BFB_SCN_MESSAGES_P1";
|
||||||
|
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;
|
||||||
@@ -43,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;
|
||||||
|
|
||||||
@@ -54,14 +56,41 @@ public class NotificationService
|
|||||||
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
|
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
|
||||||
if (notifman == null) return;
|
if (notifman == null) return;
|
||||||
|
|
||||||
NotificationChannel channel = notifman.getNotificationChannel(CHANNEL_ID);
|
|
||||||
if (channel == null)
|
|
||||||
{
|
{
|
||||||
channel = new NotificationChannel(CHANNEL_ID, "Push notifications", NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationChannel channel0 = notifman.getNotificationChannel(CHANNEL_P0_ID);
|
||||||
channel.setDescription("Push notifications from the server");
|
if (channel0 == null)
|
||||||
channel.setSound(null, null);
|
{
|
||||||
channel.setVibrationPattern(null);
|
channel0 = new NotificationChannel(CHANNEL_P0_ID, "Push notifications (low priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
notifman.createNotificationChannel(channel);
|
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);
|
||||||
|
channel0.setVibrationPattern(null);
|
||||||
|
channel0.setLightColor(Color.BLUE);
|
||||||
|
notifman.createNotificationChannel(channel0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
NotificationChannel channel1 = notifman.getNotificationChannel(CHANNEL_P1_ID);
|
||||||
|
if (channel1 == null)
|
||||||
|
{
|
||||||
|
channel1 = new NotificationChannel(CHANNEL_P1_ID, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
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);
|
||||||
|
channel1.setVibrationPattern(null);
|
||||||
|
channel1.setLightColor(Color.BLUE);
|
||||||
|
notifman.createNotificationChannel(channel1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,13 +108,7 @@ public class NotificationService
|
|||||||
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
|
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ns.EnableSound && !ns.SoundSource.isEmpty())
|
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, false);
|
||||||
{
|
|
||||||
Ringtone rt = RingtoneManager.getRingtone(SCNApp.getContext(), Uri.parse(ns.SoundSource));
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) rt.setLooping(false);
|
|
||||||
rt.play();
|
|
||||||
new Thread(() -> { try { Thread.sleep(5*1000); } catch (InterruptedException e) { /* */ } rt.stop(); }).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ns.EnableVibration)
|
if (ns.EnableVibration)
|
||||||
{
|
{
|
||||||
@@ -118,75 +141,116 @@ public class NotificationService
|
|||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||||
{
|
{
|
||||||
// old
|
// old
|
||||||
|
showBackground_old(msg, ctxt, ns, msg.Priority);
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, CHANNEL_ID);
|
|
||||||
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 (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);
|
|
||||||
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
|
|
||||||
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
|
|
||||||
|
|
||||||
if (ns.EnableSound && !ns.SoundSource.isEmpty()) mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_NOTIFICATION);
|
|
||||||
|
|
||||||
Intent intent = new Intent(ctxt, MainActivity.class);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
|
||||||
mBuilder.setContentIntent(pi);
|
|
||||||
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
Notification n = mBuilder.build();
|
|
||||||
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound) n.flags |= Notification.FLAG_INSISTENT;
|
|
||||||
|
|
||||||
if (mNotificationManager != null) mNotificationManager.notify(0, n);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// new
|
// new
|
||||||
|
showBackground_new(msg, ctxt, ns, msg.Priority);
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, CHANNEL_ID);
|
|
||||||
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 intent = new Intent(ctxt, MainActivity.class);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
|
||||||
mBuilder.setContentIntent(pi);
|
|
||||||
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (mNotificationManager == null) return;
|
|
||||||
|
|
||||||
Notification n = mBuilder.build();
|
|
||||||
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
|
||||||
|
|
||||||
mNotificationManager.notify(0, n);
|
|
||||||
|
|
||||||
if (ns.EnableSound && !ns.SoundSource.isEmpty())
|
|
||||||
{
|
|
||||||
Ringtone rt = RingtoneManager.getRingtone(SCNApp.getContext(), Uri.parse(ns.SoundSource));
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) rt.setLooping(false);
|
|
||||||
rt.play();
|
|
||||||
new Thread(() -> { try { Thread.sleep(5*1000); } catch (InterruptedException e) { /* */ } rt.stop(); }).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ns.EnableVibration)
|
|
||||||
{
|
|
||||||
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
|
||||||
v.vibrate(VibrationEffect.createOneShot(1500, VibrationEffect.DEFAULT_AMPLITUDE));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.setContentTitle(msg.Title);
|
||||||
|
mBuilder.setContentText(msg.Content);
|
||||||
|
mBuilder.setShowWhen(true);
|
||||||
|
mBuilder.setWhen(msg.Timestamp * 1000);
|
||||||
|
mBuilder.setAutoCancel(true);
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
|
||||||
|
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
|
||||||
|
|
||||||
|
if (ns.EnableSound && !ns.SoundSource.isEmpty() && !ns.RepeatSound) mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_NOTIFICATION);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctxt, MainActivity.class);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
||||||
|
mBuilder.setContentIntent(pi);
|
||||||
|
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();
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
@@ -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,23 +82,51 @@ 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)
|
||||||
{
|
{
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
@@ -47,4 +59,23 @@ public class NotificationsFragment extends Fragment
|
|||||||
{
|
{
|
||||||
if (adView != null) 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,6 +252,72 @@ 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()
|
||||||
@@ -232,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();
|
||||||
@@ -247,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();
|
||||||
@@ -262,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();
|
||||||
@@ -355,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);
|
||||||
|
}
|
||||||
}
|
}
|
16
android/app/src/main/res/drawable/ic_pause.xml
Normal file
16
android/app/src/main/res/drawable/ic_pause.xml
Normal 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>
|
12
android/app/src/main/res/drawable/ic_play.xml
Normal file
12
android/app/src/main/res/drawable/ic_play.xml
Normal 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>
|
16
android/app/src/main/res/drawable/ic_trash.xml
Normal file
16
android/app/src/main/res/drawable/ic_trash.xml
Normal 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>
|
12
android/app/src/main/res/drawable/ic_volume.xml
Normal file
12
android/app/src/main/res/drawable/ic_volume.xml
Normal 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>
|
@@ -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"
|
||||||
|
@@ -57,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"
|
||||||
@@ -202,6 +202,60 @@
|
|||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:minHeight="48dp" />
|
android:minHeight="48dp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:background="#c0c0c0"/>
|
||||||
|
|
||||||
|
<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_constraintEnd_toStartOf="@+id/btnLowVolumeTest"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icnLowVolume"
|
||||||
|
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>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
@@ -342,6 +396,60 @@
|
|||||||
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>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
@@ -499,6 +607,53 @@
|
|||||||
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>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
|
@@ -1,81 +1,117 @@
|
|||||||
<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">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<RelativeLayout
|
||||||
android:id="@+id/card_view"
|
android:id="@+id/layoutBack"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_margin="@dimen/card_margin"
|
android:layout_margin="@dimen/card_margin"
|
||||||
android:elevation="3dp"
|
android:background="@color/bg_row_background">
|
||||||
card_view:cardCornerRadius="@dimen/card_album_radius">
|
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<TextView
|
||||||
android:background="#FFFFFFFF"
|
android:layout_width="wrap_content"
|
||||||
android:layout_margin="3dp"
|
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
|
||||||
|
android:id="@+id/card_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="@dimen/card_margin"
|
||||||
|
android:elevation="3dp"
|
||||||
|
card_view:cardCornerRadius="@dimen/card_album_radius">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTimestamp"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
|
|
||||||
android:text="2018-09-11 20:22:32" />
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:background="#FFFFFFFF"
|
||||||
|
android:layout_margin="3dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvTitle"
|
android:id="@+id/tvTimestamp"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
|
android:textSize="12sp"
|
||||||
android:layout_marginEnd="4sp"
|
android:textStyle="italic"
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="@color/colorBlack"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:ellipsize="none"
|
|
||||||
android:maxLines="6"
|
|
||||||
|
|
||||||
android:text="Message from me"/>
|
android:text="2018-09-11 20:22:32" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvMessage"
|
android:id="@+id/tvTitle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
|
||||||
android:layout_margin="4sp"
|
android:layout_marginEnd="4sp"
|
||||||
android:ellipsize="none"
|
android:textSize="16sp"
|
||||||
android:maxLines="32"
|
android:textColor="@color/colorBlack"
|
||||||
android:scrollHorizontally="false"
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:maxLines="6"
|
||||||
|
|
||||||
android:text="asdasd asdasd asdasd a" />
|
android:text="Message from me"/>
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:id="@+id/ivPriority"
|
android:id="@+id/tvMessage"
|
||||||
android:tint="#BBB"
|
android:layout_width="0dp"
|
||||||
android:visibility="gone"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="24dp"
|
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
||||||
android:layout_height="24dp"
|
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tvTimestamp"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_margin="4sp"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
android:ellipsize="end"
|
||||||
android:layout_margin="4sp"
|
android:maxLines="6"
|
||||||
android:paddingTop="3dp"
|
android:scrollHorizontally="false"
|
||||||
android:contentDescription="@string/desc_priority_icon" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
android:text="asdasd asdasd asdasd a" />
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
<ImageView
|
||||||
|
android:id="@+id/ivPriority"
|
||||||
|
android:tint="#BBB"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvTimestamp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_margin="4sp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:contentDescription="@string/desc_priority_icon" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@@ -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>
|
||||||
|
@@ -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>
|
@@ -24,10 +24,14 @@
|
|||||||
<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>
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
#Mon Nov 12 13:06:09 CET 2018
|
#Sat Nov 17 03:25:30 CET 2018
|
||||||
VERSION_NAME=0.0.6
|
VERSION_NAME=0.0.8
|
||||||
VERSION_CODE=6
|
VERSION_CODE=8
|
||||||
|
77
examples/scn_send.sh
Normal file
77
examples/scn_send.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Call with `scn_send title`
|
||||||
|
# or `scn_send title content`
|
||||||
|
# or `scn_send title content priority`
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ "$#" -lt 1 ]; then
|
||||||
|
echo "no title supplied via parameter"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# INSERT YOUR DATA HERE #
|
||||||
|
################################################################################
|
||||||
|
user_id=999
|
||||||
|
user_key="????????????????????????????????????????????????????????????????"
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
title=$1
|
||||||
|
content=""
|
||||||
|
|
||||||
|
if [ "$#" -gt 1 ]; then
|
||||||
|
content=$2
|
||||||
|
fi
|
||||||
|
|
||||||
|
priority=1
|
||||||
|
|
||||||
|
if [ "$#" -gt 2 ]; then
|
||||||
|
priority=$3
|
||||||
|
fi
|
||||||
|
|
||||||
|
usr_msg_id=$(uuidgen)
|
||||||
|
|
||||||
|
while true ; do
|
||||||
|
|
||||||
|
curlresp=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-d "user_id=$user_id" -d "user_key=$user_key" -d "title=$title" \
|
||||||
|
-d "content=$content" -d "priority=$priority" -d "msg_id=$usr_msg_id" \
|
||||||
|
https://scn.blackforestbytes.com/send.php)
|
||||||
|
|
||||||
|
if [ "$curlresp" == 200 ] ; then
|
||||||
|
echo "Successfully send"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$curlresp" == 400 ] ; then
|
||||||
|
echo "Bad request - something went wrong"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$curlresp" == 401 ] ; then
|
||||||
|
echo "Unauthorized - wrong userid/userkey"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$curlresp" == 403 ] ; then
|
||||||
|
echo "Quota exceeded - wait one hour before re-try"
|
||||||
|
sleep 3600
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$curlresp" == 412 ] ; then
|
||||||
|
echo "Precondition Failed - No device linked"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$curlresp" == 500 ] ; then
|
||||||
|
echo "Internal server error - waiting for better times"
|
||||||
|
sleep 60
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if none of the above matched we probably hav no network ...
|
||||||
|
echo "Send failed (response code $curlresp) ... try again in 5s"
|
||||||
|
sleep 5
|
||||||
|
done
|
@@ -12,11 +12,17 @@
|
|||||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
<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">© blackforestbytes</a>
|
||||||
|
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwö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 \
|
||||||
@@ -34,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">© blackforestbytes</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
2
web/api/.gitignore
vendored
Normal file
2
web/api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
config.php
|
||||||
|
.verify_accesstoken
|
@@ -37,11 +37,10 @@ if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>301, 'messag
|
|||||||
$stmt = $pdo->prepare('UPDATE messages SET ack=1 WHERE scn_message_id=:smid AND sender_user_id=:uid');
|
$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]);
|
$stmt->execute(['smid' => $scn_msg_id, 'uid' => $user_id]);
|
||||||
|
|
||||||
echo json_encode(
|
api_return(200,
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'prev_ack' => $datas[0]['ack'],
|
'prev_ack' => $datas[0]['ack'],
|
||||||
'new_ack' => 1,
|
'new_ack' => 1,
|
||||||
'message' => 'ok'
|
'message' => 'ok'
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
@@ -26,19 +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)[0];
|
||||||
|
|
||||||
|
|
||||||
$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,
|
||||||
'fcm_token_set' => ($data['fcm_token'] != null),
|
'fcm_token_set' => ($data['fcm_token'] != null),
|
||||||
'message' => 'ok'
|
'unack_count' => $nack_count,
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
@@ -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()
|
||||||
@@ -190,10 +217,15 @@ function refreshVerifyToken()
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
@@ -38,7 +38,7 @@ $stmt->execute(['uid' => $user_id, 'ft' => $fcm_token]);
|
|||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
echo json_encode(
|
api_return(200,
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
@@ -48,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
56
web/api/requery.php
Normal 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' => strtotime($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,
|
||||||
|
]);
|
@@ -39,7 +39,7 @@ if ($fcm_token === null)
|
|||||||
$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,
|
||||||
@@ -49,7 +49,6 @@ if ($fcm_token === null)
|
|||||||
'is_pro' => $is_pro,
|
'is_pro' => $is_pro,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -61,7 +60,7 @@ else
|
|||||||
$stmt = $pdo->prepare('UPDATE users SET fcm_token=NULL WHERE user_id <> :uid AND fcm_token=:ft');
|
$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]);
|
$stmt->execute(['uid' => $user_id, 'ft' => $fcm_token]);
|
||||||
|
|
||||||
echo json_encode(
|
api_return(200,
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
@@ -71,5 +70,4 @@ else
|
|||||||
'is_pro' => $is_pro,
|
'is_pro' => $is_pro,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
||||||
}
|
}
|
@@ -45,7 +45,7 @@ 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,
|
||||||
@@ -54,7 +54,6 @@ if ($ispro)
|
|||||||
'is_pro' => true,
|
'is_pro' => true,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -63,7 +62,7 @@ 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,
|
||||||
@@ -72,5 +71,4 @@ else
|
|||||||
'is_pro' => false,
|
'is_pro' => false,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
|
||||||
}
|
}
|
282
web/api_more.php
Normal file
282
web/api_more.php
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||||
|
<title>Simple Cloud Notifications - API</title>
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<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>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwö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>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
With this API you can send push notifications to your phone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To recieve them you will need to install the <a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier">SimpleCloudNotifier</a> app from the play store.
|
||||||
|
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>user_key</code>.
|
||||||
|
These two values are used to identify and authenticate your device so that send messages can be routed to your phone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can at any time generate a new <code>user_key</code> in the app and invalidate the old one.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There is also a <a href="/index.php">web interface</a> for this API to manually send notifications to your phone or to test your setup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Quota</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
By default you can send up to 100 messages per day per device.
|
||||||
|
If you need more you can upgrade your account in the app to get 1000 messages per day, this has the additional benefit of removing ads and supporting the development of the app (and making sure I can pay the server costs).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>API Requests</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
To send a new notification you send a <code>POST</code> request to the URL <code>https://scn.blackforestbytes.com/send.php</code>.
|
||||||
|
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You <i>need</i> to supply a valid <code>user_id</code> - <code>user_key</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>API Response</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
If the operation was successful the API will respond with an HTTP statuscode 200 and an JSON payload indicating the send message and your remaining quota
|
||||||
|
</p>
|
||||||
|
<pre class="red-code">{
|
||||||
|
"success":true,
|
||||||
|
"message":"Message sent",
|
||||||
|
"response":
|
||||||
|
{
|
||||||
|
"multicast_id":8000000000000000006,
|
||||||
|
"success":1,
|
||||||
|
"failure":0,
|
||||||
|
"canonical_ids":0,
|
||||||
|
"results": [{"message_id":"0:10000000000000000000000000000000d"}]
|
||||||
|
},
|
||||||
|
"quota":17,
|
||||||
|
"quota_max":100
|
||||||
|
}</pre>
|
||||||
|
<p>
|
||||||
|
If the operation is <b>not</b> successful the API will respond with an 4xx HTTP statuscode.
|
||||||
|
</p>
|
||||||
|
<table class="scode_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Statuscode</th>
|
||||||
|
<th>Explanation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">200 (OK)</td>
|
||||||
|
<td data-label="Explanation">Message sent</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">400 (Bad Request)</td>
|
||||||
|
<td data-label="Explanation">The request is invalid (missing parameters or wrong values)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">401 (Unauthorized)</td>
|
||||||
|
<td data-label="Explanation">The user_id was not found or the user_key is wrong</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
There is also always a JSON payload with additional information.
|
||||||
|
The <code>success</code> field is always there and in the error state you the <code>message</code> field to get a descritpion of the problem.
|
||||||
|
</p>
|
||||||
|
<pre class="red-code">{
|
||||||
|
"success":false,
|
||||||
|
"error":2101,
|
||||||
|
"errhighlight":-1,
|
||||||
|
"message":"Daily quota reached (100)"
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Content</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Every message must have a title set.
|
||||||
|
But you also (optionally) add more content, while the title has a max length of 120 characters, the conntent can be up to 10.000 characters.
|
||||||
|
You can see the whole message with title and content in the app or when clicking on the notification.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If needed the content can be supplied in the <code>content</code> parameter.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "content={message_content}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Priority</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Currently you can send a message with three different priorities: 0 (low), 1 (normal) and 2 (high).
|
||||||
|
In the app you can then configure a different behaviour for different priorities, e.g. only playing a sound if the notification is high priority.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Priorites are either 0, 1 or 2 and are supplied in the <code>priority</code> parameter.
|
||||||
|
If no priority is supplied the message will get the default priority of 1.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "priority={0|1|2}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Uniqueness</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Sometimes your script can run in an environment with an unstable connection and you want to implement an automatic re-try mechanism to send a message again if the last try failed due to bad connectivity.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To ensure that a message is only send once you can generate a unique id for your message (I would recommend a simple <code>uuidgen</code>).
|
||||||
|
If you send a message with an UUID that was already used in the near past the API still returns OK, but no new message is sent.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "msg_id={message_id}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
<p>
|
||||||
|
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Bash script example</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Depending on your use case it can be useful to create a bash script that handles things like resending messages if you have connection problems or waiting if there is no quota left.<br/>
|
||||||
|
Here is an example how such a scrippt could look like, you can put it into <code>/usr/local/sbin</code> and call it with <code>scn_send "title" "content"</code>
|
||||||
|
</p>
|
||||||
|
<pre style="color:#000000;" class="yellow-code"><span style="color:#3f7f59; font-weight:bold;">#!/usr/bin/env bash</span>
|
||||||
|
|
||||||
|
<span style="color:#3f7f59; ">#</span>
|
||||||
|
<span style="color:#3f7f59; "># Call with `scn_send title`</span>
|
||||||
|
<span style="color:#3f7f59; "># or `scn_send title content`</span>
|
||||||
|
<span style="color:#3f7f59; "># or `scn_send title content priority`</span>
|
||||||
|
<span style="color:#3f7f59; ">#</span>
|
||||||
|
<span style="color:#3f7f59; ">#</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -lt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"no title supplied via parameter"</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#3f7f59; ">################################################################################</span>
|
||||||
|
<span style="color:#3f7f59; "># INSERT YOUR DATA HERE #</span>
|
||||||
|
<span style="color:#3f7f59; ">################################################################################</span>
|
||||||
|
user_id=999
|
||||||
|
user_key=<span style="color:#2a00ff; ">"????????????????????????????????????????????????????????????????"</span>
|
||||||
|
<span style="color:#3f7f59; ">################################################################################</span>
|
||||||
|
|
||||||
|
title=$1
|
||||||
|
content=<span style="color:#2a00ff; ">""</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
content=$2
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
priority=1
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 2 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
priority=$3
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
usr_msg_id=$(uuidgen)
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">while</span> true ; <span style="color:#7f0055; font-weight:bold; ">do</span>
|
||||||
|
|
||||||
|
curlresp=$(curl -s -o <span style="color:#3f3fbf; ">/dev/null</span> -w <span style="color:#2a00ff; ">"%{http_code}"</span> <span style="color:#2a00ff; ">\</span>
|
||||||
|
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_id</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_key</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_key</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">title</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$title</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
|
||||||
|
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">content</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$content</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">priority</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$priority</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">msg_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$usr_msg_id</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
|
||||||
|
https:<span style="color:#3f3fbf; ">/</span><span style="color:#3f3fbf; ">/scn.blackforestbytes.com/send.php</span>)
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 200 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Successfully send"</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">exit</span> 0
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 400 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Bad request - something went wrong"</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 401 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Unauthorized - wrong </span><span style="color:#3f3fbf; ">userid/userkey</span><span style="color:#2a00ff; ">"</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 403 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Quota exceeded - wait one hour before re-try"</span>
|
||||||
|
sleep 3600
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 412 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Precondition Failed - No device linked"</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 500 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Internal server error - waiting for better times"</span>
|
||||||
|
sleep 60
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||||
|
|
||||||
|
<span style="color:#3f7f59; "># if none of the above matched we probably hav no network ...</span>
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Send failed (response code </span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">) ... try again in 5s"</span>
|
||||||
|
sleep 5
|
||||||
|
<span style="color:#7f0055; font-weight:bold; ">done</span>
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -42,6 +42,11 @@ body
|
|||||||
border-left: .25rem solid #E53935;
|
border-left: .25rem solid #E53935;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yellow-code
|
||||||
|
{
|
||||||
|
border-left: .25rem solid #FFCB05;
|
||||||
|
}
|
||||||
|
|
||||||
#mainpnl input,
|
#mainpnl input,
|
||||||
#mainpnl textarea
|
#mainpnl textarea
|
||||||
{
|
{
|
||||||
@@ -86,13 +91,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 +104,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
|
||||||
@@ -231,3 +241,9 @@ table.scode_table th:nth-child(2) {
|
|||||||
.linkcaption:hover {
|
.linkcaption:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre, pre span
|
||||||
|
{
|
||||||
|
font-family: Menlo, Consolas, monospace;
|
||||||
|
background: #F9F9F9;;
|
||||||
|
}
|
@@ -13,12 +13,18 @@
|
|||||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwö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>
|
||||||
@@ -57,10 +63,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="copyinfo">
|
|
||||||
<a href="https://www.blackforestbytes.com">© 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>
|
||||||
|
@@ -1,192 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
|
||||||
<title>Simple Cloud Notifications - API</title>
|
|
||||||
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
|
||||||
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2>Introduction</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
With this API you can send push notifications to your phone.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To recieve them you will need to install the <a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier">SimpleCloudNotifier</a> app from the play store.
|
|
||||||
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>user_key</code>.
|
|
||||||
These two values are used to identify and authenticate your device so that send messages can be routed to your phone.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You can at any time generate a new <code>user_key</code> in the app and invalidate the old one.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
There is also a <a href="/index.php">web interface</a> for this API to manually send notifications to your phone or to test your setup.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Quota</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
By default you can send up to 100 messages per day per device.
|
|
||||||
If you need more you can upgrade your account in the app to get 1000 messages per day, this has the additional benefit of removing ads and supporting the development of the app (and making sure I can pay the server costs).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>API Requests</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
To send a new notification you send a <code>POST</code> request to the URL <code>https://scn.blackforestbytes.com/send.php</code>.
|
|
||||||
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You <i>need</i> to supply a valid <code>user_id</code> - <code>user_key</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>API Response</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
If the operation was successful the API will respond with an HTTP statuscode 200 and an JSON payload indicating the send message and your remaining quota
|
|
||||||
</p>
|
|
||||||
<pre class="red-code">{
|
|
||||||
"success":true,
|
|
||||||
"message":"Message sent",
|
|
||||||
"response":
|
|
||||||
{
|
|
||||||
"multicast_id":8000000000000000006,
|
|
||||||
"success":1,
|
|
||||||
"failure":0,
|
|
||||||
"canonical_ids":0,
|
|
||||||
"results": [{"message_id":"0:10000000000000000000000000000000d"}]
|
|
||||||
},
|
|
||||||
"messagecount":623,
|
|
||||||
"quota":17,
|
|
||||||
"quota_max":100
|
|
||||||
}</pre>
|
|
||||||
<p>
|
|
||||||
If the operation is <b>not</b> successful the API will respond with an 4xx HTTP statuscode.
|
|
||||||
</p>
|
|
||||||
<table class="scode_table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Statuscode</th>
|
|
||||||
<th>Explanation</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td data-label="Statuscode">200 (OK)</td>
|
|
||||||
<td data-label="Explanation">Message sent</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td data-label="Statuscode">400 (Bad Request)</td>
|
|
||||||
<td data-label="Explanation">The request is invalid (missing parameters or wrong values)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td data-label="Statuscode">401 (Unauthorized)</td>
|
|
||||||
<td data-label="Explanation">The user_id was not found or the user_key is wrong</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p>
|
|
||||||
There is also always a JSON payload with additional information.
|
|
||||||
The <code>success</code> field is always there and in the error state you the <code>message</code> field to get a descritpion of the problem.
|
|
||||||
</p>
|
|
||||||
<pre class="red-code">{
|
|
||||||
"success":false,
|
|
||||||
"error":2101,
|
|
||||||
"errhighlight":-1,
|
|
||||||
"message":"Daily quota reached (100)"
|
|
||||||
}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Message Content</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
Every message must have a title set.
|
|
||||||
But you also (optionally) add more content, while the title has a max length of 120 characters, the conntent can be up to 10.000 characters.
|
|
||||||
You can see the whole message with title and content in the app or when clicking on the notification.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If needed the content can be supplied in the <code>content</code> parameter.
|
|
||||||
</p>
|
|
||||||
<pre>curl \
|
|
||||||
--data "user_id={userid}" \
|
|
||||||
--data "user_key={userkey}" \
|
|
||||||
--data "title={message_title}" \
|
|
||||||
--data "content={message_content}" \
|
|
||||||
https://scn.blackforestbytes.com/send.php</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Message Priority</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
Currently you can send a message with three different priorities: 0 (low), 1 (normal) and 2 (high).
|
|
||||||
In the app you can then configure a different behaviour for different priorities, e.g. only playing a sound if the notification is high priority.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Priorites are either 0, 1 or 2 and are supplied in the <code>priority</code> parameter.
|
|
||||||
If no priority is supplied the message will get the default priority of 1.
|
|
||||||
</p>
|
|
||||||
<pre>curl \
|
|
||||||
--data "user_id={userid}" \
|
|
||||||
--data "user_key={userkey}" \
|
|
||||||
--data "title={message_title}" \
|
|
||||||
--data "priority={0|1|2}" \
|
|
||||||
https://scn.blackforestbytes.com/send.php</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Message Uniqueness</h2>
|
|
||||||
<div class="section">
|
|
||||||
<p>
|
|
||||||
Sometimes your script can run in an environment with an unstable connection and you want to implement an automatic re-try mechanism to send a message again if the last try failed due to bad connectivity.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To ensure that a message is only send once you can generate a unique id for your message (I would recommend a simple <code>uuidgen</code>).
|
|
||||||
If you send a message with an UUID that was already used in the near past the API still returns OK, but no new message is sent.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter.
|
|
||||||
</p>
|
|
||||||
<pre>curl \
|
|
||||||
--data "user_id={userid}" \
|
|
||||||
--data "user_key={userkey}" \
|
|
||||||
--data "title={message_title}" \
|
|
||||||
--data "msg_id={message_id}" \
|
|
||||||
https://scn.blackforestbytes.com/send.php</pre>
|
|
||||||
<p>
|
|
||||||
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="copyinfo">
|
|
||||||
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -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 +
|
||||||
'"a=' + resp.quota +
|
'"a=' + resp.quota +
|
||||||
|
@@ -13,6 +13,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwörer</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="mainpnl">
|
<div id="mainpnl">
|
||||||
|
|
||||||
@@ -41,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">© blackforestbytes</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
48
web/send.php
48
web/send.php
@@ -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,12 +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, json_encode(['success' => false, 'error' => 1204, 'errhighlight' => -1, 'message' => 'MessageID too long (64 characters)']));
|
if ($usrmsgid != null && strlen($usrmsgid) > 64) api_return(400, ['success' => false, 'error' => ERR::USR_MSG_ID_TOO_LONG, 'errhighlight' => -1, 'message' => 'MessageID too long (64 characters)']);
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -42,22 +44,22 @@ 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)
|
if ($fcm == null || $fcm == '' || $fcm == false)
|
||||||
{
|
{
|
||||||
api_return(412, json_encode(['success' => false, 'error' => 1401, 'errhighlight' => -1, 'message' => 'No device linked with this account']));
|
api_return(412, ['success' => false, 'error' => ERR::NO_DEVICE_LINKED, 'errhighlight' => -1, 'message' => 'No device linked with this account']);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
@@ -69,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',
|
||||||
@@ -79,7 +81,7 @@ 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']),
|
||||||
]));
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +138,14 @@ try
|
|||||||
{
|
{
|
||||||
reportError("FCM communication failed (success_1 <> true)\n\n".$httpresult);
|
reportError("FCM communication failed (success_1 <> true)\n\n".$httpresult);
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
api_return(500, json_encode(['success' => false, 'error' => 9902, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.']));
|
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);
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
api_return(500, json_encode(['success' => false, 'error' => 9901, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()]));
|
api_return(500, ['success' => false, 'error' => ERR::FIREBASE_COM_FAILED, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
|
||||||
@@ -154,9 +156,11 @@ try
|
|||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
api_return(200, json_encode(
|
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,
|
||||||
@@ -165,11 +169,11 @@ try
|
|||||||
'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,
|
'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();
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
api_return(500, json_encode(['success' => false, 'error' => 9903, 'errhighlight' => -1, 'message' => 'PHP script threw exception.'."\n\n".'Exception: ' . $e->getMessage()]));
|
api_return(500, ['success' => false, 'error' => ERR::INTERNAL_EXCEPTION, 'errhighlight' => -1, 'message' => 'PHP script threw exception.'."\n\n".'Exception: ' . $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user