Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
0a1b948042
|
|||
![]() |
74cbfb235e | ||
63c4104a89
|
|||
2597287b1e
|
|||
![]() |
316156f0f0 | ||
5286e869cc
|
|||
d6dcf28d89
|
|||
1d983b9ac0
|
|||
e525221010
|
|||
4cde4703f2
|
|||
4a07e58c16
|
|||
b12356575a
|
|||
77f571de7d
|
|||
![]() |
f5eef7563b | ||
741c09b1e4
|
|||
0c0d7d181f
|
|||
9304da9422
|
|||
![]() |
d7afdd00f2 | ||
b780ccea1c
|
|||
![]() |
5d8e871871 | ||
4655d688c9
|
|||
8263c0ad95
|
|||
a701afd09b
|
|||
![]() |
1e02d8c01f | ||
e4651375aa
|
|||
![]() |
afce4c6391 | ||
e95d0cb010
|
|||
f717355519
|
|||
3368e514ca
|
|||
9dca27177f
|
|||
c63274f7a9
|
|||
6c2d2d2345
|
|||
a6fbaa192f
|
|||
![]() |
a01f156535 | ||
3b021c09dc
|
|||
287176c881
|
|||
84eeb5a002
|
|||
b892532023
|
|||
![]() |
56cdc52bb4 | ||
9ef6b1dd91
|
|||
6f7585323b
|
|||
92135e64a3
|
|||
28efeea30b
|
|||
a8a074907b
|
|||
6c29ec9820
|
|||
![]() |
8ec23144ca | ||
b703853b28
|
|||
6f7529fc9b
|
|||
27b45098f0
|
|||
21f66d99cd
|
|||
1745051e6e
|
|||
![]() |
b395e054e6 | ||
90f4270b74
|
|||
f68d75c226
|
|||
9cf5133469
|
|||
75929aad48
|
|||
dca149ec4e
|
|||
75a7b97d24
|
|||
eb62873fb6
|
|||
f8effe7d1e
|
|||
87a3d34315
|
|||
0f38ad6e5c
|
|||
0ec6ab71b2
|
|||
b42204c87d
|
|||
53cb259ec1
|
|||
0150cc9e8e
|
|||
20214473f1
|
|||
![]() |
586ac07ef3 |
22
README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
SimpleCloudNotifier [](https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier)
|
||||
===================
|
||||
|
||||
> SimpleCloudNotifier is an app to display messages that you can send to your phone with simple POST requests.
|
||||
>
|
||||
> After you start the app it generates a UserID and a UserSecret.
|
||||
> Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
|
||||
> (see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
|
||||
>
|
||||
>
|
||||
> Use it to
|
||||
> - send yourself automated messages from cron jobs
|
||||
> - notify yourself when long-running scripts finish
|
||||
> - send server error messages directly to your phone
|
||||
> - integrate with other online services
|
||||
>
|
||||
> The possibilities are endless*
|
||||
>
|
||||
> \* Disclaimer: Developer does not actually guarantee endless possibilities
|
||||
|
||||
|
||||
  
|
57
android/.idea/assetWizardSettings.xml
generated
@@ -3,6 +3,59 @@
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageAssetPanel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="launcherLegacy">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="assetType" value="IMAGE" />
|
||||
<entry key="cropped" value="true" />
|
||||
<entry key="iconShape" value="NONE" />
|
||||
<entry key="imageAsset" value="F:\Eigene Dateien\Dropbox\Programming\Java\AndroidStudioProjects\SimpleCloudNotifier\data\icon_512_nobox.png" />
|
||||
<entry key="outputName" value="ic_notification_full" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="notification">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="assetType" value="IMAGE" />
|
||||
<entry key="imageAsset" value="F:\Eigene Dateien\Dropbox\Programming\Java\AndroidStudioProjects\SimpleCloudNotifier\data\icon_512_transparent.png" />
|
||||
<entry key="outputName" value="ic_notification_white" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputIconType" value="LAUNCHER_LEGACY" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
@@ -14,8 +67,8 @@
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="assetSourceType" value="FILE" />
|
||||
<entry key="outputName" value="priority_low" />
|
||||
<entry key="sourceFile" value="C:\Users\Mike\Downloads\Low Priority-595b40b75ba036ed117d9842.svg" />
|
||||
<entry key="outputName" value="ic_share" />
|
||||
<entry key="sourceFile" value="C:\Users\Mike\Downloads\baseline-share-24px.svg" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
|
@@ -43,15 +43,17 @@ dependencies {
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'com.google.firebase:firebase-core:16.0.4'
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.google.firebase:firebase-messaging:17.3.4'
|
||||
implementation 'com.google.android.gms:play-services-ads:17.1.0'
|
||||
implementation 'com.google.android.gms:play-services-ads:17.1.2'
|
||||
implementation 'com.android.billingclient:billing:1.2'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||
implementation "com.github.DeweyReed:UltimateMusicPicker:2.0.0"
|
||||
implementation 'com.github.duanhong169:colorpicker:1.1.5'
|
||||
|
||||
implementation 'net.danlew:android.joda:2.9.9.2'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
@@ -1,41 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.blackforestbytes.simplecloudnotifier">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
|
||||
<application
|
||||
android:name=".SCNApp"
|
||||
android:allowBackup="false"
|
||||
android:name="SCNApp"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity android:name=".view.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/icon" />
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
|
||||
<meta-data android:name="com.google.android.gms.ads.AD_MANAGER_APP" android:value="true"/>
|
||||
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3320562328966175~7579972005"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/icon" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/colorAccent" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.AD_MANAGER_APP"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||
android:value="ca-app-pub-3320562328966175~7579972005" />
|
||||
|
||||
<service android:name=".service.FBMService" android:exported="false">
|
||||
<service
|
||||
android:name=".service.FBMService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".service.BroadcastReceiverService"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".view.debug.QueryLogActivity"
|
||||
android:label="@string/title_activity_query_log"
|
||||
android:theme="@style/AppTheme" />
|
||||
<activity android:name=".view.debug.SingleQueryLogActivity"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
android/app/src/main/ic_bfb_raster-web.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/ic_notification_full-web.png
Normal file
After Width: | Height: | Size: 67 KiB |
@@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
||||
@@ -19,7 +20,7 @@ import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
public class SCNApp extends Application implements LifecycleObserver
|
||||
{
|
||||
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 DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0");
|
||||
@@ -99,36 +100,5 @@ public class SCNApp extends Application implements LifecycleObserver
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==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
|
||||
|
||||
[ ] - 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
|
||||
|
||||
*/
|
||||
//TODO: Config for collapsed line count
|
||||
//TODO: Sometimes ads but promode
|
||||
|
@@ -24,6 +24,11 @@ public final class CollectionHelper
|
||||
return output;
|
||||
}
|
||||
|
||||
public static <T> void sort_inplace(List<T> input, Comparator<T> comparator)
|
||||
{
|
||||
Collections.sort(input, comparator);
|
||||
}
|
||||
|
||||
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
|
||||
{
|
||||
return sort(input, mapper, 1);
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||
|
||||
public class Tuple5<T1, T2, T3, T4, T5>
|
||||
{
|
||||
public final T1 Item1;
|
||||
public final T2 Item2;
|
||||
public final T3 Item3;
|
||||
public final T4 Item4;
|
||||
public final T5 Item5;
|
||||
|
||||
public Tuple5(T1 i1, T2 i2, T3 i3, T4 i4, T5 i5)
|
||||
{
|
||||
Item1 = i1;
|
||||
Item2 = i2;
|
||||
Item3 = i3;
|
||||
Item4 = i4;
|
||||
Item5 = i5;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Func5to0<TInput1, TInput2, TInput3, TInput4, TInput5> {
|
||||
void invoke(TInput1 value1, TInput2 value2, TInput3 value3, TInput4 value4, TInput5 value5);
|
||||
}
|
@@ -9,6 +9,8 @@ import java.util.TimeZone;
|
||||
|
||||
public class CMessage
|
||||
{
|
||||
public boolean IsExpandedInAdapter = false;
|
||||
|
||||
public final long SCN_ID;
|
||||
public final long Timestamp;
|
||||
public final String Title;
|
||||
|
@@ -3,15 +3,23 @@ package com.blackforestbytes.simplecloudnotifier.model;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class CMessageList
|
||||
{
|
||||
private final Object msg_lock = new Object();
|
||||
|
||||
public ArrayList<CMessage> Messages;
|
||||
public Set<String> AllAcks;
|
||||
|
||||
private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>();
|
||||
|
||||
@@ -28,19 +36,25 @@ public class CMessageList
|
||||
|
||||
private CMessageList()
|
||||
{
|
||||
Messages = new ArrayList<>();
|
||||
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||
int count = sharedPref.getInt("message_count", 0);
|
||||
for (int i=0; i < count; i++)
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
long time = sharedPref.getLong("message["+i+"].timestamp", 0);
|
||||
String title = sharedPref.getString("message["+i+"].title", "");
|
||||
String content = sharedPref.getString("message["+i+"].content", "");
|
||||
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
|
||||
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
|
||||
Messages = new ArrayList<>();
|
||||
AllAcks = new HashSet<>();
|
||||
|
||||
Messages.add(new CMessage(scnid, time, title, content, prio));
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||
int count = sharedPref.getInt("message_count", 0);
|
||||
for (int i=0; i < count; i++)
|
||||
{
|
||||
long time = sharedPref.getLong("message["+i+"].timestamp", 0);
|
||||
String title = sharedPref.getString("message["+i+"].title", "");
|
||||
String content = sharedPref.getString("message["+i+"].content", "");
|
||||
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
|
||||
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
|
||||
|
||||
Messages.add(new CMessage(scnid, time, title, content, prio));
|
||||
}
|
||||
|
||||
AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,34 +67,57 @@ public class CMessageList
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||
int count = sharedPref.getInt("message_count", 0);
|
||||
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
Messages.add(msg);
|
||||
AllAcks.add(Long.toHexString(msg.SCN_ID));
|
||||
|
||||
Messages.add(msg);
|
||||
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
|
||||
}
|
||||
|
||||
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
|
||||
if (Messages.size()>1 && Messages.get(Messages.size()-2).Timestamp < msg.Timestamp)
|
||||
{
|
||||
// quick save
|
||||
|
||||
e.putInt( "message_count", count+1);
|
||||
e.putLong( "message["+count+"].timestamp", time);
|
||||
e.putString("message["+count+"].title", title);
|
||||
e.putString("message["+count+"].content", content);
|
||||
e.putInt( "message["+count+"].priority", pe.ID);
|
||||
e.putLong( "message["+count+"].scnid", scnid);
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
|
||||
e.putInt( "message_count", count+1);
|
||||
e.putLong( "message["+count+"].timestamp", time);
|
||||
e.putString("message["+count+"].title", title);
|
||||
e.putString("message["+count+"].content", content);
|
||||
e.putInt( "message["+count+"].priority", pe.ID);
|
||||
e.putLong( "message["+count+"].scnid", scnid);
|
||||
|
||||
e.putStringSet("acks", AllAcks);
|
||||
|
||||
e.apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
// full save
|
||||
|
||||
fullSave(); // does sort in here
|
||||
}
|
||||
|
||||
e.apply();
|
||||
|
||||
for (WeakReference<MessageAdapter> ref : _listener)
|
||||
{
|
||||
MessageAdapter a = ref.get();
|
||||
if (a == null) continue;
|
||||
a.customNotifyItemInserted(count);
|
||||
a.customNotifyDataSetChanged();
|
||||
a.scrollToTop();
|
||||
}
|
||||
CleanUpListener();
|
||||
});
|
||||
|
||||
if (!run)
|
||||
{
|
||||
Messages.add(new CMessage(scnid, time, title, content, pe));
|
||||
fullSave();
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
Messages.add(new CMessage(scnid, time, title, content, pe));
|
||||
AllAcks.add(Long.toHexString(msg.SCN_ID));
|
||||
}
|
||||
fullSave(); // does sort in here
|
||||
}
|
||||
|
||||
return msg;
|
||||
@@ -102,34 +139,52 @@ public class CMessageList
|
||||
|
||||
public void fullSave()
|
||||
{
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
|
||||
e.clear();
|
||||
|
||||
e.putInt("message_count", Messages.size());
|
||||
|
||||
for (int i = 0; i < Messages.size(); i++)
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp);
|
||||
e.putString("message["+i+"].title", Messages.get(i).Title);
|
||||
e.putString("message["+i+"].content", Messages.get(i).Content);
|
||||
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID);
|
||||
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
|
||||
}
|
||||
CollectionHelper.sort_inplace(Messages, (a,b) -> Long.compare(a.Timestamp, b.Timestamp));
|
||||
|
||||
e.apply();
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
|
||||
e.clear();
|
||||
|
||||
e.putInt("message_count", Messages.size());
|
||||
|
||||
for (int i = 0; i < Messages.size(); i++)
|
||||
{
|
||||
e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp);
|
||||
e.putString("message["+i+"].title", Messages.get(i).Title);
|
||||
e.putString("message["+i+"].content", Messages.get(i).Content);
|
||||
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID);
|
||||
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
|
||||
}
|
||||
|
||||
e.putStringSet("acks", AllAcks);
|
||||
|
||||
e.apply();
|
||||
}
|
||||
}
|
||||
|
||||
public CMessage tryGet(int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= Messages.size()) return null;
|
||||
return Messages.get(pos);
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
if (pos < 0 || pos >= Messages.size()) return null;
|
||||
return Messages.get(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public CMessage tryGetFromBack(int pos)
|
||||
{
|
||||
return tryGet(Messages.size() - pos - 1);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return Messages.size();
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
return Messages.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void register(MessageAdapter adp)
|
||||
@@ -145,4 +200,33 @@ public class CMessageList
|
||||
if (_listener.get(i).get() == null) _listener.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAck(long id)
|
||||
{
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
return AllAcks.contains(Long.toHexString(id));
|
||||
}
|
||||
}
|
||||
|
||||
public CMessage removeFromBack(int pos)
|
||||
{
|
||||
CMessage r;
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
int index = Messages.size() - pos - 1;
|
||||
r = Messages.remove(index);
|
||||
}
|
||||
fullSave(); // does sort in here
|
||||
return r;
|
||||
}
|
||||
|
||||
public void insert(int index, CMessage item)
|
||||
{
|
||||
synchronized (msg_lock)
|
||||
{
|
||||
Messages.add(index, item);
|
||||
}
|
||||
fullSave(); // does sort in here
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,58 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.model;
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR;
|
||||
|
||||
public String toUIString()
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case DEBUG: return "Debug";
|
||||
case INFO: return "Info";
|
||||
case WARN: return "Warning";
|
||||
case ERROR: return "Error";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
public int getColor()
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case DEBUG: return Color.GRAY;
|
||||
case WARN: return Color.rgb(171, 145, 68);
|
||||
case INFO: return Color.BLACK;
|
||||
case ERROR: return Color.RED;
|
||||
default: return Color.MAGENTA;
|
||||
}
|
||||
}
|
||||
|
||||
public int asInt()
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case DEBUG: return 0;
|
||||
case WARN: return 1;
|
||||
case INFO: return 2;
|
||||
case ERROR: return 3;
|
||||
default: return 999;
|
||||
}
|
||||
}
|
||||
|
||||
public static LogLevel fromInt(int i)
|
||||
{
|
||||
if (i == 0) return LogLevel.DEBUG;
|
||||
if (i == 1) return LogLevel.WARN;
|
||||
if (i == 2) return LogLevel.INFO;
|
||||
if (i == 3) return LogLevel.ERROR;
|
||||
|
||||
return LogLevel.ERROR; // ????
|
||||
}
|
||||
}
|
@@ -11,6 +11,9 @@ public class NotificationSettings
|
||||
public String SoundSource;
|
||||
public boolean RepeatSound;
|
||||
|
||||
public boolean ForceVolume;
|
||||
public int ForceVolumeValue;
|
||||
|
||||
public boolean EnableLED;
|
||||
public int LEDColor;
|
||||
|
||||
@@ -18,12 +21,14 @@ public class NotificationSettings
|
||||
|
||||
public NotificationSettings(PriorityEnum p)
|
||||
{
|
||||
EnableSound = (p == PriorityEnum.HIGH);
|
||||
SoundName = (p == PriorityEnum.HIGH) ? "Default" : "";
|
||||
SoundSource = (p == PriorityEnum.HIGH) ? RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString() : Uri.EMPTY.toString();
|
||||
RepeatSound = false;
|
||||
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||
LEDColor = Color.BLUE;
|
||||
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||
EnableSound = (p == PriorityEnum.HIGH);
|
||||
SoundName = "Default";
|
||||
SoundSource = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
|
||||
RepeatSound = false;
|
||||
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||
LEDColor = Color.BLUE;
|
||||
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||
ForceVolume = false;
|
||||
ForceVolumeValue = 50;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,69 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class QueryLog
|
||||
{
|
||||
private final static int MAX_HISTORY_SIZE = 192;
|
||||
|
||||
private static QueryLog _instance;
|
||||
public static QueryLog instance() { if (_instance == null) synchronized (QueryLog.class) { if (_instance == null) _instance = new QueryLog(); } return _instance; }
|
||||
|
||||
private QueryLog(){ load(); }
|
||||
|
||||
private final List<SingleQuery> history = new ArrayList<>();
|
||||
|
||||
public synchronized void add(SingleQuery r)
|
||||
{
|
||||
history.add(r);
|
||||
while (history.size() > MAX_HISTORY_SIZE) history.remove(0);
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
public synchronized List<SingleQuery> get()
|
||||
{
|
||||
List<SingleQuery> r = new ArrayList<>(history);
|
||||
CollectionHelper.sort_inplace(r, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
|
||||
return r;
|
||||
}
|
||||
|
||||
public synchronized void save()
|
||||
{
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
|
||||
e.clear();
|
||||
|
||||
e.putInt("history_count", history.size());
|
||||
|
||||
for (int i = 0; i < history.size(); i++) history.get(i).save(e, "message["+(i+1000)+"]");
|
||||
|
||||
e.apply();
|
||||
}
|
||||
|
||||
public synchronized void load()
|
||||
{
|
||||
try
|
||||
{
|
||||
Context c = SCNApp.getContext();
|
||||
SharedPreferences sharedPref = c.getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
|
||||
int count = sharedPref.getInt("history_count", 0);
|
||||
for (int i=0; i < count; i++) history.add(SingleQuery.load(sharedPref, "message["+(i+1000)+"]"));
|
||||
|
||||
CollectionHelper.sort_inplace(history, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:QL:Load", e.toString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,8 +6,8 @@ import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
@@ -15,14 +15,19 @@ import com.google.firebase.iid.FirebaseInstanceId;
|
||||
public class SCNSettings
|
||||
{
|
||||
private final static Object _lock = new Object();
|
||||
private static SCNSettings _inst = null;
|
||||
private static volatile SCNSettings _inst = null;
|
||||
public static SCNSettings inst()
|
||||
{
|
||||
synchronized (_lock)
|
||||
SCNSettings local = _inst;
|
||||
if (local == null)
|
||||
{
|
||||
if (_inst != null) return _inst;
|
||||
return _inst = new SCNSettings();
|
||||
synchronized (_lock)
|
||||
{
|
||||
local = _inst;
|
||||
if (local == null) _inst = local = new SCNSettings();
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
@@ -47,6 +52,8 @@ public class SCNSettings
|
||||
|
||||
public boolean Enabled = true;
|
||||
public int LocalCacheSize = 500;
|
||||
public boolean EnableDeleteSwipe = false;
|
||||
public int PreviewLineCount = 6;
|
||||
|
||||
public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW);
|
||||
public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL);
|
||||
@@ -70,30 +77,38 @@ public class SCNSettings
|
||||
|
||||
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
||||
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||
EnableDeleteSwipe = sharedPref.getBoolean("do_del_swipe", EnableDeleteSwipe);
|
||||
PreviewLineCount = sharedPref.getInt("preview_line_count", PreviewLineCount);
|
||||
|
||||
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||
PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||
PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||
PriorityLow.LEDColor = sharedPref.getInt( "priority_low:led_color", PriorityLow.LEDColor);
|
||||
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||
PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||
PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||
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.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
|
||||
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||
PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||
PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||
PriorityNorm.LEDColor = sharedPref.getInt( "priority_norm:led_color", PriorityNorm.LEDColor);
|
||||
PriorityNorm.EnableLED = sharedPref.getBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
|
||||
PriorityNorm.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
|
||||
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||
PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||
PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||
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.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
|
||||
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||
PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||
PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||
PriorityHigh.LEDColor = sharedPref.getInt( "priority_high:led_color", PriorityHigh.LEDColor);
|
||||
PriorityHigh.EnableLED = sharedPref.getBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
|
||||
PriorityHigh.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
|
||||
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||
PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||
PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||
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()
|
||||
@@ -101,39 +116,47 @@ public class SCNSettings
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor e = sharedPref.edit();
|
||||
|
||||
e.putInt( "quota_curr", quota_curr);
|
||||
e.putInt( "quota_max", quota_max);
|
||||
e.putInt( "user_id", user_id);
|
||||
e.putString( "user_key", user_key);
|
||||
e.putString( "fcm_token_local", fcm_token_local);
|
||||
e.putString( "fcm_token_server", fcm_token_server);
|
||||
e.putInt( "quota_curr", quota_curr);
|
||||
e.putInt( "quota_max", quota_max);
|
||||
e.putInt( "user_id", user_id);
|
||||
e.putString( "user_key", user_key);
|
||||
e.putString( "fcm_token_local", fcm_token_local);
|
||||
e.putString( "fcm_token_server", fcm_token_server);
|
||||
|
||||
e.putBoolean("app_enabled", Enabled);
|
||||
e.putInt( "local_cache_size", LocalCacheSize);
|
||||
e.putBoolean("app_enabled", Enabled);
|
||||
e.putInt( "local_cache_size", LocalCacheSize);
|
||||
e.putBoolean("do_del_swipe", EnableDeleteSwipe);
|
||||
e.putInt( "preview_line_count", PreviewLineCount);
|
||||
|
||||
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||
e.putString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||
e.putString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||
e.putInt( "priority_low:led_color", PriorityLow.LEDColor);
|
||||
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||
e.putString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||
e.putString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||
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_sound", PriorityNorm.EnableSound);
|
||||
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||
e.putString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||
e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||
e.putInt( "priority_norm:led_color", PriorityNorm.LEDColor);
|
||||
e.putBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
|
||||
e.putBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
|
||||
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||
e.putString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||
e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||
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_sound", PriorityHigh.EnableSound);
|
||||
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||
e.putString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||
e.putString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||
e.putInt( "priority_high:led_color", PriorityHigh.LEDColor);
|
||||
e.putBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
|
||||
e.putBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
|
||||
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||
e.putString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||
e.putString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||
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();
|
||||
}
|
||||
@@ -143,10 +166,12 @@ public class SCNSettings
|
||||
return user_id>=0 && user_key != null && !user_key.isEmpty();
|
||||
}
|
||||
|
||||
public String createOnlineURL()
|
||||
public String createOnlineURL(boolean longurl)
|
||||
{
|
||||
if (!isConnected()) return ServerCommunication.BASE_URL + "index.php";
|
||||
return ServerCommunication.BASE_URL + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
|
||||
String base = longurl ? ServerCommunication.PAGE_URL_LONG : ServerCommunication.PAGE_URL_SHORT;
|
||||
|
||||
if (!isConnected()) return base;
|
||||
return base + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
|
||||
}
|
||||
|
||||
public void setServerToken(String token, View loader)
|
||||
@@ -155,7 +180,7 @@ public class SCNSettings
|
||||
{
|
||||
fcm_token_local = token;
|
||||
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
|
||||
{
|
||||
@@ -172,7 +197,7 @@ public class SCNSettings
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||
{
|
||||
String newToken = instanceIdResult.getToken();
|
||||
Log.e("FB::GetInstanceId", newToken);
|
||||
Log.d("FB::GetInstanceId", newToken);
|
||||
SCNSettings.inst().setServerToken(newToken, null);
|
||||
}).addOnCompleteListener(r ->
|
||||
{
|
||||
@@ -187,7 +212,7 @@ public class SCNSettings
|
||||
{
|
||||
if (!isConnected()) return;
|
||||
|
||||
ServerCommunication.update(user_id, user_key, loader);
|
||||
ServerCommunication.resetSecret(user_id, user_key, loader);
|
||||
}
|
||||
|
||||
// refresh account data
|
||||
@@ -207,7 +232,7 @@ public class SCNSettings
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||
{
|
||||
String newToken = instanceIdResult.getToken();
|
||||
Log.e("FB::GetInstanceId", newToken);
|
||||
Log.d("FB::GetInstanceId", newToken);
|
||||
SCNSettings.inst().setServerToken(newToken, loader); // does register in here
|
||||
}).addOnCompleteListener(r ->
|
||||
{
|
||||
@@ -218,14 +243,16 @@ public class SCNSettings
|
||||
|
||||
public void updateProState(View loader)
|
||||
{
|
||||
Purchase purch = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||
boolean promode_real = (purch != null);
|
||||
Tuple3<Boolean, Boolean, String> state = IABService.inst().getPurchaseCachedExtended(IABService.IAB_PRO_MODE);
|
||||
if (!state.Item2) return; // not nitialized
|
||||
|
||||
boolean promode_real = state.Item1;
|
||||
|
||||
if (promode_real != promode_local || promode_real != promode_server)
|
||||
{
|
||||
promode_local = promode_real;
|
||||
|
||||
promode_token = promode_real ? purch.getPurchaseToken() : "";
|
||||
promode_token = promode_real ? state.Item3 : "";
|
||||
updateProStateOnServer(loader);
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,12 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func5to0;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.FBMService;
|
||||
|
||||
import org.joda.time.Instant;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
@@ -22,7 +26,9 @@ import okhttp3.ResponseBody;
|
||||
|
||||
public class ServerCommunication
|
||||
{
|
||||
public static final String BASE_URL = /*SCNApp.LOCAL_DEBUG ? "http://localhost:1010/" : */"https://scn.blackforestbytes.com/";
|
||||
public static final String PAGE_URL_LONG = "https://simplecloudnotifier.blackforestbytes.com/";
|
||||
public static final String PAGE_URL_SHORT = "https://scn.blackforestbytes.com/";
|
||||
public static final String BASE_URL = "https://scn.blackforestbytes.com/api/";
|
||||
|
||||
private static final OkHttpClient client = new OkHttpClient();
|
||||
|
||||
@@ -41,21 +47,21 @@ public class ServerCommunication
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
Log.e("SC:register", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("register", call, null, Str.Empty, true, e);
|
||||
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
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);
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
@@ -74,11 +80,12 @@ public class ServerCommunication
|
||||
SCNSettings.inst().save();
|
||||
|
||||
SCNApp.refreshAccountTab();
|
||||
|
||||
handleSuccess("register", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:register", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("register", call, response, r, false, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -89,17 +96,16 @@ public class ServerCommunication
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:register", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("register", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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();
|
||||
|
||||
client.newCall(request).enqueue(new Callback()
|
||||
@@ -107,21 +113,21 @@ public class ServerCommunication
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
Log.e("SC:update_1", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("update<1>", call, null, Str.Empty, true, e);
|
||||
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
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);
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
@@ -140,10 +146,12 @@ public class ServerCommunication
|
||||
SCNSettings.inst().save();
|
||||
|
||||
SCNApp.refreshAccountTab();
|
||||
|
||||
handleSuccess("update<1>", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:update_1", e.toString());
|
||||
handleError("update<1>", call, response, r, false, e);
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
}
|
||||
finally
|
||||
@@ -155,35 +163,38 @@ public class ServerCommunication
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:update_1", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("update<1>", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(int id, String key, View loader)
|
||||
public static void resetSecret(int id, String key, View loader)
|
||||
{
|
||||
try
|
||||
{
|
||||
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();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
Log.e("SC:update_2", e.toString());
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
handleError("update<1>", call, null, Str.Empty, true, e);
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) {
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
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);
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
@@ -200,10 +211,16 @@ public class ServerCommunication
|
||||
SCNSettings.inst().save();
|
||||
|
||||
SCNApp.refreshAccountTab();
|
||||
} catch (Exception e) {
|
||||
Log.e("SC:update_2", e.toString());
|
||||
|
||||
handleSuccess("update<2>", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("update<2>", call, response, r, false, e);
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
} finally {
|
||||
}
|
||||
finally
|
||||
{
|
||||
SCNApp.runOnUiThread(() -> {
|
||||
if (loader != null) loader.setVisibility(View.GONE);
|
||||
});
|
||||
@@ -213,8 +230,7 @@ public class ServerCommunication
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:update_2", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("update<2>", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,22 +245,24 @@ public class ServerCommunication
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
Log.e("SC:info", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("info", call, null, Str.Empty, true, e);
|
||||
SCNApp.runOnUiThread(() -> {
|
||||
if (loader != null) loader.setVisibility(View.GONE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) {
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
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);
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
@@ -280,10 +298,91 @@ public class ServerCommunication
|
||||
SCNSettings.inst().save();
|
||||
|
||||
SCNApp.refreshAccountTab();
|
||||
} catch (Exception e) {
|
||||
Log.e("SC:info", e.toString());
|
||||
|
||||
if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
|
||||
|
||||
handleSuccess("info", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("info", call, response, r, false, e);
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
} finally {
|
||||
}
|
||||
finally
|
||||
{
|
||||
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("info", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
handleError("requery", call, null, Str.Empty, true, e);
|
||||
SCNApp.runOnUiThread(() -> {
|
||||
if (loader != null) loader.setVisibility(View.GONE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
try (ResponseBody responseBody = response.body())
|
||||
{
|
||||
if (!response.isSuccessful())
|
||||
throw new IOException("Unexpected code " + response);
|
||||
if (responseBody == null) throw new IOException("No response");
|
||||
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
if (!json_bool(json, "success"))
|
||||
{
|
||||
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = json_int(json, "count");
|
||||
JSONArray arr = json.getJSONArray("data");
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
JSONObject o = arr.getJSONObject(i);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
handleSuccess("requery", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("requery", call, response, r, false, e);
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SCNApp.runOnUiThread(() -> {
|
||||
if (loader != null) loader.setVisibility(View.GONE);
|
||||
});
|
||||
@@ -293,8 +392,7 @@ public class ServerCommunication
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:info", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("requery", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,22 +406,26 @@ public class ServerCommunication
|
||||
.url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
client.newCall(request).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
Log.e("SC:upgrade", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
handleError("upgrade", call, null, Str.Empty, true, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) {
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
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);
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
@@ -339,10 +441,15 @@ public class ServerCommunication
|
||||
SCNSettings.inst().save();
|
||||
|
||||
SCNApp.refreshAccountTab();
|
||||
} catch (Exception e) {
|
||||
Log.e("SC:upgrade", e.toString());
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
} finally {
|
||||
|
||||
handleSuccess("upgrade", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("upgrade", call, response, r, false, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||
}
|
||||
}
|
||||
@@ -350,34 +457,120 @@ public class ServerCommunication
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
handleError("upgrade", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ack(int id, String key, CMessage msg)
|
||||
public static void ack(int id, String key, long msg_scn_id)
|
||||
{
|
||||
try
|
||||
{
|
||||
Request request = new Request.Builder()
|
||||
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg.SCN_ID)
|
||||
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
client.newCall(request).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
Log.e("SC:ack", e.toString());
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
handleError("ack", call, null, Str.Empty, true, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) {
|
||||
// ????
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
try (ResponseBody responseBody = response.body())
|
||||
{
|
||||
if (!response.isSuccessful())
|
||||
throw new IOException("Unexpected code " + response);
|
||||
if (responseBody == null) throw new IOException("No response");
|
||||
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
if (!json_bool(json, "success")) SCNApp.showToast(json_str(json, "message"), 4000);
|
||||
|
||||
handleSuccess("ack", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("ack", call, response, r, false, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SC:ack", e.toString());
|
||||
handleError("ack", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void expand(int id, String key, long scn_msg_id, View loader, Func5to0<String, String, PriorityEnum, Long, Long> okResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
Request request = new Request.Builder()
|
||||
.url(BASE_URL + "expand.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + scn_msg_id)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
handleError("expand", call, null, Str.Empty, true, e);
|
||||
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
String r = Str.Empty;
|
||||
try (ResponseBody responseBody = response.body())
|
||||
{
|
||||
if (!response.isSuccessful())
|
||||
throw new IOException("Unexpected code " + response);
|
||||
if (responseBody == null) throw new IOException("No response");
|
||||
|
||||
r = responseBody.string();
|
||||
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||
|
||||
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||
|
||||
if (!json_bool(json, "success"))
|
||||
{
|
||||
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject o = json.getJSONObject("data");
|
||||
|
||||
long time = json_lng(o, "timestamp");
|
||||
String title = json_str(o, "title");
|
||||
String content = json_str(o, "body");
|
||||
PriorityEnum prio = PriorityEnum.parseAPI(json_int(o, "priority"));
|
||||
long scn_id = json_lng(o, "scn_msg_id");
|
||||
|
||||
okResult.invoke(title, content, prio, time, scn_id);
|
||||
|
||||
handleSuccess("expand", call, response, r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("expand", call, response, r, false, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
handleError("expand", null, null, Str.Empty, false, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,8 +589,66 @@ public class ServerCommunication
|
||||
return o.getInt(key);
|
||||
}
|
||||
|
||||
private static long json_lng(JSONObject o, String key) throws JSONException
|
||||
{
|
||||
return o.getLong(key);
|
||||
}
|
||||
|
||||
private static String json_str(JSONObject o, String key) throws JSONException
|
||||
{
|
||||
return o.getString(key);
|
||||
}
|
||||
|
||||
private static void handleSuccess(String source, Call call, Response resp, String respBody)
|
||||
{
|
||||
Log.d("SC:"+source, respBody);
|
||||
|
||||
try
|
||||
{
|
||||
Instant i = Instant.now();
|
||||
String s = source;
|
||||
String u = call.request().url().toString();
|
||||
int rc = resp.code();
|
||||
String r = respBody;
|
||||
LogLevel l = LogLevel.INFO;
|
||||
|
||||
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "SUCCESS");
|
||||
QueryLog.instance().add(q);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
Log.e("SC:HandleSuccess", e2.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleError(String source, Call call, Response resp, String respBody, boolean isio, Exception e)
|
||||
{
|
||||
Log.e("SC:"+source, e.toString());
|
||||
|
||||
if (isio)
|
||||
{
|
||||
SCNApp.showToast("Can't connect to server", 3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
SCNApp.showToast("Communication with server failed", 4000);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Instant i = Instant.now();
|
||||
String s = source;
|
||||
String u = (call==null)?Str.Empty:call.request().url().toString();
|
||||
int rc = (resp==null)?-1:resp.code();
|
||||
String r = respBody;
|
||||
LogLevel l = isio?LogLevel.WARN:LogLevel.ERROR;
|
||||
|
||||
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, e.toString());
|
||||
QueryLog.instance().add(q);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
Log.e("SC:HandleError", e2.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,82 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.model;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.BaseBundle;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
|
||||
import org.joda.time.Instant;
|
||||
|
||||
public class SingleQuery
|
||||
{
|
||||
public final Instant Timestamp;
|
||||
|
||||
public final LogLevel Level;
|
||||
public final String Name;
|
||||
public final String URL;
|
||||
public final String Response;
|
||||
public final int ResponseCode;
|
||||
public final String ExceptionString;
|
||||
|
||||
public SingleQuery(LogLevel l, Instant i, String n, String u, String r, int rc, String e)
|
||||
{
|
||||
Level=l;
|
||||
Timestamp=i;
|
||||
Name=n;
|
||||
URL=u;
|
||||
Response=r;
|
||||
ResponseCode=rc;
|
||||
ExceptionString=e;
|
||||
}
|
||||
|
||||
public void save(SharedPreferences.Editor e, String base)
|
||||
{
|
||||
e.putInt(base+".Level", Level.asInt());
|
||||
e.putLong(base+".Timestamp", Timestamp.getMillis());
|
||||
e.putString(base+".Name", Name);
|
||||
e.putString(base+".URL", URL);
|
||||
e.putString(base+".Response", Response);
|
||||
e.putInt(base+".ResponseCode", ResponseCode);
|
||||
e.putString(base+".ExceptionString", ExceptionString);
|
||||
}
|
||||
|
||||
public void save(BaseBundle e, String base)
|
||||
{
|
||||
e.putInt(base+".Level", Level.asInt());
|
||||
e.putLong(base+".Timestamp", Timestamp.getMillis());
|
||||
e.putString(base+".Name", Name);
|
||||
e.putString(base+".URL", URL);
|
||||
e.putString(base+".Response", Response);
|
||||
e.putInt(base+".ResponseCode", ResponseCode);
|
||||
e.putString(base+".ExceptionString", ExceptionString);
|
||||
}
|
||||
|
||||
public static SingleQuery load(SharedPreferences e, String base)
|
||||
{
|
||||
return new SingleQuery
|
||||
(
|
||||
LogLevel.fromInt(e.getInt(base+".Level", 0)),
|
||||
new Instant(e.getLong(base+".Timestamp", 0)),
|
||||
e.getString(base+".Name", Str.Empty),
|
||||
e.getString(base+".URL", Str.Empty),
|
||||
e.getString(base+".Response", Str.Empty),
|
||||
e.getInt(base+".ResponseCode", -1),
|
||||
e.getString(base+".ExceptionString", Str.Empty)
|
||||
);
|
||||
}
|
||||
|
||||
public static SingleQuery load(BaseBundle e, String base)
|
||||
{
|
||||
return new SingleQuery
|
||||
(
|
||||
LogLevel.fromInt(e.getInt(base+".Level", 0)),
|
||||
new Instant(e.getLong(base+".Timestamp", 0)),
|
||||
e.getString(base+".Name", Str.Empty),
|
||||
e.getString(base+".URL", Str.Empty),
|
||||
e.getString(base+".Response", Str.Empty),
|
||||
e.getInt(base+".ResponseCode", -1),
|
||||
e.getString(base+".ExceptionString", Str.Empty)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -4,14 +4,24 @@ import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple4;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple5;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.LogLevel;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||
import com.google.android.gms.common.util.JsonUtils;
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
import org.joda.time.Instant;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class FBMService extends FirebaseMessagingService
|
||||
{
|
||||
@Override
|
||||
@@ -38,19 +48,20 @@ public class FBMService extends FirebaseMessagingService
|
||||
String content = remoteMessage.getData().get("body");
|
||||
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
|
||||
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
|
||||
boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
|
||||
|
||||
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
|
||||
|
||||
if (SCNApp.isBackground())
|
||||
SingleQuery q = new SingleQuery(LogLevel.INFO, Instant.now(), "FBM<recieve>", Str.Empty, new JSONObject(remoteMessage.getData()).toString(), 0, "SUCCESS");
|
||||
QueryLog.instance().add(q);
|
||||
|
||||
if (trimmed)
|
||||
{
|
||||
NotificationService.inst().showBackground(msg);
|
||||
ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationService.inst().showForeground(msg);
|
||||
recieveData(time, title, content, prio, scn_id, false);
|
||||
}
|
||||
|
||||
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, msg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -58,4 +69,27 @@ public class FBMService extends FirebaseMessagingService
|
||||
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck)
|
||||
{
|
||||
if (CMessageList.inst().isAck(scn_id))
|
||||
{
|
||||
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
|
||||
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
|
||||
return;
|
||||
}
|
||||
|
||||
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
|
||||
|
||||
if (SCNApp.isBackground())
|
||||
{
|
||||
NotificationService.inst().showBackground(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationService.inst().showForeground(msg);
|
||||
}
|
||||
|
||||
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package com.blackforestbytes.simplecloudnotifier.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -11,13 +12,18 @@ import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple2;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Dictionary;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -45,12 +51,21 @@ public class IABService implements PurchasesUpdatedListener
|
||||
}
|
||||
}
|
||||
|
||||
public enum SimplePurchaseState { YES, NO, UNINITIALIZED }
|
||||
|
||||
private BillingClient client;
|
||||
private boolean isServiceConnected;
|
||||
private final List<Purchase> purchases = new ArrayList<>();
|
||||
private boolean _isInitialized = false;
|
||||
|
||||
private Map<String, Boolean> _localCache= new HashMap<>();
|
||||
|
||||
public IABService(Context c)
|
||||
{
|
||||
_isInitialized = false;
|
||||
|
||||
loadCache();
|
||||
|
||||
client = BillingClient
|
||||
.newBuilder(c)
|
||||
.setListener(this)
|
||||
@@ -59,6 +74,45 @@ public class IABService implements PurchasesUpdatedListener
|
||||
startServiceConnection(this::queryPurchases, false);
|
||||
}
|
||||
|
||||
private void loadCache()
|
||||
{
|
||||
_localCache.clear();
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
int count = sharedPref.getInt("c", 0);
|
||||
for (int i=0; i < count; i++)
|
||||
{
|
||||
String k = sharedPref.getString("["+i+"]->key", null);
|
||||
boolean v = sharedPref.getBoolean("["+i+"]->value", false);
|
||||
if (k==null)continue;
|
||||
_localCache.put(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCache()
|
||||
{
|
||||
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor= sharedPref.edit();
|
||||
|
||||
editor.putInt("c", _localCache.size());
|
||||
int i = 0;
|
||||
for (Map.Entry<String, Boolean> e : _localCache.entrySet())
|
||||
{
|
||||
editor.putString("["+i+"]->key", e.getKey());
|
||||
editor.putBoolean("["+i+"]->value", e.getValue());
|
||||
i++;
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private synchronized void updateCache(String k, boolean v)
|
||||
{
|
||||
if (_localCache.containsKey(k) && _localCache.get(k)==v) return;
|
||||
|
||||
_localCache.put(k, v);
|
||||
saveCache();
|
||||
}
|
||||
|
||||
public void queryPurchases()
|
||||
{
|
||||
Func0to0 queryToExecute = () ->
|
||||
@@ -71,10 +125,12 @@ public class IABService implements PurchasesUpdatedListener
|
||||
{
|
||||
for (Purchase p : purchasesResult.getPurchasesList())
|
||||
{
|
||||
handlePurchase(p);
|
||||
handlePurchase(p, false);
|
||||
}
|
||||
|
||||
boolean newProMode = getPurchaseCached(IAB_PRO_MODE) != null;
|
||||
_isInitialized = true;
|
||||
|
||||
boolean newProMode = getPurchaseCachedSimple(IAB_PRO_MODE);
|
||||
if (newProMode != SCNSettings.inst().promode_local)
|
||||
{
|
||||
refreshProModeListener();
|
||||
@@ -102,7 +158,8 @@ public class IABService implements PurchasesUpdatedListener
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest) {
|
||||
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest)
|
||||
{
|
||||
if (isServiceConnected)
|
||||
{
|
||||
runnable.invoke();
|
||||
@@ -130,28 +187,31 @@ public class IABService implements PurchasesUpdatedListener
|
||||
{
|
||||
for (Purchase purchase : purchases)
|
||||
{
|
||||
handlePurchase(purchase);
|
||||
handlePurchase(purchase, true);
|
||||
}
|
||||
}
|
||||
else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null)
|
||||
{
|
||||
for (Purchase purchase : purchases)
|
||||
{
|
||||
handlePurchase(purchase);
|
||||
handlePurchase(purchase, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePurchase(Purchase purchase)
|
||||
private void handlePurchase(Purchase purchase, boolean triggerUpdate)
|
||||
{
|
||||
Log.d(TAG, "Got a verified purchase: " + purchase);
|
||||
|
||||
purchases.add(purchase);
|
||||
|
||||
refreshProModeListener();
|
||||
if (triggerUpdate) refreshProModeListener();
|
||||
|
||||
updateCache(purchase.getSku(), true);
|
||||
}
|
||||
|
||||
private void refreshProModeListener() {
|
||||
private void refreshProModeListener()
|
||||
{
|
||||
MainActivity ma = SCNApp.getMainActivity();
|
||||
if (ma != null) ma.adpTabs.tab3.updateProState();
|
||||
if (ma != null) ma.adpTabs.tab1.updateProState();
|
||||
@@ -183,13 +243,31 @@ public class IABService implements PurchasesUpdatedListener
|
||||
});
|
||||
}
|
||||
|
||||
public Purchase getPurchaseCached(String id)
|
||||
public boolean getPurchaseCachedSimple(String id)
|
||||
{
|
||||
for (Purchase p : purchases)
|
||||
return getPurchaseCachedExtended(id).Item1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public Tuple3<Boolean, Boolean, String> getPurchaseCachedExtended(String id)
|
||||
{
|
||||
// <state, initialized, token>
|
||||
|
||||
if (!_isInitialized)
|
||||
{
|
||||
if (Str.equals(p.getSku(), id)) return p;
|
||||
if (_localCache.containsKey(id) && _localCache.get(id)) return new Tuple3<>(true, false, Str.Empty);
|
||||
}
|
||||
|
||||
return null;
|
||||
for (Purchase p : purchases)
|
||||
{
|
||||
if (Str.equals(p.getSku(), id))
|
||||
{
|
||||
updateCache(id, true);
|
||||
return new Tuple3<>(true, true, p.getPurchaseToken());
|
||||
}
|
||||
}
|
||||
|
||||
updateCache(id, false);
|
||||
return new Tuple3<>(false, true, Str.Empty);
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,9 @@ import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioAttributes;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
@@ -18,17 +17,21 @@ import android.widget.Toast;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.NotificationSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
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 static NotificationService _inst = null;
|
||||
@@ -43,10 +46,10 @@ public class NotificationService
|
||||
|
||||
private NotificationService()
|
||||
{
|
||||
updateChannels();
|
||||
createChannels();
|
||||
}
|
||||
|
||||
public void updateChannels()
|
||||
private void createChannels()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||
|
||||
@@ -54,14 +57,45 @@ public class NotificationService
|
||||
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
|
||||
if (notifman == null) return;
|
||||
|
||||
NotificationChannel channel = notifman.getNotificationChannel(CHANNEL_ID);
|
||||
if (channel == null)
|
||||
{
|
||||
channel = new NotificationChannel(CHANNEL_ID, "Push notifications", NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.setDescription("Push notifications from the server");
|
||||
channel.setSound(null, null);
|
||||
channel.setVibrationPattern(null);
|
||||
notifman.createNotificationChannel(channel);
|
||||
NotificationChannel channel0 = notifman.getNotificationChannel(CHANNEL_P0_ID);
|
||||
if (channel0 == null)
|
||||
{
|
||||
channel0 = new NotificationChannel(CHANNEL_P0_ID, "Push notifications (low priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||
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.CYAN);
|
||||
channel0.enableLights(true);
|
||||
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.CYAN);
|
||||
channel1.enableLights(true);
|
||||
notifman.createNotificationChannel(channel1);
|
||||
}
|
||||
}
|
||||
{
|
||||
NotificationChannel channel2 = notifman.getNotificationChannel(CHANNEL_P2_ID);
|
||||
if (channel2 == null)
|
||||
{
|
||||
channel2 = new NotificationChannel(CHANNEL_P2_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.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
channel2.setLightColor(Color.CYAN);
|
||||
channel2.enableLights(true);
|
||||
notifman.createNotificationChannel(channel2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,21 +113,15 @@ public class NotificationService
|
||||
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, false);
|
||||
|
||||
if (ns.EnableVibration)
|
||||
{
|
||||
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
v.vibrate(VibrationEffect.createOneShot(1500, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
} else {
|
||||
v.vibrate(1500);
|
||||
v.vibrate(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,75 +146,122 @@ public class NotificationService
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
{
|
||||
// old
|
||||
|
||||
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);
|
||||
showBackground_old(msg, ctxt, ns, msg.Priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
// new
|
||||
|
||||
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));
|
||||
}
|
||||
showBackground_new(msg, ctxt, ns, msg.Priority);
|
||||
}
|
||||
}
|
||||
|
||||
private String getChannel(PriorityEnum p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case LOW: return CHANNEL_P0_ID;
|
||||
case NORMAL: return CHANNEL_P1_ID;
|
||||
case HIGH: return CHANNEL_P2_ID;
|
||||
|
||||
default: return CHANNEL_P0_ID;
|
||||
}
|
||||
}
|
||||
|
||||
private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
|
||||
{
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
|
||||
mBuilder.setSmallIcon(R.drawable.ic_notification_white);
|
||||
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
|
||||
mBuilder.setContentTitle(msg.Title);
|
||||
mBuilder.setContentText(msg.Content);
|
||||
mBuilder.setShowWhen(true);
|
||||
mBuilder.setWhen(msg.Timestamp * 1000);
|
||||
mBuilder.setAutoCancel(true);
|
||||
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
|
||||
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
|
||||
|
||||
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
|
||||
if (msg.Priority == PriorityEnum.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((int)msg.SCN_ID, 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_notification_white);
|
||||
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
|
||||
mBuilder.setContentTitle(msg.Title);
|
||||
mBuilder.setContentText(msg.Content);
|
||||
mBuilder.setShowWhen(true);
|
||||
mBuilder.setWhen(msg.Timestamp * 1000);
|
||||
mBuilder.setAutoCancel(true);
|
||||
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
|
||||
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
|
||||
|
||||
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
|
||||
|
||||
if (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((int)msg.SCN_ID, n);
|
||||
|
||||
if (ns.EnableVibration)
|
||||
{
|
||||
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
v.vibrate(VibrationEffect.createOneShot(500, 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,56 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
|
||||
public class MaxHeightScrollView extends ScrollView
|
||||
{
|
||||
public int maxHeight = Integer.MAX_VALUE;//dp
|
||||
|
||||
public MaxHeightScrollView(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MaxHeightScrollView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
|
||||
try {
|
||||
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
|
||||
try {
|
||||
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(getResources(), maxHeight), MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private int dpToPx(Resources res, int dp)
|
||||
{
|
||||
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.util;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.view.View;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
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;
|
||||
|
||||
private int dir = 0;
|
||||
|
||||
public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener)
|
||||
{
|
||||
super(dragDirs, swipeDirs);
|
||||
this.dir = swipeDirs;
|
||||
this.listener = listener;
|
||||
updateEnabled();
|
||||
}
|
||||
|
||||
public void updateEnabled()
|
||||
{
|
||||
int sdir = SCNSettings.inst().EnableDeleteSwipe ? ItemTouchHelper.LEFT : 0;
|
||||
if (dir == sdir) return;
|
||||
setDefaultSwipeDirs(dir = sdir);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.util;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
||||
public abstract class TextChangedListener<T> implements TextWatcher {
|
||||
private T target;
|
||||
|
||||
public TextChangedListener(T target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
this.onTextChanged(target, s);
|
||||
}
|
||||
|
||||
public abstract void onTextChanged(T target, Editable s);
|
||||
}
|
@@ -6,12 +6,14 @@ import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
@@ -91,7 +93,7 @@ public class AccountFragment extends Fragment
|
||||
|
||||
builder.setPositiveButton("YES", (dialog, which) -> {
|
||||
CMessageList.inst().clear();
|
||||
SCNApp.showToast("Notifications cleared", 1000);
|
||||
SCNApp.showToast("Messages cleared", 1000);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
@@ -103,7 +105,7 @@ public class AccountFragment extends Fragment
|
||||
|
||||
v.findViewById(R.id.btnQR).setOnClickListener(cv ->
|
||||
{
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL()));
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL(true)));
|
||||
startActivity(browserIntent);
|
||||
});
|
||||
|
||||
@@ -126,10 +128,11 @@ public class AccountFragment extends Fragment
|
||||
public void updateUI(View v)
|
||||
{
|
||||
if (v == null) return;
|
||||
TextView tvUserID = v.findViewById(R.id.tvUserID);
|
||||
TextView tvUserKey = v.findViewById(R.id.tvUserKey);
|
||||
TextView tvQuota = v.findViewById(R.id.tvQuota);
|
||||
ImageButton btnQR = v.findViewById(R.id.btnQR);
|
||||
TextView tvUserID = v.findViewById(R.id.tvUserID);
|
||||
TextView tvUserKey = v.findViewById(R.id.tvUserKey);
|
||||
TextView tvQuota = v.findViewById(R.id.tvQuota);
|
||||
ImageView ivQuota = v.findViewById(R.id.ic_img_quota);
|
||||
ImageButton btnQR = v.findViewById(R.id.btnQR);
|
||||
|
||||
SCNSettings s = SCNSettings.inst();
|
||||
|
||||
@@ -138,7 +141,8 @@ public class AccountFragment extends Fragment
|
||||
tvUserID.setText(String.valueOf(s.user_id));
|
||||
tvUserKey.setText(s.user_key);
|
||||
tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max));
|
||||
btnQR.setImageBitmap(QRCode.from(s.createOnlineURL()).to(ImageType.PNG).withSize(512, 512).bitmap());
|
||||
btnQR.setImageBitmap(QRCode.from(s.createOnlineURL(false)).to(ImageType.PNG).withSize(512, 512).bitmap());
|
||||
ivQuota.setColorFilter(s.quota_curr>=s.quota_max ? Color.rgb(200, 0, 0) : Color.rgb(128, 128, 128));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -146,6 +150,7 @@ public class AccountFragment extends Fragment
|
||||
tvUserKey.setText(R.string.str_not_connected);
|
||||
tvQuota.setText(R.string.str_not_connected);
|
||||
btnQR.setImageResource(R.drawable.qr_default);
|
||||
ivQuota.setColorFilter(0x80_80_80);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,21 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.icu.text.SymbolTable;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -18,17 +26,23 @@ import androidx.viewpager.widget.ViewPager;
|
||||
public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
public TabAdapter adpTabs;
|
||||
public RelativeLayout layoutRoot;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
QueryLog.instance();
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
NotificationService.inst();
|
||||
CMessageList.inst();
|
||||
|
||||
layoutRoot = findViewById(R.id.layoutRoot);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setOnClickListener(this::onToolbackClicked);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ViewPager viewPager = findViewById(R.id.pager);
|
||||
@@ -39,6 +53,22 @@ public class MainActivity extends AppCompatActivity
|
||||
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||
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);
|
||||
IABService.startup(this);
|
||||
SCNSettings.inst().work(this);
|
||||
@@ -61,4 +91,16 @@ public class MainActivity extends AppCompatActivity
|
||||
CMessageList.inst().fullSave();
|
||||
IABService.inst().destroy();
|
||||
}
|
||||
|
||||
private int clickCount = 0;
|
||||
private long lastClick = 0;
|
||||
private void onToolbackClicked(View v)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastClick > 200) clickCount=0;
|
||||
clickCount++;
|
||||
lastClick = now;
|
||||
|
||||
if (clickCount == 4) startActivity(new Intent(this, QueryLogActivity.class));
|
||||
}
|
||||
}
|
||||
|
@@ -1,25 +1,44 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.google.android.material.button.MaterialButton;
|
||||
|
||||
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.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class MessageAdapter extends RecyclerView.Adapter
|
||||
{
|
||||
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);
|
||||
|
||||
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||
@@ -36,9 +55,17 @@ public class MessageAdapter extends RecyclerView.Adapter
|
||||
@Override
|
||||
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;
|
||||
view.setMessage(msg);
|
||||
view.setMessage(msg, position);
|
||||
|
||||
viewHolders.put(view, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder)
|
||||
{
|
||||
if (holder instanceof MessagePresenter) viewHolders.remove(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,26 +86,74 @@ public class MessageAdapter extends RecyclerView.Adapter
|
||||
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 CMessage removeItem(int position)
|
||||
{
|
||||
CMessage i = CMessageList.inst().removeFromBack(position);
|
||||
notifyDataSetChanged();
|
||||
return i;
|
||||
}
|
||||
|
||||
public void restoreItem(CMessage item, int position)
|
||||
{
|
||||
CMessageList.inst().insert(position, item);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
|
||||
{
|
||||
private TextView tvTimestamp;
|
||||
private TextView tvTitle;
|
||||
private TextView tvMessage;
|
||||
private ImageView ivPriority;
|
||||
|
||||
public RelativeLayout viewForeground;
|
||||
public RelativeLayout viewBackground;
|
||||
|
||||
public MaterialButton btnShare;
|
||||
public MaterialButton btnDelete;
|
||||
|
||||
private CMessage data;
|
||||
private int datapos;
|
||||
|
||||
MessagePresenter(View itemView)
|
||||
{
|
||||
super(itemView);
|
||||
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
||||
tvTitle = itemView.findViewById(R.id.tvTitle);
|
||||
tvMessage = itemView.findViewById(R.id.tvMessage);
|
||||
ivPriority = itemView.findViewById(R.id.ivPriority);
|
||||
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
||||
tvTitle = itemView.findViewById(R.id.tvTitle);
|
||||
tvMessage = itemView.findViewById(R.id.tvMessage);
|
||||
ivPriority = itemView.findViewById(R.id.ivPriority);
|
||||
viewForeground = itemView.findViewById(R.id.layoutFront);
|
||||
viewBackground = itemView.findViewById(R.id.layoutBack);
|
||||
btnShare = itemView.findViewById(R.id.btnShare);
|
||||
btnDelete = itemView.findViewById(R.id.btnDelete);
|
||||
|
||||
itemView.setOnClickListener(this);
|
||||
tvTimestamp.setOnClickListener(this);
|
||||
tvTitle.setOnClickListener(this);
|
||||
tvMessage.setOnClickListener(this);
|
||||
ivPriority.setOnClickListener(this);
|
||||
viewForeground.setOnClickListener(this);
|
||||
|
||||
btnShare.setOnClickListener(v ->
|
||||
{
|
||||
if (data == null) return;
|
||||
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
sharingIntent.setType("text/plain");
|
||||
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, data.Title);
|
||||
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, data.Content);
|
||||
SCNApp.getMainActivity().startActivity(Intent.createChooser(sharingIntent, "Share message"));
|
||||
|
||||
});
|
||||
btnDelete.setOnClickListener(v -> { if (data != null) SCNApp.getMainActivity().adpTabs.tab1.deleteMessage(datapos); });
|
||||
|
||||
}
|
||||
|
||||
void setMessage(CMessage msg)
|
||||
void setMessage(CMessage msg, int pos)
|
||||
{
|
||||
tvTimestamp.setText(msg.formatTimestamp());
|
||||
tvTitle.setText(msg.Title);
|
||||
@@ -89,23 +164,63 @@ public class MessageAdapter extends RecyclerView.Adapter
|
||||
case LOW:
|
||||
ivPriority.setVisibility(View.VISIBLE);
|
||||
ivPriority.setImageResource(R.drawable.priority_low);
|
||||
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
|
||||
break;
|
||||
case NORMAL:
|
||||
ivPriority.setVisibility(View.GONE);
|
||||
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
|
||||
break;
|
||||
case HIGH:
|
||||
ivPriority.setVisibility(View.VISIBLE);
|
||||
ivPriority.setImageResource(R.drawable.priority_high);
|
||||
ivPriority.setColorFilter(Color.rgb(200, 0, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
data = msg;
|
||||
datapos = pos;
|
||||
|
||||
if (msg.IsExpandedInAdapter) expand(true); else collapse(true);
|
||||
}
|
||||
|
||||
private void expand(boolean force)
|
||||
{
|
||||
if (data != null && data.IsExpandedInAdapter && !force) return;
|
||||
if (data != null) data.IsExpandedInAdapter = true;
|
||||
if (tvMessage != null) tvMessage.setMaxLines(9999);
|
||||
if (btnDelete != null) btnDelete.setVisibility(View.VISIBLE);
|
||||
if (btnShare != null) btnShare.setVisibility(View.VISIBLE);
|
||||
|
||||
}
|
||||
|
||||
private int norm(int i) { return (i<=0)?0:((i>9999)?9999:i); }
|
||||
|
||||
private void collapse(boolean force)
|
||||
{
|
||||
if (data != null && !data.IsExpandedInAdapter && !force) return;
|
||||
if (data != null) data.IsExpandedInAdapter = false;
|
||||
if (tvMessage != null) tvMessage.setMaxLines(norm(SCNSettings.inst().PreviewLineCount));
|
||||
if (btnDelete != null) btnDelete.setVisibility(View.GONE);
|
||||
if (btnShare != null) btnShare.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
//SCNApp.showToast(data.Title, Toast.LENGTH_LONG);
|
||||
if (data.IsExpandedInAdapter)
|
||||
{
|
||||
collapse(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessagePresenter holder : MessageAdapter.this.viewHolders.keySet())
|
||||
{
|
||||
if (holder == null) continue;
|
||||
if (holder == this) continue;
|
||||
holder.collapse(false);
|
||||
}
|
||||
|
||||
expand(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,33 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
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.PublisherAdView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class NotificationsFragment extends Fragment
|
||||
public class NotificationsFragment extends Fragment implements MessageAdapterTouchHelper.MessageAdapterTouchHelperListener
|
||||
{
|
||||
private PublisherAdView adView;
|
||||
private MessageAdapter adpMessages;
|
||||
|
||||
public MessageAdapterTouchHelper touchHelper;
|
||||
|
||||
public NotificationsFragment()
|
||||
{
|
||||
@@ -31,8 +40,12 @@ public class NotificationsFragment extends Fragment
|
||||
View v = inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||
|
||||
RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
|
||||
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
||||
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
||||
LinearLayoutManager lman = new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, false);
|
||||
rvMessages.setLayoutManager(lman);
|
||||
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
|
||||
|
||||
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = touchHelper = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this);
|
||||
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages);
|
||||
|
||||
adView = v.findViewById(R.id.adBanner);
|
||||
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
||||
@@ -45,6 +58,33 @@ public class NotificationsFragment extends Fragment
|
||||
|
||||
public void updateProState()
|
||||
{
|
||||
if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE);
|
||||
if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position)
|
||||
{
|
||||
if (viewHolder instanceof MessageAdapter.MessagePresenter)
|
||||
{
|
||||
deleteMessage(viewHolder.getAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteMessage(int pos)
|
||||
{
|
||||
final int deletedIndex = pos;
|
||||
|
||||
final CMessage deletedItem = adpMessages.removeItem(pos);
|
||||
String name = deletedItem.Title;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public void updateDeleteSwipeEnabled()
|
||||
{
|
||||
if (touchHelper != null) touchHelper.updateEnabled();
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,46 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.billingclient.api.Purchase;
|
||||
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.service.IABService;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||
import com.blackforestbytes.simplecloudnotifier.util.TextChangedListener;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import top.defaults.colorpicker.ColorPickerPopup;
|
||||
@@ -36,6 +54,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
private Button prefUpgradeAccount;
|
||||
private TextView prefUpgradeAccount_msg;
|
||||
private TextView prefUpgradeAccount_info;
|
||||
private Switch prefEnableDeleteSwipe;
|
||||
private EditText prefPreviewLineCount;
|
||||
|
||||
private Switch prefMsgLowEnableSound;
|
||||
private TextView prefMsgLowRingtone_value;
|
||||
@@ -45,6 +65,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
private View prefMsgLowLedColor_container;
|
||||
private ImageView prefMsgLowLedColor_value;
|
||||
private Switch prefMsgLowEnableVibrations;
|
||||
private Switch prefMsgLowForceVolume;
|
||||
private SeekBar prefMsgLowVolume;
|
||||
private ImageView prefMsgLowVolumeTest;
|
||||
|
||||
private Switch prefMsgNormEnableSound;
|
||||
private TextView prefMsgNormRingtone_value;
|
||||
@@ -54,6 +77,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
private View prefMsgNormLedColor_container;
|
||||
private ImageView prefMsgNormLedColor_value;
|
||||
private Switch prefMsgNormEnableVibrations;
|
||||
private Switch prefMsgNormForceVolume;
|
||||
private SeekBar prefMsgNormVolume;
|
||||
private ImageView prefMsgNormVolumeTest;
|
||||
|
||||
private Switch prefMsgHighEnableSound;
|
||||
private TextView prefMsgHighRingtone_value;
|
||||
@@ -63,9 +89,14 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
private View prefMsgHighLedColor_container;
|
||||
private ImageView prefMsgHighLedColor_value;
|
||||
private Switch prefMsgHighEnableVibrations;
|
||||
private Switch prefMsgHighForceVolume;
|
||||
private SeekBar prefMsgHighVolume;
|
||||
private ImageView prefMsgHighVolumeTest;
|
||||
|
||||
private int musicPickerSwitch = -1;
|
||||
|
||||
private MediaPlayer[] mPlayers = new MediaPlayer[3];
|
||||
|
||||
public SettingsFragment()
|
||||
{
|
||||
// Required empty public constructor
|
||||
@@ -90,6 +121,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
||||
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
|
||||
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
|
||||
prefEnableDeleteSwipe = v.findViewById(R.id.prefEnableDeleteSwipe);
|
||||
prefPreviewLineCount = v.findViewById(R.id.prefPreviewLineCount);
|
||||
|
||||
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
||||
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
||||
@@ -99,6 +132,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgLowLedColor_value = v.findViewById(R.id.prefMsgLowLedColor_value);
|
||||
prefMsgLowLedColor_container = v.findViewById(R.id.prefMsgLowLedColor_container);
|
||||
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);
|
||||
prefMsgNormRingtone_value = v.findViewById(R.id.prefMsgNormRingtone_value);
|
||||
@@ -108,6 +144,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgNormLedColor_value = v.findViewById(R.id.prefMsgNormLedColor_value);
|
||||
prefMsgNormLedColor_container = v.findViewById(R.id.prefMsgNormLedColor_container);
|
||||
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);
|
||||
prefMsgHighRingtone_value = v.findViewById(R.id.prefMsgHighRingtone_value);
|
||||
@@ -117,8 +156,16 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgHighLedColor_value = v.findViewById(R.id.prefMsgHighLedColor_value);
|
||||
prefMsgHighLedColor_container = v.findViewById(R.id.prefMsgHighLedColor_container);
|
||||
prefMsgHighEnableVibrations = v.findViewById(R.id.prefMsgHighEnableVibrations);
|
||||
prefMsgHighForceVolume = v.findViewById(R.id.prefMsgHighForceVolume);
|
||||
prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume);
|
||||
prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest);
|
||||
|
||||
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(v.getContext(), android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
|
||||
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
prefLocalCacheSize.setAdapter(plcsa);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void updateUI()
|
||||
{
|
||||
SCNSettings s = SCNSettings.inst();
|
||||
@@ -126,15 +173,14 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
if (c == null) return;
|
||||
|
||||
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
||||
if (prefEnableDeleteSwipe.isChecked() != s.EnableDeleteSwipe) prefEnableDeleteSwipe.setChecked(s.EnableDeleteSwipe);
|
||||
if (!prefPreviewLineCount.getText().toString().equals(Integer.toString(s.PreviewLineCount))) prefPreviewLineCount.setText(Integer.toString(s.PreviewLineCount));
|
||||
|
||||
prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||
prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||
prefUpgradeAccount_msg.setVisibility( SCNSettings.inst().promode_local ? View.VISIBLE : View.GONE );
|
||||
|
||||
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
|
||||
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
prefLocalCacheSize.setAdapter(plcsa);
|
||||
prefLocalCacheSize.setSelection(getCacheSizeIndex(s.LocalCacheSize));
|
||||
if (prefLocalCacheSize.getSelectedItemPosition() != getCacheSizeIndex(s.LocalCacheSize)) prefLocalCacheSize.setSelection(getCacheSizeIndex(s.LocalCacheSize));
|
||||
|
||||
if (prefMsgLowEnableSound.isChecked() != s.PriorityLow.EnableSound) prefMsgLowEnableSound.setChecked(s.PriorityLow.EnableSound);
|
||||
if (!prefMsgLowRingtone_value.getText().equals(s.PriorityLow.SoundName)) prefMsgLowRingtone_value.setText(s.PriorityLow.SoundName);
|
||||
@@ -142,6 +188,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
if (prefMsgLowEnableLED.isChecked() != s.PriorityLow.EnableLED) prefMsgLowEnableLED.setChecked(s.PriorityLow.EnableLED);
|
||||
prefMsgLowLedColor_value.setColorFilter(s.PriorityLow.LEDColor);
|
||||
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 (!prefMsgNormRingtone_value.getText().equals(s.PriorityNorm.SoundName)) prefMsgNormRingtone_value.setText(s.PriorityNorm.SoundName);
|
||||
@@ -149,6 +201,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
if (prefMsgNormEnableLED.isChecked() != s.PriorityNorm.EnableLED) prefMsgNormEnableLED.setChecked(s.PriorityNorm.EnableLED);
|
||||
prefMsgNormLedColor_value.setColorFilter(s.PriorityNorm.LEDColor);
|
||||
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 (!prefMsgHighRingtone_value.getText().equals(s.PriorityHigh.SoundName)) prefMsgHighRingtone_value.setText(s.PriorityHigh.SoundName);
|
||||
@@ -156,13 +214,26 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
if (prefMsgHighEnableLED.isChecked() != s.PriorityHigh.EnableLED) prefMsgHighEnableLED.setChecked(s.PriorityHigh.EnableLED);
|
||||
prefMsgHighLedColor_value.setColorFilter(s.PriorityHigh.LEDColor);
|
||||
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()
|
||||
{
|
||||
SCNSettings s = SCNSettings.inst();
|
||||
|
||||
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { s.Enabled=b; saveAndUpdate(); });
|
||||
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); });
|
||||
prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
|
||||
prefPreviewLineCount.addTextChangedListener(new TextChangedListener<EditText>(prefPreviewLineCount) {
|
||||
@Override
|
||||
public void onTextChanged(EditText target, Editable ed) {
|
||||
if (!ed.toString().isEmpty()) try { s.PreviewLineCount=Integer.parseInt(ed.toString()); saveAndUpdate(); } catch (Exception e) { /* */ }
|
||||
}
|
||||
});
|
||||
|
||||
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
|
||||
{
|
||||
@@ -181,6 +252,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgLowEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableLED=b; saveAndUpdate(); });
|
||||
prefMsgLowLedColor_container.setOnClickListener(a -> chooseLEDColorLow());
|
||||
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(); });
|
||||
prefMsgNormRingtone_container.setOnClickListener(a -> chooseRingtoneNorm());
|
||||
@@ -188,6 +262,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgNormEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableLED=b; saveAndUpdate(); });
|
||||
prefMsgNormLedColor_container.setOnClickListener(a -> chooseLEDColorNorm());
|
||||
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(); });
|
||||
prefMsgHighRingtone_container.setOnClickListener(a -> chooseRingtoneHigh());
|
||||
@@ -195,12 +272,91 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefMsgHighEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableLED=b; saveAndUpdate(); });
|
||||
prefMsgHighLedColor_container.setOnClickListener(a -> chooseLEDColorHigh());
|
||||
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 updateEnabled(boolean prev, boolean now)
|
||||
{
|
||||
if (!prev && now)
|
||||
{
|
||||
SCNApp.showToast("SimpleCloudNotifier is now enabled", Toast.LENGTH_SHORT);
|
||||
}
|
||||
else if (prev && !now)
|
||||
{
|
||||
SCNApp.showToast("SimpleCloudNotifier is now disabled\nYou won't recieve new messages.", Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVolume(int idx, int volume)
|
||||
{
|
||||
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()
|
||||
{
|
||||
SCNSettings.inst().save();
|
||||
updateUI();
|
||||
SCNApp.getMainActivity().adpTabs.tab1.updateDeleteSwipeEnabled();
|
||||
}
|
||||
|
||||
private void onUpgradeAccount()
|
||||
@@ -210,11 +366,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
|
||||
public void updateProState()
|
||||
{
|
||||
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||
boolean pmode = IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE);
|
||||
|
||||
if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE);
|
||||
if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE);
|
||||
if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE );
|
||||
if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( pmode ? View.GONE : View.VISIBLE);
|
||||
if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(pmode ? View.GONE : View.VISIBLE);
|
||||
if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( pmode ? View.VISIBLE : View.GONE );
|
||||
}
|
||||
|
||||
private int getCacheSizeIndex(int value)
|
||||
@@ -232,7 +388,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||
ump.windowTitle("Choose notification sound");
|
||||
ump.removeSilent();
|
||||
ump.streamType(AudioManager.STREAM_ALARM);
|
||||
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||
ump.ringtone();
|
||||
ump.notification();
|
||||
ump.alarm();
|
||||
@@ -247,7 +403,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||
ump.windowTitle("Choose notification sound");
|
||||
ump.removeSilent();
|
||||
ump.streamType(AudioManager.STREAM_ALARM);
|
||||
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||
ump.ringtone();
|
||||
ump.notification();
|
||||
ump.alarm();
|
||||
@@ -262,7 +418,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||
ump.windowTitle("Choose notification sound");
|
||||
ump.removeSilent();
|
||||
ump.streamType(AudioManager.STREAM_ALARM);
|
||||
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||
ump.ringtone();
|
||||
ump.notification();
|
||||
ump.alarm();
|
||||
@@ -355,4 +511,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
{
|
||||
musicPickerSwitch = -1;
|
||||
}
|
||||
|
||||
public void onViewpagerHide()
|
||||
{
|
||||
stopSound(0, prefMsgLowVolumeTest);
|
||||
stopSound(1, prefMsgNormVolumeTest);
|
||||
stopSound(2, prefMsgHighVolumeTest);
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ public class TabAdapter extends FragmentStatePagerAdapter {
|
||||
{
|
||||
switch (position)
|
||||
{
|
||||
case 0: return "Notifications";
|
||||
case 0: return "Messages";
|
||||
case 1: return "Account";
|
||||
case 2: return "Settings";
|
||||
default: return null;
|
||||
|
@@ -0,0 +1,44 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
|
||||
public class QueryLogActivity extends AppCompatActivity
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_querylog);
|
||||
|
||||
ListView lvMain = findViewById(R.id.lvQueryList);
|
||||
SingleQuery[] arr = QueryLog.instance().get().toArray(new SingleQuery[0]);
|
||||
QueryLogAdapter a = new QueryLogAdapter(this, arr);
|
||||
lvMain.setAdapter(a);
|
||||
|
||||
lvMain.setOnItemClickListener((parent, view, position, id) ->
|
||||
{
|
||||
if (position >= 0 && position < arr.length)
|
||||
{
|
||||
Intent i = new Intent(QueryLogActivity.this, SingleQueryLogActivity.class);
|
||||
Bundle b = new Bundle();
|
||||
arr[position].save(b, "data");
|
||||
i.putExtra("query", b);
|
||||
startActivity(i);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
public class QueryLogAdapter extends ArrayAdapter<SingleQuery>
|
||||
{
|
||||
public static DateTimeFormatter UI_FULLTIME_FORMATTER = DateTimeFormat.forPattern("HH:mm:ss");
|
||||
|
||||
public QueryLogAdapter(@NonNull Context context, @NonNull SingleQuery[] objects)
|
||||
{
|
||||
super(context, R.layout.adapter_querylog, objects);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View convertView, @NonNull ViewGroup parent)
|
||||
{
|
||||
View v = convertView;
|
||||
|
||||
if (v == null) {
|
||||
LayoutInflater vi;
|
||||
vi = LayoutInflater.from(getContext());
|
||||
v = vi.inflate(R.layout.adapter_querylog, parent, false);
|
||||
}
|
||||
|
||||
SingleQuery p = getItem(position);
|
||||
|
||||
if (p != null)
|
||||
{
|
||||
TextView tt1 = v.findViewById(R.id.list_item_debuglogrow_time);
|
||||
if (tt1 != null) tt1.setText(p.Timestamp.toString(UI_FULLTIME_FORMATTER));
|
||||
if (tt1 != null) tt1.setTextColor(Color.BLACK);
|
||||
|
||||
TextView tt2 = v.findViewById(R.id.list_item_debuglogrow_level);
|
||||
if (tt2 != null) tt2.setText(p.Level.toUIString());
|
||||
if (tt2 != null) tt2.setTextColor(Color.BLACK);
|
||||
|
||||
TextView tt3 = v.findViewById(R.id.list_item_debuglogrow_info);
|
||||
if (tt3 != null) tt3.setText("");
|
||||
if (tt3 != null) tt3.setTextColor(Color.BLUE);
|
||||
|
||||
TextView tt4 = v.findViewById(R.id.list_item_debuglogrow_id);
|
||||
if (tt4 != null) tt4.setText(p.Name);
|
||||
if (tt4 != null) tt4.setTextColor(p.Level.getColor());
|
||||
|
||||
TextView tt5 = v.findViewById(R.id.list_item_debuglogrow_message);
|
||||
if (tt5 != null) tt5.setText(p.ExceptionString.length()> 40 ? p.ExceptionString.substring(0, 40-3)+"..." : p.ExceptionString);
|
||||
if (tt5 != null) tt5.setTextColor(p.Level.getColor());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.lib.string.CompactJsonFormatter;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SingleQueryLogActivity extends AppCompatActivity
|
||||
{
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_singlequerylog);
|
||||
|
||||
SingleQuery q = SingleQuery.load(getIntent().getBundleExtra("query"), "data");
|
||||
|
||||
this.<TextView>findViewById(R.id.tvQL_Timestamp).setText(q.Timestamp.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
this.<TextView>findViewById(R.id.tvQL_Level).setText(q.Level.toUIString());
|
||||
this.<TextView>findViewById(R.id.tvQL_Level).setTextColor(q.Level.getColor());
|
||||
this.<TextView>findViewById(R.id.tvQL_Name).setText(q.Name);
|
||||
this.<TextView>findViewById(R.id.tvQL_URL).setText(q.URL.replace("?", "\r\n?").replace("&", "\r\n&"));
|
||||
this.<TextView>findViewById(R.id.tvQL_Response).setText(CompactJsonFormatter.formatJSON(q.Response, 999));
|
||||
this.<TextView>findViewById(R.id.tvQL_ResponseCode).setText(Integer.toString(q.ResponseCode));
|
||||
this.<TextView>findViewById(R.id.tvQL_ExceptionString).setText(q.ExceptionString);
|
||||
}
|
||||
}
|
BIN
android/app/src/main/res/drawable-hdpi/ic_notification_white.png
Normal file
After Width: | Height: | Size: 472 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification_white.png
Normal file
After Width: | Height: | Size: 320 B |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="@android:color/white" />
|
||||
<stroke android:width="1dip" android:color="#888888"/>
|
||||
</shape>
|
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 949 B |
After Width: | Height: | Size: 1.0 KiB |
@@ -1,15 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="1000"
|
||||
android:viewportWidth="1000" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M500,500m-500,0a500,500 0,1 1,1000 0a500,500 0,1 1,-1000 0"
|
||||
android:strokeColor="#000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M300,694L700,694L500,136L300,694"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M473,559L527,559L527,774L473,774"/>
|
||||
<path android:fillColor="#000000" android:pathData="M376,640L624,640L500,295L376,640"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M100,730L500,730L300,172L100,730"/>
|
||||
<path android:fillColor="#000000" android:pathData="M176,676L424,676L300,331L176,676"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M273,595L327,595L327,810L273,810"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M500,730L900,730L700,172L500,730"/>
|
||||
<path android:fillColor="#000000" android:pathData="M576,676L824,676L700,331L576,676"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M673,595L727,595L727,810L673,810"/>
|
||||
</vector>
|
16
android/app/src/main/res/drawable/ic_pause.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<vector
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportHeight="438.536"
|
||||
android:viewportWidth="438.536"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<path
|
||||
android:fillColor="#FF3F51B5"
|
||||
android:pathData="M164.453,0H18.276C13.324,0 9.041,1.807 5.425,5.424C1.808,9.04 0.001,13.322 0.001,18.271v401.991c0,4.948 1.807,9.233 5.424,12.847c3.619,3.617 7.902,5.428 12.851,5.428h146.181c4.949,0 9.231,-1.811 12.847,-5.428c3.617,-3.613 5.424,-7.898 5.424,-12.847V18.271c0,-4.952 -1.807,-9.231 -5.428,-12.847C173.685,1.807 169.402,0 164.453,0z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#FF3F51B5"
|
||||
android:pathData="M433.113,5.424C429.496,1.807 425.215,0 420.267,0H274.086c-4.949,0 -9.237,1.807 -12.847,5.424c-3.621,3.615 -5.432,7.898 -5.432,12.847v401.991c0,4.948 1.811,9.233 5.432,12.847c3.609,3.617 7.897,5.428 12.847,5.428h146.181c4.948,0 9.229,-1.811 12.847,-5.428c3.614,-3.613 5.421,-7.898 5.421,-12.847V18.271C438.534,13.319 436.73,9.04 433.113,5.424z"/>
|
||||
|
||||
</vector>
|
12
android/app/src/main/res/drawable/ic_play.xml
Normal file
@@ -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>
|
10
android/app/src/main/res/drawable/ic_share_small.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12sp"
|
||||
android:height="12sp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
16
android/app/src/main/res/drawable/ic_trash.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<vector
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportHeight="53"
|
||||
android:viewportWidth="53"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M42.943,6H33.5V3c0,-1.654 -1.346,-3 -3,-3h-8c-1.654,0 -3,1.346 -3,3v3h-9.443C8.096,6 6.5,7.596 6.5,9.557V14h2h36h2V9.557C46.5,7.596 44.904,6 42.943,6zM31.5,6h-10V3c0,-0.552 0.449,-1 1,-1h8c0.551,0 1,0.448 1,1V6z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M8.5,49.271C8.5,51.327 10.173,53 12.229,53h28.541c2.057,0 3.729,-1.673 3.729,-3.729V16h-36V49.271z"/>
|
||||
|
||||
</vector>
|
16
android/app/src/main/res/drawable/ic_trash_small.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<vector
|
||||
android:height="12sp"
|
||||
android:width="12sp"
|
||||
android:viewportHeight="53"
|
||||
android:viewportWidth="53"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M42.943,6H33.5V3c0,-1.654 -1.346,-3 -3,-3h-8c-1.654,0 -3,1.346 -3,3v3h-9.443C8.096,6 6.5,7.596 6.5,9.557V14h2h36h2V9.557C46.5,7.596 44.904,6 42.943,6zM31.5,6h-10V3c0,-0.552 0.449,-1 1,-1h8c0.551,0 1,0.448 1,1V6z"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M8.5,49.271C8.5,51.327 10.173,53 12.229,53h28.541c2.057,0 3.729,-1.673 3.729,-3.729V16h-36V49.271z"/>
|
||||
|
||||
</vector>
|
12
android/app/src/main/res/drawable/ic_volume.xml
Normal file
@@ -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,30 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
<RelativeLayout android:id="@+id/layoutRoot"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:showIn="@layout/activity_main">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
app:titleTextColor="@color/colorOnPrimary"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="6dp"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
android:minHeight="?attr/actionBarSize" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/toolbar"
|
||||
app:titleTextColor="@color/colorOnPrimary"
|
||||
app:tabTextColor="@color/colorOnPrimary"
|
||||
app:tabSelectedTextColor="@color/colorSecondary"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="6dp"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
|
||||
android:minHeight="?attr/actionBarSize" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/pager"
|
||||
|
13
android/app/src/main/res/layout/activity_querylog.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
android:id="@+id/layoutRoot"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/lvQueryList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</RelativeLayout>
|
240
android/app/src/main/res/layout/activity_singlequerylog.xml
Normal file
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:padding="4sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:textAlignment="center"
|
||||
android:textStyle="bold"
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:text="Server Query" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="Timestamp" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_margin="2dip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_Timestamp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="Level" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_margin="2dip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_Level"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="Name" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:layout_margin="2dip"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_Name"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="URL" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:layout_margin="2dip"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_URL"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text=""/>
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="ResponeCode" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="64"
|
||||
android:layout_margin="2dip"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_ResponseCode"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="Response" />
|
||||
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:layout_margin="2dip"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_Response"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_margin="2dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="100dp"
|
||||
android:text="Exception" />
|
||||
|
||||
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||
app:maxHeightOverride="100"
|
||||
android:layout_margin="2dip"
|
||||
android:background="@drawable/simple_black_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQL_ExceptionString"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="1dip"
|
||||
android:textIsSelectable="true"
|
||||
android:text="" />
|
||||
|
||||
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
50
android/app/src/main/res/layout/adapter_querylog.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/list_item_imagerow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_item_debuglogrow_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_item_debuglogrow_level"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_item_debuglogrow_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_item_debuglogrow_id"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_item_debuglogrow_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
@@ -198,19 +198,27 @@
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnAccountReset"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" />
|
||||
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnAccountReset"
|
||||
app:cornerRadius="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:backgroundTint="#fa315b"
|
||||
android:text="@string/str_reset_account"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" />
|
||||
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnClearLocalStorage"
|
||||
app:cornerRadius="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Clear Messages"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:backgroundTint="#607D8B"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@@ -57,7 +57,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLocalCacheSize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/str_localcachesize"
|
||||
android:textColor="#000"
|
||||
@@ -79,6 +79,62 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="#c0c0c0"/>
|
||||
|
||||
<Switch
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:id="@+id/prefEnableDeleteSwipe"
|
||||
android:text="@string/str_deleteswipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="#c0c0c0"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPreviewLineCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/str_previewlinecount"
|
||||
android:textColor="#000"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/prefPreviewLineCount"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:minWidth="64dp"
|
||||
android:id="@+id/prefPreviewLineCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:importantForAutofill="no"
|
||||
tools:ignore="LabelFor,UnusedAttribute" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
@@ -95,8 +151,10 @@
|
||||
android:gravity="center|center"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/prefUpgradeAccount"
|
||||
app:cornerRadius="0dp"
|
||||
android:backgroundTint="#4CAF50"
|
||||
android:text="@string/str_upgrade_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
@@ -202,6 +260,60 @@
|
||||
android:layout_marginEnd="4dp"
|
||||
android:minHeight="48dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="#c0c0c0"/>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/prefMsgLowForceVolume"
|
||||
android:text="@string/str_forcevolume"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:minHeight="48dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icnLowVolume"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/volume_icon"
|
||||
android:src="@drawable/ic_volume"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/prefMsgLowVolume"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
@@ -342,6 +454,60 @@
|
||||
tools:ignore="HardcodedText" />
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
@@ -499,6 +665,53 @@
|
||||
tools:ignore="HardcodedText" />
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
|
@@ -1,81 +1,154 @@
|
||||
<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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_view"
|
||||
<RelativeLayout
|
||||
android:id="@+id/layoutBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/card_margin"
|
||||
android:elevation="3dp"
|
||||
card_view:cardCornerRadius="@dimen/card_album_radius">
|
||||
android:background="@color/bg_row_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete_icon"
|
||||
android:layout_width="@dimen/ic_delete"
|
||||
android:layout_height="@dimen/ic_delete"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="@dimen/padd_10"
|
||||
android:src="@drawable/ic_trash"
|
||||
android:contentDescription="@string/delete" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:background="#FFFFFFFF"
|
||||
android:layout_margin="3dp"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="@dimen/padd_10"
|
||||
android:layout_toLeftOf="@id/delete_icon"
|
||||
android:text="@string/delete"
|
||||
android:textColor="#fff"
|
||||
android:textSize="13dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/layoutFront"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_view"
|
||||
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
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
|
||||
android:layout_marginEnd="4sp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/colorBlack"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="none"
|
||||
android:maxLines="6"
|
||||
<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="Message from me"/>
|
||||
android:text="2018-09-11 20:22:32" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
||||
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_margin="4sp"
|
||||
android:ellipsize="none"
|
||||
android:maxLines="32"
|
||||
android:scrollHorizontally="false"
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
|
||||
android:layout_marginEnd="4sp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/colorBlack"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="none"
|
||||
android:maxLines="6"
|
||||
|
||||
android:text="asdasd asdasd asdasd a" />
|
||||
android:text="Message from me"/>
|
||||
|
||||
<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" />
|
||||
<TextView
|
||||
android:id="@+id/tvMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
||||
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_margin="4sp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="6"
|
||||
android:scrollHorizontally="false"
|
||||
|
||||
</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>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnShare"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
app:cornerRadius="0dp"
|
||||
app:icon="@drawable/ic_share_small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="8sp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:textSize="12sp"
|
||||
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
|
||||
app:layout_constraintRight_toLeftOf="@id/btnDelete"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:backgroundTint="#03A9F4"
|
||||
android:text="Share" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnDelete"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
app:cornerRadius="0dp"
|
||||
app:icon="@drawable/ic_trash_small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="25dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:textSize="12sp"
|
||||
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:backgroundTint="#F44336"
|
||||
android:text="Delete"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_notification_full.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_notification_full.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_notification_full.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_notification_full.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_notification_full.png
Normal file
After Width: | Height: | Size: 17 KiB |
8
android/app/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="MaxHeightScrollView">
|
||||
<attr name="maxHeightOverride" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@@ -6,5 +6,10 @@
|
||||
<color name="colorHeader">#3F51B5</color>
|
||||
<color name="colorHeaderForeground">#FFFFFF</color>
|
||||
|
||||
<color name="colorOnPrimary">#ecf0f1</color>
|
||||
<color name="colorSecondary">#FF5722</color>
|
||||
|
||||
<color name="colorBlack">#000</color>
|
||||
|
||||
<color name="bg_row_background">#fa315b</color>
|
||||
</resources>
|
||||
|
@@ -2,4 +2,9 @@
|
||||
<resources>
|
||||
<dimen name="card_margin">5dp</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>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
@@ -11,23 +11,30 @@
|
||||
<string name="ic_img_fuel_desc">Icon Fuel</string>
|
||||
<string name="str_qr_code">QR Code</string>
|
||||
<string name="str_reset_account">Reset Account</string>
|
||||
<string name="no_notifications">No notifications</string>
|
||||
<string name="no_notifications">No messages</string>
|
||||
<string name="str_not_connected">not connected</string>
|
||||
<string name="str_reload">reload</string>
|
||||
<string name="desc_priority_icon">Priority icon</string>
|
||||
<string name="str_common_settings">Common Settings</string>
|
||||
<string name="str_enabled">Enabled</string>
|
||||
<string name="str_localcachesize">Remember the last x notifications locally</string>
|
||||
<string name="str_localcachesize">Remember the last x messages locally</string>
|
||||
<string name="str_header_prio0">Notifications (priority 0 - Low)</string>
|
||||
<string name="str_header_prio1">Notifications (priority 1 - Normal)</string>
|
||||
<string name="str_header_prio2">Notifications (priority 2 - High)</string>
|
||||
<string name="str_msg_enablesound">Enable notification sound</string>
|
||||
<string name="str_notificationsound">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_ledcolor">Notification light color</string>
|
||||
<string name="str_enable_vibration">Enable notification vibration</string>
|
||||
<string name="str_upgrade_account">Upgrade account</string>
|
||||
<string name="str_deleteswipe">Delete messages by swiping left</string>
|
||||
<string name="str_previewlinecount">Number of visibile lines in collapsed messages</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="volume_icon">Volume icon</string>
|
||||
<string name="play_test_sound">Play test sound</string>
|
||||
<string name="delete">DELETE</string>
|
||||
<string name="title_activity_query_log">QueryLogActivity</string>
|
||||
</resources>
|
||||
|
@@ -1,13 +1,20 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- your app branding color for the app bar -->
|
||||
<item name="colorPrimary">#3F51B5</item>
|
||||
|
||||
<!-- darker variant for the status bar and contextual app bars -->
|
||||
<item name="colorPrimaryDark">#303F9F</item>
|
||||
|
||||
<!-- theme UI controls like checkboxes and text fields -->
|
||||
<item name="colorAccent">#FF4081</item>
|
||||
<item name="colorAccent">#FF5722</item>
|
||||
<item name="colorSecondary">#FF5722</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
|
||||
|
||||
</resources>
|
||||
|
@@ -1,3 +1,3 @@
|
||||
#Mon Nov 12 13:06:09 CET 2018
|
||||
VERSION_NAME=0.0.6
|
||||
VERSION_CODE=6
|
||||
#Fri Dec 14 18:37:41 CET 2018
|
||||
VERSION_NAME=1.4.0
|
||||
VERSION_CODE=18
|
||||
|
BIN
data/README/badge_amazon.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
data/README/badge_apple.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
data/README/badge_apple_2.png
Normal file
After Width: | Height: | Size: 729 B |
BIN
data/README/badge_google.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
data/README/badge_google_2.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
data/README/badge_microsoft.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
data/icon_512_nobox.png
Normal file
After Width: | Height: | Size: 237 KiB |
BIN
data/icon_512_transparent.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
data/phone.pdn
Normal file
BIN
data/phone.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
78
examples/scn_send.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# Call with `scn_send title`
|
||||
# or `scn_send title content`
|
||||
# or `scn_send title content priority`
|
||||
#
|
||||
#
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "no title supplied via parameter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
################################################################################
|
||||
# INSERT YOUR DATA HERE #
|
||||
################################################################################
|
||||
user_id=999
|
||||
user_key="????????????????????????????????????????????????????????????????"
|
||||
################################################################################
|
||||
|
||||
title=$1
|
||||
content=""
|
||||
sendtime=$(date +%s)
|
||||
|
||||
if [ "$#" -gt 1 ]; then
|
||||
content=$2
|
||||
fi
|
||||
|
||||
priority=1
|
||||
|
||||
if [ "$#" -gt 2 ]; then
|
||||
priority=$3
|
||||
fi
|
||||
|
||||
usr_msg_id=$(uuidgen)
|
||||
|
||||
while true ; do
|
||||
|
||||
curlresp=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-d "user_id=$user_id" -d "user_key=$user_key" -d "title=$title" -d "timestamp=$sendtime" \
|
||||
-d "content=$content" -d "priority=$priority" -d "msg_id=$usr_msg_id" \
|
||||
https://scn.blackforestbytes.com/send.php)
|
||||
|
||||
if [ "$curlresp" == 200 ] ; then
|
||||
echo "Successfully send"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$curlresp" == 400 ] ; then
|
||||
echo "Bad request - something went wrong"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$curlresp" == 401 ] ; then
|
||||
echo "Unauthorized - wrong userid/userkey"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$curlresp" == 403 ] ; then
|
||||
echo "Quota exceeded - wait one hour before re-try"
|
||||
sleep 3600
|
||||
fi
|
||||
|
||||
if [ "$curlresp" == 412 ] ; then
|
||||
echo "Precondition Failed - No device linked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$curlresp" == 500 ] ; then
|
||||
echo "Internal server error - waiting for better times"
|
||||
sleep 60
|
||||
fi
|
||||
|
||||
# if none of the above matched we probably hav no network ...
|
||||
echo "Send failed (response code $curlresp) ... try again in 5s"
|
||||
sleep 5
|
||||
done
|
@@ -1,13 +1,13 @@
|
||||
SimpleCloudNotifier is a app to display messages that you can send to your phone with a simple POST request to the right URL.
|
||||
|
||||
After you start the app it generates a userID and a private key.
|
||||
After you start the app it generates a UserID and a UserSecret.
|
||||
Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
|
||||
(see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
|
||||
|
||||
|
||||
Use it to
|
||||
- send yourself automated messages from cron jobs
|
||||
- notify youreself when long-running scripts finish
|
||||
- notify yourself when long-running scripts finish
|
||||
- send server error messages directly to your phone
|
||||
- integrate with other online services
|
||||
|
||||
|
BIN
store/screenshot_1.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
store/screenshot_2.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
store/screenshot_3.png
Normal file
After Width: | Height: | Size: 55 KiB |
@@ -1,5 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<?php
|
||||
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
|
||||
{
|
||||
require_once('/var/www/openwebanalytics/owa_php.php');
|
||||
$owa = new owa_php();
|
||||
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
|
||||
$owa->setPageTitle('API (Short)');
|
||||
$owa->trackPageView();
|
||||
}
|
||||
?>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||
@@ -12,11 +22,17 @@
|
||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mainpnl">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||
|
||||
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||
<div id="copyinfo">
|
||||
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwörer</a>
|
||||
</div>
|
||||
|
||||
<div id="mainpnl">
|
||||
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||
|
||||
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||
|
||||
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
|
||||
<pre>curl \
|
||||
@@ -34,12 +50,8 @@
|
||||
--data "title={message_title}" \
|
||||
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 id="copyinfo">
|
||||
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
2
web/api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config.php
|
||||
.verify_accesstoken
|
56
web/api/__config_example.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
// insert your values here and rename to config.php
|
||||
|
||||
return
|
||||
[
|
||||
'global' =>
|
||||
[
|
||||
'prod' => true,
|
||||
],
|
||||
|
||||
'database' =>
|
||||
[
|
||||
'host' => '?',
|
||||
'database' => '?',
|
||||
'user' => '?',
|
||||
'password' => '?',
|
||||
],
|
||||
|
||||
'firebase' =>
|
||||
[
|
||||
'type' => 'service_account',
|
||||
'project_id' => '?',
|
||||
'private_key_id' => '???',
|
||||
'client_email' => '???.iam.gserviceaccount.com',
|
||||
'client_id' => '???',
|
||||
'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
|
||||
'token_uri' => 'https://oauth2.googleapis.com/token',
|
||||
'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
|
||||
'client_x509_cert_url' => 'https://www.googleapis.com/robot/v1/metadata/x509/???f.iam.gserviceaccount.com',
|
||||
'private_key' => "-----BEGIN PRIVATE KEY-----\n"
|
||||
. "??????????\n"
|
||||
. "-----END PRIVATE KEY-----\n",
|
||||
'server_key' => '????',
|
||||
],
|
||||
|
||||
'verify_api' =>
|
||||
[
|
||||
'package_name' => 'com.blackforestbytes.simplecloudnotifier',
|
||||
'product_id' => '???',
|
||||
|
||||
'clientid' => '???.apps.googleusercontent.com',
|
||||
'clientsecret' => '???',
|
||||
'accesstoken' => file_exists('.verify_accesstoken') ? file_get_contents('.verify_accesstoken') : '',
|
||||
'refreshtoken' => '???',
|
||||
'scope' => 'https://www.googleapis.com/auth/androidpublisher',
|
||||
],
|
||||
|
||||
'error_reporting' =>
|
||||
[
|
||||
'send-mail' => true,
|
||||
'email-error-target' => '???@???.com',
|
||||
'email-error-sender' => '???@???.com',
|
||||
],
|
||||
|
||||
];
|
@@ -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->execute(['smid' => $scn_msg_id, 'uid' => $user_id]);
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'prev_ack' => $datas[0]['ack'],
|
||||
'new_ack' => 1,
|
||||
'message' => 'ok'
|
||||
]);
|
||||
return 0;
|
||||
]);
|
53
web/api/expand.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
include_once 'model.php';
|
||||
|
||||
$INPUT = array_merge($_GET, $_POST);
|
||||
|
||||
|
||||
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errid'=>101, 'message' => 'Missing parameter [[user_id]]']));
|
||||
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errid'=>102, 'message' => 'Missing parameter [[user_key]]']));
|
||||
if (!isset($INPUT['scn_msg_id'])) die(json_encode(['success' => false, 'errid'=>103, 'message' => 'Missing parameter [[scn_msg_id]]']));
|
||||
|
||||
$user_id = $INPUT['user_id'];
|
||||
$user_key = $INPUT['user_key'];
|
||||
$scn_msg_id = $INPUT['scn_msg_id'];
|
||||
|
||||
//----------------------
|
||||
|
||||
$pdo = getDatabase();
|
||||
|
||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day, fcm_token FROM users WHERE user_id = :uid LIMIT 1');
|
||||
$stmt->execute(['uid' => $user_id]);
|
||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>201, 'message' => 'User not found']));
|
||||
$data = $datas[0];
|
||||
|
||||
if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message' => 'User not found']));
|
||||
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID not found']));
|
||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM messages WHERE scn_message_id=:smid AND sender_user_id=:uid LIMIT 1');
|
||||
$stmt->execute(['smid' => $scn_msg_id, 'uid' => $user_id]);
|
||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>301, 'message' => 'Message not found']));
|
||||
|
||||
$msg = $datas[0];
|
||||
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'data' =>
|
||||
[
|
||||
'title' => $msg['title'],
|
||||
'body' => $msg['content'],
|
||||
'trimmed' => false,
|
||||
'priority' => $msg['priority'],
|
||||
'timestamp' => $msg['sendtime'],
|
||||
'usr_msg_id' => $msg['usr_message_id'],
|
||||
'scn_msg_id' => $msg['scn_message_id'],
|
||||
],
|
||||
'message' => 'ok'
|
||||
]);
|
@@ -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_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||
|
||||
|
||||
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM messages WHERE ack=0 AND sender_user_id=:uid');
|
||||
$stmt->execute(['uid' => $user_id]);
|
||||
$nack_count = $stmt->fetch(PDO::FETCH_NUM)[0];
|
||||
|
||||
|
||||
$quota = $data['quota_today'];
|
||||
$is_pro = $data['is_pro'];
|
||||
|
||||
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'message' => 'ok',
|
||||
'user_id' => $user_id,
|
||||
'quota' => $quota,
|
||||
'quota_max' => Statics::quota_max($is_pro),
|
||||
'is_pro' => $is_pro,
|
||||
'fcm_token_set' => ($data['fcm_token'] != null),
|
||||
'message' => 'ok'
|
||||
]);
|
||||
return 0;
|
||||
'unack_count' => $nack_count,
|
||||
]);
|
@@ -2,12 +2,48 @@
|
||||
|
||||
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 TIMESTAMP_OUT_OF_RANGE = 1205;
|
||||
|
||||
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
|
||||
{
|
||||
public static $DB = 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; }
|
||||
|
||||
public static function contentlen_max($is_pro) { return $is_pro ? 16384 : 2048; }
|
||||
}
|
||||
|
||||
function str_limit($str, $len)
|
||||
{
|
||||
if (strlen($str)>$len) return substr($str, 0, $len-3)."...";
|
||||
return $str;
|
||||
}
|
||||
|
||||
function getConfig()
|
||||
@@ -190,10 +226,15 @@ function refreshVerifyToken()
|
||||
return $obj['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $http_code
|
||||
* @param array $message
|
||||
*/
|
||||
function api_return($http_code, $message)
|
||||
{
|
||||
http_response_code($http_code);
|
||||
echo $message;
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($message);
|
||||
die();
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ $stmt->execute(['uid' => $user_id, 'ft' => $fcm_token]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'user_id' => $user_id,
|
||||
@@ -47,6 +47,4 @@ echo json_encode(
|
||||
'quota_max' => Statics::quota_max($ispro),
|
||||
'is_pro' => $ispro,
|
||||
'message' => 'New user registered'
|
||||
]);
|
||||
|
||||
return 0;
|
||||
]);
|
56
web/api/requery.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
include_once 'model.php';
|
||||
|
||||
$INPUT = array_merge($_GET, $_POST);
|
||||
|
||||
|
||||
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errid'=>101, 'message' => 'Missing parameter [[user_id]]']));
|
||||
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errid'=>102, 'message' => 'Missing parameter [[user_key]]']));
|
||||
|
||||
$user_id = $INPUT['user_id'];
|
||||
$user_key = $INPUT['user_key'];
|
||||
|
||||
//----------------------
|
||||
|
||||
$pdo = getDatabase();
|
||||
|
||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day, fcm_token FROM users WHERE user_id = :uid LIMIT 1');
|
||||
$stmt->execute(['uid' => $user_id]);
|
||||
|
||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>201, 'message' => 'User not found']));
|
||||
$data = $datas[0];
|
||||
|
||||
if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message' => 'User not found']));
|
||||
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID not found']));
|
||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||
|
||||
//-------------------
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM messages WHERE ack=0 AND sender_user_id=:uid ORDER BY `timestamp_real` 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['sendtime'],
|
||||
'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,
|
||||
]);
|
@@ -23,12 +23,13 @@ CREATE TABLE `messages`
|
||||
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`sender_user_id` INT(11) NOT NULL,
|
||||
|
||||
`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`ack` BIT NOT NULL DEFAULT 0,
|
||||
|
||||
`title` VARCHAR(256) NOT NULL,
|
||||
`content` VARCHAR(12288) NULL,
|
||||
`priority` INT(11) NOT NULL,
|
||||
`sendtime` BIGINT UNSIGNED NOT NULL,
|
||||
|
||||
`fcm_message_id` VARCHAR(256) NULL,
|
||||
`usr_message_id` VARCHAR(256) NULL,
|
@@ -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->execute(['uid' => $user_id, 'at' => $new_userkey]);
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'user_id' => $user_id,
|
||||
@@ -49,7 +49,6 @@ if ($fcm_token === null)
|
||||
'is_pro' => $is_pro,
|
||||
'message' => 'user updated'
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -61,7 +60,7 @@ else
|
||||
$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]);
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'user_id' => $user_id,
|
||||
@@ -71,5 +70,4 @@ else
|
||||
'is_pro' => $is_pro,
|
||||
'message' => 'user updated'
|
||||
]);
|
||||
return 0;
|
||||
}
|
@@ -45,7 +45,7 @@ if ($ispro)
|
||||
$stmt = $pdo->prepare('UPDATE users SET is_pro=0, pro_token=NULL WHERE user_id <> :uid AND pro_token = :ptk');
|
||||
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'user_id' => $user_id,
|
||||
@@ -54,7 +54,6 @@ if ($ispro)
|
||||
'is_pro' => true,
|
||||
'message' => 'user updated'
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
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->execute(['uid' => $user_id]);
|
||||
|
||||
echo json_encode(
|
||||
api_return(200,
|
||||
[
|
||||
'success' => true,
|
||||
'user_id' => $user_id,
|
||||
@@ -72,5 +71,4 @@ else
|
||||
'is_pro' => false,
|
||||
'message' => 'user updated'
|
||||
]);
|
||||
return 0;
|
||||
}
|
309
web/api_more.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<?php
|
||||
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
|
||||
{
|
||||
require_once('/var/www/openwebanalytics/owa_php.php');
|
||||
$owa = new owa_php();
|
||||
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
|
||||
$owa->setPageTitle('API (Long)');
|
||||
$owa->trackPageView();
|
||||
}
|
||||
?>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||
<title>Simple Cloud Notifications - API</title>
|
||||
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="/favicon.png"/>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="copyinfo">
|
||||
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwörer</a>
|
||||
</div>
|
||||
|
||||
<div id="mainpnl">
|
||||
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||
|
||||
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
With this API you can send push notifications to your phone.
|
||||
</p>
|
||||
<p>
|
||||
To recieve them you will need to install the <a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier">SimpleCloudNotifier</a> app from the play store.
|
||||
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>user_key</code>.
|
||||
These two values are used to identify and authenticate your device so that send messages can be routed to your phone.
|
||||
</p>
|
||||
<p>
|
||||
You can at any time generate a new <code>user_key</code> in the app and invalidate the old one.
|
||||
</p>
|
||||
<p>
|
||||
There is also a <a href="/index.php">web interface</a> for this API to manually send notifications to your phone or to test your setup.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Quota</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
By default you can send up to 100 messages per day per device.
|
||||
If you need more you can upgrade your account in the app to get 1000 messages per day, this has the additional benefit of removing ads and supporting the development of the app (and making sure I can pay the server costs).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>API Requests</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
To send a new notification you send a <code>POST</code> request to the URL <code>https://scn.blackforestbytes.com/send.php</code>.
|
||||
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body.
|
||||
</p>
|
||||
<p>
|
||||
You <i>need</i> to supply a valid <code>user_id</code> - <code>user_key</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>API Response</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
If the operation was successful the API will respond with an HTTP statuscode 200 and an JSON payload indicating the send message and your remaining quota
|
||||
</p>
|
||||
<pre class="red-code">{
|
||||
"success":true,
|
||||
"message":"Message sent",
|
||||
"response":
|
||||
{
|
||||
"multicast_id":8000000000000000006,
|
||||
"success":1,
|
||||
"failure":0,
|
||||
"canonical_ids":0,
|
||||
"results": [{"message_id":"0:10000000000000000000000000000000d"}]
|
||||
},
|
||||
"quota":17,
|
||||
"quota_max":100
|
||||
}</pre>
|
||||
<p>
|
||||
If the operation is <b>not</b> successful the API will respond with an 4xx HTTP statuscode.
|
||||
</p>
|
||||
<table class="scode_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Statuscode</th>
|
||||
<th>Explanation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td data-label="Statuscode">200 (OK)</td>
|
||||
<td data-label="Explanation">Message sent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">400 (Bad Request)</td>
|
||||
<td data-label="Explanation">The request is invalid (missing parameters or wrong values)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">401 (Unauthorized)</td>
|
||||
<td data-label="Explanation">The user_id was not found or the user_key is wrong</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">403 (Forbidden)</td>
|
||||
<td data-label="Explanation">The user has exceeded its daily quota - wait 24 hours or upgrade your account</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">412 (Precondition Failed)</td>
|
||||
<td data-label="Explanation">There is no device connected with this account - open the app and press the refresh button in the account tab</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">500 (Internal Server Error)</td>
|
||||
<td data-label="Explanation">There was an internal error while sending your data - try again later</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
There is also always a JSON payload with additional information.
|
||||
The <code>success</code> field is always there and in the error state you the <code>message</code> field to get a descritpion of the problem.
|
||||
</p>
|
||||
<pre class="red-code">{
|
||||
"success":false,
|
||||
"error":2101,
|
||||
"errhighlight":-1,
|
||||
"message":"Daily quota reached (100)"
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<h2>Message Content</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
Every message must have a title set.
|
||||
But you also (optionally) add more content, while the title has a max length of 120 characters, the conntent can be up to 10.000 characters.
|
||||
You can see the whole message with title and content in the app or when clicking on the notification.
|
||||
</p>
|
||||
<p>
|
||||
If needed the content can be supplied in the <code>content</code> parameter.
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "title={message_title}" \
|
||||
--data "content={message_content}" \
|
||||
https://scn.blackforestbytes.com/send.php</pre>
|
||||
</div>
|
||||
|
||||
<h2>Message Priority</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
Currently you can send a message with three different priorities: 0 (low), 1 (normal) and 2 (high).
|
||||
In the app you can then configure a different behaviour for different priorities, e.g. only playing a sound if the notification is high priority.
|
||||
</p>
|
||||
<p>
|
||||
Priorites are either 0, 1 or 2 and are supplied in the <code>priority</code> parameter.
|
||||
If no priority is supplied the message will get the default priority of 1.
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "title={message_title}" \
|
||||
--data "priority={0|1|2}" \
|
||||
https://scn.blackforestbytes.com/send.php</pre>
|
||||
</div>
|
||||
|
||||
<h2>Message Uniqueness</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
Sometimes your script can run in an environment with an unstable connection and you want to implement an automatic re-try mechanism to send a message again if the last try failed due to bad connectivity.
|
||||
</p>
|
||||
<p>
|
||||
To ensure that a message is only send once you can generate a unique id for your message (I would recommend a simple <code>uuidgen</code>).
|
||||
If you send a message with an UUID that was already used in the near past the API still returns OK, but no new message is sent.
|
||||
</p>
|
||||
<p>
|
||||
The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter.
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "title={message_title}" \
|
||||
--data "msg_id={message_id}" \
|
||||
https://scn.blackforestbytes.com/send.php</pre>
|
||||
<p>
|
||||
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Custom Time</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
You can modify the displayed timestamp of a message by sending the <code>timestamp</code> parameter. The format must be a valid UNIX timestamp (elapsed seconds since 1970-01-01 GMT)
|
||||
</p>
|
||||
<p>
|
||||
The custom timestamp must be within 48 hours of the current time. This parameter is only intended to supply a more precise value in case the message sending was delayed.
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "title={message_title}" \
|
||||
--data "timestamp={unix_timestamp}" \
|
||||
https://scn.blackforestbytes.com/send.php</pre>
|
||||
</div>
|
||||
|
||||
<h2>Bash script example</h2>
|
||||
<div class="section">
|
||||
<p>
|
||||
Depending on your use case it can be useful to create a bash script that handles things like resending messages if you have connection problems or waiting if there is no quota left.<br/>
|
||||
Here is an example how such a scrippt could look like, you can put it into <code>/usr/local/sbin</code> and call it with <code>scn_send "title" "content"</code>
|
||||
</p>
|
||||
<pre style="color:#000000;" class="yellow-code"><span style="color:#3f7f59; font-weight:bold;">#!/usr/bin/env bash</span>
|
||||
|
||||
<span style="color:#3f7f59; ">#</span>
|
||||
<span style="color:#3f7f59; "># Call with `scn_send title`</span>
|
||||
<span style="color:#3f7f59; "># or `scn_send title content`</span>
|
||||
<span style="color:#3f7f59; "># or `scn_send title content priority`</span>
|
||||
<span style="color:#3f7f59; ">#</span>
|
||||
<span style="color:#3f7f59; ">#</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -lt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"no title supplied via parameter"</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#3f7f59; ">################################################################################</span>
|
||||
<span style="color:#3f7f59; "># INSERT YOUR DATA HERE #</span>
|
||||
<span style="color:#3f7f59; ">################################################################################</span>
|
||||
user_id=999
|
||||
user_key=<span style="color:#2a00ff; ">"????????????????????????????????????????????????????????????????"</span>
|
||||
<span style="color:#3f7f59; ">################################################################################</span>
|
||||
|
||||
title=$1
|
||||
content=<span style="color:#2a00ff; ">""</span>
|
||||
sendtime=$(date +%s)
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 1 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
content=$2
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
priority=1
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"$#"</span> -gt 2 ]; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
priority=$3
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
usr_msg_id=$(uuidgen)
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">while</span> true ; <span style="color:#7f0055; font-weight:bold; ">do</span>
|
||||
|
||||
curlresp=$(curl -s -o <span style="color:#3f3fbf; ">/dev/null</span> -w <span style="color:#2a00ff; ">"%{http_code}"</span> <span style="color:#2a00ff; ">\</span>
|
||||
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_id</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">user_key</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$user_key</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">title</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$title</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">timestamp</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$sendtime</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
|
||||
-d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">content</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$content</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">priority</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$priority</span><span style="color:#2a00ff; ">"</span> -d <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">msg_id</span><span style="color:#2a00ff; ">=</span><span style="color:#2a00ff; ">$usr_msg_id</span><span style="color:#2a00ff; ">"</span> <span style="color:#2a00ff; ">\</span>
|
||||
https:<span style="color:#3f3fbf; ">/</span><span style="color:#3f3fbf; ">/scn.blackforestbytes.com/send.php</span>)
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 200 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Successfully send"</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">exit</span> 0
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 400 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Bad request - something went wrong"</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 401 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Unauthorized - wrong </span><span style="color:#3f3fbf; ">userid/userkey</span><span style="color:#2a00ff; ">"</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 403 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Quota exceeded - wait one hour before re-try"</span>
|
||||
sleep 3600
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 412 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Precondition Failed - No device linked"</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">exit</span> 1
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#7f0055; font-weight:bold; ">if</span> [ <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">"</span> == 500 ] ; <span style="color:#7f0055; font-weight:bold; ">then</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Internal server error - waiting for better times"</span>
|
||||
sleep 60
|
||||
<span style="color:#7f0055; font-weight:bold; ">fi</span>
|
||||
|
||||
<span style="color:#3f7f59; "># if none of the above matched we probably hav no network ...</span>
|
||||
<span style="color:#7f0055; font-weight:bold; ">echo</span> <span style="color:#2a00ff; ">"Send failed (response code </span><span style="color:#2a00ff; ">$curlresp</span><span style="color:#2a00ff; ">) ... try again in 5s"</span>
|
||||
sleep 5
|
||||
<span style="color:#7f0055; font-weight:bold; ">done</span>
|
||||
</pre>
|
||||
<p>
|
||||
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@@ -42,6 +42,11 @@ body
|
||||
border-left: .25rem solid #E53935;
|
||||
}
|
||||
|
||||
.yellow-code
|
||||
{
|
||||
border-left: .25rem solid #FFCB05;
|
||||
}
|
||||
|
||||
#mainpnl input,
|
||||
#mainpnl textarea
|
||||
{
|
||||
@@ -86,13 +91,10 @@ body
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: -999;
|
||||
}
|
||||
|
||||
#copyinfo a:hover
|
||||
{
|
||||
font-family: "Courier New", monospace;
|
||||
color: #00F;
|
||||
//z-index: -999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#copyinfo a,
|
||||
@@ -102,6 +104,14 @@ body
|
||||
font-family: "Courier New", monospace;
|
||||
color: #AAA;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#copyinfo a:hover
|
||||
{
|
||||
font-family: "Courier New", monospace;
|
||||
color: #0288D1;
|
||||
}
|
||||
|
||||
#tr_link
|
||||
@@ -228,6 +238,13 @@ table.scode_table th:nth-child(2) {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.linkcaption:hover {
|
||||
.linkcaption:hover,
|
||||
.linkcaption:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre, pre span
|
||||
{
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
background: #F9F9F9;;
|
||||
}
|
@@ -1,5 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<?php
|
||||
if (file_exists('/var/www/openwebanalytics/owa_php.php'))
|
||||
{
|
||||
require_once('/var/www/openwebanalytics/owa_php.php');
|
||||
$owa = new owa_php();
|
||||
$owa->setSiteId('6386b0efc00d2e84ef642525345e1207');
|
||||
$owa->setPageTitle('Index');
|
||||
$owa->trackPageView();
|
||||
}
|
||||
?>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Simple Cloud Notifications</title>
|
||||
@@ -13,12 +23,18 @@
|
||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="copyinfo">
|
||||
<a tabindex="-1" href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||
<a tabindex="-1" href="https://www.mikescher.com">made by Mike Schwörer</a>
|
||||
</div>
|
||||
|
||||
<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 href="/index_api.php" class="button bordered" id="tr_link">API</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 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="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
|
||||
@@ -48,7 +64,7 @@
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
|
||||
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8"></textarea></div>
|
||||
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8" maxlength="2048"></textarea></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -57,10 +73,6 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="copyinfo">
|
||||
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||
</div>
|
||||
|
||||
<script src="/js/logic.js" type="text/javascript" ></script>
|
||||
<script src="/js/toastify.js"></script>
|
||||
</body>
|
||||
|