Compare commits

...

20 Commits

Author SHA1 Message Date
androidacy-user 253ccb9127 final tweaks v2
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
Weblate 2e1ff92e63 Update multiple translations
Co-authored-by: Francesco Procaccini <fra.procaccc@gmail.com>
1 year ago
androidacy-user c47d9dddf4 final tweaks
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user fd8f746a22 update deps + release v2.0.2
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user eae09bf812 hide search on scroll down
fixes #313

Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
Weblate b9986b30c7 Update multiple translations
Co-authored-by: PhSnake Lawyer <phsnake78@gmail.com>
1 year ago
Weblate f75da22155 Update multiple translations
Co-authored-by: Lim Xiang Yann <xiangyann@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Олександр Зімін <msoftdogsua@gmail.com>
1 year ago
androidacy-user 7987aea230 fix online repo
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user 3434c2202b tweak settings
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user c46b50e1ef various fixes
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user 4111b91fe2 improve security
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user ec9da3adf8 fix some
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user d53eeff6d4 fix path
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user 6c54d19bf7 define version
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user f579aabde5 define version
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user cb277f70ca Update deps workflow
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user 9e818241a1 Add deps workflow
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
androidacy-user 6f0b8ac689 Fix actions builds
Signed-off-by: androidacy-user <opensource@androidacy.com>
1 year ago
Androidacy Service Account 5a38e9ad71
Update FUNDING.yml
Signed-off-by: Androidacy Service Account <opensource@androidacy.com>
1 year ago
Androidacy Service Account f64995055e
Add CodeQL
Signed-off-by: Androidacy Service Account <opensource@androidacy.com>
1 year ago

@ -1 +1,2 @@
custom: ["https://www.paypal.com/paypalme/fox2code"]
patreon: Androidacy
custom: ["https://www.androidacy.com/membership-join/#utm_source=github&utm_medium=web&utm_campaign=ghsponsors"]

@ -44,6 +44,7 @@ jobs:
caches
notifications
jdks
${{ github.workspace }}/.gradle/configuration-cache
- name: Change wrapper permissions
run: chmod +x ./gradlew

@ -0,0 +1,94 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request: # The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '24 17 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Set up Java 19
uses: actions/setup-java@v3
with:
java-version: 19
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-includes: |
caches
notifications
jdks
${{ github.workspace }}/.gradle/configuration-cache
- name: Change wrapper permissions
run: chmod +x ./gradlew
- name: Build apk debug
run: ./gradlew app:assembleDefaultDebug
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

@ -0,0 +1,39 @@
name: Dependencies
on:
push:
branches:
- master # run the action on your projects default branch
pull_request:
branches:
- master # run the action on your projects default branch
jobs:
build:
name: Dependencies
runs-on: ubuntu-latest
permissions: # The Dependency Submission API requires write permission
contents: write
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: Set up JDK 19
uses: actions/setup-java@v3
with:
java-version: 19
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Change wrapper permissions
run: chmod +x ./gradlew
- name: Run snapshot action
uses: mikepenz/gradle-dependency-submission@v1
with:
gradle-project-path: "."
gradle-build-module: ":app"

