in-app-purchase: pro-mode
This commit is contained in:
@@ -35,7 +35,7 @@ android {
|
||||
dependencies {
|
||||
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.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
@@ -45,7 +45,8 @@ dependencies {
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'com.google.firebase:firebase-core:16.0.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.github.kenglxn.QRGen:android:2.5.0'
|
||||
|
@@ -4,6 +4,7 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||
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.view.View;
|
||||
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
public class SCNSettings
|
||||
@@ -36,6 +38,10 @@ public class SCNSettings
|
||||
public String fcm_token_local;
|
||||
public String fcm_token_server;
|
||||
|
||||
public String promode_token;
|
||||
public boolean promode_local;
|
||||
public boolean promode_server;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
public boolean Enabled = true;
|
||||
@@ -57,6 +63,9 @@ public class SCNSettings
|
||||
user_key = sharedPref.getString("user_key", "");
|
||||
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
||||
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);
|
||||
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||
@@ -151,10 +160,12 @@ public class SCNSettings
|
||||
{
|
||||
fcm_token_local = token;
|
||||
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)
|
||||
{
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||
@@ -166,8 +177,11 @@ public class SCNSettings
|
||||
{
|
||||
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||
});
|
||||
|
||||
updateProState(null);
|
||||
}
|
||||
|
||||
// reset account key
|
||||
public void reset(View loader)
|
||||
{
|
||||
if (!isConnected()) return;
|
||||
@@ -175,23 +189,50 @@ public class SCNSettings
|
||||
ServerCommunication.update(user_id, user_key, loader);
|
||||
}
|
||||
|
||||
// refresh account data
|
||||
public void refresh(View loader, Activity a)
|
||||
{
|
||||
if (isConnected())
|
||||
{
|
||||
ServerCommunication.info(user_id, user_key, loader);
|
||||
if (promode_server != promode_local)
|
||||
{
|
||||
updateProState(loader);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get token then register
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||
{
|
||||
String newToken = instanceIdResult.getToken();
|
||||
Log.e("FB::GetInstanceId", newToken);
|
||||
SCNSettings.inst().setServerToken(newToken, loader);
|
||||
SCNSettings.inst().setServerToken(newToken, loader); // does register in here
|
||||
}).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 java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -25,12 +26,12 @@ public class ServerCommunication
|
||||
|
||||
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
|
||||
{
|
||||
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();
|
||||
|
||||
client.newCall(request).enqueue(new Callback()
|
||||
@@ -67,6 +68,7 @@ public class ServerCommunication
|
||||
SCNSettings.inst().fcm_token_server = token;
|
||||
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();
|
||||
@@ -132,6 +134,7 @@ public class ServerCommunication
|
||||
SCNSettings.inst().fcm_token_server = token;
|
||||
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();
|
||||
@@ -187,10 +190,11 @@ public class ServerCommunication
|
||||
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().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();
|
||||
@@ -247,9 +251,10 @@ public class ServerCommunication
|
||||
return;
|
||||
}
|
||||
|
||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||
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();
|
||||
@@ -270,4 +275,63 @@ public class ServerCommunication
|
||||
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.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
@@ -1,17 +1,15 @@
|
||||
package com.blackforestbytes.simplecloudnotifier.view;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
@@ -42,7 +40,7 @@ public class MainActivity extends AppCompatActivity
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
SCNApp.register(this);
|
||||
|
||||
IABService.startup(this);
|
||||
SCNSettings.inst().work(this);
|
||||
}
|
||||
|
||||
@@ -51,6 +49,16 @@ public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
super.onStop();
|
||||
|
||||
SCNSettings.inst().save();
|
||||
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 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.PublisherAdView;
|
||||
|
||||
@@ -16,6 +18,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class NotificationsFragment extends Fragment
|
||||
{
|
||||
private PublisherAdView adView;
|
||||
|
||||
public NotificationsFragment()
|
||||
{
|
||||
// Required empty public constructor
|
||||
@@ -30,11 +34,17 @@ public class NotificationsFragment extends Fragment
|
||||
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
||||
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();
|
||||
mPublisherAdView.loadAd(adRequest);
|
||||
adView.loadAd(adRequest);
|
||||
|
||||
adView.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||
|
||||
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;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
@@ -16,8 +15,10 @@ import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.blackforestbytes.simplecloudnotifier.R;
|
||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -33,6 +34,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
private Switch prefAppEnabled;
|
||||
private Spinner prefLocalCacheSize;
|
||||
private Button prefUpgradeAccount;
|
||||
private TextView prefUpgradeAccount_msg;
|
||||
private TextView prefUpgradeAccount_info;
|
||||
|
||||
private Switch prefMsgLowEnableSound;
|
||||
private TextView prefMsgLowRingtone_value;
|
||||
@@ -85,6 +88,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
||||
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -197,7 +206,16 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||
|
||||
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)
|
||||
|
@@ -96,7 +96,8 @@
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="#c0c0c0"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_width="match_parent"
|
||||
@@ -107,12 +108,25 @@
|
||||
android:id="@+id/prefUpgradeAccount"
|
||||
android:text="@string/str_upgrade_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</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>
|
||||
|
||||
|
@@ -28,4 +28,6 @@
|
||||
<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_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>
|
||||
|
Reference in New Issue
Block a user