Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/277/head
androidacy-user 1 year ago
parent 7865b62255
commit 5499ab0b43

@ -298,12 +298,6 @@ dependencies {
// Icons
// implementation "com.mikepenz:iconics-core:3.2.5"
//implementation "androidx.appcompat:appcompat:${versions.appCompat}"
// db
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
if (hasSentryConfig) {

@ -13,6 +13,7 @@ import android.os.Build;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@ -328,6 +329,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
}
if (!isOfficial) {
Log.w("MainApplication", "This is not an official build of FoxMMM. This warning can be safely ignored if this is expected, otherwise you may be running an untrusted build.");
// Show a toast to warn the user
Toast.makeText(this, R.string.not_official_build, Toast.LENGTH_LONG).show();
}
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences();
// We are only one process so it's ok to do this

@ -12,9 +12,9 @@ import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.module.ModuleViewListBuilder;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.IntentHelper;
import java.io.BufferedReader;
import java.io.File;
@ -29,7 +29,7 @@ interface NotificationTypeCst {
}
public enum NotificationType implements NotificationTypeCst {
DEBUG(R.string.debug_build, R.drawable.ic_baseline_bug_report_24) {
DEBUG(R.string.debug_build, R.drawable.ic_baseline_bug_report_24, R.attr.colorSecondary, R.attr.colorOnSecondary) {
@Override
public boolean shouldRemove() {
return !BuildConfig.DEBUG;
@ -154,7 +154,9 @@ public enum NotificationType implements NotificationTypeCst {
public static boolean needPatch(File target) throws IOException {
try (ZipFile zipFile = new ZipFile(target)) {
boolean validEntries = zipFile.getEntry("module.prop") == null && zipFile.getEntry("anykernel.sh") == null && zipFile.getEntry("META-INF/com/google/android/magisk/module.prop") == null;
boolean validEntries = zipFile.getEntry("module.prop") != null;
// ensure there's no anykernel.sh
validEntries &= zipFile.getEntry("anykernel.sh") == null;
if (validEntries) {
// Ensure id of module is not empty and matches ^[a-zA-Z][a-zA-Z0-9._-]+$ regex
// We need to get the module.prop and parse the id= line

@ -19,11 +19,11 @@ import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.XRepo;
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.utils.SyncManager;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.Hashes;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.SyncManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
@ -36,25 +36,18 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
public final class RepoManager extends SyncManager {
public static final String MAGISK_REPO =
"https://raw.githubusercontent.com/Magisk-Modules-Repo/submission/modules/modules.json";
public static final String MAGISK_REPO = "https://raw.githubusercontent.com/Magisk-Modules-Repo/submission/modules/modules.json";
public static final String MAGISK_REPO_HOMEPAGE = "https://github.com/Magisk-Modules-Repo";
public static final String MAGISK_ALT_REPO =
"https://raw.githubusercontent.com/Magisk-Modules-Alt-Repo/json/main/modules.json";
public static final String MAGISK_ALT_REPO_HOMEPAGE =
"https://github.com/Magisk-Modules-Alt-Repo";
public static final String MAGISK_ALT_REPO_JSDELIVR =
"https://cdn.jsdelivr.net/gh/Magisk-Modules-Alt-Repo/json@main/modules.json";
public static final String ANDROIDACY_MAGISK_REPO_ENDPOINT =
"https://production-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT =
"https://staging-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE =
"https://www.androidacy.com/modules-repo";
public static final String MAGISK_ALT_REPO = "https://raw.githubusercontent.com/Magisk-Modules-Alt-Repo/json/main/modules.json";
public static final String MAGISK_ALT_REPO_HOMEPAGE = "https://github.com/Magisk-Modules-Alt-Repo";
public static final String MAGISK_ALT_REPO_JSDELIVR = "https://cdn.jsdelivr.net/gh/Magisk-Modules-Alt-Repo/json@main/modules.json";
public static final String ANDROIDACY_MAGISK_REPO_ENDPOINT = "https://production-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT = "https://staging-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE = "https://www.androidacy.com/modules-repo";
private static final String TAG = "RepoManager";
private static final String MAGISK_REPO_MANAGER =
"https://magisk-modules-repo.github.io/submission/modules.json";
private static final String MAGISK_REPO_MANAGER = "https://magisk-modules-repo.github.io/submission/modules.json";
private static final Object lock = new Object();
private static final double STEP1 = 0.1D;
private static final double STEP2 = 0.8D;
@ -78,18 +71,17 @@ public final class RepoManager extends SyncManager {
this.modules = new HashMap<>();
// We do not have repo list config yet.
this.androidacyRepoData = this.addAndroidacyRepoData();
RepoData altRepo = this.addRepoData(
MAGISK_ALT_REPO, "Magisk Modules Alt Repo");
RepoData altRepo = this.addRepoData(MAGISK_ALT_REPO, "Magisk Modules Alt Repo");
altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE;
altRepo.defaultSubmitModule =
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues";
altRepo.defaultSubmitModule = "https://github.com/Magisk-Modules-Alt-Repo/submission/issues";
this.customRepoManager = new CustomRepoManager(mainApplication, this);
XHooks.onRepoManagerInitialize();
// Populate default cache
boolean x = false;
for (RepoData repoData : this.repoData.values()) {
if (repoData == this.androidacyRepoData) {
if (x) return;
if (x)
return;
x = true;
}
this.populateDefaultCache(repoData);
@ -140,8 +132,7 @@ public final class RepoManager extends SyncManager {
case ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT:
return "androidacy_repo";
default:
return "repo_" + Hashes.hashSha256(
url.getBytes(StandardCharsets.UTF_8));
return "repo_" + Hashes.hashSha256(url.getBytes(StandardCharsets.UTF_8));
}
}
@ -162,8 +153,7 @@ public final class RepoManager extends SyncManager {
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isAndroidacyRepoEnabled() {
return INSTANCE != null && INSTANCE.androidacyRepoData != null &&
INSTANCE.androidacyRepoData.isEnabled();
return INSTANCE != null && INSTANCE.androidacyRepoData != null && INSTANCE.androidacyRepoData.isEnabled();
}
@SuppressWarnings("StatementWithEmptyBody")
@ -187,7 +177,8 @@ public final class RepoManager extends SyncManager {
}
public RepoData get(String url) {
if (url == null) return null;
if (url == null)
return null;
if (MAGISK_ALT_REPO_JSDELIVR.equals(url)) {
url = MAGISK_ALT_REPO;
}
@ -205,8 +196,7 @@ public final class RepoManager extends SyncManager {
synchronized (this.syncLock) {
repoData = this.repoData.get(url);
if (repoData == null) {
if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) ||
ANDROIDACY_MAGISK_REPO_ENDPOINT.equals(url)) {
if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) || ANDROIDACY_MAGISK_REPO_ENDPOINT.equals(url)) {
//noinspection ReplaceNullCheck
if (this.androidacyRepoData != null) {
return this.androidacyRepoData;
@ -233,23 +223,56 @@ public final class RepoManager extends SyncManager {
RepoData[] repoDatas = new LinkedHashSet<>(this.repoData.values()).toArray(new RepoData[0]);
RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length];
int moduleToUpdate = 0;
this.hasInternet = false;
// Check if we have internet connection
// Attempt to contact connectivitycheck.gstatic.com/generate_204
// If we can't, we don't have internet connection
try {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Checking internet connection...");
}
HttpURLConnection urlConnection = (HttpURLConnection) new URL("https://networkcheck.kde.org").openConnection();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Opened connection to " + urlConnection.getURL());
}
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setReadTimeout(1000);
urlConnection.setUseCaches(false);
urlConnection.getInputStream().close();
// should return a 200 and the content should be "OK"
if (urlConnection.getResponseCode() == 200 && urlConnection.getContentLength() == 3) {
if (BuildConfig.DEBUG)
Log.i("RepoManager", "Internet connection detected");
this.hasInternet = true;
} else {
if (BuildConfig.DEBUG)
Log.w("RepoManager", "Internet connection not detected");
}
} catch (
IOException e) {
Log.e(TAG, "Failed to check internet connection", e);
}
for (int i = 0; i < repoDatas.length; i++) {
if (BuildConfig.DEBUG) Log.d("RepoManager", "Fetching: " + repoDatas[i].getName());
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Preparing to fetch: " + repoDatas[i].getName());
moduleToUpdate += (repoUpdaters[i] = new RepoUpdater(repoDatas[i])).fetchIndex();
updateListener.update(STEP1 / repoDatas.length * (i + 1));
}
if (BuildConfig.DEBUG) Log.d("RepoManag3er", "Updating meta-data");
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Updating meta-data");
int updatedModules = 0;
boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter();
for (int i = 0; i < repoUpdaters.length; i++) {
// Check if the repo is enabled
if (!repoUpdaters[i].repoData.isEnabled()) {
if (BuildConfig.DEBUG) Log.d("RepoManager", "Skipping disabled repo: " + repoUpdaters[i].repoData.getName());
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Skipping disabled repo: " + repoUpdaters[i].repoData.getName());
continue;
}
List<RepoModule> repoModules = repoUpdaters[i].toUpdate();
RepoData repoData = repoDatas[i];
if (BuildConfig.DEBUG) Log.d("RepoManager", "Registering " + repoData.getName());
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Registering " + repoData.getName());
for (RepoModule repoModule : repoModules) {
try {
if (repoModule.propUrl != null && !repoModule.propUrl.isEmpty()) {
@ -269,7 +292,8 @@ public final class RepoManager extends SyncManager {
this.modules.put(repoModule.id, repoModule);
}
}
} catch (Exception e) {
} catch (
Exception e) {
Log.e(TAG, "Failed to get \"" + repoModule.id + "\" metadata", e);
}
updatedModules++;
@ -290,32 +314,18 @@ public final class RepoManager extends SyncManager {
}
}
}
if (BuildConfig.DEBUG) Log.d("RepoManager", "Finishing update");
this.hasInternet = false;
// Check if we have internet connection
// Attempt to contact connectivitycheck.gstatic.com/generate_204
// If we can't, we don't have internet connection
try {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(
"https://connectivitycheck.gstatic.com/generate_204").openConnection();
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setReadTimeout(1000);
urlConnection.setUseCaches(false);
urlConnection.getInputStream().close();
if (urlConnection.getResponseCode() == 204 && urlConnection.getContentLength() == 0) {
this.hasInternet = true;
}
} catch (IOException e) {
Log.e(TAG, "Failed to check internet connection", e);
}
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Finishing update");
if (hasInternet) {
for (int i = 0; i < repoDatas.length; i++) {
// If repo is not enabled, skip
if (!repoDatas[i].isEnabled()) {
if (BuildConfig.DEBUG) Log.d("RepoManager", "Skipping " + repoDatas[i].getName() + " because it's disabled");
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Skipping " + repoDatas[i].getName() + " because it's disabled");
continue;
}
if (BuildConfig.DEBUG) Log.d("RepoManager", "Finishing: " + repoUpdaters[i].repoData.getName());
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Finishing: " + repoUpdaters[i].repoData.getName());
this.repoLastSuccess = repoUpdaters[i].finish();
if (!this.repoLastSuccess) {
Log.e(TAG, "Failed to update " + repoUpdaters[i].repoData.getName());
@ -371,11 +381,8 @@ public final class RepoManager extends SyncManager {
private RepoData addRepoData(String url, String fallBackName) {
String id = internalIdOfUrl(url);
File cacheRoot = new File(this.mainApplication.getCacheDir(), id);
SharedPreferences sharedPreferences = this.mainApplication
.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE);
RepoData repoData = id.startsWith("repo_") ?
new CustomRepoData(url, cacheRoot, sharedPreferences) :
new RepoData(url, cacheRoot, sharedPreferences);
SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE);
RepoData repoData = id.startsWith("repo_") ? new CustomRepoData(url, cacheRoot, sharedPreferences) : new RepoData(url, cacheRoot, sharedPreferences);
if (fallBackName != null && !fallBackName.isEmpty()) {
repoData.defaultName = fallBackName;
if (repoData instanceof CustomRepoData) {
@ -399,10 +406,8 @@ public final class RepoManager extends SyncManager {
private AndroidacyRepoData addAndroidacyRepoData() {
File cacheRoot = new File(this.mainApplication.getCacheDir(), "androidacy_repo");
SharedPreferences sharedPreferences = this.mainApplication
.getSharedPreferences("mmm_androidacy_repo", Context.MODE_PRIVATE);
AndroidacyRepoData repoData = new AndroidacyRepoData(cacheRoot,
sharedPreferences, MainApplication.isAndroidacyTestMode());
SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_androidacy_repo", Context.MODE_PRIVATE);
AndroidacyRepoData repoData = new AndroidacyRepoData(cacheRoot, sharedPreferences, MainApplication.isAndroidacyTestMode());
this.repoData.put(ANDROIDACY_MAGISK_REPO_ENDPOINT, repoData);
this.repoData.put(ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT, repoData);
return repoData;

@ -27,6 +27,12 @@ public class RepoUpdater {
}
public int fetchIndex() {
if (!RepoManager.getINSTANCE().hasConnectivity()) {
this.indexRaw = null;
this.toUpdate = Collections.emptyList();
this.toApply = Collections.emptySet();
return 0;
}
if (!this.repoData.isEnabled()) {
this.indexRaw = null;
this.toUpdate = Collections.emptyList();

@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.EditTextPreference;
@ -58,9 +59,9 @@ import com.fox2code.mmm.repo.CustomRepoManager;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.ProcessHelper;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.sentry.SentryMain;
import com.fox2code.rosettax.LanguageActivity;
import com.fox2code.rosettax.LanguageSwitcher;
@ -73,7 +74,11 @@ import com.topjohnwu.superuser.internal.UiThreadHandler;
import org.json.JSONException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
@ -524,6 +529,54 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
return true;
});
// pref_fox2code_thanks should lead to https://github.com/Fox2Code
linkClickable = findPreference("pref_fox2code_thanks");
linkClickable.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(p.getContext(), "https://github.com/Fox2Code");
return true;
});
linkClickable.setOnPreferenceLongClickListener(p -> {
String toastText = requireContext().getString(R.string.link_copied);
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code"));
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
return true;
});
// handle pref_save_logs which saves logs to our external storage and shares them
Preference saveLogs = findPreference("pref_save_logs");
saveLogs.setOnPreferenceClickListener(p -> {
// Save logs to external storage
File logsFile = new File(requireContext().getExternalFilesDir(null), "logs.txt");
try {
//noinspection ResultOfMethodCallIgnored
logsFile.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(logsFile);
// first, write some info about the device
fileOutputStream.write(("FoxMagiskModuleManager version: " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")\n").getBytes());
fileOutputStream.write(("Android version: " + Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")\n").getBytes());
fileOutputStream.write(("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")\n").getBytes());
fileOutputStream.write(("Magisk version: " + InstallerInitializer.peekMagiskVersion() + "\n").getBytes());
fileOutputStream.write(("Has internet: " + (RepoManager.getINSTANCE().hasConnectivity() ? "Yes" : "No") + "\n").getBytes());
// read our logcat but format the output to be more readable
Process process = Runtime.getRuntime().exec("logcat -d -v tag");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
fileOutputStream.write((line + "\n").getBytes());
}
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT).show();
return true;
}
// Share logs
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(requireContext(), BuildConfig.APPLICATION_ID + ".file-provider", logsFile));
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_logs)));
return true;
});
// pref_contributors should lead to the contributors page
linkClickable = findPreference("pref_contributors");
linkClickable.setOnPreferenceClickListener(p -> {

@ -1,6 +1,7 @@
package com.fox2code.mmm.utils;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
@ -100,6 +101,13 @@ public class ZipFileOpener extends FoxActivity {
zip = new ZipFile(zipFile);
if ((entry = zip.getEntry("module.prop")) == null) {
Log.e("ZipFileOpener", "onCreate: Zip file is not a valid magisk module");
if (BuildConfig.DEBUG) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d("ZipFileOpener", "onCreate: Zip file contents: " + zip.stream().map(ZipEntry::getName).reduce((a, b) -> a + ", " + b).orElse("empty"));
} else {
Log.d("ZipFileOpener", "onCreate: Zip file contents cannot be listed on this version of android");
}
}
runOnUiThread(() -> {
Toast.makeText(this, R.string.invalid_format, Toast.LENGTH_LONG).show();
finishAndRemoveTask();

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:height="24dp" android:autoMirrored="true"
android:tint="?attr/colorControlNormal" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M17,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.7,0 -3,-1.3 -3,-3s1.3,-3 3,-3 3,1.3 3,3 -1.3,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>

@ -1,5 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:height="24dp" android:autoMirrored="true"
android:tint="#000000" android:viewportWidth="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M16,18v2H8v-2H16zM11,8V16h2V8h3L12,4L8,8H11z"/>
</vector>

@ -283,5 +283,5 @@
<string name="setup_theme_title">Choose theme</string>
<string name="setup_language_button">Choose language</string>
<string name="title_activity_setup">Setup Wizard</string>
<string name="action_settings">Settings</string><string name="repo_update_failed_message">The following repos have failed to update:\\n\\n%s</string><string name="reset_api_key">Reset API keys</string><string name="upgrade_androidacy_promo">Upgrade to premium</string><string name="upgrade_androidacy_promo_desc">Upgrading to premium will remove ads, captchas, and downloads for the Androidacy Repository, and support Androidacy and the module authors.</string>
<string name="action_settings">Settings</string><string name="repo_update_failed_message">The following repos have failed to update:\\n\\n%s</string><string name="reset_api_key">Reset API keys</string><string name="upgrade_androidacy_promo">Upgrade to premium</string><string name="upgrade_androidacy_promo_desc">Upgrading to premium will remove ads, captchas, and downloads for the Androidacy Repository, and support Androidacy and the module authors.</string><string name="pref_category_contributors">Contributors</string><string name="fox2code_thanks_desc">Fox2Code is the original creator of the app. Without him, this would have never been possible.</string><string name="fox2code_thanks">Created by Fox2Code</string><string name="save_logs">Save logs to storage and share</string><string name="error_saving_logs">Could not save logs</string><string name="share_logs">Share FoxMMM logs</string><string name="not_official_build">Caution: You are running an unofficial and unsupported FoxMMM build.</string>
</resources>

@ -154,6 +154,12 @@
app:key="pref_update"
app:singleLineTitle="false"
app:title="@string/app_update" />
<!-- Save logs -->
<Preference
app:icon="@drawable/baseline_save_24"
app:key="pref_save_logs"
app:singleLineTitle="false"
app:title="@string/save_logs" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_bug_report_24"
app:key="pref_report_bug"
@ -174,6 +180,8 @@
app:key="pref_show_licenses"
app:singleLineTitle="false"
app:title="@string/show_licenses" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/pref_category_contributors">
<!-- Small lil thanks to Androidacy -->
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/baseline_favorite_24"
@ -181,6 +189,13 @@
app:singleLineTitle="false"
app:summary="@string/androidacy_thanks_desc"
app:title="@string/androidacy_thanks" />
<!-- Small lil thanks to Fox2Code -->
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/baseline_favorite_24"
app:key="pref_fox2code_thanks"
app:singleLineTitle="false"
app:summary="@string/fox2code_thanks_desc"
app:title="@string/fox2code_thanks" />
<!-- OKay, so we'll thank all the other contributors too -->
<com.fox2code.mmm.settings.LongClickablePreference
android:textAppearance="?android:attr/textAppearanceSmall"

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<!-- Share folder under internal cache folder. The base folder is context.getCacheDir() -->
<cache-path name="internal_cache" path="/" />
<!-- external storage -->
<external-path name="external_files" path="." />
</paths>
Loading…
Cancel
Save