Merge branch 'Fox2Code:master' into master

pull/179/head
Jia-Bin 2 years ago committed by GitHub
commit bf9fadb6df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,33 @@ it is a check that verify that the module is declaring the minimum required to
allow the app to show your module to the user without hurting his experience.
Filling all basic Magisk properties is often enough to not get filtered out by it.
## Custom Repo format
Note: This feature is for `0.6.0` version that is not released yet.
`last_update` fields uses unix millis.
Json format is
```json
{
"name": "Repo name",
"website": "repo website",
"support": "optional support url",
"donate": "optional support url",
"submitModule": "optional submit module URL",
"last_update": 0,
"modules": [
{
"id": "module id",
"last_update": 0,
"notes_url": "notes url",
"prop_url": "module.prop url",
"zip_url": "module.zip url"
}
]
}
```
## Properties
In addition to the following required magisk properties

@ -1,9 +1,28 @@
# Fox's Magisk Module Manager
## Fox Module contest
If you publish a modules using ANSI color styling or FoxMMM extensions
(see [`DEVELOPERS.md`](DEVELOPERS.md)) you can enter the contest to win 20€ via PayPal.
The winning module will also be displayed on this spot for at least 2 weeks.
Note: The module install script must **not** be obfuscated and
be published on a repo supported by FoxMMM to be able to participate.
The winner will be selected at the end of this month (July).
(Don't forget to join telegram to be sure I don't miss your module when selecting the winner)
## Join us on Telegram!
[![Telegram Group](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2FFox2Code_Chat)](https://telegram.dog/Fox2Code_Chat)
## Screenshots
Main activity:
[<img src="screenshot-dark.jpg" width="250"/>](screenshot-dark.jpg)
[<img src="screenshot-light.jpg" width="250"/>](screenshot-light.jpg)
## What is this?
The official Magisk is dropping support to download online modules...
@ -59,28 +78,13 @@ Support:
[![Telegram Group](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fandroidacy_discussions)](https://telegram.dog/androidacy_discussions)
#### [https://github.com/Magisk-Modules-Repo](https://github.com/Magisk-Modules-Repo)
- No longer accept new modules or receive update to existing modules
- May be shut down at any moment
- Official app dropped support for it
- No longer supported by Fox's mmm
As the main repo may shutting down due to the main app no longer supporting it, stopped
accepting new modules, and also no longer receive updates since the 21 December 2021.
If a module is in multiple repos, the manager will just pick the most up to date version
of the module, allowing developers to switch repo at their own pace if they want to.
of the module, if a module is in multiple repos it will just use first registered repo.
Note: If you or a friend uploaded a module and it doesn't appear in your module
list you can disable the low quality filter in the app settings.
Go to the [developer documentation](DEVELOPERS.md) for more info.
## Screenshots
Main activity:
[<img src="screenshot-dark.jpg" width="250"/>](screenshot-dark.jpg)
[<img src="screenshot-light.jpg" width="250"/>](screenshot-light.jpg)
## For developers
The manager can read new meta keys to allow modules to customize their own entry
@ -100,8 +104,7 @@ For more information please check the [developer documentation](DEVELOPERS.md)
See [`app/src/main/res/values/strings.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/strings.xml)
and [`app/src/main/res/values/arrays.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/arrays.xml)
If your language is right to left you should make a copy of [`app/src/main/res/values/bools.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/bools.xml)
and set `lang_support_rtl` to `true`.
If your language is right to left don't forget to set `lang_support_rtl` to `true`.
Translators are not expected to have any previous coding experience.

@ -4,14 +4,14 @@ plugins {
}
android {
compileSdk 32
compileSdk 33
defaultConfig {
applicationId "com.fox2code.mmm"
minSdk 21
targetSdk 32
versionCode 43
versionName "0.5.0"
targetSdk 33
versionCode 50
versionName "0.6.0-dev1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -36,8 +36,8 @@ android {
buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true"
buildConfigField(
"java.util.List<String>",
"DISABLED_REPOS",
"java.util.Arrays.asList()",
"ENABLED_REPOS",
"java.util.Arrays.asList(\"magisk_alt_repo\", \"dg_magisk_repo\", \"androidacy_repo\")",
)
}
@ -54,8 +54,8 @@ android {
// F-Droid flavor.
buildConfigField(
"java.util.List<String>",
"DISABLED_REPOS",
"java.util.Arrays.asList(\"androidacy_repo\")",
"ENABLED_REPOS",
"java.util.Arrays.asList(\"magisk_alt_repo\")",
)
}
}
@ -80,7 +80,7 @@ configurations {
dependencies {
// UI
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.emoji2:emoji2:1.1.0'
implementation 'androidx.emoji2:emoji2-views-helper:1.1.0'
implementation 'androidx.preference:preference:1.2.0'
@ -88,18 +88,20 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'com.google.android.material:material:1.6.0'
implementation 'com.google.android.material:material:1.6.1'
implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}"
implementation "dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0"
implementation "dev.rikka.rikkax.insets:insets:1.2.0"
implementation "dev.rikka.rikkax.insets:insets:1.3.0"
implementation 'com.github.Dimezis:BlurView:version-1.6.6'
implementation 'com.github.KieronQuinn:MonetCompat:0.4.1'
implementation 'com.github.Fox2Code:FoxCompat:0.0.2'
// Utils
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3'
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.2'
implementation 'com.github.Fox2Code:RosettaX:1.0.8'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
// Markdown
@ -110,9 +112,6 @@ dependencies {
annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4"
// Utils for compat (Needs to be outsourced ASAP)
// compileOnly "org.robolectric:android-all:11-robolectric-6757853"
// Test
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- WebView offline webpage support -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Make sure of the module active state by checking enabled modules on boot -->
<!-- Check if there is modules updates on boot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Open config apps for applications -->
<uses-permission-sdk-23 android:name="android.permission.QUERY_ALL_PACKAGES" />
@ -33,8 +33,9 @@
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
tools:targetApi="s"
tools:replace="android:supportsRtl">
<receiver android:name="com.fox2code.mmm.manager.ModuleBootReceive"
tools:replace="android:supportsRtl"
tools:ignore="ManifestResource">
<receiver android:name="com.fox2code.mmm.background.BackgroundBootListener"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@ -94,6 +95,8 @@
tools:node="merge">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
tools:node="remove" />
<meta-data android:name="androidx.work.WorkManagerInitializer"
tools:node="remove" />
</provider>
</application>
</manifest>

@ -4,6 +4,9 @@ import androidx.annotation.Keep;
import com.topjohnwu.superuser.ShellUtils;
/**
* I will probably outsource this to a separate library later
*/
@Keep
public class SystemProperties {
@Keep
@ -13,4 +16,15 @@ public class SystemProperties {
prop = prop.substring(0, prop.length() - 1).trim();
return prop;
}
@Keep
public static int getInt(String key, int def) {
try {
String value = get(key);
if (value.isEmpty()) return def;
return Integer.parseInt(value);
} catch (Exception e) {
return def;
}
}
}

@ -1,13 +1,5 @@
package com.fox2code.mmm;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@ -21,8 +13,18 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatDisplay;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
@ -32,14 +34,12 @@ import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.settings.SettingsActivity;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import eightbitlab.com.blurview.BlurView;
import eightbitlab.com.blurview.RenderScriptBlur;
public class MainActivity extends CompatActivity implements SwipeRefreshLayout.OnRefreshListener,
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener, SearchView.OnCloseListener,
OverScrollManager.OverScrollHelper {
private static final String TAG = "MainActivity";
@ -66,9 +66,16 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
}
@Override
protected void onResume() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onResume();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
IntentHelper.startActivity(this, SettingsActivity.class);
@ -105,12 +112,11 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.moduleList.setItemViewCacheSize(4); // Default is 2
this.swipeRefreshLayout.setOnRefreshListener(this);
this.actionBarBlur.setBackground(this.actionBarBackground);
this.actionBarBlur.setupWith(this.moduleList).setFrameClearDrawable(
this.getWindow().getDecorView().getBackground())
.setBlurAlgorithm(new RenderScriptBlur(this))
.setBlurRadius(4F).setBlurAutoUpdate(true)
.setHasFixedTransformationMatrix(true);
this.updateBlurState();
this.actionBarBlur.setupWith(findViewById(R.id.blur_frame))
.setFrameClearDrawable(this.getWindow().getDecorView().getBackground())
.setBlurAlgorithm(new RenderScriptBlur(this)).setBlurRadius(4F)
.setBlurAutoUpdate(true).setHasFixedTransformationMatrix(true);
this.updateBlurState();
this.moduleList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
@ -119,7 +125,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
}
});
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.searchView.setMinimumHeight(CompatDisplay.dpToPixel(16));
this.searchView.setMinimumHeight(FoxDisplay.dpToPixel(16));
this.searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH |
EditorInfo.IME_FLAG_NO_FULLSCREEN);
this.searchView.setOnQueryTextListener(this);
@ -174,6 +180,9 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
});
Log.i(TAG, "Scanning for modules!");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Log.w(TAG, "Need update on create?");
}
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () ->
progressIndicator.setProgressCompat(
(int) (value * PRECISION), true) :() ->
@ -225,7 +234,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
private void cardIconifyUpdate() {
boolean iconified = this.searchView.isIconified();
int backgroundAttr = iconified ?
int backgroundAttr = iconified ? MainApplication.isMonetEnabled() ?
R.attr.colorSecondaryContainer : // Monet is special...
R.attr.colorSecondary : R.attr.colorPrimarySurface;
Resources.Theme theme = this.searchCard.getContext().getTheme();
TypedValue value = new TypedValue();
@ -251,8 +261,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
swipeRefreshLayoutOrigStartOffset + combinedBarsHeight,
swipeRefreshLayoutOrigEndOffset + combinedBarsHeight);
this.moduleViewListBuilder.setHeaderPx(Math.max(statusBarHeight,
combinedBarsHeight - CompatDisplay.dpToPixel(4)));
this.moduleViewListBuilder.setFooterPx(CompatDisplay.dpToPixel(4) +
combinedBarsHeight - FoxDisplay.dpToPixel(4)));
this.moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4) +
bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets();
@ -328,6 +338,19 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
RepoManager.getINSTANCE().updateEnabledStates();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
runOnUiThread(() -> {
progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION);
});
RepoManager.getINSTANCE().update(value -> runOnUiThread(() ->
progressIndicator.setProgressCompat(
(int) (value * PRECISION), true)));
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE);
});
}
moduleViewListBuilder.appendRemoteModules();
Log.i(TAG, "Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
@ -338,9 +361,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
this.updateScreenInsets(newConfig);
super.onConfigurationChanged(newConfig);
protected void onWindowUpdated() {
this.updateScreenInsets();
}
@Override
@ -382,7 +404,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.searchView.clearFocus();
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return true;
}
@ -391,7 +414,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
public boolean onQueryTextChange(String query) {
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return false;
}
@ -400,7 +424,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
public boolean onClose() {
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(null)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return false;
}

@ -1,6 +1,7 @@
package com.fox2code.mmm;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@ -16,21 +17,27 @@ import androidx.annotation.StyleRes;
import androidx.emoji2.text.DefaultEmojiCompatConfig;
import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.FontRequestEmojiCompatConfig;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatApplication;
import com.fox2code.mmm.compat.CompatThemeWrapper;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxApplication;
import com.fox2code.foxcompat.FoxThemeWrapper;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.GMSProviderInstaller;
import com.fox2code.mmm.utils.Http;
import com.fox2code.rosettax.LanguageSwitcher;
import com.google.android.material.color.DynamicColors;
import com.topjohnwu.superuser.Shell;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin;
@ -47,7 +54,8 @@ import io.noties.prism4j.annotations.PrismBundle;
includeAll = true,
grammarLocatorClassName = ".Prism4jGrammarLocator"
)
public class MainApplication extends CompatApplication {
public class MainApplication extends FoxApplication
implements androidx.work.Configuration.Provider {
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static Locale timeFormatLocale =
Resources.getSystem().getConfiguration().locale;
@ -78,6 +86,13 @@ public class MainApplication extends CompatApplication {
}
public static void addSecret(Intent intent) {
ComponentName componentName = intent.getComponent();
String packageName = componentName != null ?
componentName.getPackageName() : intent.getPackage();
if (!BuildConfig.APPLICATION_ID.equalsIgnoreCase(packageName)) {
// Code safeguard, we should never reach here.
throw new IllegalArgumentException("Can't add secret to outbound Intent");
}
intent.putExtra("secret", secret);
}
@ -101,10 +116,6 @@ public class MainApplication extends CompatApplication {
return getSharedPreferences().getBoolean("pref_show_incompatible", false);
}
public static boolean isForceEnglish() {
return getSharedPreferences().getBoolean("pref_force_english", false);
}
public static boolean isForceDarkTerminal() {
return getSharedPreferences().getBoolean("pref_force_dark_terminal", false);
}
@ -118,7 +129,8 @@ public class MainApplication extends CompatApplication {
}
public static boolean isMonetEnabled() {
return getSharedPreferences().getBoolean("pref_enable_monet", false);
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
getSharedPreferences().getBoolean("pref_enable_monet", true);
}
public static boolean isBlurEnabled() {
@ -142,19 +154,12 @@ public class MainApplication extends CompatApplication {
&& isDeveloper();
}
public static boolean isFirstBoot() {
return firstBoot;
public static boolean isBackgroundUpdateCheckEnabled() {
return getSharedPreferences().getBoolean("pref_background_update_check", true);
}
public static void notifyBootListenerCompleted() {
if (MainApplication.bootSharedPreferences != null) {
MainApplication.bootSharedPreferences.edit()
.putBoolean("first_boot", false).apply();
} else if (MainApplication.INSTANCE != null) {
MainApplication.getSharedPreferences().edit()
.putBoolean("first_boot", false).apply();
}
firstBoot = false;
public static boolean isFirstBoot() {
return firstBoot;
}
public static boolean hasGottenRootAccess() {
@ -180,17 +185,16 @@ public class MainApplication extends CompatApplication {
@StyleRes
private int managerThemeResId = R.style.Theme_MagiskModuleManager;
private CompatThemeWrapper markwonThemeContext;
private FoxThemeWrapper markwonThemeContext;
private Markwon markwon;
public Markwon getMarkwon() {
if (this.markwon != null)
return this.markwon;
CompatThemeWrapper contextThemeWrapper = this.markwonThemeContext;
FoxThemeWrapper contextThemeWrapper = this.markwonThemeContext;
if (contextThemeWrapper == null) {
contextThemeWrapper = this.markwonThemeContext =
new CompatThemeWrapper(this, this.managerThemeResId);
contextThemeWrapper.setForceEnglish(isForceEnglish());
new FoxThemeWrapper(this, this.managerThemeResId);
}
Markwon markwon = Markwon.builder(contextThemeWrapper).usePlugin(HtmlPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(
@ -200,8 +204,14 @@ public class MainApplication extends CompatApplication {
return this.markwon = markwon;
}
public CompatThemeWrapper getMarkwonThemeContext() {
return markwonThemeContext;
public FoxThemeWrapper getMarkwonThemeContext() {
return this.markwonThemeContext;
}
@NonNull
@Override
public androidx.work.Configuration getWorkManagerConfiguration() {
return new androidx.work.Configuration.Builder().build();
}
private class Prism4jSwitchTheme implements Prism4jTheme {
@ -241,17 +251,24 @@ public class MainApplication extends CompatApplication {
public void updateTheme() {
@StyleRes int themeResId;
String theme;
boolean monet = isMonetEnabled();
switch (theme = getSharedPreferences().getString("pref_theme", "system")) {
default:
Log.w("MainApplication", "Unknown theme id: " + theme);
case "system":
themeResId = R.style.Theme_MagiskModuleManager;
themeResId = monet ?
R.style.Theme_MagiskModuleManager_Monet :
R.style.Theme_MagiskModuleManager;
break;
case "dark":
themeResId = R.style.Theme_MagiskModuleManager_Dark;
themeResId = monet ?
R.style.Theme_MagiskModuleManager_Monet_Dark :
R.style.Theme_MagiskModuleManager_Dark;
break;
case "light":
themeResId = R.style.Theme_MagiskModuleManager_Light;
themeResId = monet ?
R.style.Theme_MagiskModuleManager_Monet_Light :
R.style.Theme_MagiskModuleManager_Light;
break;
}
this.setManagerThemeResId(themeResId);
@ -266,15 +283,18 @@ public class MainApplication extends CompatApplication {
public boolean isLightTheme() {
switch (this.managerThemeResId) {
case R.style.Theme_MagiskModuleManager:
case R.style.Theme_MagiskModuleManager_Monet:
return (this.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK)
!= Configuration.UI_MODE_NIGHT_YES;
case R.style.Theme_MagiskModuleManager_Monet_Light:
case R.style.Theme_MagiskModuleManager_Light:
return true;
case R.style.Theme_MagiskModuleManager_Monet_Dark:
case R.style.Theme_MagiskModuleManager_Dark:
return false;
default:
throw new IllegalStateException("Non manager theme!");
return super.isLightTheme();
}
}
@ -282,11 +302,11 @@ public class MainApplication extends CompatApplication {
public void onCreate() {
if (INSTANCE == null) INSTANCE = this;
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (MainApplication.isMonetEnabled()) {
DynamicColors.applyToActivitiesIfAvailable(this);
}
}
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
DynamicColors.applyToActivitiesIfAvailable(this,
new DynamicColorsOptions.Builder().setPrecondition(
(activity, theme) -> isMonetEnabled()).build());
}*/
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences();
// We are only one process so it's ok to do this
SharedPreferences bootPrefs = MainApplication.bootSharedPreferences =
@ -326,15 +346,13 @@ public class MainApplication extends CompatApplication {
}
@Override
public void onCreateCompatActivity(CompatActivity compatActivity) {
this.setForceEnglish(isForceEnglish());
super.onCreateCompatActivity(compatActivity);
public void onCreateFoxActivity(FoxActivity compatActivity) {
super.onCreateFoxActivity(compatActivity);
compatActivity.setTheme(this.managerThemeResId);
}
@Override
public void onRefreshUI(CompatActivity compatActivity) {
this.setForceEnglish(isForceEnglish());
public void onRefreshUI(FoxActivity compatActivity) {
super.onRefreshUI(compatActivity);
compatActivity.setThemeRecreate(this.managerThemeResId);
}

@ -8,7 +8,7 @@ import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Files;
@ -75,7 +75,7 @@ public enum NotificationType implements NotificationTypeCst {
},
INSTALL_FROM_STORAGE(R.string.install_from_storage, R.drawable.ic_baseline_storage_24,
R.attr.colorBackgroundFloating, R.attr.colorOnBackground, v -> {
CompatActivity compatActivity = CompatActivity.getCompatActivity(v);
FoxActivity compatActivity = FoxActivity.getFoxActivity(v);
final File module = new File(compatActivity.getCacheDir(),
"installer" + File.separator + "module.zip");
IntentHelper.openFileTo(compatActivity, module, (d, u, s) -> {

@ -8,6 +8,7 @@ import android.webkit.WebView;
import androidx.annotation.Keep;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
/**
@ -45,7 +46,7 @@ public class XHooks {
@Keep
public static XRepo addXRepo(String url, String fallbackName) {
return RepoManager.getINSTANCE().addOrGet(url);
return RepoManager.getINSTANCE().addOrGet(url, fallbackName);
}
@Keep

@ -8,11 +8,14 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -22,12 +25,12 @@ import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewClientCompat;
import androidx.webkit.WebViewFeature;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
@ -36,7 +39,7 @@ import java.util.HashMap;
/**
* Per Androidacy repo implementation agreement, no request of this WebView shall be modified.
*/
public class AndroidacyActivity extends CompatActivity {
public class AndroidacyActivity extends FoxActivity {
private static final String TAG = "AndroidacyActivity";
static {
@ -46,6 +49,7 @@ public class AndroidacyActivity extends CompatActivity {
}
WebView webView;
TextView webViewNote;
AndroidacyWebAPI androidacyWebAPI;
boolean backOnResume;
@ -108,6 +112,7 @@ public class AndroidacyActivity extends CompatActivity {
}
}
this.webView = this.findViewById(R.id.webView);
this.webViewNote = this.findViewById(R.id.webViewNote);
WebSettings webSettings = this.webView.getSettings();
webSettings.setUserAgentString(Http.getAndroidacyUA());
webSettings.setDomStorageEnabled(true);
@ -140,12 +145,20 @@ public class AndroidacyActivity extends CompatActivity {
this.pageUrl = url;
}
private void onReceivedError(String url,int errorCode) {
@Override
public void onPageFinished(WebView view, String url) {
webViewNote.setVisibility(View.GONE);
}
private void onReceivedError(String url, int errorCode) {
if ((url.startsWith("https://api.androidacy.com/magisk/") ||
url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) {
Toast.makeText(AndroidacyActivity.this,
"Too many requests!", Toast.LENGTH_LONG).show();
AndroidacyActivity.this.runOnUiThread(AndroidacyActivity.this::onBackPressed);
} else if (url.equals(this.pageUrl)) {
postOnUiThread(() ->
webViewNote.setVisibility(View.VISIBLE));
}
}
@ -166,12 +179,34 @@ public class AndroidacyActivity extends CompatActivity {
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
CompatActivity.getCompatActivity(webView).startActivityForResult(
FoxActivity.getFoxActivity(webView).startActivityForResult(
fileChooserParams.createIntent(), (code, data) ->
filePathCallback.onReceiveValue(
FileChooserParams.parseResult(code, data)));
return true;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
switch (consoleMessage.messageLevel()) {
case TIP:
Log.v(TAG, consoleMessage.message());
break;
case LOG:
Log.i(TAG, consoleMessage.message());
break;
case WARNING:
Log.w(TAG, consoleMessage.message());
break;
case ERROR:
Log.e(TAG, consoleMessage.message());
break;
case DEBUG:
Log.d(TAG, consoleMessage.message());
break;
}
return super.onConsoleMessage(consoleMessage);
}
});
this.webView.setDownloadListener((
downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> {

@ -7,6 +7,7 @@ import android.webkit.CookieManager;
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.Http;
import com.fox2code.mmm.utils.PropUtils;
@ -49,6 +50,11 @@ public class AndroidacyRepoData extends RepoData {
this.androidacyBlockade = 0; // Don't allow time travel. Well why not???
}
}
this.defaultName = "Androidacy Modules Repo";
this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE;
this.defaultSupport = "https://t.me/androidacy_discussions";
this.defaultDonate = "https://patreon.com/androidacy";
this.defaultSubmitModule = "https://www.androidacy.com/module-repository-applications/";
}
private static String getCookies() {
@ -231,6 +237,10 @@ public class AndroidacyRepoData extends RepoData {
}
this.lastUpdate = lastLastUpdate;
this.name = name;
this.website = jsonObject.optString("website");
this.support = jsonObject.optString("support");
this.donate = jsonObject.optString("donate");
this.submitModule = jsonObject.optString("submitModule");
return newModules;
}

@ -6,20 +6,19 @@ import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Keep;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
@ -42,6 +41,7 @@ public class AndroidacyWebAPI {
private static final int MAX_COMPAT_MODE = 1;
private final AndroidacyActivity activity;
private final boolean allowInstall;
private boolean allowHideNote = true;
boolean consumedAction;
boolean downloadMode;
int effectiveCompatMode;
@ -111,7 +111,7 @@ public class AndroidacyWebAPI {
if (!this.activity.backOnResume)
this.consumedAction = false;
});
final int dim5dp = CompatDisplay.dpToPixel(5);
final int dim5dp = FoxDisplay.dpToPixel(5);
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp);
this.activity.runOnUiThread(() -> {
AlertDialog alertDialog = builder.show();
@ -322,6 +322,11 @@ public class AndroidacyWebAPI {
this.activity.runOnUiThread(() -> {
this.activity.hideActionBar();
this.consumedAction = false;
if (this.allowHideNote) {
this.allowHideNote = false;
this.activity.webViewNote
.setVisibility(View.GONE);
}
});
}

@ -0,0 +1,19 @@
package com.fox2code.mmm.background;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.fox2code.mmm.MainApplication;
public class BackgroundBootListener extends BroadcastReceiver {
private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
if (!BOOT_COMPLETED.equals(intent.getAction())) return;
if (!MainApplication.isBackgroundUpdateCheckEnabled()) return;
BackgroundUpdateChecker.onMainActivityCreate(context);
BackgroundUpdateChecker.doCheck(context);
}
}

@ -0,0 +1,115 @@
package com.fox2code.mmm.background;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class BackgroundUpdateChecker extends Worker {
private static boolean easterEggActive = false;
public static final String NOTIFICATION_CHANNEL_ID = "background_update";
public static final int NOTIFICATION_ID = 1;
public BackgroundUpdateChecker(@NonNull Context context,
@NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
if (!NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled()
|| !MainApplication.isBackgroundUpdateCheckEnabled()) return Result.success();
doCheck(this.getApplicationContext());
return Result.success();
}
static void doCheck(Context context) {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().scan();
int moduleUpdateCount = 0;
for (LocalModuleInfo localModuleInfo :
ModuleManager.getINSTANCE().getModules().values()) {
RepoModule repoModule = RepoManager.getINSTANCE()
.getModules().get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate();
if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode) {
moduleUpdateCount++;
} else if (repoModule != null &&
repoModule.moduleInfo.versionCode > localModuleInfo.versionCode) {
moduleUpdateCount++;
}
}
if (moduleUpdateCount != 0) {
postNotification(context, moduleUpdateCount);
}
}
public static void postNotification(Context context, int updateCount) {
if (!easterEggActive) easterEggActive = new Random().nextInt(100) <= updateCount;
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(context.getString(easterEggActive ?
R.string.notification_update_title_easter_egg :
R.string.notification_update_title)
.replace("%i", String.valueOf(updateCount)))
.setContentText(context.getString(R.string.notification_update_subtitle))
.setSmallIcon(R.drawable.ic_baseline_extension_24)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class).setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE : 0)).setAutoCancel(true);
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build());
}
public static void onMainActivityCreate(Context context) {
NotificationManagerCompat notificationManagerCompat =
NotificationManagerCompat.from(context);
notificationManagerCompat.createNotificationChannel(
new NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_HIGH).setShowBadge(true)
.setName(context.getString(R.string.notification_update_pref)).build());
notificationManagerCompat.cancel(BackgroundUpdateChecker.NOTIFICATION_ID);
BackgroundUpdateChecker.easterEggActive = false;
WorkManager.getInstance(context).enqueueUniquePeriodicWork("background_checker",
ExistingPeriodicWorkPolicy.REPLACE, new PeriodicWorkRequest.Builder(
BackgroundUpdateChecker.class, 6, TimeUnit.HOURS)
.setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.UNMETERED).build()).build());
}
public static void onMainActivityResume(Context context) {
NotificationManagerCompat.from(context).cancel(
BackgroundUpdateChecker.NOTIFICATION_ID);
BackgroundUpdateChecker.easterEggActive = false;
}
}

@ -1,587 +0,0 @@
package com.fox2code.mmm.compat;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ScrollView;
import androidx.annotation.AttrRes;
import androidx.annotation.CallSuper;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.R;
import com.kieronquinn.monetcompat.app.MonetCompatActivity;
import com.kieronquinn.monetcompat.extensions.views.ViewExtensions_RecyclerViewKt;
import com.kieronquinn.monetcompat.extensions.views.ViewExtensions_ScrollViewKt;
import java.lang.ref.WeakReference;
import java.util.Locale;
import java.util.Objects;
import rikka.insets.WindowInsetsHelper;
import rikka.layoutinflater.view.LayoutInflaterFactory;
/**
* I will probably outsource this to a separate library later
*/
public class CompatActivity extends AppCompatActivity {
public static final int INTENT_ACTIVITY_REQUEST_CODE = 0x01000000;
private static final String TAG = "CompatActivity";
public static final CompatActivity.OnBackPressedCallback DISABLE_BACK_BUTTON =
new CompatActivity.OnBackPressedCallback() {
@Override
public boolean onBackPressed(CompatActivity compatActivity) {
compatActivity.setOnBackPressedCallback(this);
return true;
}
};
final WeakReference<CompatActivity> selfReference;
private final CompatConfigHelper compatConfigHelper = new CompatConfigHelper(this);
private CompatActivity.OnActivityResultCallback onActivityResultCallback;
private CompatActivity.OnBackPressedCallback onBackPressedCallback;
private MenuItem.OnMenuItemClickListener menuClickListener;
private CharSequence menuContentDescription;
@StyleRes
private int setThemeDynamic = 0;
private boolean onCreateCalledOnce = false;
private boolean onCreateCalled = false;
private boolean isRefreshUi = false;
private boolean hasHardwareNavBar;
private int drawableResId;
private MenuItem menuItem;
// CompatConfigHelper
private boolean forceEnglish;
private Boolean nightModeOverride;
public CompatActivity() {
this.selfReference = new WeakReference<>(this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (!this.onCreateCalled) {
this.getLayoutInflater().setFactory2(new LayoutInflaterFactory(this.getDelegate())
.addOnViewCreatedListeners(WindowInsetsHelper.Companion.getLISTENER(),
(view, parent, name, context, attrs) -> {
if (view instanceof RecyclerView) {
ViewExtensions_RecyclerViewKt.enableStretchOverscroll(
(RecyclerView) view, null);
} else if (view instanceof NestedScrollView) {
ViewExtensions_ScrollViewKt.enableStretchOverscroll(
(NestedScrollView) view, null);
}
}));
this.hasHardwareNavBar = this.hasHardwareNavBar0();
this.onCreateCalledOnce = true;
}
Application application = this.getApplication();
if (application instanceof ApplicationCallbacks) {
((ApplicationCallbacks) application).onCreateCompatActivity(this);
}
super.onCreate(savedInstanceState);
this.onCreateCalled = true;
this.checkResourcesOverrides(
this.forceEnglish, this.nightModeOverride);
}
@Override
protected void onResume() {
this.hasHardwareNavBar = this.hasHardwareNavBar0();
super.onResume();
this.refreshUI();
}
@Override
public void finish() {
this.onActivityResultCallback = null;
boolean fadeOut = this.onCreateCalled && this.getIntent()
.getBooleanExtra(Constants.EXTRA_FADE_OUT, false);
super.finish();
if (fadeOut) {
super.overridePendingTransition(
android.R.anim.fade_in, android.R.anim.fade_out);
}
}
@CallSuper
public void refreshUI() {
// Avoid recursive calls
if (this.isRefreshUi || !this.onCreateCalled) return;
Application application = this.getApplication();
if (application instanceof ApplicationCallbacks) {
this.isRefreshUi = true;
try {
((ApplicationCallbacks) application)
.onRefreshUI(this);
} finally {
this.isRefreshUi = false;
}
this.checkResourcesOverrides(
this.forceEnglish, this.nightModeOverride);
}
}
public final void forceBackPressed() {
if (!this.isFinishing())
super.onBackPressed();
}
@Override
public void onBackPressed() {
if (this.isFinishing()) return;
OnBackPressedCallback onBackPressedCallback = this.onBackPressedCallback;
this.onBackPressedCallback = null;
if (onBackPressedCallback == null ||
!onBackPressedCallback.onBackPressed(this)) {
super.onBackPressed();
}
}
public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
compatActionBar.setDisplayHomeAsUpEnabled(showHomeAsUp);
} else {
android.app.ActionBar actionBar = this.getActionBar();
if (actionBar != null)
actionBar.setDisplayHomeAsUpEnabled(showHomeAsUp);
}
}
public void hideActionBar() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
compatActionBar.hide();
} else {
android.app.ActionBar actionBar = this.getActionBar();
if (actionBar != null)
actionBar.hide();
}
}
public void showActionBar() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
compatActionBar.show();
} else {
android.app.ActionBar actionBar = this.getActionBar();
if (actionBar != null)
actionBar.show();
}
}
public View getActionBarView() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
return compatActionBar.getCustomView();
} else {
android.app.ActionBar actionBar = this.getActionBar();
return actionBar != null ? actionBar.getCustomView() : null;
}
}
@Dimension
@Px
public int getActionBarHeight() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
View customView = null;
if (compatActionBar != null) {
return compatActionBar.isShowing() || ((customView =
compatActionBar.getCustomView()) != null &&
customView.getVisibility() == View.VISIBLE) ?
Math.max(customView == null ? 0 : customView.getHeight(),
compatActionBar.getHeight()) : 0;
} else {
android.app.ActionBar actionBar = this.getActionBar();
return actionBar != null && (actionBar.isShowing() || ((
customView = actionBar.getCustomView()) != null &&
customView.getVisibility() == View.VISIBLE)) ?
Math.max(customView == null ? 0 : customView.getHeight(),
actionBar.getHeight()) : 0;
}
}
public int getActionBarHeight(Activity activity) {
TypedValue tv = new TypedValue();
int actionBarHeight = 0;
if (activity.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}
return actionBarHeight;
}
public void setActionBarBackground(Drawable drawable) {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
compatActionBar.setBackgroundDrawable(drawable);
} else {
android.app.ActionBar actionBar = this.getActionBar();
if (actionBar != null)
actionBar.setBackgroundDrawable(drawable);
}
}
@Dimension @Px
@SuppressLint("InternalInsetResource")
public int getStatusBarHeight() {
int height = WindowInsetsCompat.CONSUMED.getInsets(
WindowInsetsCompat.Type.statusBars()).top;
// Check system resources
int id = Resources.getSystem().getIdentifier(
"status_bar_height", "dimen", "android");
return id <= 0 ? height : Math.max(height,
Resources.getSystem().getDimensionPixelSize(id));
}
@Dimension @Px
@SuppressLint("InternalInsetResource")
public int getNavigationBarHeight() {
int height = WindowInsetsCompat.CONSUMED.getInsets(
WindowInsetsCompat.Type.navigationBars()).bottom;
// Check system resources
int id = Resources.getSystem().getIdentifier(
"config_showNavigationBar", "bool", "android");
Log.d(TAG, "Nav 1: " + id);
if ((id > 0 && Resources.getSystem().getBoolean(id))
|| !this.hasHardwareNavBar()) {
id = Resources.getSystem().getIdentifier(
"navigation_bar_height", "dimen", "android");
Log.d(TAG, "Nav 2: " + id);
return id <= 0 ? height : Math.max(height,
Resources.getSystem().getDimensionPixelSize(id));
}
return height;
}
public boolean hasHardwareNavBar() {
// If onCreate has not been called yet, cached value is not valid
return this.onCreateCalledOnce ? this.hasHardwareNavBar : this.hasHardwareNavBar0();
}
private boolean hasHardwareNavBar0() {
return (ViewConfiguration.get(this).hasPermanentMenuKey() ||
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)) &&
!"0".equals(SystemProperties.get("qemu.hw.mainkeys"));
}
public void setActionBarExtraMenuButton(@DrawableRes int drawableResId,
MenuItem.OnMenuItemClickListener menuClickListener) {
this.setActionBarExtraMenuButton(drawableResId,
menuClickListener, null);
}
public void setActionBarExtraMenuButton(@DrawableRes int drawableResId,
MenuItem.OnMenuItemClickListener menuClickListener,
@StringRes int menuContentDescription) {
this.setActionBarExtraMenuButton(drawableResId,
menuClickListener, this.getString(menuContentDescription));
}
public void setActionBarExtraMenuButton(@DrawableRes int drawableResId,
MenuItem.OnMenuItemClickListener menuClickListener,
CharSequence menuContentDescription) {
Objects.requireNonNull(menuClickListener);
this.drawableResId = drawableResId;
this.menuClickListener = menuClickListener;
this.menuContentDescription = menuContentDescription;
if (this.menuItem != null) {
this.menuItem.setOnMenuItemClickListener(this.menuClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.menuItem.setContentDescription(this.menuContentDescription);
}
this.menuItem.setIcon(this.drawableResId);
this.menuItem.setEnabled(true);
this.menuItem.setVisible(true);
}
}
public void removeActionBarExtraMenuButton() {
this.drawableResId = 0;
this.menuClickListener = null;
this.menuContentDescription = null;
if (this.menuItem != null) {
this.menuItem.setOnMenuItemClickListener(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.menuItem.setContentDescription(null);
}
this.menuItem.setIcon(null);
this.menuItem.setEnabled(false);
this.menuItem.setVisible(false);
}
}
// like setTheme but recreate the activity if needed
public void setThemeRecreate(@StyleRes int resId) {
if (!this.onCreateCalled) {
this.setTheme(resId);
return;
}
if (this.setThemeDynamic == resId)
return;
if (this.setThemeDynamic != 0)
throw new IllegalStateException("setThemeDynamic called recursively");
this.setThemeDynamic = resId;
try {
super.setTheme(resId);
} finally {
this.setThemeDynamic = 0;
}
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
if (resid != 0 && this.setThemeDynamic == resid) {
super.onApplyThemeResource(theme, resid, first);
Activity parent = this.getParent();
(parent == null ? this : parent).recreate();
super.overridePendingTransition(
android.R.anim.fade_in, android.R.anim.fade_out);
} else {
this.compatConfigHelper.checkResourcesOverrides(theme,
this.forceEnglish, this.nightModeOverride);
super.onApplyThemeResource(theme, resid, first);
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
this.compatConfigHelper.checkResourcesOverrides(newConfig,
this.forceEnglish, this.nightModeOverride);
super.onConfigurationChanged(newConfig);
}
public void setOnBackPressedCallback(OnBackPressedCallback onBackPressedCallback) {
this.onBackPressedCallback = onBackPressedCallback;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
android.app.ActionBar actionBar = this.getActionBar();
if (compatActionBar != null ? (compatActionBar.getDisplayOptions() &
androidx.appcompat.app.ActionBar.DISPLAY_HOME_AS_UP) != 0 :
actionBar != null && (actionBar.getDisplayOptions() &
android.app.ActionBar.DISPLAY_HOME_AS_UP) != 0) {
this.onBackPressed();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
this.getMenuInflater().inflate(R.menu.compat_menu, menu);
this.menuItem = menu.findItem(R.id.compat_menu_item);
if (this.menuClickListener != null) {
this.menuItem.setOnMenuItemClickListener(this.menuClickListener);
this.menuItem.setIcon(this.drawableResId);
this.menuItem.setEnabled(true);
this.menuItem.setVisible(true);
}
return super.onCreateOptionsMenu(menu);
}
public void startActivityForResult(Intent intent,
OnActivityResultCallback onActivityResultCallback) {
this.startActivityForResult(intent, null, onActivityResultCallback);
}
@SuppressWarnings("deprecation")
public void startActivityForResult(Intent intent, @Nullable Bundle options,
OnActivityResultCallback onActivityResultCallback) {
super.startActivityForResult(intent, INTENT_ACTIVITY_REQUEST_CODE, options);
this.onActivityResultCallback = onActivityResultCallback;
}
@Override
@CallSuper
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == INTENT_ACTIVITY_REQUEST_CODE) {
OnActivityResultCallback callback = this.onActivityResultCallback;
if (callback != null) {
this.onActivityResultCallback = null;
callback.onActivityResult(resultCode, data);
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
public void setForceEnglish(boolean forceEnglish) {
if (this.forceEnglish == forceEnglish) return;
this.forceEnglish = forceEnglish;
this.checkResourcesOverrides(forceEnglish, this.nightModeOverride);
}
public void setNightModeOverride(Boolean nightModeOverride) {
if (this.nightModeOverride == nightModeOverride) return;
this.nightModeOverride = nightModeOverride;
this.checkResourcesOverrides(this.forceEnglish, nightModeOverride);
}
void propagateResourcesOverride(boolean forceEnglish, Boolean nightModeOverride) {
if (this.forceEnglish == forceEnglish &&
this.nightModeOverride == nightModeOverride) return;
this.forceEnglish = forceEnglish;
this.nightModeOverride = nightModeOverride;
this.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
private void checkResourcesOverrides(boolean forceEnglish, Boolean nightModeOverride) {
if (this.isRefreshUi || !this.onCreateCalled) return; // Wait before reload
this.compatConfigHelper.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
public boolean isLightTheme() {
Resources.Theme theme = this.getTheme();
TypedValue typedValue = new TypedValue();
theme.resolveAttribute(R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data != 0;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
theme.resolveAttribute(android.R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data != 0;
}
}
theme.resolveAttribute(android.R.attr.background, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return ColorUtils.calculateLuminance(typedValue.data) > 0.7D;
}
throw new IllegalStateException("Theme is not a valid theme!");
}
@ColorInt
public final int getColorCompat(@ColorRes @AttrRes int color) {
TypedValue typedValue = new TypedValue();
this.getTheme().resolveAttribute(color, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return typedValue.data;
}
return ContextCompat.getColor(this, color);
}
public Locale getUserLocale() {
return this.compatConfigHelper.getUserLocale();
}
public static CompatActivity getCompatActivity(View view) {
return getCompatActivity(view.getContext());
}
public static CompatActivity getCompatActivity(Fragment fragment) {
return getCompatActivity(fragment.getContext());
}
public static CompatActivity getCompatActivity(Context context) {
while (!(context instanceof CompatActivity)) {
if (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
} else return null;
}
return (CompatActivity) context;
}
public WeakReference<CompatActivity> asWeakReference() {
return this.selfReference;
}
@FunctionalInterface
public interface OnActivityResultCallback {
void onActivityResult(int resultCode, @Nullable Intent data);
}
@FunctionalInterface
public interface OnBackPressedCallback {
boolean onBackPressed(CompatActivity compatActivity);
}
public interface ApplicationCallbacks {
void onCreateCompatActivity(CompatActivity compatActivity);
void onRefreshUI(CompatActivity compatActivity);
}
}

@ -1,144 +0,0 @@
package com.fox2code.mmm.compat;
import android.app.Application;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import androidx.annotation.AttrRes;
import androidx.annotation.CallSuper;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.fox2code.mmm.R;
import java.lang.ref.WeakReference;
/**
* I will probably outsource this to a separate library later
*/
public class CompatApplication extends Application implements CompatActivity.ApplicationCallbacks {
private static final String TAG = "CompatApplication";
private final CompatConfigHelper compatConfigHelper = new CompatConfigHelper(this);
private WeakReference<CompatActivity> lastCompatActivity;
// CompatConfigHelper
private boolean forceEnglish;
private Boolean nightModeOverride;
private boolean propagateOverrides;
public CompatApplication() {}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
this.compatConfigHelper.checkResourcesOverrides(newConfig,
this.forceEnglish, this.nightModeOverride);
super.onConfigurationChanged(newConfig);
}
public void setForceEnglish(boolean forceEnglish) {
if (this.forceEnglish != forceEnglish) {
this.forceEnglish = forceEnglish;
this.checkResourcesOverrides(forceEnglish, this.nightModeOverride);
}
// Propagate even if local value didn't changed
if (this.propagateOverrides && this.lastCompatActivity != null) {
CompatActivity compatActivity = this.lastCompatActivity.get();
if (compatActivity != null)
compatActivity.setForceEnglish(forceEnglish);
}
}
public void setNightModeOverride(Boolean nightModeOverride) {
if (this.nightModeOverride != nightModeOverride) {
this.nightModeOverride = nightModeOverride;
this.checkResourcesOverrides(this.forceEnglish, nightModeOverride);
}
// Propagate even if local value didn't changed
if (this.propagateOverrides && this.lastCompatActivity != null) {
CompatActivity compatActivity = this.lastCompatActivity.get();
if (compatActivity != null)
compatActivity.setNightModeOverride(nightModeOverride);
}
}
public boolean isPropagateOverrides() {
return propagateOverrides;
}
public void setPropagateOverrides(boolean propagateOverrides) {
this.propagateOverrides = propagateOverrides;
WeakReference<CompatActivity> lastCompatActivity = this.lastCompatActivity;
if (lastCompatActivity != null) {
Log.d(TAG, "setPropagateOverrides(" + // This should be avoided
propagateOverrides + ") called after first activity created!");
CompatActivity compatActivity = lastCompatActivity.get();
if (compatActivity != null && propagateOverrides) {
this.propagateOverrides(compatActivity);
}
}
}
private void checkResourcesOverrides(boolean forceEnglish, Boolean nightModeOverride) {
this.compatConfigHelper.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
public boolean isLightTheme() {
Resources.Theme theme = this.getTheme();
TypedValue typedValue = new TypedValue();
theme.resolveAttribute(R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data == 1;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
theme.resolveAttribute(android.R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data == 1;
}
}
theme.resolveAttribute(android.R.attr.background, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return ColorUtils.calculateLuminance(typedValue.data) > 0.7D;
}
throw new IllegalStateException("Theme is not a valid theme!");
}
@ColorInt
public final int getColorCompat(@ColorRes @AttrRes int color) {
TypedValue typedValue = new TypedValue();
this.getTheme().resolveAttribute(color, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return typedValue.data;
}
return ContextCompat.getColor(this, color);
}
@Override
@CallSuper
public void onCreateCompatActivity(CompatActivity compatActivity) {
this.lastCompatActivity = compatActivity.selfReference;
if (this.propagateOverrides) {
this.propagateOverrides(compatActivity);
}
}
@Override
@CallSuper
public void onRefreshUI(CompatActivity compatActivity) {
this.lastCompatActivity = compatActivity.selfReference;
if (this.propagateOverrides) {
this.propagateOverrides(compatActivity);
}
}
private void propagateOverrides(CompatActivity compatActivity) {
compatActivity.propagateResourcesOverride(
this.forceEnglish, this.nightModeOverride);
}
}

@ -1,94 +0,0 @@
package com.fox2code.mmm.compat;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;
/**
* I will probably outsource this to a separate library later
*/
final class CompatConfigHelper {
// ENGLISH like this is an unnatural local, as it doesn't precise the country
// All english locales settable by the user precise the country (Ex: en-US)
private static final Locale englishLocale = Locale.ENGLISH;
private static final Object englishLocales =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
new LocaleList(englishLocale) : null;
private final Context context;
private Object userLocales;
private Locale userLocale;
CompatConfigHelper(Context context) {
this.context = context;
}
void checkResourcesOverrides(boolean forceEnglish,
Boolean nightModeOverride) {
this.checkResourcesOverrides(
this.context.getTheme(),
forceEnglish, nightModeOverride);
}
void checkResourcesOverrides(Resources.Theme theme, boolean forceEnglish,
Boolean nightModeOverride) {
Resources res = theme.getResources();
if (this.checkResourcesOverrides(res.getConfiguration(),
forceEnglish, nightModeOverride)) {
res.updateConfiguration(
res.getConfiguration(),
res.getDisplayMetrics());
}
}
boolean checkResourcesOverrides(Configuration conf, boolean forceEnglish,
Boolean nightModeOverride) {
Locale current = conf.locale;
boolean didChange = false;
boolean wasForceEnglish = englishLocale.equals(current);
if (forceEnglish != wasForceEnglish) {
didChange = true;
if (forceEnglish) {
this.userLocale = conf.locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.userLocales = conf.getLocales();
}
conf.locale = englishLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
conf.setLocales((LocaleList) englishLocales);
}
} else {
conf.locale = this.userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
conf.setLocales((LocaleList) this.userLocales);
}
}
}
int nightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK;
int sysNightMode = Resources.getSystem()
.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeOverride == null ? sysNightMode != nightMode :
nightMode != (nightModeOverride ?
Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO)) {
didChange = true;
nightMode = nightModeOverride == null ? sysNightMode : nightModeOverride ?
Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO;
conf.uiMode = nightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
}
if (!forceEnglish && !wasForceEnglish) {
this.userLocale = null;
this.userLocales = null;
}
return didChange;
}
public Locale getUserLocale() {
// Only use cached value if force english
Locale locale = this.context.getResources().getConfiguration().locale;
return englishLocale.equals(locale) ? this.userLocale : locale;
}
}

@ -1,21 +0,0 @@
package com.fox2code.mmm.compat;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import androidx.annotation.Dimension;
import androidx.annotation.Px;
public final class CompatDisplay {
@Dimension @Px
public static int dpToPixel(@Dimension(unit = Dimension.DP) float dp){
return (int) (dp * ((float) Resources.getSystem().getDisplayMetrics()
.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
}
@Dimension(unit = Dimension.DP)
public static float pixelsToDp(@Dimension @Px int px){
return (px / ((float) Resources.getSystem().getDisplayMetrics()
.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
}
}

@ -1,96 +0,0 @@
package com.fox2code.mmm.compat;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.TypedValue;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.fox2code.mmm.R;
/**
* I will probably outsource this to a separate library later
*/
public class CompatThemeWrapper extends ContextThemeWrapper {
private final CompatConfigHelper compatConfigHelper = new CompatConfigHelper(this);
private boolean canReload;
// CompatConfigHelper
private boolean forceEnglish;
private Boolean nightModeOverride;
public CompatThemeWrapper(Context base, @StyleRes int themeResId) {
super(base, themeResId);
this.canReload = true;
this.checkResourcesOverrides(
this.forceEnglish, this.nightModeOverride);
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
boolean couldReload = this.canReload;
if (couldReload) this.canReload = false;
this.compatConfigHelper.checkResourcesOverrides(theme,
this.forceEnglish, this.nightModeOverride);
super.onApplyThemeResource(theme, resid, first);
if (couldReload) this.canReload = true;
// In case value change while reload, should have no effect
this.compatConfigHelper.checkResourcesOverrides(theme,
this.forceEnglish, this.nightModeOverride);
}
public void setForceEnglish(boolean forceEnglish) {
if (this.forceEnglish == forceEnglish) return;
this.forceEnglish = forceEnglish;
this.checkResourcesOverrides(forceEnglish, this.nightModeOverride);
}
public void setNightModeOverride(Boolean nightModeOverride) {
if (this.nightModeOverride == nightModeOverride) return;
this.nightModeOverride = nightModeOverride;
this.checkResourcesOverrides(this.forceEnglish, nightModeOverride);
}
private void checkResourcesOverrides(boolean forceEnglish,Boolean nightModeOverride) {
if (!this.canReload) return; // Do not reload during theme reload
this.compatConfigHelper.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
public boolean isLightTheme() {
Resources.Theme theme = this.getTheme();
TypedValue typedValue = new TypedValue();
theme.resolveAttribute(R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data == 1;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
theme.resolveAttribute(android.R.attr.isLightTheme, typedValue, true);
if (typedValue.type == TypedValue.TYPE_INT_BOOLEAN) {
return typedValue.data == 1;
}
}
theme.resolveAttribute(android.R.attr.background, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return ColorUtils.calculateLuminance(typedValue.data) > 0.7D;
}
throw new IllegalStateException("Theme is not a valid theme!");
}
@ColorInt
public final int getColorCompat(@ColorRes @AttrRes int color) {
TypedValue typedValue = new TypedValue();
this.getTheme().resolveAttribute(color, typedValue, true);
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
return typedValue.data;
}
return ContextCompat.getColor(this, color);
}
}

@ -2,7 +2,6 @@ package com.fox2code.mmm.installer;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
@ -17,13 +16,13 @@ import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.androidansi.AnsiConstants;
import com.fox2code.androidansi.AnsiParser;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.module.ActionButtonType;
import com.fox2code.mmm.utils.FastException;
import com.fox2code.mmm.utils.Files;
@ -50,7 +49,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class InstallerActivity extends CompatActivity {
public class InstallerActivity extends FoxActivity {
private static final String TAG = "InstallerActivity";
public LinearProgressIndicator progressIndicator;
public ExtendedFloatingActionButton rebootFloatingButton;
@ -267,8 +266,8 @@ public class InstallerActivity extends CompatActivity {
}
installerMonitor = new InstallerMonitor(installScript);
installJob = Shell.cmd("export MMM_EXT_SUPPORT=1",
"export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" :
Resources.getSystem().getConfiguration().locale.toLanguageTag()),
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().locale.toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
AnsiConstants.ANSI_CMD_SUPPORT,
@ -360,7 +359,7 @@ public class InstallerActivity extends CompatActivity {
installExecutable = this.extractInstallScript("anykernel3_installer.sh");
if (installExecutable == null) {
this.setInstallStateFinished(false,
"! Failed to extract AnyKernel3 install script", null);
"! Failed to extract AnyKernel3 install script", "");
return;
}
// "unshare -m" is needed to force mount namespace isolation.
@ -379,7 +378,7 @@ public class InstallerActivity extends CompatActivity {
installExecutable = this.extractInstallScript("module_installer_compat.sh");
if (installExecutable == null) {
this.setInstallStateFinished(false,
"! Failed to extract Magisk module install script", null);
"! Failed to extract Magisk module install script", "");
return;
}
installCommand = ASH + " \"" +
@ -387,7 +386,7 @@ public class InstallerActivity extends CompatActivity {
" 3 1 \"" + file.getAbsolutePath() + "\"";
} else {
this.setInstallStateFinished(false,
"! Zip file is not a valid Magisk module or AnyKernel3 zip!", null);
"! Zip file is not a valid Magisk module or AnyKernel3 zip!", "");
return;
}
installerMonitor = new InstallerMonitor(installExecutable);
@ -403,11 +402,11 @@ public class InstallerActivity extends CompatActivity {
installCommand).to(installerController, installerMonitor);
} else {
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NO_ANSI) != 0)
this.installerTerminal.enableAnsi();
else this.installerTerminal.disableAnsi();
this.installerTerminal.disableAnsi();
else this.installerTerminal.enableAnsi();
installJob = Shell.cmd(arch32, "export MMM_EXT_SUPPORT=1",
"export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" :
Resources.getSystem().getConfiguration().locale.toLanguageTag()),
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().locale.toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
this.installerTerminal.isAnsiEnabled() ?
@ -697,7 +696,7 @@ public class InstallerActivity extends CompatActivity {
if (message != null && !message.isEmpty())
this.installerTerminal.addLine(message);
if (!optionalLink.isEmpty()) {
if (optionalLink != null && !optionalLink.isEmpty()) {
this.setActionBarExtraMenuButton(ActionButtonType.supportIconForUrl(optionalLink),
menu -> {
IntentHelper.openUrl(this, optionalLink);

@ -42,6 +42,11 @@ public class InstallerInitializer extends Shell.Initializer {
InstallerInitializer.MAGISK_PATH + "/.magisk/mirror";
}
public static String peekModulesPath() {
return InstallerInitializer.MAGISK_PATH == null ? null :
InstallerInitializer.MAGISK_PATH + "/.magisk/modules";
}
public static int peekMagiskVersion() {
return InstallerInitializer.MAGISK_VERSION_CODE;
}

@ -1,32 +0,0 @@
package com.fox2code.mmm.manager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.installer.InstallerInitializer;
public class ModuleBootReceive extends BroadcastReceiver {
private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !BOOT_COMPLETED.equals(intent.getAction())
|| !MainApplication.hasGottenRootAccess()) {
return;
}
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
MainApplication.notifyBootListenerCompleted();
ModuleManager.getINSTANCE().scan();
}
@Override
public void onFailure(int error) {
MainApplication.setHasGottenRootAccess(false);
}
});
}
}

@ -4,6 +4,7 @@ import android.content.SharedPreferences;
import android.util.Log;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.PropUtils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
@ -43,7 +44,7 @@ public final class ModuleManager {
}
// MultiThread friendly method
public final void scan() {
public void scan() {
if (!this.scanning) {
// Do scan
synchronized (scanLock) {
@ -61,11 +62,11 @@ public final class ModuleManager {
}
// Pause execution until the scan is completed if one is currently running
public final void afterScan() {
public void afterScan() {
if (this.scanning) synchronized (this.scanLock) {}
}
public final void runAfterScan(Runnable runnable) {
public void runAfterScan(Runnable runnable) {
synchronized (this.scanLock) {
runnable.run();
}
@ -86,7 +87,13 @@ public final class ModuleManager {
v.support = null;
v.config = null;
}
String modulesPath = InstallerInitializer.peekModulesPath();
String[] modules = new SuFile("/data/adb/modules").list();
boolean needFallback = modulesPath == null ||
!new SuFile(modulesPath).exists();
if (needFallback) {
Log.e(TAG, "Failed to detect modules folder, using fallback instead.");
}
if (modules != null) {
for (String module : modules) {
if (!new SuFile("/data/adb/modules/" + module).isDirectory())
@ -99,28 +106,22 @@ public final class ModuleManager {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING_ONLY;
}
moduleInfo.flags &= ~FLAGS_RESET_UPDATE;
boolean disabled = new SuFile(
"/data/adb/modules/" + module + "/disable").exists();
if (disabled) {
if (new SuFile("/data/adb/modules/" + module + "/disable").exists()) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_DISABLED;
if (firstBoot && firstScan) {
editor.putBoolean("module_" + moduleInfo.id + "_active", true);
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_MAYBE_ACTIVE;
}
} else {
} else if (needFallback && firstScan) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE;
editor.putBoolean("module_" + moduleInfo.id + "_active", true);
}
if (new SuFile("/data/adb/modules/" + module + "/remove").exists()) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UNINSTALLING;
}
if ((!needFallback && new SuFile(modulesPath, module).exists()) || (!firstBoot
&& bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false))) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE;
if (firstScan) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE;
editor.putBoolean("module_" + moduleInfo.id + "_active", true);
} else if (!moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_MAYBE_ACTIVE) &&
bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false)) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE;
}
}
boolean uninstalling = new SuFile(
"/data/adb/modules/" + module + "/remove").exists();
if (uninstalling) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UNINSTALLING;
}
try {
PropUtils.readProperties(moduleInfo,
"/data/adb/modules/" + module + "/module.prop", true);

@ -1,9 +1,7 @@
package com.fox2code.mmm.markdown;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@ -12,33 +10,24 @@ import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.graphics.ColorUtils;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.OverScrollManager;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.io.IOException;
@ -48,8 +37,7 @@ import java.util.HashMap;
import eightbitlab.com.blurview.BlurView;
import eightbitlab.com.blurview.RenderScriptBlur;
public class MarkdownActivity extends CompatActivity {
public class MarkdownActivity extends FoxActivity {
private static final String TAG = "MarkdownActivity";
private static final HashMap<String, String> redirects = new HashMap<>(4);
private static final String[] variants = new String[]{
@ -239,6 +227,10 @@ public class MarkdownActivity extends CompatActivity {
this.updateBlurState();
}
@Override
protected void onWindowUpdated() {
this.updateScreenInsets();
}
private void addChip(MarkdownChip markdownChip) {
this.makeChip(this.getString(markdownChip.title),

@ -11,11 +11,11 @@ import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AlertDialog;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
@ -121,7 +121,7 @@ public enum ActionButtonType {
moduleInfo.name, moduleInfo.config, updateZipChecksum);
});
}
int dim5dp = CompatDisplay.dpToPixel(5);
int dim5dp = FoxDisplay.dpToPixel(5);
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp);
AlertDialog alertDialog = builder.show();
for (int i = -3; i < 0; i++) {
@ -176,7 +176,7 @@ public enum ActionButtonType {
Toast.LENGTH_SHORT).show();
} else {
moduleHolder.moduleInfo = null;
CompatActivity.getCompatActivity(button).refreshUI();
FoxActivity.getFoxActivity(button).refreshUI();
}
}).setNegativeButton(R.string.master_delete_no, (v, i) -> {
}).create().show();
@ -218,13 +218,8 @@ public enum ActionButtonType {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
int icon = R.drawable.ic_baseline_monetization_on_24;
if (moduleInfo.donate.startsWith("https://www.paypal.me/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (moduleInfo.donate.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
button.setChipIcon(button.getContext().getDrawable(icon));
button.setChipIcon(button.getContext().getDrawable(
donateIconForUrl(moduleInfo.donate)));
button.setText(R.string.donate);
}
@ -252,6 +247,19 @@ public enum ActionButtonType {
return icon;
}
@DrawableRes
public static int donateIconForUrl(String url) {
int icon = R.drawable.ic_baseline_monetization_on_24;
if (url.startsWith("https://www.paypal.me/") ||
url.startsWith("https://www.paypal.com/paypalme/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (url.startsWith("https://patreon.com/") ||
url.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
return icon;
}
@DrawableRes
private final int iconId;

@ -17,12 +17,13 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.cardview.widget.CardView;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.NotificationType;
import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
@ -267,10 +268,10 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.moduleOptionsHolder.setVisibility(View.GONE);
this.moduleLayoutHelper.setVisibility(View.GONE);
} else if (this.actionButtonsTypes.size() > 2 || !hasUpdateText) {
this.moduleLayoutHelper.setMinHeight(Math.max(CompatDisplay.dpToPixel(36F),
this.moduleOptionsHolder.getHeight() - CompatDisplay.dpToPixel(14F)));
this.moduleLayoutHelper.setMinHeight(Math.max(FoxDisplay.dpToPixel(36F),
this.moduleOptionsHolder.getHeight() - FoxDisplay.dpToPixel(14F)));
} else {
this.moduleLayoutHelper.setMinHeight(CompatDisplay.dpToPixel(4F));
this.moduleLayoutHelper.setMinHeight(FoxDisplay.dpToPixel(4F));
}
this.cardView.setClickable(false);
if (moduleHolder.isModuleHolder() &&
@ -365,6 +366,9 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
if (bgColor == Color.WHITE) {
bgColor = 0xFFF8F8F8;
}
if (theme.getResources().getBoolean(R.bool.force_transparency)) {
bgColor = ColorUtils.setAlphaComponent(bgColor, 0x80);
}
this.titleText.setTextColor(fgColor);
this.buttonAction.setColorFilter(fgColor);
this.cardView.setCardBackgroundColor(bgColor);

@ -0,0 +1,49 @@
package com.fox2code.mmm.repo;
import android.content.SharedPreferences;
import com.fox2code.mmm.utils.Http;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public final class CustomRepoData extends RepoData {
boolean loadedExternal;
String override;
CustomRepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
super(url, cacheRoot, cachedPreferences);
}
@Override
public boolean isEnabledByDefault() {
return this.override != null || this.loadedExternal;
}
@Override
public String getPreferenceId() {
return this.override == null ?
this.id : this.override;
}
@Override
public boolean isLimited() {
return true;
}
public void quickPrePopulate() throws IOException, JSONException {
JSONObject jsonObject = new JSONObject(
new String(Http.doHttpGet(this.getUrl(),
false), StandardCharsets.UTF_8));
this.name = jsonObject.getString("name").trim();
this.website = jsonObject.optString("website");
this.support = jsonObject.optString("support");
this.donate = jsonObject.optString("donate");
this.submitModule = jsonObject.optString("submitModule");
}
}

@ -0,0 +1,125 @@
package com.fox2code.mmm.repo;
import android.content.Context;
import android.content.SharedPreferences;
import com.fox2code.mmm.MainApplication;
public class CustomRepoManager {
private static final boolean AUTO_RECOMPILE = true;
public static final int MAX_CUSTOM_REPOS = 5;
private final MainApplication mainApplication;
private final RepoManager repoManager;
private final String[] customRepos;
private int customReposCount;
boolean dirty;
CustomRepoManager(MainApplication mainApplication, RepoManager repoManager) {
this.mainApplication = mainApplication;
this.repoManager = repoManager;
this.customRepos = new String[MAX_CUSTOM_REPOS];
this.customReposCount = 0;
SharedPreferences sharedPreferences = this.getSharedPreferences();
int lastFilled = 0;
for (int i = 0; i < MAX_CUSTOM_REPOS; i++) {
String repo = sharedPreferences.getString("repo_" + i, "");
if (!repo.isEmpty() && !RepoManager.isBuiltInRepo(repo)) {
lastFilled = i;
int index = AUTO_RECOMPILE ?
this.customReposCount : i;
this.customRepos[index] = repo;
this.customReposCount++;
((CustomRepoData) this.repoManager.addOrGet(repo))
.override = "custom_repo_" + index;
}
}
if (AUTO_RECOMPILE && (lastFilled + 1) != this.customReposCount) {
SharedPreferences.Editor editor = sharedPreferences.edit().clear();
for (int i = 0; i < MAX_CUSTOM_REPOS; i++) {
if (this.customRepos[i] != null)
editor.putString("repo_" + i, this.customRepos[i]);
}
editor.apply();
}
}
private SharedPreferences getSharedPreferences() {
return this.mainApplication.getSharedPreferences(
"mmm_custom_repos", Context.MODE_PRIVATE);
}
public CustomRepoData addRepo(String repo) {
if (RepoManager.isBuiltInRepo(repo))
throw new IllegalArgumentException("Can't add built-in repo to custom repos");
for (String repoEntry : this.customRepos) {
if (repo.equals(repoEntry))
return (CustomRepoData) this.repoManager.get(repoEntry);
}
int i = 0;
while (customRepos[i] != null) i++;
customRepos[i] = repo;
this.getSharedPreferences().edit()
.putString("repo_" + i, repo).apply();
this.dirty = true;
CustomRepoData customRepoData = (CustomRepoData)
this.repoManager.addOrGet(repo);
customRepoData.override = "custom_repo_" + i;
customRepoData.updateEnabledState();
return customRepoData;
}
public CustomRepoData getRepo(int index) {
if (index >= MAX_CUSTOM_REPOS) return null;
String repo = customRepos[index];
return repo == null ? null :
(CustomRepoData) this.repoManager.get(repo);
}
public void removeRepo(int index) {
String oldRepo = customRepos[index];
if (oldRepo != null) {
customRepos[index] = null;
customReposCount--;
CustomRepoData customRepoData =
(CustomRepoData) this.repoManager.get(oldRepo);
if (customRepoData != null) {
customRepoData.setEnabled(false);
customRepoData.override = null;
}
this.getSharedPreferences().edit()
.remove("repo_" + index).apply();
this.dirty = true;
}
}
public boolean hasRepo(String repo) {
for (String repoEntry : this.customRepos) {
if (repo.equals(repoEntry))
return true;
}
return false;
}
public boolean canAddRepo() {
return this.customReposCount < MAX_CUSTOM_REPOS;
}
public boolean canAddRepo(String repo) {
if (RepoManager.isBuiltInRepo(repo) ||
this.hasRepo(repo) || !this.canAddRepo())
return false;
return repo.startsWith("https://") &&
repo.indexOf('/', 9) != -1;
}
public int getRepoCount() {
return this.customReposCount;
}
public boolean needUpdate() {
boolean needUpdate = this.dirty;
if (needUpdate) this.dirty = false;
return needUpdate;
}
}

@ -1,6 +1,9 @@
package com.fox2code.mmm.repo;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
@ -32,7 +35,9 @@ public class RepoData extends XRepo {
public final File metaDataCache;
public final HashMap<String, RepoModule> moduleHashMap;
public long lastUpdate;
public String name;
protected String defaultName, defaultWebsite,
defaultSupport, defaultDonate, defaultSubmitModule;
public String name, website, support, donate, submitModule;
private boolean enabled; // Cache for speed
protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
@ -43,8 +48,10 @@ public class RepoData extends XRepo {
this.metaDataCache = new File(cacheRoot, "modules.json");
this.moduleHashMap = new HashMap<>();
this.name = this.url; // Set url as default name
this.enabled = MainApplication.getSharedPreferences()
this.enabled = !this.isLimited() && MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
this.defaultName = url;
this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/";
if (!this.cacheRoot.isDirectory()) {
this.cacheRoot.mkdirs();
} else {
@ -96,6 +103,7 @@ public class RepoData extends XRepo {
String moduleZipUrl = module.getString("zip_url");
String moduleChecksum = module.optString("checksum");
String moduleStars = module.optString("stars");
String moduleDownloads = module.optString("downloads");
RepoModule repoModule = this.moduleHashMap.get(moduleId);
if (repoModule == null) {
repoModule = new RepoModule(this, moduleId);
@ -119,6 +127,11 @@ public class RepoData extends XRepo {
repoModule.qualityValue = Integer.parseInt(moduleStars);
repoModule.qualityText = R.string.module_stars;
} catch (NumberFormatException ignored) {}
} else if (!moduleDownloads.isEmpty()) {
try {
repoModule.qualityValue = Integer.parseInt(moduleDownloads);
repoModule.qualityText = R.string.module_downloads;
} catch (NumberFormatException ignored) {}
}
}
// Remove no longer existing modules
@ -133,13 +146,17 @@ public class RepoData extends XRepo {
// Update final metadata
this.name = name;
this.lastUpdate = lastUpdate;
this.website = jsonObject.optString("website");
this.support = jsonObject.optString("support");
this.donate = jsonObject.optString("donate");
this.submitModule = jsonObject.optString("submitModule");
}
return newModules;
}
@Override
public boolean isEnabledByDefault() {
return !BuildConfig.DISABLED_REPOS.contains(this.id);
return BuildConfig.ENABLED_REPOS.contains(this.id);
}
public void storeMetadata(RepoModule repoModule,byte[] data) throws IOException {
@ -166,12 +183,6 @@ public class RepoData extends XRepo {
return false;
}
public String getNameOrFallback(String fallback) {
return this.name == null ||
this.name.equals(this.url) ?
fallback : this.name;
}
@Override
public boolean isEnabled() {
return this.enabled;
@ -181,15 +192,64 @@ public class RepoData extends XRepo {
public void setEnabled(boolean enabled) {
this.enabled = enabled;
MainApplication.getSharedPreferences().edit()
.putBoolean("pref_" + this.id + "_enabled", enabled).apply();
.putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply();
}
public void updateEnabledState() {
this.enabled = MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
.getBoolean("pref_" + this.getPreferenceId() + "_enabled", this.isEnabledByDefault());
}
public String getUrl() {
return this.url;
}
public boolean isLimited() {
return false;
}
public String getPreferenceId() {
return this.id;
}
private static boolean isNonNull(String str) {
return str != null && !str.isEmpty() && !"null".equals(str);
}
// Repo data info getters
@NonNull
public String getName() {
if (isNonNull(this.name))
return this.name;
if (this.defaultName != null)
return this.defaultName;
return this.url;
}
@NonNull
public String getWebsite() {
if (isNonNull(this.website))
return this.website;
if (this.defaultWebsite != null)
return this.defaultWebsite;
return this.url;
}
public String getSupport() {
if (isNonNull(this.support))
return this.support;
return this.defaultSupport;
}
public String getDonate() {
if (isNonNull(this.donate))
return this.donate;
return this.defaultDonate;
}
public String getSubmitModule() {
if (isNonNull(this.submitModule))
return this.submitModule;
return this.defaultSubmitModule;
}
}

@ -21,6 +21,13 @@ import java.util.List;
public final class RepoManager {
private static final String TAG = "RepoManager";
private static final String MAGISK_REPO_MANAGER =
"https://magisk-modules-repo.github.io/submission/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 =
@ -35,6 +42,8 @@ public final class RepoManager {
public static final String DG_MAGISK_REPO =
"https://repo.dergoogler.com/modules.json";
public static final String DG_MAGISK_REPO_GITHUB =
"https://googlers-magisk-repo.github.io/modules.json";
private static final Object lock = new Object();
private static volatile RepoManager INSTANCE;
@ -60,6 +69,7 @@ public final class RepoManager {
private final LinkedHashMap<String, RepoData> repoData;
private final HashMap<String, RepoModule> modules;
private final AndroidacyRepoData androidacyRepoData;
private final CustomRepoManager customRepoManager;
private boolean initialized;
private RepoManager(MainApplication mainApplication) {
@ -68,9 +78,16 @@ public final class RepoManager {
this.repoData = new LinkedHashMap<>();
this.modules = new HashMap<>();
// We do not have repo list config yet.
this.addRepoData(MAGISK_ALT_REPO);
this.androidacyRepoData =
this.addAndroidacyRepoData();
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";
RepoData dgRepo = this.addRepoData(
DG_MAGISK_REPO_GITHUB, "Googlers Magisk Repo");
dgRepo.defaultWebsite = "https://dergoogler.com/repo";
this.androidacyRepoData = this.addAndroidacyRepoData();
this.customRepoManager = new CustomRepoManager(mainApplication, this);
// Populate default cache
for (RepoData repoData:this.repoData.values()) {
this.populateDefaultCache(repoData);
@ -104,6 +121,10 @@ public final class RepoManager {
}
public RepoData addOrGet(String url) {
return this.addOrGet(url, null);
}
public RepoData addOrGet(String url, String fallBackName) {
RepoData repoData;
synchronized (this.repoUpdateLock) {
repoData = this.repoData.get(url);
@ -111,7 +132,7 @@ public final class RepoManager {
if (ANDROIDACY_MAGISK_REPO_ENDPOINT.equals(url)) {
return this.addAndroidacyRepoData();
} else {
return this.addRepoData(url);
return this.addRepoData(url, fallBackName);
}
}
}
@ -142,6 +163,8 @@ public final class RepoManager {
// MultiThread friendly method
public void update(UpdateListener updateListener) {
if (updateListener == null)
updateListener = value -> {};
if (!this.repoUpdating) {
// Do scan
synchronized (this.repoUpdateLock) {
@ -229,8 +252,13 @@ public final class RepoManager {
}
public void updateEnabledStates() {
for (RepoData repoData:this.repoData.values())
repoData.updateEnabledState();
for (RepoData repoData:this.repoData.values()) {
boolean wasEnabled = repoData.isEnabled();
repoData.updateEnabledState();
if (!wasEnabled && repoData.isEnabled()) {
this.customRepoManager.dirty = true;
}
}
}
public HashMap<String, RepoModule> getModules() {
@ -250,6 +278,7 @@ public final class RepoManager {
case ANDROIDACY_MAGISK_REPO_ENDPOINT:
return "androidacy_repo";
case DG_MAGISK_REPO:
case DG_MAGISK_REPO_GITHUB:
return "dg_magisk_repo";
default:
return "repo_" + Hashes.hashSha1(
@ -257,12 +286,44 @@ public final class RepoManager {
}
}
private RepoData addRepoData(String url) {
static boolean isBuiltInRepo(String repo) {
switch (repo) {
case RepoManager.MAGISK_ALT_REPO:
case RepoManager.MAGISK_ALT_REPO_JSDELIVR:
case RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT:
case RepoManager.DG_MAGISK_REPO:
case RepoManager.DG_MAGISK_REPO_GITHUB:
return true;
}
return false;
}
private RepoData addRepoData(String url, String fallBackName) {
if (MAGISK_ALT_REPO_JSDELIVR.equals(url))
url = MAGISK_ALT_REPO;
if (DG_MAGISK_REPO.equals(url))
url = DG_MAGISK_REPO_GITHUB;
String id = internalIdOfUrl(url);
File cacheRoot = new File(this.mainApplication.getCacheDir(), id);
SharedPreferences sharedPreferences = this.mainApplication
.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE);
RepoData repoData = new RepoData(url, cacheRoot, sharedPreferences);
RepoData repoData = id.startsWith("repo_") ?
new CustomRepoData(url, cacheRoot, sharedPreferences) :
new RepoData(url, cacheRoot, sharedPreferences);
if (fallBackName != null && !fallBackName.isEmpty()) {
if (repoData instanceof CustomRepoData) {
((CustomRepoData) repoData).loadedExternal = true;
this.customRepoManager.dirty = true;
repoData.updateEnabledState();
}
repoData.defaultName = fallBackName;
}
switch (url) {
case MAGISK_REPO:
case MAGISK_REPO_MANAGER: {
repoData.defaultWebsite = MAGISK_REPO_HOMEPAGE;
}
}
this.repoData.put(url, repoData);
if (this.initialized) {
this.populateDefaultCache(repoData);
@ -283,4 +344,8 @@ public final class RepoManager {
public AndroidacyRepoData getAndroidacyRepoData() {
return this.androidacyRepoData;
}
public CustomRepoManager getCustomRepoManager() {
return customRepoManager;
}
}

@ -5,12 +5,17 @@ import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.ListPreference;
@ -18,28 +23,42 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.TwoStatePreference;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.foxcompat.FoxViewCompat;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.OverScrollManager;
import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.module.ActionButtonType;
import com.fox2code.mmm.repo.CustomRepoData;
import com.fox2code.mmm.repo.CustomRepoManager;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.rosettax.LanguageActivity;
import com.fox2code.rosettax.LanguageSwitcher;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.internal.TextWatcherAdapter;
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.mikepenz.aboutlibraries.LibsBuilder;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import org.json.JSONException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Random;
public class SettingsActivity extends CompatActivity implements LanguageActivity {
public class SettingsActivity extends FoxActivity implements LanguageActivity {
private static final int LANGUAGE_SUPPORT_LEVEL = 1;
private static final String TAG = "SettingsActivity";
private static int devModeStep = 0;
@Override
@ -59,19 +78,26 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
}
@Override
@SuppressLint("InlinedApi")
public void refreshRosettaX() {
Intent mStartActivity = new Intent(this, MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
@SuppressLint("InlinedApi") PendingIntent mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId,
PendingIntent mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0); // Exit app process
}
@Override
protected void onPause() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onPause();
}
public static class SettingsFragment extends PreferenceFragmentCompat
implements CompatActivity.OnBackPressedCallback {
implements FoxActivity.OnBackPressedCallback {
@Override
@SuppressWarnings("ConstantConditions")
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -93,7 +119,7 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
devModeStep = 0;
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
CompatActivity.getCompatActivity(this).setThemeRecreate(
FoxActivity.getFoxActivity(this).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
return true;
@ -109,6 +135,14 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
disableMonet.setSummary(R.string.require_android_12);
disableMonet.setEnabled(false);
}
disableMonet.setOnPreferenceClickListener(preference -> {
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
((FoxActivity) this.requireActivity()).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
return true;
});
findPreference("pref_dns_over_https").setOnPreferenceChangeListener((p, v) -> {
Http.setDoh((Boolean) v);
@ -119,10 +153,12 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
HashSet<String> supportedLocales = new HashSet<>();
supportedLocales.add("cs");
supportedLocales.add("de");
supportedLocales.add("el");
supportedLocales.add("es-rMX");
supportedLocales.add("et");
supportedLocales.add("fr");
supportedLocales.add("id");
supportedLocales.add("it");
supportedLocales.add("ja");
supportedLocales.add("nb-rNO");
supportedLocales.add("pl");
@ -144,18 +180,20 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
return true;
});
int nightModeFlags =
getContext().getResources().getConfiguration().uiMode &
Configuration.UI_MODE_NIGHT_MASK;
switch (nightModeFlags) {
case Configuration.UI_MODE_NIGHT_YES:
findPreference("pref_force_dark_terminal").setEnabled(false);
break;
case Configuration.UI_MODE_NIGHT_NO:
case Configuration.UI_MODE_NIGHT_UNDEFINED:
findPreference("pref_force_dark_terminal").setEnabled(true);
break;
int level = this.currentLanguageLevel();
if (level != LANGUAGE_SUPPORT_LEVEL) {
Log.e(TAG, "Detected language level " + level +
", latest is " + LANGUAGE_SUPPORT_LEVEL);
languageSelector.setSummary(R.string.language_support_outdated);
} else {
if (!"Translated by Fox2Code".equals( // I don't "translate" english
this.getString(R.string.language_translated_by))) {
languageSelector.setSummary(R.string.language_translated_by);
} else {
languageSelector.setSummary(null);
}
}
if (!MainApplication.isDeveloper()) {
findPreference("pref_disable_low_quality_module_filter").setVisible(false);
}
@ -163,6 +201,23 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
|| !MainApplication.isDeveloper()) {
findPreference("pref_use_magisk_install_command").setVisible(false);
}
Preference debugNotification = findPreference("pref_background_update_check_debug");
debugNotification.setEnabled(MainApplication.isBackgroundUpdateCheckEnabled());
debugNotification.setVisible(MainApplication.isDeveloper());
debugNotification.setOnPreferenceClickListener(preference -> {
BackgroundUpdateChecker.postNotification(
this.requireContext(), new Random().nextInt(4) + 2);
return true;
});
findPreference("pref_background_update_check").setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = Boolean.parseBoolean(String.valueOf(newValue));
debugNotification.setEnabled(enabled);
if (!enabled) {
BackgroundUpdateChecker.onMainActivityResume(
this.requireContext());
}
return true;
});
final LibsBuilder libsBuilder = new LibsBuilder().withShowLoadingProgress(false)
.withLicenseShown(true).withAboutMinimalDesign(false);
@ -175,13 +230,30 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
"https://github.com/Fox2Code/FoxMagiskModuleManager/releases");
return true;
});
if (BuildConfig.DEBUG || BuildConfig.ENABLE_AUTO_UPDATER) {
findPreference("pref_report_bug").setOnPreferenceClickListener(p -> {
devModeStep = 0;
IntentHelper.openUrl(p.getContext(),
"https://github.com/Fox2Code/FoxMagiskModuleManager/issues");
return true;
});
} else {
findPreference("pref_report_bug").setVisible(false);
}
findPreference("pref_source_code").setOnPreferenceClickListener(p -> {
if (devModeStep == 2 && (BuildConfig.DEBUG || !MainApplication.isDeveloper())) {
if (devModeStep == 2) {
devModeStep = 0;
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", true).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
if (MainApplication.isDeveloper() && !BuildConfig.DEBUG) {
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", false).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_disabled, Toast.LENGTH_SHORT).show();
} else {
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", true).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
}
return true;
}
IntentHelper.openUrl(p.getContext(),
@ -195,6 +267,7 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
});
findPreference("pref_show_licenses").setOnPreferenceClickListener(p -> {
devModeStep = devModeStep == 1 ? 2 : 0;
BackgroundUpdateChecker.onMainActivityResume(this.requireContext());
openFragment(libsBuilder.supportFragment(), R.string.licenses);
return true;
});
@ -205,7 +278,7 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
}
private void openFragment(Fragment fragment, @StringRes int title) {
CompatActivity compatActivity = getCompatActivity(this);
FoxActivity compatActivity = getFoxActivity(this);
compatActivity.setOnBackPressedCallback(this);
compatActivity.setTitle(title);
compatActivity.getSupportFragmentManager()
@ -216,7 +289,7 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
}
@Override
public boolean onBackPressed(CompatActivity compatActivity) {
public boolean onBackPressed(FoxActivity compatActivity) {
compatActivity.setTitle(R.string.app_name);
compatActivity.getSupportFragmentManager()
.beginTransaction().replace(R.id.settings, this)
@ -224,91 +297,225 @@ public class SettingsActivity extends CompatActivity implements LanguageActivity
.commit();
return true;
}
private int currentLanguageLevel() {
int declaredLanguageLevel =
this.getResources().getInteger(R.integer.language_support_level);
if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL)
return declaredLanguageLevel;
if (!this.getResources().getConfiguration().locale.getLanguage().equals("en") &&
this.getResources().getString(R.string.notification_update_pref)
.equals("Background modules update check") &&
this.getResources().getString(R.string.notification_update_desc)
.equals("May increase battery usage")) {
return 0;
}
return LANGUAGE_SUPPORT_LEVEL;
}
}
public static class RepoFragment extends PreferenceFragmentCompat {
private static final int CUSTOM_REPO_ENTRIES = 5;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
getPreferenceManager().setSharedPreferencesName("mmm");
setPreferencesFromResource(R.xml.repo_preferences, rootKey);
setRepoData(RepoManager.MAGISK_ALT_REPO,
"Magisk Modules Alt Repo", RepoManager.MAGISK_ALT_REPO_HOMEPAGE,
null, null,
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues");
// Androidacy backend not yet implemented!
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT,
"Androidacy Modules Repo",
RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE,
"https://t.me/androidacy_discussions",
"https://patreon.com/androidacy",
"https://www.androidacy.com/module-repository-applications/");
setRepoData(RepoManager.MAGISK_ALT_REPO);
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
setRepoData(RepoManager.DG_MAGISK_REPO_GITHUB);
updateCustomRepoList(true);
}
private void setRepoData(String url,
String fallbackTitle, String homepage,
String supportUrl, String donateUrl,
String submissionUrl) {
String preferenceName = "pref_" + RepoManager.internalIdOfUrl(url);
Preference preference = findPreference(preferenceName);
@SuppressLint("RestrictedApi")
public void updateCustomRepoList(boolean initial) {
final SharedPreferences sharedPreferences = Objects.requireNonNull(
this.getPreferenceManager().getSharedPreferences());
final CustomRepoManager customRepoManager =
RepoManager.getINSTANCE().getCustomRepoManager();
for (int i = 0; i < CUSTOM_REPO_ENTRIES; i++) {
CustomRepoData repoData = customRepoManager.getRepo(i);
setRepoData(repoData, "pref_custom_repo_" + i);
if (initial) {
Preference preference =
findPreference("pref_custom_repo_" + i + "_delete");
if (preference == null) continue;
final int index = i;
preference.setOnPreferenceClickListener(preference1 -> {
sharedPreferences.edit().putBoolean(
"pref_custom_repo_" + index + "_enabled", false).apply();
customRepoManager.removeRepo(index);
updateCustomRepoList(false);
return true;
});
}
}
Preference preference = findPreference("pref_custom_add_repo");
if (preference == null) return;
preference.setVisible(customRepoManager.canAddRepo() &&
customRepoManager.getRepoCount() < CUSTOM_REPO_ENTRIES);
if (initial) { // Custom repo add button part.
preference = findPreference("pref_custom_add_repo_button");
if (preference == null) return;
preference.setOnPreferenceClickListener(preference1 -> {
final Context context = this.requireContext();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
final MaterialAutoCompleteTextView input =
new MaterialAutoCompleteTextView(context);
input.setHint(R.string.custom_url);
builder.setIcon(R.drawable.ic_baseline_add_box_24);
builder.setTitle(R.string.add_repo);
builder.setView(input);
builder.setPositiveButton("OK", (dialog, which) -> {
String text = String.valueOf(input.getText());
if (customRepoManager.canAddRepo(text)) {
final CustomRepoData customRepoData =
customRepoManager.addRepo(text);
customRepoData.setEnabled(true);
new Thread("Add Custom Repo Thread") {
@Override
public void run() {
try {
customRepoData.quickPrePopulate();
} catch (IOException|JSONException e) {
Log.e(TAG, "Failed to preload repo values", e);
}
UiThreadHandler.handler.post(() -> {
updateCustomRepoList(false);
});
}
}.start();
}
});
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
AlertDialog alertDialog = builder.show();
final Button positiveButton =
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
input.setValidator(new AutoCompleteTextView.Validator() {
@Override
public boolean isValid(CharSequence charSequence) {
return customRepoManager.canAddRepo(charSequence.toString());
}
@Override
public CharSequence fixText(CharSequence charSequence) {
return charSequence;
}
});
input.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(
@NonNull CharSequence charSequence, int i, int i1, int i2) {
positiveButton.setEnabled(customRepoManager
.canAddRepo(charSequence.toString()) &&
customRepoManager.getRepoCount() < CUSTOM_REPO_ENTRIES);
}
});
positiveButton.setEnabled(false);
int dp10 = FoxDisplay.dpToPixel(10),
dp20 = FoxDisplay.dpToPixel(20);
FoxViewCompat.setMargin(input, dp20, dp10, dp20, dp10);
return true;
});
}
}
private void setRepoData(String url) {
final RepoData repoData = RepoManager.getINSTANCE().get(url);
preference.setTitle(repoData == null ? fallbackTitle :
repoData.getNameOrFallback(fallbackTitle));
setRepoData(repoData, "pref_" + (repoData == null ?
RepoManager.internalIdOfUrl(url) : repoData.getPreferenceId()));
}
private void setRepoData(final RepoData repoData, String preferenceName) {
if (repoData == null) {
hideRepoData(preferenceName);
return;
}
Preference preference = findPreference(preferenceName);
if (preference == null) return;
preference.setVisible(true);
preference.setTitle(repoData.getName());
preference = findPreference(preferenceName + "_enabled");
if (preference != null) {
if (repoData == null) {
preference.setTitle(R.string.repo_disabled);
preference.setEnabled(false);
} else {
((TwoStatePreference) preference).setChecked(repoData.isEnabled());
preference.setTitle(repoData.isEnabled() ?
((TwoStatePreference) preference).setChecked(repoData.isEnabled());
preference.setTitle(repoData.isEnabled() ?
R.string.repo_enabled : R.string.repo_disabled);
preference.setOnPreferenceChangeListener((p, newValue) -> {
p.setTitle(((Boolean) newValue) ?
R.string.repo_enabled : R.string.repo_disabled);
preference.setOnPreferenceChangeListener((p, newValue) -> {
p.setTitle(((Boolean) newValue) ?
R.string.repo_enabled : R.string.repo_disabled);
return true;
});
}
preference = findPreference(preferenceName + "_website");
String homepage = repoData.getWebsite();
if (preference != null) {
if (!homepage.isEmpty()) {
preference.setVisible(true);
preference.setOnPreferenceClickListener(p -> {
if (homepage.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), homepage, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), homepage);
}
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_website");
if (preference != null && homepage != null) {
preference.setOnPreferenceClickListener(p -> {
if (homepage.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getCompatActivity(this), homepage, true);
} else {
IntentHelper.openUrl(getCompatActivity(this), homepage);
}
return true;
});
}
preference = findPreference(preferenceName + "_support");
if (preference != null && supportUrl != null) {
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getCompatActivity(this), supportUrl);
return true;
});
String supportUrl = repoData.getSupport();
if (preference != null) {
if (supportUrl != null && !supportUrl.isEmpty()) {
preference.setVisible(true);
preference.setIcon(ActionButtonType.supportIconForUrl(supportUrl));
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), supportUrl);
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_donate");
if (preference != null && donateUrl != null) {
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getCompatActivity(this), donateUrl);
return true;
});
String donateUrl = repoData.getDonate();
if (preference != null) {
if (donateUrl != null) {
preference.setVisible(true);
preference.setIcon(ActionButtonType.donateIconForUrl(donateUrl));
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), donateUrl);
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_submit");
if (preference != null && submissionUrl != null) {
preference.setOnPreferenceClickListener(p -> {
if (submissionUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getCompatActivity(this), submissionUrl, true);
} else {
IntentHelper.openUrl(getCompatActivity(this), submissionUrl);
}
return true;
});
String submissionUrl = repoData.getSubmitModule();
if (preference != null) {
if (submissionUrl != null && !submissionUrl.isEmpty()) {
preference.setVisible(true);
preference.setOnPreferenceClickListener(p -> {
if (submissionUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), submissionUrl, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), submissionUrl);
}
return true;
});
} else {
preference.setVisible(false);
}
}
}
private void hideRepoData(String preferenceName) {
Preference preference = findPreference(preferenceName);
if (preference == null) return;
preference.setVisible(false);
}
}
}

@ -18,13 +18,13 @@ import android.widget.Toast;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.app.BundleCompat;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.androidacy.AndroidacyActivity;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.installer.InstallerActivity;
import com.fox2code.mmm.markdown.MarkdownActivity;
import com.topjohnwu.superuser.CallbackList;
@ -263,7 +263,7 @@ public class IntentHelper {
intent1.putExtras(bundle);
}
intent1.putExtra(IntentHelper.EXTRA_TAB_EXIT_ANIMATION_BUNDLE, param);
if (activity instanceof CompatActivity) {
if (activity instanceof FoxActivity) {
TypedValue typedValue = new TypedValue();
activity.getTheme().resolveAttribute(
android.R.attr.background, typedValue, true);
@ -271,7 +271,7 @@ public class IntentHelper {
typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
intent1.putExtra(IntentHelper.EXTRA_TAB_TOOLBAR_COLOR, typedValue.data);
intent1.putExtra(IntentHelper.EXTRA_TAB_COLOR_SCHEME,
((CompatActivity) activity).isLightTheme() ?
((FoxActivity) activity).isLightTheme() ?
IntentHelper.EXTRA_TAB_COLOR_SCHEME_LIGHT :
IntentHelper.EXTRA_TAB_COLOR_SCHEME_DARK);
}
@ -320,7 +320,7 @@ public class IntentHelper {
public static final int RESPONSE_URL = 2;
@SuppressLint("SdCardPath")
public static void openFileTo(CompatActivity compatActivity, File destination,
public static void openFileTo(FoxActivity compatActivity, File destination,
OnFileReceivedCallback callback) {
File destinationFolder;
if (destination == null || (destinationFolder = destination.getParentFile()) == null ||

@ -106,6 +106,10 @@ public class PropUtils {
String line;
int lineNum = 0;
while ((line = bufferedReader.readLine()) != null) {
if (lineNum == 0 && line.startsWith("\u0000")) {
while (line.startsWith("\u0000"))
line = line.substring(1);
}
lineNum++;
int index = line.indexOf('=');
if (index == -1 || line.startsWith("#"))
@ -319,6 +323,8 @@ public class PropUtils {
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
while (line.startsWith("\u0000"))
line = line.substring(1);
if (line.startsWith("id=")) {
moduleId = line.substring(3).trim();
}

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
</vector>

@ -16,11 +16,17 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list"
<!-- FrameLayout is the best way to fix blurring -->
<FrameLayout
android:id="@+id/blur_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:edgeToEdge="true" />
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:edgeToEdge="true" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<eightbitlab.com.blurview.BlurView

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fitsSystemWindowsInsets="left|right">
@ -9,4 +10,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Androidacy Web Interface"
android:textColor="#ff808080"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/webViewNote"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,7 @@
<resources>
<string-array name="theme_values_names">
<item>Προεπιλογή συστήματως</item>
<item>Σκωτεινό</item>
<item>Ανοιχτό</item>
</string-array>
</resources>

@ -0,0 +1,133 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Fox\'s Magisk Module Manager</string>
<string name="app_name_short">Fox\'s Mmm</string>
<string name="fail_root_magisk">Δεν ήταν δυνατή η πρόσβαση σε δικαιώματα root ή στο magisk</string>
<string name="loading">Φόρτωση…</string>
<string name="updatable">Αναβαθμίσιμο</string>
<string name="installed">Εγκατεστημένο</string>
<string name="online_repo">Ηλεκτρονικό αποθετήριο</string>
<string name="showcase_mode">Η εφαρμογή βρίσκεται σε κατάσταση lockdown</string>
<string name="failed_download">Δεν ήταν δυνατή η λήψη του αρχείου</string>
<string name="slow_modules">Τα Modules χρειάστηκαν πολύ χρόνο για εκκίνηση τους, σκεφτείτε να απενεργοποιήσετε ορισμένα απο αυτά</string>
<string name="fail_internet">Δεν ήταν δυνατή η σύνδεση στο Διαδίκτυο</string>
<string name="no_web_view">Δεν ήταν δυνατό το άνοιγμα του πλοηγητή συστήματως</string>
<string name="title_activity_settings">Δραστηριότητα ρυθμίσεων</string>
<string name="app_update_available">Μια νεότερη έκδοση της εφαρμογής είναι διαθέσιμη</string>
<string name="app_update">Αναβάθμιση</string>
<string name="no_desc_found">Δεν βρέθηκε περιγραφή.</string>
<string name="download_module">Λήξη του Module</string>
<string name="install_module">Εγκατάσταση του Module</string>
<string name="update_module">Αναβάθμιση του Module</string>
<string name="changelog">Ημερολόγιο αλλαγών</string>
<string name="website">Ιστοσελίδα</string>
<string name="support">Υποστήριξη</string>
<string name="donate">Δωρίστε</string>
<string name="update">Ενημέρωση</string>
<string name="install">Εγκατάσταση</string>
<string name="description">Περιγραφή</string>
<string name="uninstall">Απεγκατάσταση</string>
<string name="config">Παραμέτροποίηση</string>
<string name="submit_modules">Υποβάλετε ένα module</string>
<string name="require_android_6">Απαιτεί το Android 6.0+</string>
<string name="require_android_12">Απαιτεί το Android 12+</string>
<string name="install_terminal_reboot_now">Επανεκκίνηση</string>
<string name="language">Γλώσσα</string>
<string name="yes" translatable="false">@android:string/yes</string>
<string name="no" translatable="false">@android:string/no</string>
<string name="ok" translatable="false">@android:string/ok</string>
<string name="cancel" translatable="false">@android:string/cancel</string>
<!-- Module section translation -->
<string name="module_last_update">Τελευταία ενημέρωση:</string>
<string name="module_repo">Αποθετήριο:</string>
<string name="module_by">απο</string>
<string name="module_downloads">Λήψεις:</string>
<string name="module_stars">Αστέρια:</string>
<string name="module_needs_ramdisk">Χρειάζεται Ramdisk</string>
<string name="module_needs_ramdisk_desc">Αυτό το module χρειάζεται το Ramdisk εκκίνησης για να εγκατασταθεί</string>
<string name="module_can_change_boot">Μπορεί να αλλάξει την εκκίνηση</string>
<string name="module_can_change_boot_desc">Αυτή η ενότητα μπορεί να αλλάξει την εικόνα εκκίνησης(boot image)</string>
<string name="module_min_magisk_chip">Ελάχ. έκδοση Magisk "%s"</string>
<string name="module_min_sdk_chip">Ελάχ. έκδοση Android</string>
<string name="module_max_sdk_chip">Μέγ. έκδοση Android</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="manage_repos_pref">Διαχείριση αποθετηρίων</string>
<string name="showcase_mode_pref">Λειτουργία lockdown</string>
<string name="showcase_mode_desc">Η λειτουργία lockdown εμποδίζει τον διαχειριστή να πραγματοποιήσει ενέργειες στα modules</string>
<string name="prevent_reboot_pref">Αποτροπή επανεκκίνησης</string>
<string name="prevent_reboot_desc">Αποτρέπει απροσδόκητες επανεκκινήσεις</string>
<string name="pref_category_settings">ω</string>
<string name="enable_monet">Ενεργοποίηση Monet</string>
<string name="pref_category_info">Πληροφορίες</string>
<string name="show_licenses">Εμφάνιση αδειών</string>
<string name="licenses">Άδειες</string>
<string name="show_incompatible_pref">Εμφάνιση ασυμβίβαστων module</string>
<string name="show_incompatible_desc">Εμφάνιση module που είναι απίθανο να εγκατασταθούν στη συσκευή σας με βάση τα μεταδεδομένα τους</string>
<string name="magisk_outdated">Υπάρχει μια νέα έκδοση του Magisk για εγκατάσταση!</string>
<string name="pref_category_repos">Αποθετήρια</string>
<string name="pref_category_security">Ασφάλεια</string>
<string name="pref_category_appearance">Εμφάνιση</string>
<string name="pref_category_general">Γενικά</string>
<string name="repo_main_desc">Το αποθετήριο που φιλοξενεί modules για το magisk</string>
<string name="repo_main_alt">Μια εναλλακτική λύση για το magisk-modules-repo με λιγότερους περιορισμούς.</string>
<string name="master_delete">Διαγραφή των αρχείων module;</string>
<string name="master_delete_no">Διατήρηση</string>
<string name="master_delete_yes">Διαγραφή</string>
<string name="master_delete_fail">Δεν ήταν δυνατή η διαγραφή των αρχείων module</string>
<string name="theme_pref">Θέμα</string>
<string name="theme_mode_pref">Λειτουργία θέματος</string>
<string name="module_id_prefix">Αναγνωριστικό module: </string>
<string name="install_from_storage">Εγκατάσταση module από αποθηκευτικό χώρο</string>
<string name="invalid_format">Το επιλεγμένο module έχει μη έγκυρη μορφή</string>
<string name="low_quality_module">Module χαμηλής ποιότητας</string>
<string name="local_install_title">Τοπική εγκατάσταση</string>
<string name="source_code">Πηγαίος κώδικας</string>
<string name="magisk_builtin_module">Ενσωματωμένο Magisk module</string>
<string name="substratum_builtin_module">Ενσωματωμένο module υποστρώματος</string>
<string name="force_dark_terminal_title">Σκούρο τερματικό</string>
<string name="file_picker_failure">Ο τρέχων επιλογέας αρχείων δεν έχει πρόσβαση στο αρχείο.</string>
<string name="remote_install_title">Απομακρυσμένη εγκατάσταση</string>
<string name="file_picker_wierd">Ο επιλογέας αρχείων σας επέστρεψε μια μη τυποποιημένη απάντηση.</string>
<string name="use_magisk_install_command_pref" tools:ignore="TypographyDashes">Χρήση της εντολής \"magisk --install-module\"</string>
<string name="use_magisk_install_command_desc">
Κατά τη διάρκεια των δοκιμών προκάλεσε προβλήματα στο εργαλείο διάγνωσης σφάλματος εγκατάστασης ενότητας,
Για αυτό τον λόγο αυτή η επιλογή είναι κρυμμένη πίσω από τη λειτουργία για προγραμματιστές.\nEνεργοποιήστε την ρύθμιση με δική σας ευθύνη!
</string>
<string name="dev_mode_enabled">Λειτουργία για προγραμματιστές ενεργοποιημένη</string>
<string name="force_english_pref">Αγγλική γλώσσα εφαρμογής</string>
<string name="disable_low_quality_module_filter_pref">Εμφάνιση module χαμηλής ποιότητας</string>
<string name="disable_low_quality_module_filter_desc">
Ορισμένες ενότητες δεν δηλώνουν σωστά τα μεταδεδομένα τους, προκαλώντας οπτικές δυσλειτουργίες,
και/ή υποδεικνύοντας την κακή ποιότητα τού module.\nΑπενεργοποιήστε την ρύθμιση με δική σας ευθύνη!
</string>
<string name="dns_over_https_pref">DNS μέσω HTTPS</string>
<string name="dns_over_https_desc">
Μπορεί να διορθώσει τα θέματα συνδέσεων σε ορισμένες περιπτώσεις.
(Δεν ισχύει για το webview.)
</string>
<string name="disable_extensions_pref">nΑπενεργοποίηση Mmm</string>
<string name="disable_extensions_desc">
Απενεργοποιήστε τις επεκτάσεις MMM του Fox, εμποδίζοντας τη χρήση των
επεκτάσεων τερματικού απο τα module.\nΧρήσιμο εάν ένα module καταχράται τις επεκτάσεις MMM του Fox.
</string>
<string name="wrap_text_pref">Αναδίπλωση κειμένου</string>
<string name="wrap_text_desc">
Εμφάνιση κειμένου σε πολλαπλές γραμμές αντί να τοποθετείται
όλο το κείμενο στην ίδια γραμμή κατά την εγκατάσταση ενος module.
</string>
<string name="enable_blur_pref">Θολούρα</string>
<string name="disable_chips_in_description">Απενεργοποιήστε τα τσιπ στην περιγραφή</string>
<string name="repo_enabled">Ενεργοποιημένο αποθετήριο</string>
<string name="repo_disabled">Απενεργοποιημένο αποθετήριο</string>
<string name="add_repo">Προσθήκη αποθετηρίου</string>
<string name="remove_repo">Αφαίρεση αποθετηρίου</string>
<string name="custom_url">Προσαρμοσμένη διεύθυνση url</string>
<string name="androidacy_repo_info">Το αποθετήριο Androidacy έχει διαφημίσεις και ιχνηλάτες.</string>
<string name="backup_module_list">Εφεδρικά modules</string>
<string name="restore_module_list">Επαναφορά module</string>
<string name="require_internet">Αυτή η λειτουργία απαιτεί σύνδεση στο Διαδίκτυο</string>
<!-- Set to true in translation file if your language is right to left -->
<bool name="lang_support_rtl">false</bool>
</resources>

@ -10,6 +10,7 @@
<string name="failed_download">No fue posible descargar el archivo.</string>
<string name="slow_modules">Los módulos tardaron mucho en iniciar, considere eliminar algunos.</string>
<string name="fail_internet">No fue posible conectarse a Internet.</string>
<string name="no_web_view">No se pudo abrir el WebView del sistema.</string>
<string name="title_activity_settings">SettingsActivity</string>
<string name="app_update_available">Actualización disponible</string>
<string name="app_update">Actualizar</string>
@ -21,8 +22,20 @@
<string name="website">Sitio web</string>
<string name="support">Soporte</string>
<string name="donate">Donar</string>
<string name="update">Actualizar</string>
<string name="install">Instalar</string>
<string name="description">Descripción</string>
<string name="uninstall">Desinstalar</string>
<string name="config">Configuración</string>
<string name="submit_modules">Enviar un módulo</string>
<string name="require_android_6">Requiere Android 6.0 o superior</string>
<string name="require_android_12">Requiere Android 12 o superior</string>
<string name="install_terminal_reboot_now">Reiniciar</string>
<string name="language">Idioma</string>
<string name="yes" translatable="false">@android:string/yes</string>
<string name="no" translatable="false">@android:string/no</string>
<string name="ok" translatable="false">@android:string/ok</string>
<string name="cancel" translatable="false">@android:string/cancel</string>
<!-- Module section translation -->
<string name="module_last_update">Actualizado el:</string>
@ -30,13 +43,23 @@
<string name="module_by">por</string>
<string name="module_downloads">Descargas:</string>
<string name="module_stars">Estrellas:</string>
<string name="module_needs_ramdisk">Requiere ramdisk</string>
<string name="module_needs_ramdisk_desc">Este módulo requiere un ramdisk de arranque para ser instalado</string>
<string name="module_can_change_boot">Puede cambiar el arranque</string>
<string name="module_can_change_boot_desc">Este módulo puede cambiar la imagen de arranque</string>
<string name="module_min_magisk_chip">Min. Magisk "%s"</string>
<string name="module_min_sdk_chip">Min. Android</string>
<string name="module_max_sdk_chip">Máx. Android</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="manage_repos_pref">Administrar repositorios</string>
<string name="showcase_mode_pref">Modo Lockdown</string>
<string name="showcase_mode_desc">Modo Lockdown previene que la aplicación haga cambios en los módulos</string>
<string name="prevent_reboot_pref">Evitar reinicio</string>
<string name="prevent_reboot_desc">Evita los reincios inesperados</string>
<string name="pref_category_settings">Ajustes</string>
<string name="enable_monet">Habilitar Monet</string>
<string name="pref_category_info">Info</string>
<string name="show_licenses">Mostrar licencias</string>
<string name="licenses">Licencias</string>
@ -44,6 +67,9 @@
<string name="show_incompatible_desc">Muestra los módulos que son incompatibles con su dispositivo basandose en sus metadatos</string>
<string name="magisk_outdated">Magisk está desactualizado!</string>
<string name="pref_category_repos">Repositorios</string>
<string name="pref_category_security">Seguridad</string>
<string name="pref_category_appearance">Apariencia</string>
<string name="pref_category_general">General</string>
<string name="repo_main_desc">Repositorio que aloja los módulos de Magisk</string>
<string name="repo_main_alt">Una alternativa del repositorio de módulos de Magisk pero con menos restricciones.</string>
<string name="master_delete">Desea borrar los archivos del módulo?</string>
@ -55,6 +81,7 @@
<string name="module_id_prefix">Id del módulo: </string>
<string name="install_from_storage">Instalar módulo desde el almacenamiento</string>
<string name="invalid_format">El módulo seleccionado tiene un formato inválido</string>
<string name="low_quality_module">Módulo de baja calidad</string>
<string name="local_install_title">Instalación local</string>
<string name="source_code">Código fuente</string>
<string name="magisk_builtin_module">Módulo incorporado de Magisk</string>
@ -91,6 +118,14 @@
todo el texto en la misma linea durante la instalación de un módulo
</string>
<string name="enable_blur_pref">Activar desenfoque</string>
<string name="disable_chips_in_description">Deshabilitar chips en la descripción</string>
<string name="repo_enabled">Repositorio activado</string>
<string name="repo_disabled">Repositorio desactivado</string>
</resources>
<string name="add_repo">Añadir Repositorio</string>
<string name="remove_repo">Eliminar Repositorio</string>
<string name="custom_url">URL Personalizado</string>
<string name="androidacy_repo_info">El repositorio Androidacy tiene anuncios y trackers.</string>
<string name="backup_module_list">Hacer copia de seguridad de los módulos</string>
<string name="restore_module_list">Restaurar módulos</string>
<string name="require_internet">Esta operación requiere una conexión a Internet.</string>
</resources>

@ -10,6 +10,7 @@
<string name="failed_download">Échec du téléchargement du fichier.</string>
<string name="slow_modules">Les modules mettent trop de temps à démarrer, envisagez de désactiver certains modules</string>
<string name="fail_internet">Échec de la connexion à internet</string>
<string name="no_web_view">Impossible d\ouvrir le WebView du système</string>
<string name="title_activity_settings">SettingsActivity</string>
<string name="app_update_available">Mis-à-jour de l\application disponible</string>
<string name="app_update">Mis-à-jour</string>
@ -21,8 +22,20 @@
<string name="website">Site web</string>
<string name="support">Support</string>
<string name="donate">Dons</string>
<string name="update">Mise-à-jour</string>
<string name="install">Installer</string>
<string name="description">Description</string>
<string name="uninstall">Désinstaller</string>
<string name="config">Configuration</string>
<string name="submit_modules">Soumettre un module</string>
<string name="require_android_6">Requière Android 6.0+</string>
<string name="require_android_12">Requière Android 12+</string>
<string name="install_terminal_reboot_now">Redémarrer</string>
<string name="language">Language</string>
<string name="yes" translatable="false">@android:string/yes</string>
<string name="no" translatable="false">@android:string/no</string>
<string name="ok" translatable="false">@android:string/ok</string>
<string name="cancel" translatable="false">@android:string/cancel</string>
<!-- Module section translation -->
<string name="module_last_update">Dernière mis-à-jour:</string>
@ -30,20 +43,33 @@
<string name="module_by">par</string>
<string name="module_downloads">Téléchargements:</string>
<string name="module_stars">Étoiles:</string>
<string name="module_needs_ramdisk">Ramdisk nécessaire</string>
<string name="module_needs_ramdisk_desc">Ce module a besoin de démarrer le ramdisk pour être installé</string>
<string name="module_can_change_boot">Peut changer le démarrage</string>
<string name="module_can_change_boot_desc">Ce module peut changer l\image de démarrage</string>
<string name="module_min_magisk_chip">Magisk min. "%s"</string>
<string name="module_min_sdk_chip">Android Min.</string>
<string name="module_max_sdk_chip">Android Max.</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="manage_repos_pref">Gestion des dépôts</string>
<string name="showcase_mode_pref">Mode verrouillage</string>
<string name="showcase_mode_desc">Le mode verrouillage empêche toute action du gestionnaire sur les modules</string>
<string name="prevent_reboot_pref">Empêcher redémarrages</string>
<string name="prevent_reboot_desc">Empêcher les redémarrages inattendus</string>
<string name="pref_category_settings">Réglages</string>
<string name="enable_monet">Activer Monet</string>
<string name="pref_category_info">Infos</string>
<string name="show_licenses">Afficher les licenses</string>
<string name="licenses">Licences</string>
<string name="show_incompatible_pref">Afficher les modules incompatibles</string>
<string name="show_incompatible_desc">Afficher les modules qui sont incompatibles avec votre périphérique basé sur leurs métadonnées</string>
<string name="magisk_outdated">Magisk est périmé!</string>
<string name="magisk_outdated">Il y a une nouvelle version de Magisk à installer!</string>
<string name="pref_category_repos">Dépôts</string>
<string name="pref_category_security">Sécurité</string>
<string name="pref_category_appearance">Apparance</string>
<string name="pref_category_general">Général</string>
<string name="repo_main_desc">Le dépôt héberge les Modules Magisk</string>
<string name="repo_main_alt">Une alternative à Magisk-Modules-Repo avec moins de restrictions.</string>
<string name="master_delete">Supprimer les fichiers du modules?</string>
@ -55,6 +81,7 @@
<string name="module_id_prefix">Id du module: </string>
<string name="install_from_storage">Installer un module module depuis le stockage</string>
<string name="invalid_format">Le module sélectionné est dans un format invalide</string>
<string name="low_quality_module">Low-quality module</string>
<string name="local_install_title">Installation locale</string>
<string name="source_code">Code source</string>
<string name="magisk_builtin_module">Module intégré à Magisk</string>
@ -91,7 +118,14 @@
tout le texte sur la même ligne lors de l\installation d\un module
</string>
<string name="enable_blur_pref">Activer l\effet de flou</string>
<string name="disable_chips_in_description">Désactiver chips dans la description</string>
<string name="repo_enabled">Dépôt activé</string>
<string name="repo_disabled">Dépôt désactivé</string>
<string name="add_repo">Ajouter dépôt</string>
<string name="remove_repo">Supprimer dépôt</string>
<string name="custom_url">URL personnalisée</string>
<string name="androidacy_repo_info">Le dépôt Androidacy utilise des publicités et des pisteurs.</string>
<string name="backup_module_list">Sauvegarder les modules</string>
<string name="restore_module_list">Restaurer les modules</string>
<string name="require_internet">Cette opération requière une connexion internet</string>
</resources>

@ -1,64 +1,127 @@
<resources>
<string name="app_name">Fox\'s Magisk Module Manager</string>
<string name="app_name_short">Fox\'s Mmm</string>
<string name="app_name">Fox Magisk Module Manager</string>
<string name="app_name_short">Fox Mmm</string>
<string name="fail_root_magisk">Impossibile ottenere l\'accesso al Root o a Magisk</string>
<string name="loading">Caricamento</string>
<string name="updatable">Aggiornamento disponibile</string>
<string name="installed">Installato</string>
<string name="online_repo">Repository Online</string>
<string name="showcase_mode">L\'applicazione e\' in modalita\' protetta </string>
<string name="showcase_mode">L\'applicazione è in modalità protetta </string>
<string name="failed_download">Scaricamento del file non riuscito.</string>
<string name="slow_modules">L\'avvio dei moduli e\' lento, prova a disabilitarne alcuni</string>
<string name="slow_modules">L\'avvio dei moduli è lento, prova a disabilitarne alcuni</string>
<string name="fail_internet">Impossibile connettersi a internet</string>
<string name="title_activity_settings">Impostazione attivita\'</string>
<string name="no_web_view">Impossibile aprire la WebView di sistema</string>
<string name="title_activity_settings">Impostazione attività</string>
<string name="app_update_available">Aggiornamento disponibile</string>
<string name="app_update">Aggiornamento</string>
<string name="no_desc_found">Nessuna descrizione trovata.</string>
<string name="download_module">Scarica modulo</string>
<string name="install_module">Installa modulo</string>
<string name="update_module">Aggiorna modulo</string>
<string name="changelog">Registri dei cambiamenti</string>
<string name="website">Sito web</string>
<string name="support">Supporto</string>
<string name="donate">Dona</string>
<string name="update">Aggiorna</string>
<string name="install">Installa</string>
<string name="description">Descrizione</string>
<string name="uninstall">Disinstalla</string>
<string name="config">Configurazione</string>
<string name="submit_modules">Invia un modulo</string>
<string name="require_android_6">Richiede Android 6.0+</string>
<string name="require_android_12">Richiede Android 12+</string>
<string name="install_terminal_reboot_now">Riavvia</string>
<string name="language">Lingua</string>
<!-- Module section translation -->
<string name="module_last_update">Ultimo aggiornamento:</string>
<string name="module_repo">Repo:</string>
<string name="module_by">da</string>
<string name="module_downloads">Scaricamenti:</string>
<string name="module_stars">Preferiti:</string>
<string name="module_needs_ramdisk">Necessita ramdisk</string>
<string name="module_needs_ramdisk_desc">Questo modulo ha bisogno dell\'avvio ramdisk per essere installato</string>
<string name="module_can_change_boot">Può cambiare avvio</string>
<string name="module_can_change_boot_desc">Questo modulo può cambiare l\'immagine di avvio</string>
<string name="module_min_magisk_chip">Min. Magisk "%s"</string>
<string name="module_min_sdk_chip">Min. Android</string>
<string name="module_max_sdk_chip">Max. Android</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="showcase_mode_pref">Modalita\' protetta</string>
<string name="showcase_mode_desc">La modalita\' protetta impedisce la modifica dei moduli da parte del gestore</string>
<string name="manage_repos_pref">Gestisci repository</string>
<string name="showcase_mode_pref">Modalità protetta</string>
<string name="showcase_mode_desc">La modalità protetta impedisce la modifica dei moduli da parte del gestore</string>
<string name="prevent_reboot_pref">Impedisci riavvio</string>
<string name="prevent_reboot_desc">Impedisce riavvii inaspettati</string>
<string name="pref_category_settings">Impostazioni</string>
<string name="pref_category_info">Info</string>
<string name="show_licenses">Mostra le autorizzazioni</string>
<string name="licenses">Autorizzazioni</string>
<string name="enable_monet">Abilita Monet</string>
<string name="pref_category_info">Informazioni</string>
<string name="show_licenses">Mostra le licenze</string>
<string name="licenses">Licenze</string>
<string name="show_incompatible_pref">Mostra moduli incompatibili</string>
<string name="show_incompatible_desc">Mostra i moduli che in base ai metadati sono incompatibili con il tuo dispositivo </string>
<string name="magisk_outdated">Magisk non e\' aggiornato!</string>
<string name="pref_category_repos">Repos</string>
<string name="show_incompatible_desc">Mostra i moduli che in base ai metadati sono incompatibili con il tuo dispositivo</string>
<string name="magisk_outdated">Magisk non è aggiornato!</string>
<string name="pref_category_repos">Repository</string>
<string name="pref_category_security">Sicurezza</string>
<string name="pref_category_appearance">Aspetto</string>
<string name="pref_category_general">Generale</string>
<string name="repo_main_desc">La repository che fornisce i moduli Magisk</string>
<string name="repo_main_alt">Un\' alternativa alla Repository di default con meno restrizioni.</string>
<string name="repo_main_alt">Un\'alternativa alla Repository di default con meno restrizioni.</string>
<string name="master_delete">Rimuovere i dati del modulo?</string>
<string name="master_delete_no">Mantieni i dati</string>
<string name="master_delete_yes">Rimuovi i dati</string>
<string name="master_delete_fail">Impossibile rimuovere i dati del modulo</string>
<string name="theme_pref">Tema</string>
<string name="theme_mode_pref">Modalità tema</string>
<string name="module_id_prefix">Id del modulo: </string>
<string name="install_from_storage">Installa un modulo dalla memoria di archiviazione</string>
<string name="invalid_format">Il modulo selezionato ha un formato non valido</string>
<string name="low_quality_module">Modulo qualità-bassa</string>
<string name="local_install_title">Installazione locale</string>
<string name="source_code">Codice sorgente</string>
<string name="magisk_builtin_module">Modulo di Magisk integrato</string>
<string name="substratum_builtin_module">modulo del sottostrato integrato</string>
<string name="force_dark_terminal_title">Forza il terminale in modalita\' scura</string>
<string name="substratum_builtin_module">Modulo di Substratum integrato</string>
<string name="force_dark_terminal_title">Forza il terminale in modalità scura</string>
<string name="file_picker_failure">Il tuo gestore file non ha potuto accedere ai dati sul dispositivo.</string>
<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 e\' disponibile solo in modalita\' sviluppatore, abilita a tuo rischio e pericolo!
Questo ha causato dei problemi durante i test per l\'installazione dei moduli,
per questo l\'opzione è disponibile solo in modalità sviluppatore, abilita a tuo rischio e pericolo!
</string>
<string name="dev_mode_enabled">Modalita\' sviluppatore attivata</string>
<string name="dev_mode_enabled">Modalità sviluppatore attivata</string>
<string name="force_english_pref">Forza la lingua inglese</string>
<string name="disable_low_quality_module_filter_pref">Disabilita il filtro qualita\' per i moduli</string>
<string name="disable_low_quality_module_filter_pref">Disabilita il filtro qualità per i moduli</string>
<string name="disable_low_quality_module_filter_desc">
Alcuni moduli non forniscono correttamente i loro metadati,causando errori grafici,
e/o indicando bassa qualita\', disabilita a tuo rischio e pericolo!
e/o indicando bassa qualità.\nDisabilita a tuo rischio e pericolo!
</string>
<string name="dns_over_https_pref">DNS over HTTPS</string>
<string name="dns_over_https_desc">
Potrebbe risolvere porblemi di connessione in alcuni casi.
(Non si applica alla WebView.)
</string>
<string name="disable_extensions_pref">No Mmm</string>
<string name="disable_extensions_desc">
Disattiva le estensioni di Fox Mmm, impedendo ai moduli di usare
le estensioni per il terminale.\nUtile se un modulo abusa le estensioni di Fox Mmm.
</string>
<string name="wrap_text_pref">Avvolgimento del testo</string>
<string name="wrap_text_desc">
Mostra il testo su righee multiple invece che mettere
tutto il testo sulla stessa riga quando si installa un modulo.
</string>
<string name="enable_blur_pref">Sfocatura</string>
<string name="disable_chips_in_description">Disabilita i chip nella descrizione</string>
<string name="repo_enabled">Repository attiva</string>
<string name="repo_disabled">Repository non attiva</string>
<string name="add_repo">Aggiungi Repository</string>
<string name="remove_repo">Rimuovi Repository</string>
<string name="custom_url">Url personalizzato</string>
<string name="androidacy_repo_info">La repository di Androidacy contiene pubblicità e trackers.</string>
<string name="backup_module_list">Salva moduli</string>
<string name="restore_module_list">Ripristina moduli</string>
<string name="require_internet">Questa operazione richiede una connessione ad internet</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MagiskModuleManager.Monet" parent="Theme.MagiskModuleManager.Monet.Dark" />
</resources>

@ -27,6 +27,10 @@
<string name="description">ıklama</string>
<string name="uninstall">Kaldır</string>
<string name="config">Yapılandırma</string>
<string name="favourite">Favori</string>
<string name="report_bugs">Hataları bildir</string>
<string name="sniffed_modules">Koklanmış modüller</string>
<string name="sniffing_modules">Koklama modülleri</string>
<string name="submit_modules">Bir modül gönder</string>
<string name="require_android_6">Android 6.0+ gerekir</string>
<string name="require_android_12">Android 12+ gerekir</string>
@ -95,7 +99,8 @@
Test sırasında modül yükleme hata teşhis aracında sorunlar meydana geldi,
bu yüzden o seçeneği geliştirici modunun arkasına gizledim, bunu etkinleştirmenizde sorumluluk size aittir!
</string>
<string name="dev_mode_enabled">Geliştirici modu etkin</string>
<string name="dev_mode_enabled">Geliştirici modu açık</string>
<string name="dev_mode_disabled">Geliştirici modu kapalı</string>
<string name="force_english_pref">İngilizce dilini zorla</string>
<string name="disable_low_quality_module_filter_pref">Düşük kalite modül filtresini devre dışı bırak</string>
<string name="disable_low_quality_module_filter_desc">
@ -126,4 +131,20 @@
<string name="backup_module_list">Modülleri yedekle</string>
<string name="restore_module_list">Modülleri geri yükle</string>
<string name="require_internet">Bu işlem bir internet bağlantısı gerektirir</string>
<!-- Background Notification translation -->
<string name="notification_update_title">%i modül güncellemesi bulundu</string>
<string name="notification_update_title_easter_egg">%i modül güncellemesi koklandı</string>
<string name="notification_update_subtitle">Uygulamayı açmak için tıkla</string>
<string name="notification_update_pref">Modül güncellemesi arkaplan kontrolü</string>
<string name="notification_update_desc">Pil kullanımını arttırabilir</string>
<string name="notification_update_debug_pref">Bildirim Testi</string>
<!-- Set to true in translation file if your language is right to left -->
<bool name="lang_support_rtl">false</bool>
<!-- Always copy language_support_level when translating -->
<integer name="language_support_level">1</integer>
<string name="language_support_outdated">Şimdiki dil için bazı çeviriler güncel değil, lütfen GitHub\'daki uygulama çevirilerine katkıda bulunmayı düşünün</string>
<!-- Replace with your own username when translating -->
<string name="language_translated_by">Alprnn357 tarafından çevrildi</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="monet_enabled_by_default">true</bool>
</resources>

@ -1,11 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.MagiskModuleManager.Light" parent="Theme.Material3.DynamicColors.Light">
<style name="Theme.MagiskModuleManager.Monet.Light" parent="Theme.Material3.DynamicColors.Light">
<item name="android:statusBarColor">@color/status_bar_color</item>
<item name="colorBackgroundFloating">@color/system_accent2_200</item>
<item name="android:windowBackground">@color/system_accent2_100</item>
<item name="chipStyle">@style/Widget.Material3.Chip.Choice.Light</item>
</style>
<style name="Widget.Material3.Chip.Choice.Light" parent="Widget.Material3.Chip.Assist">
@ -13,14 +12,14 @@
<item name="chipStrokeWidth">0dp</item>
<item name="chipIconTint">?attr/colorControlNormal</item>
</style>
<style name="Theme.MagiskModuleManager.Transparent.Light" parent="Theme.MagiskModuleManager.Light" />
<style name="Theme.MagiskModuleManager.Dark" parent="Theme.Material3.DynamicColors.Dark">
<style name="Theme.MagiskModuleManager.Monet.Dark" parent="Theme.Material3.DynamicColors.Dark">
<item name="android:statusBarColor">@color/status_bar_color</item>
<item name="colorBackgroundFloating">@color/system_accent2_800</item>
<item name="android:windowBackground">@color/system_accent2_900</item>
<item name="chipStyle">@style/Widget.Material3.Chip.Choice.Dark</item>
</style>
<style name="Widget.Material3.Chip.Choice.Dark" parent="Widget.Material3.Chip.Assist">
@ -31,7 +30,6 @@
<!-- Base application theme. -->
<style name="Theme.MagiskModuleManager.Transparent.Dark" parent="Theme.MagiskModuleManager.Dark" />
<style name="Theme.MagiskModuleManager" parent="Theme.MagiskModuleManager.Light" />
<style name="Theme.MagiskModuleManager.Monet" parent="Theme.MagiskModuleManager.Monet.Light" />
</resources>

@ -1,4 +1,4 @@
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Trình quản lý Mô-đun Magisk của Fox</string>
<string name="app_name_short">Quản lý MM</string>
<string name="fail_root_magisk">Thất bại khi lấy quyền truy cập Root hoặc Magisk</string>
@ -10,6 +10,7 @@
<string name="failed_download">Tải xuống tệp tin thất bại.</string>
<string name="slow_modules">Các mô-đun mất quá nhiều thời gian để khởi động, hãy xem xét việc tắt một số mô-đun</string>
<string name="fail_internet">Kết nối với internet thất bại</string>
<string name="no_web_view">Không thể mở WebView của Hệ thống</string>
<string name="title_activity_settings">Cài đặt Hoạt động</string>
<string name="app_update_available">Bản cập nhật ứng dụng có sẵn</string>
<string name="app_update">Cập nhật</string>
@ -21,8 +22,16 @@
<string name="website">Trang web</string>
<string name="support">Hỗ trợ</string>
<string name="donate">Ủng hộ</string>
<string name="update">Cập nhật</string>
<string name="install">Cài đặt</string>
<string name="description">Mô tả</string>
<string name="uninstall">Gỡ cài đặt</string>
<string name="config">Cấu hình</string>
<string name="submit_modules">Nộp một mô-đun</string>
<string name="require_android_6">Yêu cầu Android 6.0+</string>
<string name="require_android_12">Yêu cầu Android 12+</string>
<string name="install_terminal_reboot_now">Khởi động lại</string>
<string name="language">Ngôn ngữ</string>
<!-- Module section translation -->
<string name="module_last_update">Cập nhật lần cuối:</string>
@ -30,20 +39,33 @@
<string name="module_by">bởi</string>
<string name="module_downloads">Lượt tải xuống:</string>
<string name="module_stars">Sao:</string>
<string name="module_needs_ramdisk">Cần ramdisk</string>
<string name="module_needs_ramdisk_desc">Cần có boot ramdisk để cài đặt mô-đun này</string>
<string name="module_can_change_boot">Có thể thay đổi khởi động</string>
<string name="module_can_change_boot_desc">Mô-đun này có thể sẽ thay đổi boot image</string>
<string name="module_min_magisk_chip">Tối thiểu Magisk "%s"</string>
<string name="module_min_sdk_chip">Tối thiểu Android</string>
<string name="module_max_sdk_chip">Tối đa Android</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="manage_repos_pref">Quản lý kho</string>
<string name="showcase_mode_pref">Chế độ khóa</string>
<string name="showcase_mode_desc">Chế độ khóa ngăn trình quản lý thực hiện hành động trên các mô-đun</string>
<string name="prevent_reboot_pref">Ngăn khởi động lại</string>
<string name="prevent_reboot_desc">Ngăn khởi động lại bất ngờ</string>
<string name="pref_category_settings">Cài đặt</string>
<string name="enable_monet">Kích hoạt Monet</string>
<string name="pref_category_info">Thông tin</string>
<string name="show_licenses">Hiển thị giấy phép</string>
<string name="licenses">Giấy phép</string>
<string name="show_incompatible_pref">Hiển thị các mô-đun không tương thích</string>
<string name="show_incompatible_desc">Hiển thị các mô-đun không tương thích với thiết bị của bạn dựa trên siêu dữ liệu của chúng</string>
<string name="magisk_outdated">Magisk đã lỗi thời!</string>
<string name="show_incompatible_desc">Hiển thị các mô-đun dường như sẽ không tương thích với thiết bị của bạn dựa trên siêu dữ liệu của chúng</string>
<string name="magisk_outdated">Có một phiên bản Magisk mới hơn để cài đặt!</string>
<string name="pref_category_repos">Kho</string>
<string name="pref_category_security">Bảo mật</string>
<string name="pref_category_appearance">Giao diện</string>
<string name="pref_category_general">Chung</string>
<string name="repo_main_desc">Kho lưu trữ Mô-đun Magisk</string>
<string name="repo_main_alt">Một giải pháp thay thế cho Kho Mô-đun Magisk với ít hạn chế hơn.</string>
<string name="master_delete">Xóa các tệp mô-đun?</string>
@ -55,6 +77,7 @@
<string name="module_id_prefix">ID mô-đun: </string>
<string name="install_from_storage">Cài đặt mô-đun từ bộ nhớ</string>
<string name="invalid_format">Mô-đun đã chọn có định dạng không hợp lệ</string>
<string name="low_quality_module">Mô-đun chất lượng kém</string>
<string name="local_install_title">Cài đặt nội bộ</string>
<string name="source_code">Mã nguồn</string>
<string name="magisk_builtin_module">Mô-đun tích hợp Magisk</string>
@ -93,4 +116,11 @@
<string name="enable_blur_pref">Kích hoạt làm mờ</string>
<string name="repo_enabled">Kho đã được kích hoạt</string>
<string name="repo_disabled">Kho đã bị vô hiệu hóa</string>
<string name="add_repo">Thêm kho</string>
<string name="remove_repo">Gỡ kho</string>
<string name="custom_url">Url tuỳ chỉnh</string>
<string name="androidacy_repo_info">Kho Androidacy có các quảng cáo và trình theo dõi.</string>
<string name="backup_module_list">Sao lưu mô-đun</string>
<string name="restore_module_list">Khôi phục mô-đun</string>
<string name="require_internet">Quá trình này yêu cầu kết nối internet</string>
</resources>

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="lang_support_rtl">false</bool>
<bool name="monet_enabled_by_default">false</bool>
</resources>

@ -27,6 +27,10 @@
<string name="description">Description</string>
<string name="uninstall">Uninstall</string>
<string name="config">Config</string>
<string name="favourite">Favourite</string>
<string name="report_bugs">Report bugs</string>
<string name="sniffed_modules">Sniffed modules</string>
<string name="sniffing_modules">Sniffing modules</string>
<string name="submit_modules">Submit a module</string>
<string name="require_android_6">Requires Android 6.0+</string>
<string name="require_android_12">Requires Android 12+</string>
@ -96,6 +100,7 @@
so this option behind is hidden behind developer mode.\nTurn this on at your own risk!
</string>
<string name="dev_mode_enabled">Developer mode on</string>
<string name="dev_mode_disabled">Developer mode off</string>
<string name="force_english_pref">English app language</string>
<string name="disable_low_quality_module_filter_pref">Show low-quality modules</string>
<string name="disable_low_quality_module_filter_desc">
@ -128,4 +133,21 @@
<string name="backup_module_list">Backup modules</string>
<string name="restore_module_list">Restore modules</string>
<string name="require_internet">This operation require an internet connection</string>
<!-- Background Notification translation -->
<string name="notification_update_title">Found %i module updates</string>
<string name="notification_update_title_easter_egg">Sniffed %i module updates</string>
<string name="notification_update_subtitle">Click to open the app</string>
<string name="notification_update_pref">Background modules update check</string>
<string name="notification_update_desc">May increase battery usage</string>
<string name="notification_update_debug_pref">Test Notification</string>
<!-- Set to true in translation file if your language is right to left -->
<bool name="lang_support_rtl">false</bool>
<!-- Always copy language_support_level when translating -->
<integer name="language_support_level">1</integer>
<string name="language_support_outdated">Some translations for the current language are
not up-to-date, please consider contributing to the app translations on GitHub</string>
<!-- Replace with your own username when translating -->
<string name="language_translated_by">Translated by Fox2Code</string>
</resources>

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Resource file for values that can be overwritten with an overlay -->
<bool name="force_transparency">false</bool>
<bool name="force_invalidate_blur">false</bool>
</resources>

@ -14,6 +14,16 @@
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
@ -56,4 +66,218 @@
app:summary="@string/androidacy_repo_info"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_dg_magisk_repo"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_dg_magisk_repo_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_dg_magisk_repo_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_dg_magisk_repo_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_dg_magisk_repo_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_dg_magisk_repo_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_repo_0"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_custom_repo_0_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_0_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_0_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_0_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_0_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_0_delete"
app:icon="@drawable/ic_baseline_delete_forever_24"
app:title="@string/remove_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_repo_1"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_custom_repo_1_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_1_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_1_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_1_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_1_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_1_delete"
app:icon="@drawable/ic_baseline_delete_forever_24"
app:title="@string/remove_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_repo_2"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_custom_repo_2_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_2_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_2_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_2_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_2_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_2_delete"
app:icon="@drawable/ic_baseline_delete_forever_24"
app:title="@string/remove_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_repo_3"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_custom_repo_3_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_3_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_3_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_3_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_3_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_3_delete"
app:icon="@drawable/ic_baseline_delete_forever_24"
app:title="@string/remove_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_repo_4"
app:title="@string/loading">
<SwitchPreferenceCompat
app:key="pref_custom_repo_4_enabled"
app:icon="@drawable/ic_baseline_extension_24"
app:switchTextOn="@string/repo_enabled"
app:switchTextOff="@string/repo_disabled"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_4_website"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_4_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_4_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_4_submit"
app:icon="@drawable/ic_baseline_upload_file_24"
app:title="@string/submit_modules"
app:singleLineTitle="false" />
<Preference
app:key="pref_custom_repo_4_delete"
app:icon="@drawable/ic_baseline_delete_forever_24"
app:title="@string/remove_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_custom_add_repo"
app:title="@string/add_repo">
<Preference
app:key="pref_custom_add_repo_button"
app:icon="@drawable/ic_baseline_add_box_24"
app:title="@string/add_repo"
app:singleLineTitle="false" />
</PreferenceCategory>
</PreferenceScreen>

@ -31,6 +31,19 @@
app:title="@string/use_magisk_install_command_pref"
app:summary="@string/use_magisk_install_command_desc"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:key="pref_background_update_check"
app:icon="@drawable/ic_baseline_notifications_24"
app:title="@string/notification_update_pref"
app:summary="@string/notification_update_desc"
app:singleLineTitle="false" />
<Preference
app:key="pref_background_update_check_debug"
app:title="@string/notification_update_debug_pref"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/pref_category_appearance">
@ -63,7 +76,7 @@
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:defaultValue="@bool/monet_enabled_by_default"
app:key="pref_enable_monet"
app:icon="@drawable/ic_baseline_design_services_24"
app:title="@string/enable_monet"
@ -111,6 +124,11 @@
app:icon="@drawable/ic_baseline_system_update_24"
app:title="@string/app_update"
app:singleLineTitle="false" />
<Preference
app:key="pref_report_bug"
app:icon="@drawable/ic_baseline_bug_report_24"
app:title="@string/report_bugs"
app:singleLineTitle="false" />
<Preference
app:key="pref_source_code"
app:icon="@drawable/ic_github"

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
gradlePluginPortal()
}
project.ext.latestAboutLibsRelease = "10.3.0"
project.ext.latestAboutLibsRelease = "10.3.1"
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}"

Binary file not shown.
Loading…
Cancel
Save