in-app-purchase: pro-mode
This commit is contained in:
parent
e69b1232d7
commit
083945852b
@ -35,7 +35,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
@ -45,7 +45,8 @@ dependencies {
|
|||||||
implementation 'com.google.android.material:material:1.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.4'
|
||||||
implementation 'com.google.firebase:firebase-messaging:17.3.4'
|
implementation 'com.google.firebase:firebase-messaging:17.3.4'
|
||||||
implementation 'com.google.android.gms:play-services-ads:17.0.0'
|
implementation 'com.google.android.gms:play-services-ads:17.1.0'
|
||||||
|
implementation 'com.android.billingclient:billing:1.2'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Application;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.android;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public final class ThreadUtils
|
||||||
|
{
|
||||||
|
public static void safeSleep(int millisMin, int millisMax)
|
||||||
|
{
|
||||||
|
safeSleep(millisMin + (int)(Math.random()*(millisMax-millisMin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void safeSleep(int millis)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(millis);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
Log.d("ThreadUtils", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.collections;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public final class CollectionHelper
|
||||||
|
{
|
||||||
|
public static <T, C> List<T> unique(List<T> input, Func1to1<T, C> mapping)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input.size());
|
||||||
|
|
||||||
|
HashSet<C> seen = new HashSet<>();
|
||||||
|
|
||||||
|
for (T v : input) if (seen.add(mapping.invoke(v))) output.add(v);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> sort(List<T> input, Comparator<T> comparator)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, comparator);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
|
||||||
|
{
|
||||||
|
return sort(input, mapper, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper, int sortMod)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, (o1, o2) -> sortMod * mapper.invoke(o1).compareTo(mapper.invoke(o2)));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class IntRange
|
||||||
|
{
|
||||||
|
private int Start;
|
||||||
|
public int Start() { return Start; }
|
||||||
|
|
||||||
|
private int End;
|
||||||
|
public int End() { return End; }
|
||||||
|
|
||||||
|
public IntRange(int s, int e) { Start = s; End = e; }
|
||||||
|
|
||||||
|
private IntRange() { }
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class NInt
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
|
||||||
|
public NInt(int v) { Value = v; }
|
||||||
|
|
||||||
|
private NInt() { }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public final class Nothing
|
||||||
|
{
|
||||||
|
public final static Nothing Inst = new Nothing();
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple1<T1>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
|
||||||
|
public Tuple1(T1 i1)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple2<T1, T2>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
|
||||||
|
public Tuple2(T1 i1, T2 i2)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple3<T1, T2, T3>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
|
||||||
|
public Tuple3(T1 i1, T2 i2, T3 i3)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple4<T1, T2, T3, T4>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
public final T4 Item4;
|
||||||
|
|
||||||
|
public Tuple4(T1 i1, T2 i2, T3 i3, T4 i4)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
Item4 = i4;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to0 {
|
||||||
|
|
||||||
|
Func0to0 EMPTY = ()->{};
|
||||||
|
|
||||||
|
void invoke();
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1<TResult> {
|
||||||
|
TResult invoke();
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1WithIOException<TResult> {
|
||||||
|
TResult invoke() throws IOException;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to0<TInput1> {
|
||||||
|
void invoke(TInput1 value);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to1<TInput1, TResult> {
|
||||||
|
TResult invoke(TInput1 value);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to0<TInput1, TInput2> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to1<TInput1, TInput2, TResult> {
|
||||||
|
TResult invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
// from MonoSAMFramework.Portable.DebugTools.CompactJsonFormatter
|
||||||
|
public class CompactJsonFormatter
|
||||||
|
{
|
||||||
|
private static final String INDENT_STRING = " ";
|
||||||
|
|
||||||
|
public static String formatJSON(String str, int maxIndent)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
if (indent >= maxIndent) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent + 1 >= maxIndent) { sb.append(ch); break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (indent >= maxIndent) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compressJson(String str, int compressionLevel)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
int compress = 0;
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress == 0 && getJsonDepth(str, i) <= compressionLevel)
|
||||||
|
compress = 1;
|
||||||
|
else if (compress > 0)
|
||||||
|
compress++;
|
||||||
|
|
||||||
|
indent++;
|
||||||
|
if (compress > 0) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (compress > 0) { compress--; sb.append(ch); break; }
|
||||||
|
compress--;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress > 0) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getJsonDepth(String str, int i)
|
||||||
|
{
|
||||||
|
int maxindent = 0;
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
for (; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
maxindent = Math.max(indent, maxindent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent <= 0) return maxindent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxindent;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Str
|
||||||
|
{
|
||||||
|
public final static String Empty = "";
|
||||||
|
|
||||||
|
public static String format(String fmt, Object... data)
|
||||||
|
{
|
||||||
|
return MessageFormat.format(fmt, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String rformat(int fmtResId, Object... data)
|
||||||
|
{
|
||||||
|
Context inst = SCNApp.getContext();
|
||||||
|
if (inst == null)
|
||||||
|
{
|
||||||
|
Log.e("StringFormat", "rformat::NoInstance --> inst==null for" + fmtResId);
|
||||||
|
return "?ERR?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageFormat.format(inst.getResources().getString(fmtResId), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String firstLine(String content)
|
||||||
|
{
|
||||||
|
int idx = content.indexOf('\n');
|
||||||
|
if (idx == -1) return content;
|
||||||
|
|
||||||
|
if (idx == 0) return Str.Empty;
|
||||||
|
|
||||||
|
if (content.charAt(idx-1) == '\r') return content.substring(0, idx-1);
|
||||||
|
|
||||||
|
return content.substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrWhitespace(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0 || str.trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean equals(String a, String b)
|
||||||
|
{
|
||||||
|
if (a == null) return (b == null);
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String join(String sep, List<String> list)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (String v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(v);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> String join(String sep, List<T> list, Func1to1<T, String> map)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (T v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(map.invoke(v));
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer tryParseToInt(String s)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,9 @@ import android.content.SharedPreferences;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
|
||||||
public class SCNSettings
|
public class SCNSettings
|
||||||
@ -36,6 +38,10 @@ public class SCNSettings
|
|||||||
public String fcm_token_local;
|
public String fcm_token_local;
|
||||||
public String fcm_token_server;
|
public String fcm_token_server;
|
||||||
|
|
||||||
|
public String promode_token;
|
||||||
|
public boolean promode_local;
|
||||||
|
public boolean promode_server;
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
public boolean Enabled = true;
|
public boolean Enabled = true;
|
||||||
@ -57,6 +63,9 @@ public class SCNSettings
|
|||||||
user_key = sharedPref.getString("user_key", "");
|
user_key = sharedPref.getString("user_key", "");
|
||||||
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
||||||
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
||||||
|
promode_local = sharedPref.getBoolean("promode_local", false);
|
||||||
|
promode_server = sharedPref.getBoolean("promode_server", false);
|
||||||
|
promode_token = sharedPref.getString("promode_token", "");
|
||||||
|
|
||||||
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
||||||
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||||
@ -151,10 +160,12 @@ public class SCNSettings
|
|||||||
{
|
{
|
||||||
fcm_token_local = token;
|
fcm_token_local = token;
|
||||||
save();
|
save();
|
||||||
ServerCommunication.register(fcm_token_local, loader);
|
ServerCommunication.register(fcm_token_local, loader, promode_local, promode_token);
|
||||||
|
updateProState(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called at app start
|
||||||
public void work(Activity a)
|
public void work(Activity a)
|
||||||
{
|
{
|
||||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
@ -166,8 +177,11 @@ public class SCNSettings
|
|||||||
{
|
{
|
||||||
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateProState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset account key
|
||||||
public void reset(View loader)
|
public void reset(View loader)
|
||||||
{
|
{
|
||||||
if (!isConnected()) return;
|
if (!isConnected()) return;
|
||||||
@ -175,23 +189,50 @@ public class SCNSettings
|
|||||||
ServerCommunication.update(user_id, user_key, loader);
|
ServerCommunication.update(user_id, user_key, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh account data
|
||||||
public void refresh(View loader, Activity a)
|
public void refresh(View loader, Activity a)
|
||||||
{
|
{
|
||||||
if (isConnected())
|
if (isConnected())
|
||||||
{
|
{
|
||||||
ServerCommunication.info(user_id, user_key, loader);
|
ServerCommunication.info(user_id, user_key, loader);
|
||||||
|
if (promode_server != promode_local)
|
||||||
|
{
|
||||||
|
updateProState(loader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// get token then register
|
||||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
{
|
{
|
||||||
String newToken = instanceIdResult.getToken();
|
String newToken = instanceIdResult.getToken();
|
||||||
Log.e("FB::GetInstanceId", newToken);
|
Log.e("FB::GetInstanceId", newToken);
|
||||||
SCNSettings.inst().setServerToken(newToken, loader);
|
SCNSettings.inst().setServerToken(newToken, loader); // does register in here
|
||||||
}).addOnCompleteListener(r ->
|
}).addOnCompleteListener(r ->
|
||||||
{
|
{
|
||||||
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null); // info again for safety
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProState(View loader)
|
||||||
|
{
|
||||||
|
Purchase purch = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||||
|
boolean promode_real = (purch != null);
|
||||||
|
|
||||||
|
if (promode_real != promode_local || promode_real != promode_server)
|
||||||
|
{
|
||||||
|
promode_local = promode_real;
|
||||||
|
|
||||||
|
promode_token = promode_real ? purch.getPurchaseToken() : "";
|
||||||
|
updateProStateOnServer(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProStateOnServer(View loader)
|
||||||
|
{
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
ServerCommunication.upgrade(user_id, user_key, loader, promode_local, promode_token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import org.json.JSONObject;
|
|||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
@ -25,12 +26,12 @@ public class ServerCommunication
|
|||||||
|
|
||||||
private ServerCommunication(){ throw new Error("no."); }
|
private ServerCommunication(){ throw new Error("no."); }
|
||||||
|
|
||||||
public static void register(String token, View loader)
|
public static void register(String token, View loader, boolean pro, String pro_token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(BASE_URL + "register.php?fcm_token="+token)
|
.url(BASE_URL + "register.php?fcm_token=" + token + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback()
|
client.newCall(request).enqueue(new Callback()
|
||||||
@ -67,6 +68,7 @@ public class ServerCommunication
|
|||||||
SCNSettings.inst().fcm_token_server = token;
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@ -132,6 +134,7 @@ public class ServerCommunication
|
|||||||
SCNSettings.inst().fcm_token_server = token;
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@ -187,10 +190,11 @@ public class ServerCommunication
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
SCNSettings.inst().user_key = json.getString("user_key");
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@ -247,9 +251,10 @@ public class ServerCommunication
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@ -270,4 +275,63 @@ public class ServerCommunication
|
|||||||
SCNApp.showToast("Communication with server failed", 4000);
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.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() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success")) {
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
} finally {
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener;
|
||||||
|
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.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.List;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import static androidx.constraintlayout.widget.Constraints.TAG;
|
||||||
|
|
||||||
|
public class IABService implements PurchasesUpdatedListener
|
||||||
|
{
|
||||||
|
public static final String IAB_PRO_MODE = "scn.pro.tier1";
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static IABService _inst = null;
|
||||||
|
public static IABService inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
throw new Error("IABService == null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void startup(MainActivity a)
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
_inst = new IABService(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BillingClient client;
|
||||||
|
private boolean isServiceConnected;
|
||||||
|
private final List<Purchase> purchases = new ArrayList<>();
|
||||||
|
|
||||||
|
public IABService(Context c)
|
||||||
|
{
|
||||||
|
client = BillingClient
|
||||||
|
.newBuilder(c)
|
||||||
|
.setListener(this)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
startServiceConnection(this::queryPurchases, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queryPurchases()
|
||||||
|
{
|
||||||
|
Func0to0 queryToExecute = () ->
|
||||||
|
{
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
Purchase.PurchasesResult purchasesResult = client.queryPurchases(BillingClient.SkuType.INAPP);
|
||||||
|
Log.i(TAG, "Querying purchases elapsed time: " + (System.currentTimeMillis() - time) + "ms");
|
||||||
|
|
||||||
|
if (purchasesResult.getResponseCode() == BillingClient.BillingResponse.OK)
|
||||||
|
{
|
||||||
|
for (Purchase p : purchasesResult.getPurchasesList())
|
||||||
|
{
|
||||||
|
handlePurchase(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean newProMode = getPurchaseCached(IAB_PRO_MODE) != null;
|
||||||
|
if (newProMode != SCNSettings.inst().promode_local)
|
||||||
|
{
|
||||||
|
refreshProModeListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.w(TAG, "queryPurchases() got an error response code: " + purchasesResult.getResponseCode());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(queryToExecute, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void purchase(Activity a, String id)
|
||||||
|
{
|
||||||
|
executeServiceRequest(() ->
|
||||||
|
{
|
||||||
|
BillingFlowParams flowParams = BillingFlowParams
|
||||||
|
.newBuilder()
|
||||||
|
.setSku(id)
|
||||||
|
.setType(BillingClient.SkuType.INAPP) // SkuType.SUB for subscription
|
||||||
|
.build();
|
||||||
|
client.launchBillingFlow(a, flowParams);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest) {
|
||||||
|
if (isServiceConnected)
|
||||||
|
{
|
||||||
|
runnable.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If billing service was disconnected, we try to reconnect 1 time.
|
||||||
|
// (feel free to introduce your retry policy here).
|
||||||
|
startServiceConnection(runnable, userRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
if (client != null && client.isReady()) {
|
||||||
|
client.endConnection();
|
||||||
|
client = null;
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases)
|
||||||
|
{
|
||||||
|
if (responseCode == BillingClient.BillingResponse.OK && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePurchase(Purchase purchase)
|
||||||
|
{
|
||||||
|
Log.d(TAG, "Got a verified purchase: " + purchase);
|
||||||
|
|
||||||
|
purchases.add(purchase);
|
||||||
|
|
||||||
|
refreshProModeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshProModeListener() {
|
||||||
|
MainActivity ma = SCNApp.getMainActivity();
|
||||||
|
if (ma != null) ma.adpTabs.tab3.updateProState();
|
||||||
|
if (ma != null) ma.adpTabs.tab1.updateProState();
|
||||||
|
SCNSettings.inst().updateProState(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServiceConnection(final Func0to0 executeOnSuccess, final boolean userRequest)
|
||||||
|
{
|
||||||
|
client.startConnection(new BillingClientStateListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode)
|
||||||
|
{
|
||||||
|
if (billingResponseCode == BillingClient.BillingResponse.OK)
|
||||||
|
{
|
||||||
|
isServiceConnected = true;
|
||||||
|
if (executeOnSuccess != null) executeOnSuccess.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (userRequest) SCNApp.showToast("Could not connect to google services", Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBillingServiceDisconnected() {
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Purchase getPurchaseCached(String id)
|
||||||
|
{
|
||||||
|
for (Purchase p : purchases)
|
||||||
|
{
|
||||||
|
if (Str.equals(p.getSku(), id)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,6 @@ import android.app.NotificationManager;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.media.AudioAttributes;
|
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier.view;
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
@ -42,7 +40,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
SCNApp.register(this);
|
SCNApp.register(this);
|
||||||
|
IABService.startup(this);
|
||||||
SCNSettings.inst().work(this);
|
SCNSettings.inst().work(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +49,16 @@ public class MainActivity extends AppCompatActivity
|
|||||||
{
|
{
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
|
SCNSettings.inst().save();
|
||||||
CMessageList.inst().fullSave();
|
CMessageList.inst().fullSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy()
|
||||||
|
{
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
CMessageList.inst().fullSave();
|
||||||
|
IABService.inst().destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
|
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
|
||||||
import com.google.android.gms.ads.doubleclick.PublisherAdView;
|
import com.google.android.gms.ads.doubleclick.PublisherAdView;
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
public class NotificationsFragment extends Fragment
|
public class NotificationsFragment extends Fragment
|
||||||
{
|
{
|
||||||
|
private PublisherAdView adView;
|
||||||
|
|
||||||
public NotificationsFragment()
|
public NotificationsFragment()
|
||||||
{
|
{
|
||||||
// Required empty public constructor
|
// Required empty public constructor
|
||||||
@ -30,11 +34,17 @@ public class NotificationsFragment extends Fragment
|
|||||||
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
||||||
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
||||||
|
|
||||||
PublisherAdView mPublisherAdView = v.findViewById(R.id.adBanner);
|
adView = v.findViewById(R.id.adBanner);
|
||||||
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
||||||
mPublisherAdView.loadAd(adRequest);
|
adView.loadAd(adRequest);
|
||||||
|
|
||||||
|
adView.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier.view;
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -16,8 +15,10 @@ import android.widget.Spinner;
|
|||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -33,6 +34,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
private Switch prefAppEnabled;
|
private Switch prefAppEnabled;
|
||||||
private Spinner prefLocalCacheSize;
|
private Spinner prefLocalCacheSize;
|
||||||
private Button prefUpgradeAccount;
|
private Button prefUpgradeAccount;
|
||||||
|
private TextView prefUpgradeAccount_msg;
|
||||||
|
private TextView prefUpgradeAccount_info;
|
||||||
|
|
||||||
private Switch prefMsgLowEnableSound;
|
private Switch prefMsgLowEnableSound;
|
||||||
private TextView prefMsgLowRingtone_value;
|
private TextView prefMsgLowRingtone_value;
|
||||||
@ -85,6 +88,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
||||||
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
||||||
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
||||||
|
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
|
||||||
|
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
|
||||||
|
|
||||||
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
||||||
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
||||||
@ -122,6 +127,10 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
|
|
||||||
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
||||||
|
|
||||||
|
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);
|
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);
|
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
prefLocalCacheSize.setAdapter(plcsa);
|
prefLocalCacheSize.setAdapter(plcsa);
|
||||||
@ -197,7 +206,16 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
|
|
||||||
private void onUpgradeAccount()
|
private void onUpgradeAccount()
|
||||||
{
|
{
|
||||||
//TODO
|
IABService.inst().purchase(getActivity(), IABService.IAB_PRO_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||||
|
|
||||||
|
prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE );
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getCacheSizeIndex(int value)
|
private int getCacheSizeIndex(int value)
|
||||||
|
@ -96,7 +96,8 @@
|
|||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:background="#c0c0c0"/>
|
android:background="#c0c0c0"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -107,12 +108,25 @@
|
|||||||
android:id="@+id/prefUpgradeAccount"
|
android:id="@+id/prefUpgradeAccount"
|
||||||
android:text="@string/str_upgrade_account"
|
android:text="@string/str_upgrade_account"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<TextView
|
||||||
|
android:id="@+id/prefUpgradeAccount_info"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/str_promode_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/prefUpgradeAccount2"
|
||||||
|
android:textColor="#FF4D00"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/str_promode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -28,4 +28,6 @@
|
|||||||
<string name="str_ledcolor">Notification light color</string>
|
<string name="str_ledcolor">Notification light color</string>
|
||||||
<string name="str_enable_vibration">Enable notification vibration</string>
|
<string name="str_enable_vibration">Enable notification vibration</string>
|
||||||
<string name="str_upgrade_account">Upgrade account</string>
|
<string name="str_upgrade_account">Upgrade account</string>
|
||||||
|
<string name="str_promode">Thank you for supporting the app and using the pro mode</string>
|
||||||
|
<string name="str_promode_info">Increase your daily quota, remove the ad banner and support the developer (that\'s me)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -8,7 +8,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||||
classpath 'com.google.gms:google-services:4.0.1'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
web/.gitignore
vendored
3
web/.gitignore
vendored
@ -180,4 +180,5 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
#################
|
#################
|
||||||
|
|
||||||
config.php
|
config.php
|
||||||
|
.verify_accesstoken
|
15
web/info.php
15
web/info.php
@ -15,7 +15,7 @@ $user_key = $INPUT['user_key'];
|
|||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
$stmt->execute(['uid' => $user_id]);
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@ -27,16 +27,17 @@ if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'er
|
|||||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
$quota = $data['quota_today'];
|
$quota = $data['quota_today'];
|
||||||
$quota_max = $data['quota_max'];
|
$is_pro = $data['is_pro'];
|
||||||
|
|
||||||
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max' => Statics::quota_max($is_pro),
|
||||||
'message' => 'ok'
|
'is_pro' => $is_pro,
|
||||||
|
'message' => 'ok'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
102
web/model.php
102
web/model.php
@ -6,6 +6,8 @@ class Statics
|
|||||||
{
|
{
|
||||||
public static $DB = NULL;
|
public static $DB = NULL;
|
||||||
public static $CFG = NULL;
|
public static $CFG = NULL;
|
||||||
|
|
||||||
|
public static function quota_max($is_pro) { return $is_pro ? 1000 : 100; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfig()
|
function getConfig()
|
||||||
@ -15,6 +17,34 @@ function getConfig()
|
|||||||
return Statics::$CFG = require "config.php";
|
return Statics::$CFG = require "config.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportError($msg)
|
||||||
|
{
|
||||||
|
$subject = "SCN_Server has encountered an Error at " . date("Y-m-d H:i:s") . "] ";
|
||||||
|
|
||||||
|
$content = "";
|
||||||
|
|
||||||
|
$content .= 'HTTP_HOST: ' . ParamServerOrUndef('HTTP_HOST') . "\n";
|
||||||
|
$content .= 'REQUEST_URI: ' . ParamServerOrUndef('REQUEST_URI') . "\n";
|
||||||
|
$content .= 'TIME: ' . date('Y-m-d H:i:s') . "\n";
|
||||||
|
$content .= 'REMOTE_ADDR: ' . ParamServerOrUndef('REMOTE_ADDR') . "\n";
|
||||||
|
$content .= 'HTTP_X_FORWARDED_FOR: ' . ParamServerOrUndef('HTTP_X_FORWARDED_FOR') . "\n";
|
||||||
|
$content .= 'HTTP_USER_AGENT: ' . ParamServerOrUndef('HTTP_USER_AGENT') . "\n";
|
||||||
|
$content .= 'MESSAGE:' . "\n" . $msg . "\n";
|
||||||
|
$content .= '$_GET:' . "\n" . print_r($_GET, true) . "\n";
|
||||||
|
$content .= '$_POST:' . "\n" . print_r($_POST, true) . "\n";
|
||||||
|
$content .= '$_FILES:' . "\n" . print_r($_FILES, true) . "\n";
|
||||||
|
|
||||||
|
if (getConfig()['error_reporting']['send-mail'])sendMail($subject, $content, getConfig()['error_reporting']['email-error-target'], getConfig()['error_reporting']['email-error-sender']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $idx
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function ParamServerOrUndef($idx) {
|
||||||
|
return isset($_SERVER[$idx]) ? $_SERVER[$idx] : 'NOT_SET';
|
||||||
|
}
|
||||||
|
|
||||||
function getDatabase()
|
function getDatabase()
|
||||||
{
|
{
|
||||||
if (Statics::$DB !== NULL) return Statics::$DB;
|
if (Statics::$DB !== NULL) return Statics::$DB;
|
||||||
@ -57,6 +87,13 @@ function generateRandomAuthKey()
|
|||||||
return $random;
|
return $random;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $url
|
||||||
|
* @param $body
|
||||||
|
* @param $header
|
||||||
|
* @return array|object|string
|
||||||
|
* @throws \Httpful\Exception\ConnectionErrorException
|
||||||
|
*/
|
||||||
function sendPOST($url, $body, $header)
|
function sendPOST($url, $body, $header)
|
||||||
{
|
{
|
||||||
$builder = \Httpful\Request::post($url);
|
$builder = \Httpful\Request::post($url);
|
||||||
@ -71,3 +108,68 @@ function sendPOST($url, $body, $header)
|
|||||||
|
|
||||||
return $response->body;
|
return $response->body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function verifyOrderToken($tok)
|
||||||
|
{
|
||||||
|
// https://developers.google.com/android-publisher/api-ref/purchases/products/get
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$package = getConfig()['verify_api']['package_name'];
|
||||||
|
$product = getConfig()['verify_api']['product_id'];
|
||||||
|
$acctoken = getConfig()['verify_api']['accesstoken'];
|
||||||
|
|
||||||
|
if ($acctoken == '') $acctoken = refreshVerifyToken();
|
||||||
|
|
||||||
|
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
|
||||||
|
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
|
||||||
|
if ($obj === null || $obj === false)
|
||||||
|
{
|
||||||
|
reportError('verify-token returned NULL');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($obj['error']) && isset($obj['error']['code']) && $obj['error']['code'] == 401) // "Invalid Credentials" -- refresh acces_token
|
||||||
|
{
|
||||||
|
$acctoken = refreshVerifyToken();
|
||||||
|
|
||||||
|
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
|
||||||
|
if ($obj === null || $obj === false)
|
||||||
|
{
|
||||||
|
reportError('verify-token returned NULL');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($obj['purchaseState']) && $obj['purchaseState'] === 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
reportError("VerifyOrder token threw exception: " . $e . "\n" . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws Exception */
|
||||||
|
function refreshVerifyToken()
|
||||||
|
{
|
||||||
|
$url = 'https://accounts.google.com/o/oauth2/token'.
|
||||||
|
'?grant_type=refresh_token'.
|
||||||
|
'&refresh_token='.getConfig()['verify_api']['refreshtoken'].
|
||||||
|
'&client_id='.getConfig()['verify_api']['clientid'].
|
||||||
|
'&client_secret='.getConfig()['verify_api']['clientsecret'];
|
||||||
|
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
file_put_contents('.verify_accesstoken', $obj['access_token']);
|
||||||
|
|
||||||
|
return $obj->access_token;
|
||||||
|
}
|
@ -5,24 +5,34 @@ include_once 'model.php';
|
|||||||
$INPUT = array_merge($_GET, $_POST);
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
if (!isset($INPUT['fcm_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[fcm_token]]']));
|
if (!isset($INPUT['fcm_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[fcm_token]]']));
|
||||||
|
if (!isset($INPUT['pro'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro]]']));
|
||||||
|
if (!isset($INPUT['pro_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro_token]]']));
|
||||||
|
|
||||||
$fcmtoken = $INPUT['fcm_token'];
|
$fcmtoken = $INPUT['fcm_token'];
|
||||||
|
$ispro = $INPUT['pro'] == 'true';
|
||||||
|
$pro_token = $INPUT['pro_token'];
|
||||||
$user_key = generateRandomAuthKey();
|
$user_key = generateRandomAuthKey();
|
||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, timestamp_accessed) VALUES (:key, :token, NOW())');
|
if ($ispro)
|
||||||
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken]);
|
{
|
||||||
|
if (!verifyOrderToken($pro_token)) die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, is_pro, pro_token, timestamp_accessed) VALUES (:key, :token, :bpro, :spro, NOW())');
|
||||||
|
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken, 'bpro' => $ispro, 'spro' => $ispro ? $pro_token : null]);
|
||||||
$user_id = $pdo->lastInsertId('user_id');
|
$user_id = $pdo->lastInsertId('user_id');
|
||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $user_key,
|
'user_key' => $user_key,
|
||||||
'quota' => 0,
|
'quota' => 0,
|
||||||
'quota_max'=> 100,
|
'quota_max' => Statics::quota_max($ispro),
|
||||||
'message' => 'New user registered'
|
'is_pro' => $ispro,
|
||||||
|
'message' => 'New user registered'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
@ -9,7 +9,9 @@ CREATE TABLE `users`
|
|||||||
|
|
||||||
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
||||||
`quota_day` DATE NULL DEFAULT NULL,
|
`quota_day` DATE NULL DEFAULT NULL,
|
||||||
`quota_max` INT(11) NOT NULL DEFAULT '100',
|
|
||||||
|
`is_pro` BIT NOT NULL DEFAULT 0,
|
||||||
|
`pro_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||||
|
|
||||||
PRIMARY KEY (`user_id`)
|
PRIMARY KEY (`user_id`)
|
||||||
);
|
);
|
||||||
|
15
web/send.php
15
web/send.php
@ -32,7 +32,7 @@ if (strlen($content) > 10000) die(json_encode(['success' => false, 'errhighli
|
|||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, fcm_token, messages_sent, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
$stmt = $pdo->prepare('SELECT user_id, user_key, fcm_token, messages_sent, quota_today, is_pro, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
$stmt->execute(['uid' => $user_id]);
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@ -47,7 +47,7 @@ $fcm = $data['fcm_token'];
|
|||||||
|
|
||||||
$new_quota = $data['quota_today'] + 1;
|
$new_quota = $data['quota_today'] + 1;
|
||||||
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
|
||||||
if ($new_quota > $data['quota_max']) die(json_encode(['success' => false, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.$data['quota_max'].')']));
|
if ($new_quota > Statics::quota_max($data['is_pro'])) die(json_encode(['success' => false, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.Statics::quota_max($data['is_pro']).')']));
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
@ -90,11 +90,12 @@ $stmt->execute(['uid' => $user_id, 'q' => $new_quota]);
|
|||||||
|
|
||||||
echo (json_encode(
|
echo (json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Message sent',
|
'message' => 'Message sent',
|
||||||
'response' => $httpresult,
|
'response' => $httpresult,
|
||||||
'messagecount' => $data['messages_sent']+1,
|
'messagecount' => $data['messages_sent']+1,
|
||||||
'quota'=>$new_quota,
|
'quota' => $new_quota,
|
||||||
'quota_max'=>$data['quota_max'],
|
'is_pro' => $data['is_pro'],
|
||||||
|
'quota_max' => Statics::quota_max($data['is_pro']),
|
||||||
]));
|
]));
|
||||||
return 0;
|
return 0;
|
@ -16,7 +16,7 @@ $fcm_token = isset($INPUT['fcm_token']) ? $INPUT['fcm_token'] : null;
|
|||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_day, is_pro FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
$stmt->execute(['uid' => $user_id]);
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@ -28,7 +28,7 @@ if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'me
|
|||||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
$quota = $data['quota_today'];
|
$quota = $data['quota_today'];
|
||||||
$quota_max = $data['quota_max'];
|
$is_pro = $data['is_pro'];
|
||||||
|
|
||||||
$new_userkey = generateRandomAuthKey();
|
$new_userkey = generateRandomAuthKey();
|
||||||
|
|
||||||
@ -39,12 +39,13 @@ if ($fcm_token === null)
|
|||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $new_userkey,
|
'user_key' => $new_userkey,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max'=> Statics::quota_max($data['is_pro']),
|
||||||
'message' => 'user updated'
|
'is_pro' => $is_pro,
|
||||||
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -59,7 +60,8 @@ else
|
|||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $new_userkey,
|
'user_key' => $new_userkey,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max'=> Statics::quota_max($data['is_pro']),
|
||||||
|
'is_pro' => $is_pro,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
||||||
|
78
web/upgrade.php
Normal file
78
web/upgrade.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_key]]']));
|
||||||
|
if (!isset($INPUT['pro'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro]]']));
|
||||||
|
if (!isset($INPUT['pro_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro_token]]']));
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
$ispro = $INPUT['pro'] == 'true';
|
||||||
|
$pro_token = $INPUT['pro_token'];
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_day, is_pro, pro_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, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) die(json_encode(['success' => false, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
if ($ispro)
|
||||||
|
{
|
||||||
|
// set pro=true
|
||||||
|
|
||||||
|
if ($data['pro_token'] != $pro_token)
|
||||||
|
{
|
||||||
|
if (!verifyOrderToken($pro_token)) die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), is_pro=1, pro_token=:ptk WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
|
||||||
|
|
||||||
|
$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(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $data['quota'],
|
||||||
|
'quota_max'=> Statics::quota_max(true),
|
||||||
|
'is_pro' => true,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set pro=false
|
||||||
|
|
||||||
|
$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(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $data['quota'],
|
||||||
|
'quota_max'=> Statics::quota_max(false),
|
||||||
|
'is_pro' => false,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user