only support versionCode

would love to support version name but it's not going to work out very well

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/27/head
androidacy-user 3 years ago
parent e57829f21d
commit 159454b90e

@ -98,7 +98,11 @@ android {
// current timestamp of build // current timestamp of build
buildConfigField("long", "BUILD_TIME", "$timestamp") buildConfigField("long", "BUILD_TIME", "$timestamp")
// debug http requests. do not set this to true if you care about performance!!!!! // debug http requests. do not set this to true if you care about performance!!!!!
buildConfigField("boolean", "DEBUG_HTTP", "false") if (System.getenv("CI") != null) {
buildConfigField("boolean", "DEBUG_HTTP", "true")
} else {
buildConfigField("boolean", "DEBUG_HTTP", "false")
}
// Latest commit hash as BuildConfig.COMMIT_HASH // Latest commit hash as BuildConfig.COMMIT_HASH
buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"")
// Get the current branch name as BuildConfig.BRANCH_NAME // Get the current branch name as BuildConfig.BRANCH_NAME
@ -161,7 +165,12 @@ android {
// current timestamp of build // current timestamp of build
buildConfigField("long", "BUILD_TIME", "$timestamp") buildConfigField("long", "BUILD_TIME", "$timestamp")
// debug http requests. do not set this to true if you care about performance!!!!! // debug http requests. do not set this to true if you care about performance!!!!!
buildConfigField("boolean", "DEBUG_HTTP", "false") // if env var CI is set to true, this will be true
if (System.getenv("CI") != null) {
buildConfigField("boolean", "DEBUG_HTTP", "true")
} else {
buildConfigField("boolean", "DEBUG_HTTP", "false")
}
// Latest commit hash as BuildConfig.COMMIT_HASH // Latest commit hash as BuildConfig.COMMIT_HASH
buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"")
// Get the current branch name as BuildConfig.BRANCH_NAME // Get the current branch name as BuildConfig.BRANCH_NAME
@ -223,8 +232,11 @@ android {
// current timestamp of build // current timestamp of build
buildConfigField("long", "BUILD_TIME", "$timestamp") buildConfigField("long", "BUILD_TIME", "$timestamp")
// debug http requests. do not set this to true if you care about performance!!!!! // debug http requests. do not set this to true if you care about performance!!!!!
buildConfigField("boolean", "DEBUG_HTTP", "false") if (System.getenv("CI") != null) {
buildConfigField("boolean", "DEBUG_HTTP", "true")
} else {
buildConfigField("boolean", "DEBUG_HTTP", "false")
}
// Latest commit hash as BuildConfig.COMMIT_HASH // Latest commit hash as BuildConfig.COMMIT_HASH
buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"")
// Get the current branch name as BuildConfig.BRANCH_NAME // Get the current branch name as BuildConfig.BRANCH_NAME
@ -469,6 +481,9 @@ dependencies {
// desugaring // desugaring
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
// yes
implementation("com.github.fingerprintjs:fingerprint-android:2.0.0")
} }
android { android {

@ -34,6 +34,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.foxcompat.view.FoxDisplay; import com.fox2code.foxcompat.view.FoxDisplay;
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo; import com.fox2code.mmm.manager.LocalModuleInfo;
@ -55,6 +56,7 @@ import org.matomo.sdk.extra.TrackHelper;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import io.realm.Realm; import io.realm.Realm;
import io.realm.RealmConfiguration; import io.realm.RealmConfiguration;
@ -62,6 +64,7 @@ import timber.log.Timber;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper { public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final int PRECISION = 10000; private static final int PRECISION = 10000;
private static MainActivity INSTANCE;
public static boolean doSetupNowRunning = true; public static boolean doSetupNowRunning = true;
public static boolean doSetupRestarting = false; public static boolean doSetupRestarting = false;
public static List<LocalModuleInfo> localModuleInfoList = new ArrayList<>(); public static List<LocalModuleInfo> localModuleInfoList = new ArrayList<>();
@ -420,6 +423,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
searchView.setEnabled(true); searchView.setEnabled(true);
updateScreenInsets(getResources().getConfiguration()); updateScreenInsets(getResources().getConfiguration());
}); });
maybeShowUpgrade();
Timber.i("Finished app opening state!"); Timber.i("Finished app opening state!");
} }
}, true); }, true);
@ -702,4 +706,60 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
super.onWindowFocusChanged(hasFocus); super.onWindowFocusChanged(hasFocus);
this.updateScreenInsets(); this.updateScreenInsets();
} }
public void maybeShowUpgrade() {
if (AndroidacyRepoData.getInstance() == null || AndroidacyRepoData.getInstance().memberLevel == null) {
// wait for up to 10 seconds for AndroidacyRepoData to be initialized
int i = 0;
while (AndroidacyRepoData.getInstance() == null && i < 10) {
try {
//noinspection BusyWait
Thread.sleep(1000);
} catch (InterruptedException e) {
Timber.e(e);
}
i++;
}
if (AndroidacyRepoData.getInstance().isEnabled() && AndroidacyRepoData.getInstance().memberLevel == null) {
Timber.d("Member level is null, waiting for it to be initialized");
i = 0;
while (AndroidacyRepoData.getInstance().memberLevel == null && i < 20) {
i++;
try {
//noinspection BusyWait
Thread.sleep(500);
} catch (InterruptedException e) {
Timber.e(e);
}
}
}
// if it's still null, but it's enabled, throw an error
if (AndroidacyRepoData.getInstance().isEnabled() && AndroidacyRepoData.getInstance().memberLevel == null) {
throw new IllegalStateException("AndroidacyRepoData is enabled, but member level is null");
}
if (AndroidacyRepoData.getInstance() != null && AndroidacyRepoData.getInstance().isEnabled() && Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
runtimeUtils.showUpgradeSnackbar(this, this);
} else {
if (!AndroidacyRepoData.getInstance().isEnabled()) {
Timber.i("AndroidacyRepoData is disabled, not showing upgrade snackbar 1");
} else if (!Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
Timber.i("AndroidacyRepoData is not Guest, not showing upgrade snackbar 1. Level: %s", AndroidacyRepoData.getInstance().memberLevel);
} else {
Timber.i("Unknown error, not showing upgrade snackbar 1");
}
}
} else if (AndroidacyRepoData.getInstance().isEnabled() && Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
runtimeUtils.showUpgradeSnackbar(this, this);
} else {
if (!AndroidacyRepoData.getInstance().isEnabled()) {
Timber.i("AndroidacyRepoData is disabled, not showing upgrade snackbar 2");
} else if (!Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
Timber.i("AndroidacyRepoData is not Guest, not showing upgrade snackbar 2. Level: %s", AndroidacyRepoData.getInstance().memberLevel);
} else {
Timber.i("Unknown error, not showing upgrade snackbar 2");
}
}
}
} }

