diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 20f1d84..713aaf5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -98,7 +98,11 @@ android { // current timestamp of build buildConfigField("long", "BUILD_TIME", "$timestamp") // 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 buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") // Get the current branch name as BuildConfig.BRANCH_NAME @@ -161,7 +165,12 @@ android { // current timestamp of build buildConfigField("long", "BUILD_TIME", "$timestamp") // 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 buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") // Get the current branch name as BuildConfig.BRANCH_NAME @@ -223,8 +232,11 @@ android { // current timestamp of build buildConfigField("long", "BUILD_TIME", "$timestamp") // 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 buildConfigField("String", "COMMIT_HASH", "\"$gitCommitHash\"") // Get the current branch name as BuildConfig.BRANCH_NAME @@ -469,6 +481,9 @@ dependencies { // desugaring coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") + + // yes + implementation("com.github.fingerprintjs:fingerprint-android:2.0.0") } android { diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 1a32088..23e9f0b 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -34,6 +34,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.foxcompat.view.FoxDisplay; +import com.fox2code.mmm.androidacy.AndroidacyRepoData; import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.manager.LocalModuleInfo; @@ -55,6 +56,7 @@ import org.matomo.sdk.extra.TrackHelper; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import io.realm.Realm; 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 { private static final int PRECISION = 10000; + private static MainActivity INSTANCE; public static boolean doSetupNowRunning = true; public static boolean doSetupRestarting = false; public static List localModuleInfoList = new ArrayList<>(); @@ -420,6 +423,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe searchView.setEnabled(true); updateScreenInsets(getResources().getConfiguration()); }); + maybeShowUpgrade(); Timber.i("Finished app opening state!"); } }, true); @@ -702,4 +706,60 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe super.onWindowFocusChanged(hasFocus); 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"); + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/UpdateActivity.java b/app/src/main/java/com/fox2code/mmm/UpdateActivity.java index b3bd297..9ee7a2a 100644 --- a/app/src/main/java/com/fox2code/mmm/UpdateActivity.java +++ b/app/src/main/java/com/fox2code/mmm/UpdateActivity.java @@ -224,7 +224,7 @@ public class UpdateActivity extends FoxActivity { }); } // convert to JSON - JSONObject latestJSON = new JSONObject(Arrays.toString(lastestJSON)); + JSONObject latestJSON = new JSONObject(new String(lastestJSON)); String changelog = latestJSON.getString("body"); // set changelog text. changelog could be markdown, so we need to convert it to HTML MaterialTextView changelogTextView = findViewById(R.id.update_changelog); diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index 9fb8164..456f00d 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -275,12 +275,14 @@ public final class AndroidacyActivity extends FoxActivity { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - switch (consoleMessage.messageLevel()) { - case TIP -> Timber.tag("JSLog").i(consoleMessage.message()); - case LOG -> Timber.tag("JSLog").d(consoleMessage.message()); - case WARNING -> Timber.tag("JSLog").w(consoleMessage.message()); - case ERROR -> Timber.tag("JSLog").e(consoleMessage.message()); - default -> Timber.tag("JSLog").v(consoleMessage.message()); + if (BuildConfig.DEBUG_HTTP) { + switch (consoleMessage.messageLevel()) { + case TIP -> Timber.tag("JSLog").i(consoleMessage.message()); + case LOG -> Timber.tag("JSLog").d(consoleMessage.message()); + case WARNING -> Timber.tag("JSLog").w(consoleMessage.message()); + case ERROR -> Timber.tag("JSLog").e(consoleMessage.message()); + default -> Timber.tag("JSLog").v(consoleMessage.message()); + } } return true; } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index 6b909c6..9d8b09b 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -1,6 +1,8 @@ package com.fox2code.mmm.androidacy; import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; @@ -10,18 +12,23 @@ import android.widget.Toast; 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.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoManager; 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.net.Http; import com.fox2code.mmm.utils.io.net.HttpException; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.topjohnwu.superuser.Shell; import org.json.JSONArray; import org.json.JSONException; @@ -31,13 +38,13 @@ import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import okhttp3.HttpUrl; import timber.log.Timber; @@ -94,59 +101,36 @@ public final class AndroidacyRepoData extends RepoData { } // Try to get the device ID from the shared preferences SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("androidacy"); - String deviceIdPref = sharedPreferences.getString("device_id", null); + String deviceIdPref = sharedPreferences.getString("device_id_v2", null); if (deviceIdPref != null) { ANDROIDACY_DEVICE_ID = deviceIdPref; return deviceIdPref; } else { - // Really not that scary - just hashes some device info. We can't even get the info - // we originally hashed, so it's not like we can use it to track you. - String deviceId = null; - // Get ro.serialno if it exists - // First, we need to get an su shell - Shell.Result result = Shell.cmd("getprop ro.serialno").exec(); - // Check if the command was successful - if (result.isSuccess()) { - // Get the output - String output = result.getOut().get(0); - // Check if the output is valid - if (output != null && !output.isEmpty()) { - deviceId = output; + Fingerprinter fp = FingerprinterFactory.create(MainApplication.getINSTANCE().getApplicationContext()); + fp.getFingerprint(Fingerprinter.Version.V_5, fingerprint-> { + ANDROIDACY_DEVICE_ID = fingerprint; + // use fingerprint + // Save the device ID to the shared preferences + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("device_id_v2", ANDROIDACY_DEVICE_ID); + editor.apply(); + return null; + }); + // wait for up to 5 seconds for the fingerprint to be generated (ANDROIDACY_DEVICE_ID to be set) + long startTime = System.currentTimeMillis(); + 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 - String deviceModel = android.os.Build.MODEL; - String deviceManufacturer = android.os.Build.MANUFACTURER; - 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); + if (ANDROIDACY_DEVICE_ID == null) { + // fingerprint generation failed, use a random UUID + ANDROIDACY_DEVICE_ID = UUID.randomUUID().toString(); } - // Save it to shared preferences - 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(); + return ANDROIDACY_DEVICE_ID; } } @@ -155,8 +139,9 @@ public final class AndroidacyRepoData extends RepoData { try { byte[] resp = Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId + "&client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID, false); // response is JSON - JSONObject jsonObject = new JSONObject(Arrays.toString(resp)); + JSONObject jsonObject = new JSONObject(new String(resp)); memberLevel = jsonObject.getString("role"); + Timber.d("Member level: %s", memberLevel); JSONArray memberPermissions = jsonObject.getJSONArray("permissions"); // set role and permissions on userInfo property userInfo = new String[][]{{"role", memberLevel}, {"permissions", String.valueOf(memberPermissions)}}; @@ -252,7 +237,7 @@ public final class AndroidacyRepoData extends RepoData { try { Timber.i("Requesting new token..."); // 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 try { JSONObject jsonObject = new JSONObject(token); @@ -261,6 +246,7 @@ public final class AndroidacyRepoData extends RepoData { //noinspection SuspiciousRegexArgument Timber.d("Token: %s", token.substring(0, token.length() - 4).replaceAll(".", "*") + token.substring(token.length() - 4)); memberLevel = jsonObject.getString("role"); + Timber.d("Member level: %s", memberLevel); } catch (JSONException e) { Timber.e(e, "Failed to parse token"); // Show a toast diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java index d8ec576..55e9682 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java @@ -133,6 +133,6 @@ public enum AndroidacyUtil { if (md == null) { return null; } - return Arrays.toString(md); + return new String(md); } } diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java index 8df7c39..e6ca625 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java @@ -153,34 +153,28 @@ public class BackgroundUpdateChecker extends Worker { } RepoModule repoModule = repoModules.get(localModuleInfo.id); localModuleInfo.checkModuleUpdate(); - String remoteVersion = localModuleInfo.updateVersion; String remoteVersionCode = String.valueOf(localModuleInfo.updateVersionCode); if (repoModule != null) { - remoteVersion = repoModule.moduleInfo.version; remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode); } - // now, coerce everything into an int int localVersionCode = Integer.parseInt(String.valueOf(localModuleInfo.versionCode)); int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode); - // we also have to match the version name - int localVersion = Integer.parseInt(localModuleInfo.version); - int remoteVersionInt = Integer.parseInt(remoteVersion); - int wantsVersion = Integer.parseInt(version.split(":")[1]); + int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", "")); // 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 (version.startsWith("^")) { // this version and newer - if (wantsVersion > localVersion || wantsVersion > remoteVersionInt || wantsVersion > remoteVersionCodeInt || wantsVersion < localVersionCode) { + if (wantsVersion <= remoteVersionCodeInt || wantsVersion <= localVersionCode) { // if it is, we skip it continue; } } else if (version.endsWith("$")) { // this version and older - if (wantsVersion < localVersion || wantsVersion < remoteVersionInt || wantsVersion < remoteVersionCodeInt || wantsVersion > localVersionCode) { + if (wantsVersion >= remoteVersionCodeInt || wantsVersion >= localVersionCode) { // if it is, we skip it continue; } - } else if (wantsVersion == localVersion || wantsVersion == remoteVersionInt || wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { + } else if (wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { // if it is, we skip it continue; } diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleHolder.java b/app/src/main/java/com/fox2code/mmm/module/ModuleHolder.java index 3557999..5bf9ec4 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ModuleHolder.java +++ b/app/src/main/java/com/fox2code/mmm/module/ModuleHolder.java @@ -152,34 +152,29 @@ public final class ModuleHolder implements Comparable { // get the one matching version = stringSet.stream().filter(s -> s.startsWith(moduleInfo.id)).findFirst().orElse(""); } - String remoteVersion = moduleInfo.updateVersion; String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode); if (repoModule != null) { - remoteVersion = repoModule.moduleInfo.version; remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode); } // now, coerce everything into an int int localVersionCode = Integer.parseInt(String.valueOf(moduleInfo.versionCode)); int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode); - // we also have to match the version name - int localVersion = Integer.parseInt(moduleInfo.version); - int remoteVersionInt = Integer.parseInt(remoteVersion); - int wantsVersion = Integer.parseInt(version.split(":")[1]); + int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", "")); // 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 (version.startsWith("^")) { // this version and newer - if (wantsVersion > localVersion || wantsVersion > remoteVersionInt || wantsVersion > remoteVersionCodeInt || wantsVersion < localVersionCode) { + if (wantsVersion <= remoteVersionCodeInt || wantsVersion <= localVersionCode) { // if it is, we skip it ignoreUpdate = true; } } else if (version.endsWith("$")) { // this version and older - if (wantsVersion < localVersion || wantsVersion < remoteVersionInt || wantsVersion < remoteVersionCodeInt || wantsVersion > localVersionCode) { + if (wantsVersion >= remoteVersionCodeInt || wantsVersion >= localVersionCode) { // if it is, we skip it ignoreUpdate = true; } - } else if (wantsVersion == localVersion || wantsVersion == remoteVersionInt || wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { + } else if (wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) { // if it is, we skip it ignoreUpdate = true; } diff --git a/app/src/main/java/com/fox2code/mmm/repo/CustomRepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/CustomRepoManager.java index 6faed56..d1631d7 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/CustomRepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/CustomRepoManager.java @@ -75,7 +75,7 @@ public class CustomRepoManager { // parse json JSONObject jsonObject; try { - jsonObject = new JSONObject(Arrays.toString(json)); + jsonObject = new JSONObject(new String(json)); } catch (Exception e) { Timber.e(e, "Failed to parse json from repo"); return null; diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index c5c33a6..43db237 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -671,8 +671,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { EditText editText = (EditText) layout.getChildAt(i); String text = editText.getText().toString(); if (!text.isEmpty()) { - // text cannot contain a colon because we use that to split id and version - text = text.replace(":", ""); + // text can only contain numbers + text = text.replaceAll("[^0-9]", ""); // 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); 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 LongClickablePreference pref_donate_androidacy = findPreference("pref_donate_androidacy"); if (!BuildConfig.FLAVOR.equals("play")) { - pref_donate_androidacy.setOnPreferenceClickListener(p -> { - // copy FOX2CODE promo code to clipboard and toast user that they can use it for half off any subscription - String toastText = requireContext().getString(R.string.promo_code_copied); - clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "FOX2CODE")); - Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); - // open androidacy - 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 -> { - // copy to clipboard - String toastText = requireContext().getString(R.string.link_copied); - clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate")); - Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); - return true; - }); + if (Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) { + pref_donate_androidacy.setOnPreferenceClickListener(p -> { + // copy FOX2CODE promo code to clipboard and toast user that they can use it for half off any subscription + String toastText = requireContext().getString(R.string.promo_code_copied); + clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "FOX2CODE")); + Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); + // open androidacy + 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 -> { + // copy to clipboard + String toastText = requireContext().getString(R.string.link_copied); + clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate")); + 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 { pref_donate_androidacy.setVisible(false); } diff --git a/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt index 741df11..9a7fedf 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt @@ -219,4 +219,35 @@ class RuntimeUtils { snackbar.show() 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") + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27407b7..d3d9124 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -419,8 +419,12 @@ Ignore specific versions when checking for module updates. Disabling update checks per-module above overrides this setting! Exclude version(s) from update checks Exclude version(s) of modules from checks - 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. + 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. "Version: " Remote version - Version(s) to ignore + Version code to ignore + 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. + You\'ve already upgraded! + Androidacy Premium offers faster downloads, an ad-free experience, and more! + Upgrade