@ -2,6 +2,8 @@
### Developed by Androidacy. Find us on the web [here](https://www.androidacy.com/?utm_source=fox-readme&utm_medium=web&utm_campagin=github).
_If you're seeing this at the Fox2Code repo, the new repo is at [Androidacy/AndroidacyModuleManager](https://github.com/Androidacy/AndroidacyModuleManager)! The old repo may not receive consistent updates anymore!_
## About
The official Magisk Manager app has dropped it's support for downloading online modules, leaving users without a way to easily search for and download them. This app was created to help users download and install modules, and manage their own modules.
@ -10,8 +12,6 @@ The official Magisk Manager app has dropped it's support for downloading online
**The modules shown in this app are not affiliated with this app or Magisk**.
_If you're seeing this at the Fox2Code repo, the new repo is at [Androidacy/AndroidacyModuleManager](https://github.com/Androidacy/AndroidacyModuleManager)!_
## Features
- Download and install modules
- Manage your own modules
@ -134,7 +134,7 @@ translations: [https://translate.nift4.org/engage/foxmmm/](https://translate.nif
### GitHub method (advanced users)
See [`app/src/main/res/values/strings.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/strings.xml)
See [`app/src/main/res/values/strings.xml`](https://github.com/Androidacy/MagiskModuleManager/blob/master/app/src/main/res/values/strings.xml)
If your language is right to left don't forget to set `lang_support_rtl` to `true`.

@ -38,13 +38,13 @@ android {
applicationId = "com.fox2code.mmm"
minSdk = 24
targetSdk = 33
versionCode = 68
versionName = "2.0.1"
versionCode = 70
versionName = "2.0.2"
vectorDrawables {
useSupportLibrary = true
}
multiDexEnabled = true
resourceConfigurations.addAll(listOf("ar", "bs", "de", "es-rMX", "fr", "hu", "id", "ja", "nl", "pl", "pt", "pt-rBR", "ro", "ru", "tr", "uk", "zh", "zh-rTW", "en"))
resourceConfigurations.addAll(listOf("ar", "bs", "cs", "de", "es-rMX", "fr", "hu", "id", "ja", "nl", "pl", "pt", "pt-rBR", "ro", "ru", "tr", "uk", "zh", "zh-rTW", "en"))
}
splits {
@ -55,8 +55,7 @@ android {
// Enables building multiple APKs per ABI.
isEnable = true
// By default all ABIs are included, so use reset() and include to specify that you only
// want APKs for x86 and x86_64.
// By default all ABIs are included, so use reset()
// Resets the list of ABIs for Gradle to create APKs for to none.
reset()
@ -330,7 +329,7 @@ sentry {
autoInstallation {
enabled.set(true)
sentryVersion.set("6.17.0")
sentryVersion.set("6.18.1")
}
includeDependenciesReport.set(true)
@ -414,7 +413,7 @@ dependencies {
// implementation("com.google.protobuf:protobuf-javalite:3.22.2")
// google guava, maybe fix a bug
implementation("com.google.guava:guava:31.1-android")
implementation("com.google.guava:guava:31.1-jre")
val libsuVersion = "5.0.5"
@ -431,12 +430,12 @@ dependencies {
implementation("com.github.Fox2Code:AndroidANSI:1.0.1")
// sentry
implementation("io.sentry:sentry-android:6.17.0")
implementation("io.sentry:sentry-android-timber:6.17.0")
implementation("io.sentry:sentry-android-fragment:6.17.0")
implementation("io.sentry:sentry-android-okhttp:6.17.0")
implementation("io.sentry:sentry-kotlin-extensions:6.17.0")
implementation("io.sentry:sentry-android-ndk:6.17.0")
implementation("io.sentry:sentry-android:6.18.1")
implementation("io.sentry:sentry-android-timber:6.18.1")
implementation("io.sentry:sentry-android-fragment:6.18.1")
implementation("io.sentry:sentry-android-okhttp:6.18.1")
implementation("io.sentry:sentry-kotlin-extensions:6.18.1")
implementation("io.sentry:sentry-android-ndk:6.18.1")
// Markdown
// TODO: switch to an updated implementation

@ -188,13 +188,14 @@
}
# Keep all of Cronet API and google's internal classes
-keep class org.chromium.** { *; }
-keep class org.chromium.net.** { *; }
-keep class com.google.common.util.concurrent.** { *; }
-keepclassmembers class kotlin.SafePublicationLazyImpl {
java.lang.Object _value;
}
# fix bug with androidx work and future
-keep class androidx.work.impl.utils.futures.* { *; }
# Silence some warnings
-dontwarn android.os.SystemProperties
-dontwarn android.view.ThreadedRenderer

@ -28,7 +28,7 @@ public class AppUpdateManager {
public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080;
public static final int FLAG_COMPAT_MMT_REBORN = 0x0100;
public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200;
public static final String RELEASES_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases/latest";
public static final String RELEASES_API_URL = "https://api.github.com/repos/Androidacy/MagiskModuleManager/releases/latest";
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object();

@ -23,6 +23,8 @@ import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.Toast;
@ -46,6 +48,7 @@ import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.module.ModuleViewAdapter;
import com.fox2code.mmm.module.ModuleViewListBuilder;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.settings.SettingsActivity;
import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.io.net.Http;
@ -58,6 +61,8 @@ import com.google.android.material.snackbar.Snackbar;
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;
@ -68,6 +73,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
private static final int PRECISION = 10000;
public static boolean doSetupNowRunning = true;
public static boolean doSetupRestarting = false;
public static List<LocalModuleInfo> localModuleInfoList = new ArrayList<>();
public static List<RepoModule> onlineModuleInfoList = new ArrayList<>();
public final ModuleViewListBuilder moduleViewListBuilder;
public final ModuleViewListBuilder moduleViewListBuilderOnline;
public LinearProgressIndicator progressIndicator;
@ -173,6 +180,40 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState != RecyclerView.SCROLL_STATE_IDLE)
MainActivity.this.searchView.clearFocus();
// hide search view when scrolling
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
MainActivity.this.searchCard.animate().translationY(-MainActivity.this.searchCard.getHeight()).setInterpolator(new AccelerateInterpolator(2)).start();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// if the user scrolled up, show the search bar
if (dy < 0) {
MainActivity.this.searchCard.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
}
}
});
// same for online
this.moduleListOnline.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState != RecyclerView.SCROLL_STATE_IDLE)
MainActivity.this.searchView.clearFocus();
// hide search view when scrolling
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
MainActivity.this.searchCard.animate().translationY(-MainActivity.this.searchCard.getHeight()).setInterpolator(new AccelerateInterpolator(2)).start();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// if the user scrolled up, show the search bar
if (dy < 0) {
MainActivity.this.searchCard.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
}
}
});
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
@ -365,6 +406,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}
if (BuildConfig.DEBUG) Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
moduleViewListBuilder.applyTo(moduleListOnline, moduleViewAdapterOnline);
moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
// if moduleViewListBuilderOnline has the upgradeable notification, show a badge on the online repo nav item
if (MainApplication.getINSTANCE().modulesHaveUpdates) {
@ -525,6 +568,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
if (BuildConfig.DEBUG) Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
Timber.i("Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
Timber.i("Common After");
}
@ -588,9 +632,10 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
});
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
RepoManager.getINSTANCE().updateEnabledStates();
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendInstalledModules);
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
}, "Repo update thread").start();
}
@ -755,7 +800,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
if (BuildConfig.DEBUG) Timber.i("Checking if we need to run setup");
// Check if this is the first launch using prefs and if doSetupRestarting was passed in the intent
SharedPreferences prefs = MainApplication.getSharedPreferences("mmm");
boolean firstLaunch = !Objects.equals(prefs.getString("last_shown_setup", null), "v1");
boolean firstLaunch = !Objects.equals(prefs.getString("last_shown_setup", null), "v2");
// First launch
// this is intentionally separate from the above if statement, because it needs to be checked even if the first launch check is true due to some weird edge cases
if (getIntent().getBooleanExtra("doSetupRestarting", false)) {

@ -382,7 +382,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
@Override
public void onCreate() {
supportedLocales.addAll(Arrays.asList("ar", "bs", "de", "es-rMX", "fr", "hu", "id", "ja", "nl", "pl", "pt", "pt-rBR", "ro", "ru", "tr", "uk", "zh", "zh-rTW", "en"));
supportedLocales.addAll(Arrays.asList("ar", "bs", "cs", "de", "es-rMX", "fr", "hu", "id", "ja", "nl", "pl", "pt", "pt-rBR", "ro", "ru", "tr", "uk", "zh", "zh-rTW", "en"));
if (INSTANCE == null) INSTANCE = this;
relPackageName = this.getPackageName();
super.onCreate();

@ -99,7 +99,7 @@ public enum NotificationType implements NotificationTypeCst {
},
UPDATE_AVAILABLE(R.string.app_update_available, R.drawable.ic_baseline_system_update_24,
androidx.appcompat.R.attr.colorPrimary, com.google.android.material.R.attr.colorOnPrimary, v -> IntentHelper.openUrl(v.getContext(),
"https://github.com/Fox2Code/FoxMagiskModuleManager/releases"), false) {
"https://github.com/Androidacy/MagiskModuleManager/releases"), false) {
@Override
public boolean shouldRemove() {
return !AppUpdateManager.getAppUpdateManager().peekShouldUpdate();

@ -153,12 +153,9 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
// Set up the buttons
// Setup button
BottomNavigationItemView setupButton = view.findViewById(R.id.setup_finish);
// enable finish button when user scrolls to the bottom
findViewById(R.id.setupNestedScrollView).setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (scrollY > oldScrollY) {
setupButton.setEnabled(true);
}
});
// on clicking setup_agree_eula, enable the setup button if it's checked, if it's not, disable it
MaterialSwitch agreeEula = view.findViewById(R.id.setup_agree_eula);
agreeEula.setOnCheckedChangeListener((buttonView, isChecked) -> setupButton.setEnabled(isChecked));
setupButton.setOnClickListener(v -> {
Timber.i("Setup button clicked");
// get instance of editor
@ -200,7 +197,7 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
r.close();
Timber.d("Realm transaction committed");
});
editor.putString("last_shown_setup", "v1");
editor.putString("last_shown_setup", "v2");
// Commit the changes
editor.commit();
// sleep to allow the realm transaction to finish

@ -7,6 +7,8 @@ import androidx.annotation.Nullable;
import com.fox2code.mmm.BuildConfig;
import java.util.Objects;
public enum AndroidacyUtil {
;
public static final String REFERRER = "utm_source=FoxMMM&utm_medium=app";
@ -21,13 +23,13 @@ public enum AndroidacyUtil {
static boolean isAndroidacyLink(@NonNull String url, @NonNull Uri uri) {
int i; // Check both string and Uri to mitigate parse exploit
return url.startsWith("https://") && (i = url.indexOf("/", 8)) != -1 && url.substring(8, i).endsWith("api.androidacy.com") && uri.getHost().endsWith("api.androidacy.com");
return url.startsWith("https://") && (i = url.indexOf("/", 8)) != -1 && url.substring(8, i).endsWith("api.androidacy.com") && Objects.requireNonNull(uri.getHost()).endsWith("api.androidacy.com");
}
public static boolean isAndroidacyFileUrl(@Nullable String url) {
if (url == null)
return false;
for (String prefix : new String[]{"https://production-api.androidacy.com/magisk/file/", "https://staging-api.androidacy.com/magisk/file/"}) { // Make both staging and non staging act the same
for (String prefix : new String[]{"https://production-api.androidacy.com/downloads/", "https://production-api.androidacy.com/magisk/file/", "https://staging-api.androidacy.com/magisk/file/"}) { // Make both staging and non staging act the same
if (url.startsWith(prefix))
return true;
}

@ -64,6 +64,12 @@ public class AndroidacyWebAPI {
void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle, String checksum, boolean canInstall) {
if (BuildConfig.DEBUG)
Timber.d("ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall);
// moduleUrl should be a valid URL, i.e. in the androidacy.com domain
// if it is not, do not proceed
if (!AndroidacyUtil.isAndroidacyFileUrl(moduleUrl)) {
Timber.e("ModuleDialog, invalid URL: %s", moduleUrl);
return;
}
this.downloadMode = false;
RepoModule repoModule = AndroidacyRepoData.getInstance().moduleHashMap.get(installTitle);
String title, description;
@ -381,8 +387,10 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public String getAndroidacyModuleFile(String moduleId, String moduleFile) {
moduleId = moduleId.replaceAll("\\.", "").replaceAll("/", "");
if (moduleFile == null || this.consumedAction || !this.isAndroidacyModule(moduleId))
return "";
moduleFile = moduleFile.replaceAll("\\.", "").replaceAll("/", "");
File moduleFolder = new File("/data/adb/modules/" + moduleId);
File absModuleFile = new File(moduleFolder, moduleFile).getAbsoluteFile();
if (!absModuleFile.getPath().startsWith(moduleFolder.getPath()))
@ -401,6 +409,7 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public boolean setAndroidacyModuleMeta(String moduleId, String content) {
moduleId = moduleId.replaceAll("\\.", "").replaceAll("/", "");
if (content == null || this.consumedAction || !this.isAndroidacyModule(moduleId))
return false;
File androidacyMetaFile = new File("/data/adb/modules/" + moduleId + "/.androidacy");

@ -100,7 +100,7 @@ public class BackgroundUpdateChecker extends Worker {
// check if wifi is connected
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network networkInfo = connectivityManager.getActiveNetwork();
if (networkInfo == null || !connectivityManager.getNetworkCapabilities(networkInfo).hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
if (networkInfo == null || !Objects.requireNonNull(connectivityManager.getNetworkCapabilities(networkInfo)).hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
Timber.w("Background update check: wifi not connected but required");
return;
}
@ -136,7 +136,7 @@ public class BackgroundUpdateChecker extends Worker {
if ("twrp-keep".equals(localModuleInfo.id)) continue;
// exclude all modules with id's stored in the pref pref_background_update_check_excludes
try {
if (MainApplication.getSharedPreferences("mmm").getStringSet("pref_background_update_check_excludes", null).contains(localModuleInfo.id))
if (Objects.requireNonNull(MainApplication.getSharedPreferences("mmm").getStringSet("pref_background_update_check_excludes", null)).contains(localModuleInfo.id))
continue;
} catch (Exception ignored) {
}
@ -213,7 +213,7 @@ public class BackgroundUpdateChecker extends Worker {
public static void onMainActivityCreate(Context context) {
// Refuse to run if first_launch pref is not false
if (!Objects.equals(MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", null), "v1"))
if (!Objects.equals(MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", null), "v2"))
return;
// create notification channel group
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

@ -57,8 +57,8 @@ public final class ModuleManager extends SyncManager {
}
protected void scanInternal(@NonNull UpdateListener updateListener) {
// if last_shown_setup is not "v1", then refuse to continue
if (!MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "").equals("v1")) {
// if last_shown_setup is not "v2", then refuse to continue
if (!MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "").equals("v2")) {
return;
}
boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true);

@ -27,6 +27,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.matomo.sdk.extra.TrackHelper;
import java.util.Objects;
import io.noties.markwon.Markwon;
import timber.log.Timber;
@ -66,20 +68,24 @@ public enum ActionButtonType {
}, UPDATE_INSTALL() {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
int icon = moduleHolder.hasUpdate() ? R.drawable.ic_baseline_update_24 : R.drawable.ic_baseline_system_update_24;
button.setChipIcon(button.getContext().getDrawable(icon));
int icon;
if (moduleHolder.hasUpdate()) {
icon = R.drawable.ic_baseline_update_24;
button.setText(R.string.update);
} else if (moduleHolder.moduleInfo != null) {
icon = R.drawable.ic_baseline_refresh_24;
button.setText(R.string.reinstall);
} else {
icon = R.drawable.ic_baseline_system_update_24;
button.setText(R.string.install);
}
button.setChipIcon(button.getContext().getDrawable(icon));
}
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
if (moduleInfo == null)
return;
if (moduleInfo == null) return;
String name;
if (moduleHolder.moduleInfo != null) {
@ -89,8 +95,7 @@ public enum ActionButtonType {
}
TrackHelper.track().event("view_update_install", name).with(MainApplication.getINSTANCE().getTracker());
String updateZipUrl = moduleHolder.getUpdateZipUrl();
if (updateZipUrl == null)
return;
if (updateZipUrl == null) return;
// Androidacy manage the selection between download and install
if (AndroidacyUtil.isAndroidacyLink(updateZipUrl)) {
IntentHelper.openUrlAndroidacy(button.getContext(), updateZipUrl, true, moduleInfo.name, moduleInfo.config);
@ -132,7 +137,7 @@ public enum ActionButtonType {
}
}
if (markwon != null) {
TextView messageView = alertDialog.getWindow().findViewById(android.R.id.message);
TextView messageView = Objects.requireNonNull(alertDialog.getWindow()).findViewById(android.R.id.message);
markwon.setParsedMarkdown(messageView, (Spanned) desc);
}
}
@ -197,8 +202,7 @@ public enum ActionButtonType {
@Override
public void doAction(Chip button, ModuleHolder moduleHolder) {
String config = moduleHolder.getMainModuleConfig();
if (config == null)
return;
if (config == null) return;
String name;
if (moduleHolder.moduleInfo != null) {

@ -32,6 +32,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
public final Type separator;
public int footerPx;
public View.OnClickListener onClickListener;
public LocalModuleInfo moduleInfo;
public RepoModule repoModule;
public int filterLevel;
@ -71,30 +72,19 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
}
public ModuleInfo getMainModuleInfo() {
return this.repoModule != null && (this.moduleInfo == null ||
this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode)
? this.repoModule.moduleInfo : this.moduleInfo;
return this.repoModule != null && (this.moduleInfo == null || this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.moduleInfo : this.moduleInfo;
}
public String getUpdateZipUrl() {
return this.moduleInfo == null || (this.repoModule != null &&
this.moduleInfo.updateVersionCode <
this.repoModule.moduleInfo.versionCode) ?
this.repoModule.zipUrl : this.moduleInfo.updateZipUrl;
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.zipUrl : this.moduleInfo.updateZipUrl;
}
public String getUpdateZipRepo() {
return this.moduleInfo == null || (this.repoModule != null &&
this.moduleInfo.updateVersionCode <
this.repoModule.moduleInfo.versionCode) ?
this.repoModule.repoData.id : "update_json";
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.repoData.id : "update_json";
}
public String getUpdateZipChecksum() {
return this.moduleInfo == null || (this.repoModule != null &&
this.moduleInfo.updateVersionCode <
this.repoModule.moduleInfo.versionCode) ?
this.repoModule.checksum : this.moduleInfo.updateChecksum;
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.checksum : this.moduleInfo.updateChecksum;
}
public String getMainModuleName() {
@ -120,8 +110,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
public String getUpdateTimeText() {
if (this.repoModule == null) return "";
long timeStamp = this.repoModule.lastUpdated;
return timeStamp <= 0 ? "" :
MainApplication.formatTime(timeStamp);
return timeStamp <= 0 ? "" : MainApplication.formatTime(timeStamp);
}
public String getRepoName() {
@ -134,19 +123,18 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
}
public Type getType() {
Timber.d("Getting type for %s", this.moduleId);
if (this.footerPx != -1) {
Timber.i("Module %s is footer", this.moduleId);
return Type.FOOTER;
} else if (this.separator != null) {
Timber.i("Module %s is separator", this.moduleId);
return Type.SEPARATOR;
} else if (this.notificationType != null) {
Timber.i("Module %s is notification", this.moduleId);
return Type.NOTIFICATION;
} else if (this.moduleInfo == null) {
Timber.d("Module %s is not installed", this.moduleId);
return Type.INSTALLABLE;
} else if (this.moduleInfo.versionCode < this.moduleInfo.updateVersionCode ||
(this.repoModule != null && this.moduleInfo.versionCode <
this.repoModule.moduleInfo.versionCode)) {
} else if (this.moduleInfo.versionCode < this.moduleInfo.updateVersionCode || (this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode)) {
Timber.d("Module %s has update", this.moduleId);
MainApplication.getINSTANCE().modulesHaveUpdates = true;
if (!MainApplication.getINSTANCE().updateModules.contains(this.moduleId)) {
@ -157,7 +145,6 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
Timber.d("Module %s has update", this.moduleId);
return Type.UPDATABLE;
} else {
Timber.d("Module %s is installed", this.moduleId);
return Type.INSTALLED;
}
}
@ -165,8 +152,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
public Type getCompareType(Type type) {
if (this.separator != null) {
return this.separator;
} else if (this.notificationType != null &&
this.notificationType.special) {
} else if (this.notificationType != null && this.notificationType.special) {
return Type.SPECIAL_NOTIFICATIONS;
} else {
return type;
@ -174,11 +160,12 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
}
public boolean shouldRemove() {
return this.notificationType != null ? this.notificationType.shouldRemove() :
this.footerPx == -1 && this.moduleInfo == null &&
(this.repoModule == null || !this.repoModule.repoData.isEnabled() ||
(PropUtils.isLowQualityModule(this.repoModule.moduleInfo) &&
!MainApplication.isDisableLowQualityModuleFilter()));
// okay so this is quite possibly the hackiest fucking piece of code i've ever written
// basically, if we have a repomodule that has moduleinfo but no update, remove it-
if (this.repoModule != null && this.moduleInfo != null && !hasUpdate()) {
return true;
}
return this.notificationType != null ? this.notificationType.shouldRemove() : this.footerPx == -1 && this.moduleInfo == null && (this.repoModule == null || !this.repoModule.repoData.isEnabled() || (PropUtils.isLowQualityModule(this.repoModule.moduleInfo) && !MainApplication.isDisableLowQualityModuleFilter()));
}
public void getButtons(Context context, List<ActionButtonType> buttonTypeList, boolean showcaseMode) {
@ -194,8 +181,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
if (this.repoModule != null && this.repoModule.notesUrl != null) {
buttonTypeList.add(ActionButtonType.INFO);
}
if ((this.repoModule != null || (localModuleInfo != null &&
localModuleInfo.updateZipUrl != null))) {
if ((this.repoModule != null || (localModuleInfo != null && localModuleInfo.updateZipUrl != null))) {
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL);
}
String config = this.getMainModuleConfig();
@ -208,8 +194,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
XHooks.checkConfigTargetExists(context, pkg, config);
buttonTypeList.add(ActionButtonType.CONFIG);
} catch (PackageManager.NameNotFoundException e) {
Timber.w("Config package \"" + pkg +
"\" missing for module \"" + this.moduleId + "\"");
Timber.w("Config package \"" + pkg + "\" missing for module \"" + this.moduleId + "\"");
}
}
}
@ -232,8 +217,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
}
public boolean hasUpdate() {
return this.moduleInfo != null && this.repoModule != null &&
this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode;
return this.moduleInfo != null && this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode;
}
@Override
@ -244,29 +228,29 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
Type selfType = this.getCompareType(selfTypeReal);
Type otherType = o.getCompareType(otherTypeReal);
int compare = selfType.compareTo(otherType);
return compare != 0 ? compare :
selfTypeReal == otherTypeReal ?
selfTypeReal.compare(this, o) :
selfTypeReal.compareTo(otherTypeReal);
return compare != 0 ? compare : selfTypeReal == otherTypeReal ? selfTypeReal.compare(this, o) : selfTypeReal.compareTo(otherTypeReal);
}
@NonNull
@Override
public String toString() {
return "ModuleHolder{" + "moduleId='" + moduleId + '\'' + ", notificationType=" + notificationType + ", separator=" + separator + ", footerPx=" + footerPx + '}';
}
public enum Type implements Comparator<ModuleHolder> {
HEADER(R.string.loading, false, false),
SEPARATOR(R.string.loading, false, false) {
HEADER(R.string.loading, false, false), SEPARATOR(R.string.loading, false, false) {
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.separator.compareTo(o2.separator);
}
},
NOTIFICATION(R.string.loading, true, false) {
}, NOTIFICATION(R.string.loading, true, false) {
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.notificationType.compareTo(o2.notificationType);
}
},
UPDATABLE(R.string.updatable, true, true) {
}, UPDATABLE(R.string.updatable, true, true) {
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
@ -277,18 +261,15 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
}
},
INSTALLED(R.string.installed, true, true) {
}, INSTALLED(R.string.installed, true, true) {
// get stacktrace for debugging
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
return o1.getMainModuleNameLowercase()
.compareTo(o2.getMainModuleNameLowercase());
return o1.getMainModuleNameLowercase().compareTo(o2.getMainModuleNameLowercase());
}
},
SPECIAL_NOTIFICATIONS(R.string.loading, true, false),
INSTALLABLE(R.string.online_repo, true, true) {
}, SPECIAL_NOTIFICATIONS(R.string.loading, true, false), INSTALLABLE(R.string.online_repo, true, true) {
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
@ -299,8 +280,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
}
},
FOOTER(R.string.loading, false, false);
}, FOOTER(R.string.loading, false, false);
@StringRes
public final int title;
@ -327,15 +307,4 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
}
}
}
@NonNull
@Override
public String toString() {
return "ModuleHolder{" +
"moduleId='" + moduleId + '\'' +
", notificationType=" + notificationType +
", separator=" + separator +
", footerPx=" + footerPx +
'}';
}
}

@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.NotificationType;
import com.fox2code.mmm.installer.InstallerInitializer;
@ -84,6 +85,8 @@ public class ModuleViewListBuilder {
moduleManager.runAfterScan(() -> {
Timber.i("A1: %s", moduleManager.getModules().size());
for (LocalModuleInfo moduleInfo : moduleManager.getModules().values()) {
// add the local module to the list in MainActivity
MainActivity.localModuleInfoList.add(moduleInfo);
ModuleHolder moduleHolder = this.mappedModuleHolders.get(moduleInfo.id);
if (moduleHolder == null) {
this.mappedModuleHolders.put(moduleInfo.id,
@ -109,6 +112,8 @@ public class ModuleViewListBuilder {
Timber.i("A2: %s", repoManager.getModules().size());
boolean no32bitSupport = Build.SUPPORTED_32_BIT_ABIS.length == 0;
for (RepoModule repoModule : repoManager.getModules().values()) {
// add the remote module to the list in MainActivity
MainActivity.onlineModuleInfoList.add(repoModule);
// if repoData is null, something is wrong
if (repoModule.repoData == null) {
Timber.w("RepoData is null for module %s", repoModule.id);
@ -138,6 +143,14 @@ public class ModuleViewListBuilder {
moduleHolder = new ModuleHolder(repoModule.id));
}
moduleHolder.repoModule = repoModule;
// check if local module is installed
// iterate over MainActivity.localModuleInfoList until we hit the module with the same id
for (LocalModuleInfo localModuleInfo : MainActivity.localModuleInfoList) {
if (localModuleInfo.id.equals(repoModule.id)) {
moduleHolder.moduleInfo = localModuleInfo;
break;
}
}
}
});
}
@ -174,7 +187,7 @@ public class ModuleViewListBuilder {
return false;
}
public void applyTo(final RecyclerView moduleList,final ModuleViewAdapter moduleViewAdapter) {
public void applyTo(final RecyclerView moduleList, final ModuleViewAdapter moduleViewAdapter) {
if (this.updating) return;
this.updating = true;
ModuleManager.getINSTANCE().afterScan();

@ -410,7 +410,7 @@ public class RepoData extends XRepo {
long diffMinutes = diff / (60 * 1000) % 60;
Timber.d("Repo " + this.id + " updated: " + diffMinutes + " minutes ago");
realm.close();
return diffMinutes > (BuildConfig.DEBUG ? 15 : 20);
return diffMinutes > (BuildConfig.DEBUG ? 15 : 30);
} else {
Timber.d("Repo " + this.id + " should update could not find repo in database");
Timber.d("This is probably an error, please report this to the developer");

@ -149,8 +149,8 @@ public final class RepoManager extends SyncManager {
@SuppressWarnings("StatementWithEmptyBody")
private void populateDefaultCache(RepoData repoData) {
// if last_shown_setup is not "v1", them=n refuse to continue
if (!MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "").equals("v1")) {
// if last_shown_setup is not "v2", them=n refuse to continue
if (!MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "").equals("v2")) {
return;
}
// make sure repodata is not null

@ -627,7 +627,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
linkClickable.setOnPreferenceLongClickListener(p -> {
String toastText = requireContext().getString(R.string.link_copied);
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code/FoxMagiskModuleManager/releases/latest"));
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Androidacy/MagiskModuleManager/releases/latest"));
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
return true;
});
@ -646,12 +646,12 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
linkClickable.setOnPreferenceClickListener(p -> {
devModeStep = 0;
devModeStepFirstBootIgnore = true;
IntentHelper.openUrl(p.getContext(), "https://github.com/Fox2Code/FoxMagiskModuleManager/issues");
IntentHelper.openUrl(p.getContext(), "https://github.com/Androidacy/MagiskModuleManager/issues");
return true;
});
linkClickable.setOnPreferenceLongClickListener(p -> {
String toastText = requireContext().getString(R.string.link_copied);
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code/FoxMagiskModuleManager/issues"));
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Androidacy/MagiskModuleManager/issues"));
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
return true;
});
@ -1286,6 +1286,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
builder.setView(input);
builder.setPositiveButton("OK", (dialog, which) -> {
String text = String.valueOf(input.getText());
text = text.trim();
// string should not be empty, start with https://, and not contain any spaces. http links are not allowed.
if (text.matches("^https://.*") && !text.contains(" ") && !text.isEmpty()) {
if (customRepoManager.canAddRepo(text)) {
@ -1313,7 +1314,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
builder.setNeutralButton("Docs", (dialog, which) -> {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/docs/DEVELOPERS.md#custom-repo-format"));
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Androidacy/MagiskModuleManager/blob/master/docs/DEVELOPERS.md#custom-repo-format"));
startActivity(intent);
});
AlertDialog alertDialog = builder.show();

@ -61,7 +61,7 @@ public class SentryMain {
});
// If first_launch pref is not false, refuse to initialize Sentry
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("mmm");
if (!Objects.equals(sharedPreferences.getString("last_shown_setup", null), "v1")) {
if (!Objects.equals(sharedPreferences.getString("last_shown_setup", null), "v2")) {
return;
}
sentryEnabled = sharedPreferences.getBoolean("pref_crash_reporting_enabled", false);

@ -48,8 +48,9 @@
android:id="@+id/setup_scroll_down"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:text="@string/setup_scroll_down"
android:layout_marginHorizontal="2dp"
android:layout_marginVertical="4dp"
android:text="@string/setup_scroll_down_v2"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
<!-- Theme radio select. Options are system, light, dark, black, transparent_light -->
@ -284,18 +285,26 @@
android:text="@string/other_section"
android:textAppearance="@android:style/TextAppearance.Material.Headline" />
<com.google.android.material.textview.MaterialTextView
<com.google.android.material.materialswitch.MaterialSwitch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:checked="false"
android:id="@+id/setup_agree_eula"
android:layout_marginHorizontal="2dp"
android:layout_marginVertical="4dp"
android:textColorLink="@color/blue"
android:text="@string/eula_agree"
android:text="@string/eula_sum"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:drawableStart="@drawable/baseline_library_add_check_24"
android:drawablePadding="8dp" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:textColorLink="@color/blue"
android:text="@string/eula_agree_v2"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
</LinearLayout>
</ScrollView>

@ -5,6 +5,7 @@
<item
android:id="@+id/cancel_setup"
android:checked="false"
android:enabled="true"
android:icon="@drawable/baseline_close_24"
android:title="@string/cancel"
app:showAsAction="ifRoom" />

@ -358,4 +358,5 @@
<string name="low_quality_module_desc">Tento modul obsahuje metadata, která jsou buď neplatná, nebo jsou považována za označení modulu nízké kvality. Doporučuje se odinstalace.</string>
<string name="yer_a_wizard_harry">Jsi čaroděj, Harry!</string>
<string name="no_browser">Nemáte nainstalován prohlížeč. Pro pokračování nejaký nainstalujte.</string>
<string name="setup_background_update_check_require_wifi">Pro kontrolu aktualizací vyžadovat WiFi</string>
</resources>

@ -94,7 +94,8 @@
<string name="remote_install_title">Installazione remota</string>
<string name="file_picker_wierd">Il tuo gestore file ha fornito una risposta non valida.</string>
<string name="use_magisk_install_command_pref">Usa il comando di installazione del modulo magisk</string>
<string name="use_magisk_install_command_desc">Questo ha causato dei problemi durante i test per l\'installazione dei moduli, per questo l\'opzione è disponibile solo in modalità sviluppatore. Abilitala a tuo rischio e pericolo!</string>
<string name="use_magisk_install_command_desc">Questo ha causato dei problemi durante i test per l\'installazione dei moduli, per questo l\'opzione è disponibile solo in modalità sviluppatore.
\nAbilitala a tuo rischio e pericolo!</string>
<string name="dev_mode_enabled">Modalità sviluppatore attivata</string>
<string name="dev_mode_disabled">Modalità sviluppatore disattivata</string>
<string name="force_english_pref">Forza la lingua inglese</string>

@ -386,4 +386,5 @@
<string name="donate_androidacy_sum">Купите премиальную подписку на Androidacy, чтобы поддерживать приложение и репозиторий.</string>
<string name="promo_code_copied">Используйте скопированный код и получите половину скидки на первый месяц!</string>
<string name="warning_pls_restart">Обратите внимание, что некоторые настройки могут не вступить в силу, пока вы не перезапустите приложение.</string>
<string name="reinstall">Переустановить</string>
</resources>

@ -319,7 +319,7 @@
<string name="changelog_none">Змін ще немає!</string>
<string name="update_cancel_button">Скасувати оновлення</string>
<string name="invalid_repo_url">URL-адреса, яку ви ввели для репозиторію, недійсна</string>
<string name="add_repo_message">Репозиторії повинні обслуговуватися за допомогою HTTPS і відповідати специфікаціям, викладеним у <a href="https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/docs/DEVELOPERS.md#custom-repo-format">документації</a>.</string>
<string name="add_repo_message">Репозиторії повинні обслуговуватися за допомогою HTTPS і відповідати специфікаціям, викладеним у <a href="https://github.com/Androidacy/MagiskModuleManager/blob/master/docs/DEVELOPERS.md#custom-repo-format">документації</a>.</string>
<string name="notification_update_summary">Наступні модулі можуть бути оновлені:</string>
<string name="notification_update_module_template">%1$s до версії %2$s</string>
<string name="notification_channel_background_update">Перевірка оновлень…</string>
@ -383,4 +383,5 @@
<string name="donate_androidacy_sum">Придбайте преміальну підписку на Androidacy, щоб підтримувати додаток і репозиторій.</string>
<string name="promo_code_copied">Використовуйте скопійований код, щоб отримати половину знижки за перший місяць!</string>
<string name="warning_pls_restart">Зверніть увагу, що деякі налаштування можуть не робити, доки ви не перезавантажите додаток.</string>
<string name="reinstall">Перевстановити</string>
</resources>

@ -15,6 +15,8 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionBarStyle">@style/Theme.Design.NoActionBar</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<style name="Widget.Material3.Chip.Choice.Light" parent="Widget.Material3.Chip.Assist">
@ -36,6 +38,8 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionBarStyle">@style/Theme.Design.NoActionBar</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<!-- Black monet theme, which is just dark monet theme with black background -->
@ -63,6 +67,8 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionBarStyle">@style/Theme.Design.NoActionBar</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<style name="Widget.Material3.Chip.Choice.Dark" parent="Widget.Material3.Chip.Assist">

@ -387,11 +387,11 @@
<string name="language_not_available">%s 尚未被翻译。 帮忙翻译一下\?</string>
<string name="blur_desc">在某些对话框和元素后面创建模糊效果。 请注意,模糊可能在某些设备上表现不佳并且可能不适用于所有人。</string>
<string name="error_encrypted_shared_preferences">读取共享首选项时出错。 请重置应用程序。</string>
<string name="other_section"></string>
<string name="other_section"></string>
<string name="eula_agree">单击完成,即表示您同意受 LGPL-3 的约束(https://www.gnu.org/licenses/lgpl-3.0.en.html) license and the EULA (https://www.androidacy.com/foxmmm-eula/)</string>
<string name="analytics_desc">允许我们跟踪应用程序的使用和安装。完全符合 GDPR 并使用由 Androidacy 托管的 Matomo。</string>
<string name="debug_cat">调试</string>
<string name="announcements">新的和更新</string>
<string name="announcements">消息和更新</string>
<string name="back">返回</string>
<string name="donate_fox">向 Fox2Code 捐赠</string>
<string name="donate_androidacy">向 Androidacy 捐赠</string>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Fox\'s Magisk 模組管理器</string>
<string name="app_name">Fox Magisk 模組管理器</string>
<string name="app_name_short">Fox\'s Mmm</string>
<string name="fail_root_magisk">無法取得 ROOT 權限或您並沒有裝 Magisk</string>
<string name="loading">讀取中請稍後…</string>
@ -264,7 +264,7 @@
<string name="safe_message">這個模組已被倉庫認證為安全的,表示它通過某種程度的質量以及安全檢查,也通過了病毒檢測。</string>
<string name="background_update_check_excludes">要從自動更新檢查除開的模組</string>
<string name="update_title">更新應用程式</string>
<string name="update_button">請稍等...</string>
<string name="update_button">請稍等</string>
<string name="setup_magisk_alt_repo_summary">比原版的規範寬鬆。有大量的模組,不過安全性較低。</string>
<string name="setup_crash_reporting_summary">錯誤回報以及效能分析回傳。所有提交的報告將會嚴格保證匿名且私密。</string>
<string name="pref_category_contributors">貢獻者</string>
@ -345,7 +345,7 @@
<string name="add_repo_message">倉庫必須使用HTTPS並且遵循文檔內列出的規範。</string>
<string name="notification_update_summary">以下模組可以更新:</string>
<string name="notification_update_module_template">%1$s 到 版本 %2$s</string>
<string name="notification_channel_background_update">檢查更新中...</string>
<string name="notification_channel_background_update">檢查更新中</string>
<string name="notification_channel_background_update_description">FoxMMM在後台檢查更新中。</string>
<string name="notification_channel_category_background_update">後台更新狀態</string>
<string name="notification_channel_category_background_update_description">在檢查更新時顯示一個通知,不讓系統自動結束進程</string>
@ -361,4 +361,40 @@
<string name="notification_channel_category_app_update_description">當應用程式有更新時通知</string>
<string name="notification_channel_background_update_app">應用有新的更新!</string>
<string name="notification_channel_background_update_app_description">FoxMMM有更新。點擊安裝更新。</string>
<string name="crash_reporting_headline">錯誤回報</string>
<string name="setup_update_check_headline">更新檢查</string>
<string name="setup_background_update_check_require_wifi">僅透過WiFi來檢查更新</string>
<string name="setup_background_update_check_require_wifi_summary">僅透過WiFi或者無流量限制的連線來檢查更新</string>
<string name="setup_app_analytics">允許應用分析回報</string>
<string name="clear_app_cache">清楚應用快取</string>
<string name="clear_app_cache_desc">一般來說這不是必要的,不過可能可以解決部分遇到的問題。</string>
<string name="cache_cleared">快取清除完成</string>
<string name="cache_clear_failed">快取清除失敗</string>
<string name="clear_cache_dialogue_title">清除應用快取?</string>
<string name="clear_cache_dialogue_message">這將會清除應用快取。你的設定將會被保留,不過其他應用相關功能可能會暫時性的需要更長時間來處理。</string>
<string name="certificate_error">伺服器憑證無法被驗證。請確保FoxMMM的HTTPS請求沒有被攔截。</string>
<string name="pref_category_privacy">隱私</string>
<string name="crash_reporting_pii_desc">允許在發生錯誤報告時附帶更多資訊其中可能包含可個人識別化的資料例如IP地址以及終端識別碼。</string>
<string name="crash_reporting_pii">發送詳細資料</string>
<string name="setup_crash_reporting_pii">在錯誤回報中發送更詳細的資料。</string>
<string name="setup_crash_reporting_pii_summary">這可能會包含IP地址以及終端識別碼。收到的資料將不會用作分析錯誤以及提升服務品質以外的任何用途。</string>
<string name="setup_scroll_down">要完成的按鈕開啟,請滑下去並檢視所有選項。</string>
<string name="empty_field">URL不能為空</string>
<string name="repo_already_added">倉庫已存在。</string>
<string name="other_section">其他</string>
<string name="analytics_desc">允許我們追踪應用的使用以及安裝。完全支援並符合GDPR且使用Androidacy運營的Matomo。</string>
<string name="debug_cat">偵錯</string>
<string name="announcements">消息與更新</string>
<string name="back">返回</string>
<string name="error_creating_cookie_database">無法訪問WebView。功能將會受限制。</string>
<string name="showcase_mode_dialogue_message">啟用展示模式需要重新啟動應用。</string>
<string name="eula_agree">點擊完成按鈕的當下,你表明了你同意遵守以及被約束於條約之規範。本條約含 LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) 許可以及 EULA (https://www.androidacy.com/foxmmm-eula/)</string>
<string name="donate_fox">捐款給 Fox2Code</string>
<string name="donate_androidacy">捐款給 Androidacy</string>
<string name="donate_androidacy_sum">訂閱 Androidacy 的高級會員來支持應用以及倉庫。</string>
<string name="promo_code_copied">使用複製的優惠卷來取得首月半價優惠!</string>
<string name="warning_pls_restart">某些設定在重啟應用後方能生效。</string>
<string name="language_not_available">%s 還尚未被翻譯。來一起翻譯吧?</string>
<string name="blur_desc">在某些視窗以及元素啟用模糊效果。這個功能可能在某些裝置上運行效果不佳或完全無法使用。</string>
<string name="error_encrypted_shared_preferences">加載共同設定失敗。請重置本應用。</string>
</resources>

@ -403,6 +403,8 @@
<string name="showcase_mode_dialogue_message">An app restart is required to enable showcase mode.</string>
<string name="other_section">Other</string>
<string name="eula_agree">By clicking "finish", you are agreeing to be bound by the LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) license and the EULA (https://www.androidacy.com/foxmmm-eula/)</string>
<string name="eula_agree_v2">You agree to be bound by the LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) license and the EULA (https://www.androidacy.com/foxmmm-eula/), in addition to any third party terms, and that the authors of this app bear no responsibility of your usage of it, nor do we offer any warranties express or implied.</string>
<string name="eula_sum">I agree to the EULA and License</string>
<string name="analytics_desc">Allow us to track app usage and installs. Fully GDPR compliant and uses Matomo, hosted by Androidacy.</string>
<string name="debug_cat">Debugging</string>
<string name="announcements">News and updates</string>
@ -412,4 +414,6 @@
<string name="donate_androidacy_sum">Buy a premium subscription to Androidacy to support the app and the repo.</string>
<string name="promo_code_copied">Use the copied code for half off your first month!</string>
<string name="warning_pls_restart">Please note that some settings may not take effect until you restart the app.</string>
<string name="reinstall">Reinstall</string>
<string name="setup_scroll_down_v2">To enable the finish button, scroll to the bottom and acknowledge you have read and agree to the EULA and license(s).</string>
</resources>

@ -35,6 +35,7 @@
<item name="windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
</style>
<style name="Widget.Material3.Chip.Choice.Light" parent="Widget.Material3.Chip.Assist">
@ -68,6 +69,8 @@
<item name="windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<style name="Theme.MagiskModuleManager.Dark" parent="Theme.Material3.Dark">
@ -104,6 +107,8 @@
<item name="windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<!-- Black theme, which is just a dark theme with a black background -->
@ -118,7 +123,6 @@
<item name="colorPrimaryVariant">@color/orange_700</item>
<item name="colorSecondary">@color/orange_500</item>
<item name="colorSecondaryVariant">@color/orange_700</item>
<!-- Fix some unthemed dialogs -->
<item name="android:colorAccent">@color/orange_200</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:colorForeground">@color/white</item>
@ -133,6 +137,8 @@
<item name="windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<style name="Widget.Material.Chip.Choice.Dark" parent="Widget.MaterialComponents.Chip.Action">
@ -159,6 +165,8 @@
<!-- fonts -->
<item name="android:fontFamily">@font/inter</item>
<item name="font">@font/inter</item>
<item name="cornerRadius">@dimen/card_corner_radius</item>
<item name="alertDialogTheme">@style/Theme.MagiskModuleManager.AlertDialog</item>
</style>
<style name="Theme.MagiskModuleManager" parent="Theme.MagiskModuleManager.Light" />
@ -173,4 +181,13 @@
<style name="Theme.MagiskModuleManager.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.MagiskModuleManager.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="Theme.MagiskModuleManager.AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="dialogCornerRadius">28dp</item>
<item name="cornerFamily">rounded</item>
<item name="android:colorBackground">?attr/colorSurface</item>
<item name="android:layout" tools:ignore="PrivateResource">@layout/m3_alert_dialog</item>
</style>
</resources>

@ -1,16 +1,9 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory>
<Preference
app:icon="@drawable/ic_baseline_info_24"
app:key="pref_warning_restart"
app:enabled="false"
app:singleLineTitle="false"
app:title="@string/warning_pls_restart" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/warning_pls_restart" app:icon="@drawable/ic_baseline_info_24"/>
<!-- Custom repos has been announced, check https://github.com/Fox2Code/FoxMagiskModuleManager/issues/131 -->
<!-- Custom repos has been announced, check https://github.com/Androidacy/MagiskModuleManager/issues/131 -->
<PreferenceCategory app:title="@string/pref_category_repos">
<Preference
app:icon="@drawable/ic_baseline_extension_24"

@ -11,7 +11,7 @@ buildscript {
extra.apply {
set("sentryConfigFile", rootProject.file("sentry.properties"))
set("hasSentryConfig", false)
set("sentryVersion", "6.17.0")
set("sentryVersion", "6.18.1")
}
dependencies {
classpath("com.android.tools.build:gradle:8.0.0")
@ -20,7 +20,6 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
//noinspection GradleDependency
classpath("io.realm:realm-gradle-plugin:10.15.1")
classpath("io.sentry:sentry-android-gradle-plugin:3.5.0")
}

@ -170,7 +170,7 @@ mmm_exec setLastLine "The installer support mmm_exec"
# Wait to simulate the module doing something
sleep 5
mmm_exec hideLoading
mmm_exec setSupportLink https://github.com/Fox2Code/FoxMagiskModuleManager
mmm_exec setSupportLink https://github.com/Androidacy/MagiskModuleManager
```
[You may look at the examples modules and their codes.](examples)

@ -6,6 +6,6 @@ author=Fox2Code
description=Fox's Magisk Module Manager example module
minApi=21
minMagisk=19000
support=https://github.com/Fox2Code/FoxMagiskModuleManager
support=https://github.com/Androidacy/MagiskModuleManager
donate=https://paypal.me/fox2code
config=com.fox2code.mmm

@ -6,6 +6,6 @@ author=Fox2Code
description=Fox's Magisk Module Manager rainbow module
minApi=21
minMagisk=19000
support=https://github.com/Fox2Code/FoxMagiskModuleManager
support=https://github.com/Androidacy/MagiskModuleManager
donate=https://paypal.me/fox2code
config=com.fox2code.mmm

@ -23,6 +23,8 @@ dependencyResolutionManagement {
}
}
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
// val isCiServer = System.getenv().containsKey("CI")
// Cache build artifacts, so expensive operations do not need to be re-computed
buildCache {

Loading…
Cancel
Save