@ -224,7 +224,7 @@ public class UpdateActivity extends FoxActivity {
}); });
} }
// convert to JSON // convert to JSON
JSONObject latestJSON = new JSONObject(Arrays.toString(lastestJSON)); JSONObject latestJSON = new JSONObject(new String(lastestJSON));
String changelog = latestJSON.getString("body"); String changelog = latestJSON.getString("body");
// set changelog text. changelog could be markdown, so we need to convert it to HTML // set changelog text. changelog could be markdown, so we need to convert it to HTML
MaterialTextView changelogTextView = findViewById(R.id.update_changelog); MaterialTextView changelogTextView = findViewById(R.id.update_changelog);

@ -275,12 +275,14 @@ public final class AndroidacyActivity extends FoxActivity {
@Override @Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) { public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
switch (consoleMessage.messageLevel()) { if (BuildConfig.DEBUG_HTTP) {
case TIP -> Timber.tag("JSLog").i(consoleMessage.message()); switch (consoleMessage.messageLevel()) {
case LOG -> Timber.tag("JSLog").d(consoleMessage.message()); case TIP -> Timber.tag("JSLog").i(consoleMessage.message());
case WARNING -> Timber.tag("JSLog").w(consoleMessage.message()); case LOG -> Timber.tag("JSLog").d(consoleMessage.message());
case ERROR -> Timber.tag("JSLog").e(consoleMessage.message()); case WARNING -> Timber.tag("JSLog").w(consoleMessage.message());
default -> Timber.tag("JSLog").v(consoleMessage.message()); case ERROR -> Timber.tag("JSLog").e(consoleMessage.message());
default -> Timber.tag("JSLog").v(consoleMessage.message());
}
} }
return true; return true;
} }

