Compare commits

...
16 Commits
Author SHA1 Message Date
Mikescher 90f4270b74 expand on click 2018-11-17 03:24:10 +01:00
Mikescher f68d75c226 swipe to delete 2018-11-17 03:09:18 +01:00
Mikescher 9cf5133469 get nonack on info-call 2018-11-17 02:09:43 +01:00
Mikescher 75929aad48 rename php files 2018-11-17 01:30:41 +01:00
Mikescher dca149ec4e move api to sub-dir 2018-11-17 01:29:12 +01:00
Mikescher 75a7b97d24 audio playback on notification stream 2018-11-17 01:21:53 +01:00
Mikescher eb62873fb6 Android O repeat sound 2018-11-17 00:52:44 +01:00
Mikescher f8effe7d1e no LED with android O 2018-11-17 00:24:22 +01:00
Mikescher 87a3d34315 scroll to newest message 2018-11-17 00:07:41 +01:00
Mikescher 0f38ad6e5c default_quota=50 2018-11-16 23:53:47 +01:00
Mikescher 0ec6ab71b2 fixed layout wrap in settings 2018-11-16 23:51:46 +01:00
Mikescher b42204c87d fixed NPE on message recieve if app is not active 2018-11-16 23:28:48 +01:00
Mikescher 53cb259ec1 stupid sound shit 2018-11-13 18:11:17 +01:00
Mikescher 0150cc9e8e force POST in send.php 2018-11-13 16:23:04 +01:00
Mikescher 20214473f1 html styling 2018-11-13 16:06:22 +01:00
jenkins 586ac07ef3 [Jenkins] Increment version 2018-11-12 18:27:08 +01:00
47 changed files with 1395 additions and 355 deletions
+2 -2
View File
@@ -14,8 +14,8 @@
<option name="values"> <option name="values">
<map> <map>
<entry key="assetSourceType" value="FILE" /> <entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="priority_low" /> <entry key="outputName" value="ic_garbage" />
<entry key="sourceFile" value="C:\Users\Mike\Downloads\Low Priority-595b40b75ba036ed117d9842.svg" /> <entry key="sourceFile" value="C:\Users\Mike\Downloads\garbage.svg" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>
+2
View File
@@ -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,12 @@ 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 +306,89 @@ 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);
}
SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
if (!json_bool(json, "fcm_token_set")) SCNSettings.inst().fcm_token_server = "";
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
if (json_int(json, "unack_count")>0)
{
ServerCommunication.requery(id, key, loader);
}
} catch (Exception e) {
Log.e("SC:info", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
} finally {
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
}
}
});
}
catch (Exception e)
{
Log.e("SC:requery", e.toString());
SCNApp.showToast("Communication with server failed", 4000);
}
}
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
@@ -370,8 +461,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 +503,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)
{
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
if (CMessageList.inst().isAck(scn_id))
{
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
return;
}
if (SCNApp.isBackground())
{
NotificationService.inst().showBackground(msg);
}
else
{
NotificationService.inst().showForeground(msg);
}
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
}
} }
@@ -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);
}
} }
@@ -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>
@@ -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>
@@ -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>
@@ -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>
+3 -3
View File
@@ -1,3 +1,3 @@
#Mon Nov 12 13:06:09 CET 2018 #Mon Nov 12 18:26:41 CET 2018
VERSION_NAME=0.0.6 VERSION_NAME=0.0.7
VERSION_CODE=6 VERSION_CODE=7
+11 -9
View File
@@ -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">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl">
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p> <p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
<pre>curl \ <pre>curl \
@@ -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">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>
+2
View File
@@ -0,0 +1,2 @@
config.php
.verify_accesstoken
+2 -3
View File
@@ -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;
+11 -4
View File
@@ -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);
$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;
+34 -2
View File
@@ -2,12 +2,39 @@
include('lib/httpful.phar'); include('lib/httpful.phar');
class ERR
{
const NO_ERROR = 0000;
const MISSING_UID = 1101;
const MISSING_TOK = 1102;
const MISSING_TITLE = 1103;
const INVALID_PRIO = 1104;
const REQ_METHOD = 1105;
const NO_TITLE = 1201;
const TITLE_TOO_LONG = 1202;
const CONTENT_TOO_LONG = 1203;
const USR_MSG_ID_TOO_LONG = 1204;
const USER_NOT_FOUND = 1301;
const USER_AUTH_FAILED = 1302;
const NO_DEVICE_LINKED = 1401;
const QUOTA_REACHED = 2101;
const FIREBASE_COM_FAILED = 9901;
const FIREBASE_COM_ERRORED = 9902;
const INTERNAL_EXCEPTION = 9903;
}
class Statics class Statics
{ {
public static $DB = NULL; public static $DB = NULL;
public static $CFG = NULL; public static $CFG = NULL;
public static function quota_max($is_pro) { return $is_pro ? 1000 : 100; } public static function quota_max($is_pro) { return $is_pro ? 1000 : 50; }
} }
function getConfig() function getConfig()
@@ -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();
} }
+2 -4
View File
@@ -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,
@@ -47,6 +47,4 @@ echo json_encode(
'quota_max' => Statics::quota_max($ispro), 'quota_max' => Statics::quota_max($ispro),
'is_pro' => $ispro, 'is_pro' => $ispro,
'message' => 'New user registered' 'message' => 'New user registered'
]); ]);
return 0;
+56
View File
@@ -0,0 +1,56 @@
<?php
include_once 'model.php';
$INPUT = array_merge($_GET, $_POST);
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errid'=>101, 'message' => 'Missing parameter [[user_id]]']));
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errid'=>102, 'message' => 'Missing parameter [[user_key]]']));
$user_id = $INPUT['user_id'];
$user_key = $INPUT['user_key'];
//----------------------
$pdo = getDatabase();
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day, fcm_token FROM users WHERE user_id = :uid LIMIT 1');
$stmt->execute(['uid' => $user_id]);
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>201, 'message' => 'User not found']));
$data = $datas[0];
if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message' => 'User not found']));
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID not found']));
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
//-------------------
$stmt = $pdo->prepare('SELECT * FROM messages WHERE ack=0 AND sender_user_id=:uid ORDER BY `timestamp` DESC LIMIT 16');
$stmt->execute(['uid' => $user_id]);
$nonacks_sql = $stmt->fetchAll(PDO::FETCH_ASSOC);
$nonacks = [];
foreach ($nonacks_sql as $nack)
{
$nonacks []=
[
'title' => $nack['title'],
'body' => $nack['content'],
'priority' => $nack['priority'],
'timestamp' => $nack['timestamp'],
'usr_msg_id' => $nack['usr_message_id'],
'scn_msg_id' => $nack['scn_message_id'],
];
}
api_return(200,
[
'success' => true,
'message' => 'ok',
'count' => count($nonacks),
'data' => $nonacks,
]);
+2 -4
View File
@@ -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;
} }
+2 -4
View File
@@ -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;
} }
+10 -9
View File
@@ -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">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl">
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<h2>Introduction</h2> <h2>Introduction</h2>
<div class="section"> <div class="section">
@@ -71,7 +77,6 @@
"canonical_ids":0, "canonical_ids":0,
"results": [{"message_id":"0:10000000000000000000000000000000d"}] "results": [{"message_id":"0:10000000000000000000000000000000d"}]
}, },
"messagecount":623,
"quota":17, "quota":17,
"quota_max":100 "quota_max":100
}</pre> }</pre>
@@ -182,11 +187,7 @@
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again. Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
</p> </p>
</div> </div>
</div> </div>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>
+12 -7
View File
@@ -86,13 +86,10 @@ body
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: -999; //z-index: -999;
} display: flex;
flex-direction: column;
#copyinfo a:hover text-align: right;
{
font-family: "Courier New", monospace;
color: #00F;
} }
#copyinfo a, #copyinfo a,
@@ -102,6 +99,14 @@ body
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
color: #AAA; color: #AAA;
text-decoration: none; text-decoration: none;
display: block;
line-height: 1em;
}
#copyinfo a:hover
{
font-family: "Courier New", monospace;
color: #0288D1;
} }
#tr_link #tr_link
+9 -7
View File
@@ -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">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<form id="mainpnl"> <form id="mainpnl">
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a> <a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
<a href="/index_api.php" class="button bordered" id="tr_link">API</a> <a tabindex="-1" href="/api.php" class="button bordered" id="tr_link">API</a>
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div> <div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
@@ -57,10 +63,6 @@
</div> </div>
</form> </form>
<div id="copyinfo">
<a href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
</div>
<script src="/js/logic.js" type="text/javascript" ></script> <script src="/js/logic.js" type="text/javascript" ></script>
<script src="/js/toastify.js"></script> <script src="/js/toastify.js"></script>
</body> </body>
+1 -1
View File
@@ -55,7 +55,7 @@ function send()
else else
{ {
window.location.href = window.location.href =
'/index_sent.php' + '/message_sent.php' +
'?ok=' + 1 + '?ok=' + 1 +
'&message_count=' + resp.messagecount + '&message_count=' + resp.messagecount +
'&quota=' + resp.quota + '&quota=' + resp.quota +
+7 -7
View File
@@ -13,6 +13,10 @@
</head> </head>
<body> <body>
<div id="copyinfo">
<a tabindex="-1" href="https://www.blackforestbytes.com">&#169; blackforestbytes</a>
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schw&ouml;rer</a>
</div>
<div id="mainpnl"> <div id="mainpnl">
@@ -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">&#169; blackforestbytes</a>
</div>
</body> </body>
</html> </html>
+26 -22
View File
@@ -1,6 +1,6 @@
<?php <?php
include_once 'model.php'; include_once 'api/model.php';
try try
{ {
@@ -9,11 +9,13 @@ try
//sleep(1); //sleep(1);
//------------------------------------------------------------------ //------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_return(400, ['success' => false, 'error' => ERR::REQ_METHOD, 'errhighlight' => -1, 'message' => 'Invalid request method (must be POST)']);
$INPUT = array_merge($_GET, $_POST); $INPUT = array_merge($_GET, $_POST);
if (!isset($INPUT['user_id'])) api_return(400, json_encode(['success' => false, 'error' => 1101, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]'])); if (!isset($INPUT['user_id'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_UID, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]']);
if (!isset($INPUT['user_key'])) api_return(400, json_encode(['success' => false, 'error' => 1102, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]'])); if (!isset($INPUT['user_key'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_TOK, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]']);
if (!isset($INPUT['title'])) api_return(400, json_encode(['success' => false, 'error' => 1103, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]'])); if (!isset($INPUT['title'])) api_return(400, ['success' => false, 'error' => ERR::MISSING_TITLE, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]']);
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -27,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()]);
} }