@ -1,6 +1,8 @@
package com.fox2code.mmm.androidacy; package com.fox2code.mmm.androidacy;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@ -10,18 +12,23 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fingerprintjs.android.fingerprint.Fingerprinter;
import com.fingerprintjs.android.fingerprint.FingerprinterFactory;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.foxcompat.app.internal.FoxCompat;
import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R; import com.fox2code.mmm.R;
import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.RuntimeUtils;
import com.fox2code.mmm.utils.io.PropUtils; import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.io.net.Http; import com.fox2code.mmm.utils.io.net.Http;
import com.fox2code.mmm.utils.io.net.HttpException; import com.fox2code.mmm.utils.io.net.HttpException;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.topjohnwu.superuser.Shell;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -31,13 +38,13 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import timber.log.Timber; import timber.log.Timber;
@ -94,59 +101,36 @@ public final class AndroidacyRepoData extends RepoData {
} }
// Try to get the device ID from the shared preferences // Try to get the device ID from the shared preferences
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("androidacy"); SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("androidacy");
String deviceIdPref = sharedPreferences.getString("device_id", null); String deviceIdPref = sharedPreferences.getString("device_id_v2", null);
if (deviceIdPref != null) { if (deviceIdPref != null) {
ANDROIDACY_DEVICE_ID = deviceIdPref; ANDROIDACY_DEVICE_ID = deviceIdPref;
return deviceIdPref; return deviceIdPref;
} else { } else {
// Really not that scary - just hashes some device info. We can't even get the info Fingerprinter fp = FingerprinterFactory.create(MainApplication.getINSTANCE().getApplicationContext());
// we originally hashed, so it's not like we can use it to track you. fp.getFingerprint(Fingerprinter.Version.V_5, fingerprint-> {
String deviceId = null; ANDROIDACY_DEVICE_ID = fingerprint;
// Get ro.serialno if it exists // use fingerprint
// First, we need to get an su shell // Save the device ID to the shared preferences
Shell.Result result = Shell.cmd("getprop ro.serialno").exec(); SharedPreferences.Editor editor = sharedPreferences.edit();
// Check if the command was successful editor.putString("device_id_v2", ANDROIDACY_DEVICE_ID);
if (result.isSuccess()) { editor.apply();
// Get the output return null;
String output = result.getOut().get(0); });
// Check if the output is valid // wait for up to 5 seconds for the fingerprint to be generated (ANDROIDACY_DEVICE_ID to be set)
if (output != null && !output.isEmpty()) { long startTime = System.currentTimeMillis();
deviceId = output; while (ANDROIDACY_DEVICE_ID == null && System.currentTimeMillis() - startTime < 5000) {
try {
//noinspection BusyWait
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
} }
} }
// Now, get device model, manufacturer, and Android version originally from if (ANDROIDACY_DEVICE_ID == null) {
String deviceModel = android.os.Build.MODEL; // fingerprint generation failed, use a random UUID
String deviceManufacturer = android.os.Build.MANUFACTURER; ANDROIDACY_DEVICE_ID = UUID.randomUUID().toString();
String androidVersion = android.os.Build.VERSION.RELEASE;
// Append it all together
deviceId += deviceModel + deviceManufacturer + androidVersion;
// Hash it
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ignored) {
// This should never happen so we can just return the original device ID
ANDROIDACY_DEVICE_ID = deviceId;
return deviceId;
}
byte[] hash = digest.digest(deviceId.getBytes());
// Convert it to a hex string
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
} }
// Save it to shared preferences return ANDROIDACY_DEVICE_ID;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("device_id", hexString.toString());
editor.apply();
// Set ANDROIDACY_DEVICE_ID
ANDROIDACY_DEVICE_ID = hexString.toString();
// Return it
return hexString.toString();
} }
} }
@ -155,8 +139,9 @@ public final class AndroidacyRepoData extends RepoData {
try { try {
byte[] resp = Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId + "&client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID, false); byte[] resp = Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId + "&client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID, false);
// response is JSON // response is JSON
JSONObject jsonObject = new JSONObject(Arrays.toString(resp)); JSONObject jsonObject = new JSONObject(new String(resp));
memberLevel = jsonObject.getString("role"); memberLevel = jsonObject.getString("role");
Timber.d("Member level: %s", memberLevel);
JSONArray memberPermissions = jsonObject.getJSONArray("permissions"); JSONArray memberPermissions = jsonObject.getJSONArray("permissions");
// set role and permissions on userInfo property // set role and permissions on userInfo property
userInfo = new String[][]{{"role", memberLevel}, {"permissions", String.valueOf(memberPermissions)}}; userInfo = new String[][]{{"role", memberLevel}, {"permissions", String.valueOf(memberPermissions)}};
@ -252,7 +237,7 @@ public final class AndroidacyRepoData extends RepoData {
try { try {
Timber.i("Requesting new token..."); Timber.i("Requesting new token...");
// POST json request to https://production-api.androidacy.com/auth/register // POST json request to https://production-api.androidacy.com/auth/register
token = Arrays.toString(Http.doHttpPost("https://" + this.host + "/auth/register?client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID, "{\"device_id\":\"" + deviceId + "\"}", false)); token = new String(Http.doHttpPost("https://" + this.host + "/auth/register?client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID, "{\"device_id\":\"" + deviceId + "\"}", false));
// Parse token // Parse token
try { try {
JSONObject jsonObject = new JSONObject(token); JSONObject jsonObject = new JSONObject(token);
@ -261,6 +246,7 @@ public final class AndroidacyRepoData extends RepoData {
//noinspection SuspiciousRegexArgument //noinspection SuspiciousRegexArgument
Timber.d("Token: %s", token.substring(0, token.length() - 4).replaceAll(".", "*") + token.substring(token.length() - 4)); Timber.d("Token: %s", token.substring(0, token.length() - 4).replaceAll(".", "*") + token.substring(token.length() - 4));
memberLevel = jsonObject.getString("role"); memberLevel = jsonObject.getString("role");
Timber.d("Member level: %s", memberLevel);
} catch (JSONException e) { } catch (JSONException e) {
Timber.e(e, "Failed to parse token"); Timber.e(e, "Failed to parse token");
// Show a toast // Show a toast

@ -133,6 +133,6 @@ public enum AndroidacyUtil {
if (md == null) { if (md == null) {
return null; return null;
} }
return Arrays.toString(md); return new String(md);
} }
} }

@ -153,34 +153,28 @@ public class BackgroundUpdateChecker extends Worker {
} }
RepoModule repoModule = repoModules.get(localModuleInfo.id); RepoModule repoModule = repoModules.get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate(); localModuleInfo.checkModuleUpdate();
String remoteVersion = localModuleInfo.updateVersion;
String remoteVersionCode = String.valueOf(localModuleInfo.updateVersionCode); String remoteVersionCode = String.valueOf(localModuleInfo.updateVersionCode);
if (repoModule != null) { if (repoModule != null) {
remoteVersion = repoModule.moduleInfo.version;
remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode); remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode);
} }
// now, coerce everything into an int
int localVersionCode = Integer.parseInt(String.valueOf(localModuleInfo.versionCode)); int localVersionCode = Integer.parseInt(String.valueOf(localModuleInfo.versionCode));
int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode); int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode);
// we also have to match the version name int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", ""));
int localVersion = Integer.parseInt(localModuleInfo.version);
int remoteVersionInt = Integer.parseInt(remoteVersion);
int wantsVersion = Integer.parseInt(version.split(":")[1]);
// now find out if user wants up to and including this version, or this version and newer // now find out if user wants up to and including this version, or this version and newer
// if it starts with ^, it's this version and newer, if it ends with $, it's this version and older // if it starts with ^, it's this version and newer, if it ends with $, it's this version and older
if (version.startsWith("^")) { if (version.startsWith("^")) {
// this version and newer // this version and newer
if (wantsVersion > localVersion || wantsVersion > remoteVersionInt || wantsVersion > remoteVersionCodeInt || wantsVersion < localVersionCode) { if (wantsVersion <= remoteVersionCodeInt || wantsVersion <= localVersionCode) {
// if it is, we skip it // if it is, we skip it
continue; continue;
} }
} else if (version.endsWith("$")) { } else if (version.endsWith("$")) {
// this version and older // this version and older
if (wantsVersion < localVersion || wantsVersion < remoteVersionInt || wantsVersion < remoteVersionCodeInt || wantsVersion > localVersionCode) { if (wantsVersion >= remoteVersionCodeInt || wantsVersion >= localVersionCode) {
// if it is, we skip it // if it is, we skip it
continue; continue;
} }
} else if (wantsVersion == localVersion || wantsVersion == remoteVersionInt || wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { } else if (wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) {
// if it is, we skip it // if it is, we skip it
continue; continue;
} }

@ -152,34 +152,29 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
// get the one matching // get the one matching
version = stringSet.stream().filter(s -> s.startsWith(moduleInfo.id)).findFirst().orElse(""); version = stringSet.stream().filter(s -> s.startsWith(moduleInfo.id)).findFirst().orElse("");
} }
String remoteVersion = moduleInfo.updateVersion;
String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode); String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode);
if (repoModule != null) { if (repoModule != null) {
remoteVersion = repoModule.moduleInfo.version;
remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode); remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode);
} }
// now, coerce everything into an int // now, coerce everything into an int
int localVersionCode = Integer.parseInt(String.valueOf(moduleInfo.versionCode)); int localVersionCode = Integer.parseInt(String.valueOf(moduleInfo.versionCode));
int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode); int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode);
// we also have to match the version name int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", ""));
int localVersion = Integer.parseInt(moduleInfo.version);
int remoteVersionInt = Integer.parseInt(remoteVersion);
int wantsVersion = Integer.parseInt(version.split(":")[1]);
// now find out if user wants up to and including this version, or this version and newer // now find out if user wants up to and including this version, or this version and newer
// if it starts with ^, it's this version and newer, if it ends with $, it's this version and older // if it starts with ^, it's this version and newer, if it ends with $, it's this version and older
if (version.startsWith("^")) { if (version.startsWith("^")) {
// this version and newer // this version and newer
if (wantsVersion > localVersion || wantsVersion > remoteVersionInt || wantsVersion > remoteVersionCodeInt || wantsVersion < localVersionCode) { if (wantsVersion <= remoteVersionCodeInt || wantsVersion <= localVersionCode) {
// if it is, we skip it // if it is, we skip it
ignoreUpdate = true; ignoreUpdate = true;
} }
} else if (version.endsWith("$")) { } else if (version.endsWith("$")) {
// this version and older // this version and older
if (wantsVersion < localVersion || wantsVersion < remoteVersionInt || wantsVersion < remoteVersionCodeInt || wantsVersion > localVersionCode) { if (wantsVersion >= remoteVersionCodeInt || wantsVersion >= localVersionCode) {
// if it is, we skip it // if it is, we skip it
ignoreUpdate = true; ignoreUpdate = true;
} }
} else if (wantsVersion == localVersion || wantsVersion == remoteVersionInt || wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { } else if (wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) {
// if it is, we skip it // if it is, we skip it
ignoreUpdate = true; ignoreUpdate = true;
} }

@ -75,7 +75,7 @@ public class CustomRepoManager {
// parse json // parse json
JSONObject jsonObject; JSONObject jsonObject;
try { try {
jsonObject = new JSONObject(Arrays.toString(json)); jsonObject = new JSONObject(new String(json));
} catch (Exception e) { } catch (Exception e) {
Timber.e(e, "Failed to parse json from repo"); Timber.e(e, "Failed to parse json from repo");
return null; return null;

@ -671,8 +671,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
EditText editText = (EditText) layout.getChildAt(i); EditText editText = (EditText) layout.getChildAt(i);
String text = editText.getText().toString(); String text = editText.getText().toString();
if (!text.isEmpty()) { if (!text.isEmpty()) {
// text cannot contain a colon because we use that to split id and version // text can only contain numbers
text = text.replace(":", ""); text = text.replaceAll("[^0-9]", "");
// we have to use module id even though we show name // we have to use module id even though we show name
stringSetTemp.add(localModuleInfos.stream().filter(localModuleInfo -> localModuleInfo.name.equals(editText.getHint().toString())).findFirst().orElse(null).id + ":" + text); stringSetTemp.add(localModuleInfos.stream().filter(localModuleInfo -> localModuleInfo.name.equals(editText.getHint().toString())).findFirst().orElse(null).id + ":" + text);
Timber.d("text is %s for %s", text, editText.getHint().toString()); Timber.d("text is %s for %s", text, editText.getHint().toString());
@ -942,23 +942,29 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// now handle pref_donate_androidacy // now handle pref_donate_androidacy
LongClickablePreference pref_donate_androidacy = findPreference("pref_donate_androidacy"); LongClickablePreference pref_donate_androidacy = findPreference("pref_donate_androidacy");
if (!BuildConfig.FLAVOR.equals("play")) { if (!BuildConfig.FLAVOR.equals("play")) {
pref_donate_androidacy.setOnPreferenceClickListener(p -> { if (Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
// copy FOX2CODE promo code to clipboard and toast user that they can use it for half off any subscription pref_donate_androidacy.setOnPreferenceClickListener(p -> {
String toastText = requireContext().getString(R.string.promo_code_copied); // copy FOX2CODE promo code to clipboard and toast user that they can use it for half off any subscription
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "FOX2CODE")); String toastText = requireContext().getString(R.string.promo_code_copied);
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "FOX2CODE"));
// open androidacy Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
IntentHelper.openUrl(getFoxActivity(this), "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate"); // open androidacy
return true; IntentHelper.openUrl(getFoxActivity(this), "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate");
}); return true;
// handle long click on pref_donate_androidacy });
pref_donate_androidacy.setOnPreferenceLongClickListener(p -> { // handle long click on pref_donate_androidacy
// copy to clipboard pref_donate_androidacy.setOnPreferenceLongClickListener(p -> {
String toastText = requireContext().getString(R.string.link_copied); // copy to clipboard
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate")); String toastText = requireContext().getString(R.string.link_copied);
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate"));
return true; Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
}); return true;
});
} else {
// set text to "Thank you for your support!"
pref_donate_androidacy.setSummary(R.string.androidacy_thanks_up);
pref_donate_androidacy.setTitle(R.string.androidacy_thanks_up_title);
}
} else { } else {
pref_donate_androidacy.setVisible(false); pref_donate_androidacy.setVisible(false);
} }

@ -219,4 +219,35 @@ class RuntimeUtils {
snackbar.show() snackbar.show()
prefs.edit().putInt("weblate_snackbar_shown", BuildConfig.VERSION_CODE).apply() prefs.edit().putInt("weblate_snackbar_shown", BuildConfig.VERSION_CODE).apply()
} }
/**
* Shows a snackbar to upgrade androidacy membership.
* Sure it'll be wildly popular but it's only shown for 7 seconds every 7 days.
* We could y'know stick ads in the app but we're gonna play nice.
* @param context
* @param activity
*/
@SuppressLint("RestrictedApi")
fun showUpgradeSnackbar(context: Context, activity: MainActivity) {
Timber.i("showUpgradeSnackbar start")
val prefs = MainApplication.getSharedPreferences("mmm")
// if last shown < 7 days ago
if (prefs.getLong("ugsns4", 0) > System.currentTimeMillis() - 604800000) return
val snackbar: Snackbar = Snackbar.make(
context,
activity.findViewById(R.id.blur_frame),
activity.getString(R.string.upgrade_snackbar),
7000
)
snackbar.setAction(R.string.upgrade_now) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://androidacy.com/membership-join/#utm_source=foxmmm&utm_medium=app&utm_campaign=upgrade_snackbar")
activity.startActivity(intent)
}
snackbar.setAnchorView(R.id.bottom_navigation)
snackbar.show()
// do not show for another 7 days
prefs.edit().putLong("ugsns4", System.currentTimeMillis()).apply()
Timber.i("showUpgradeSnackbar done")
}
} }

@ -419,8 +419,12 @@
<string name="notification_update_ignore_version_desc">Ignore specific versions when checking for module updates. Disabling update checks per-module above overrides this setting!</string> <string name="notification_update_ignore_version_desc">Ignore specific versions when checking for module updates. Disabling update checks per-module above overrides this setting!</string>
<string name="notification_update_ignore_version_pref">Exclude version(s) from update checks</string> <string name="notification_update_ignore_version_pref">Exclude version(s) from update checks</string>
<string name="background_update_check_excludes_version">Exclude version(s) of modules from checks</string> <string name="background_update_check_excludes_version">Exclude version(s) of modules from checks</string>
<string name="background_update_check_excludes_version_summary">Specify versions of modules you\'d like to ignore. Version code or version name will work, but version code may be more accurate. Use ^ at the beginning to match that version and newer. Use $ at the end to match up until that version.</string> <string name="background_update_check_excludes_version_summary">Specify a version of module updates you\'d like to ignore. Please use the version code only. You can find this by holding down on the version in the module list. Use ^ at the beginning to match that version and newer. Use $ at the end to match up until that version.</string>
<string name="module_version">"Version: "</string> <string name="module_version">"Version: "</string>
<string name="module_remote_version">Remote version</string> <string name="module_remote_version">Remote version</string>
<string name="background_update_check_excludes_version_hint">Version(s) to ignore</string> <string name="background_update_check_excludes_version_hint">Version code to ignore</string>
<string name="androidacy_thanks_up">Thanks for upgrading! Module downloads and READMEs will not show ads, and downloads will be unthrottled. In the future. you will be able to directly download modules with one click.</string>
<string name="androidacy_thanks_up_title">You\'ve already upgraded!</string>
<string name="upgrade_snackbar">Androidacy Premium offers faster downloads, an ad-free experience, and more!</string>
<string name="upgrade_now">Upgrade</string>
</resources> </resources>

Loading…
Cancel
Save