diff --git a/LEGALS.md b/LEGALS.md
new file mode 100644
index 0000000..1cb249f
--- /dev/null
+++ b/LEGALS.md
@@ -0,0 +1,15 @@
+# LEGALS
+
+## Fox's Magisk Module Manager
+- Maintained: Yes
+ - Maintainers
+ - [Fox2Code](https://github.com/Fox2Code)
+ - [androidacybot](https://github.com/androidacybot)
+- License: [ LGPL-3.0 license](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/LICENCE)
+
+## Rosetta ([Fork](https://github.com/iamjazzar/rosetta))
+- Maintained: No
+ - Maintainers
+ - [iamjazzar](https://github.com/iamjazzar)
+ - Others unknown
+- License: [ MIT license](https://github.com/iamjazzar/rosetta/blob/master/LICENSE)
diff --git a/app/build.gradle b/app/build.gradle
index e70951f..91cb9fa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -71,7 +71,10 @@ android {
}
aboutLibraries {
- additionalLicenses = ["LGPL_3_0_only"]
+ additionalLicenses = [
+ "LGPL_3_0_only",
+ "rosetta"
+ ]
}
configurations {
@@ -88,7 +91,7 @@ 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.5.0'
+ implementation 'com.google.android.material:material:1.6.0'
implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}"
implementation "dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0"
implementation "dev.rikka.rikkax.insets:insets:1.2.0"
@@ -98,6 +101,7 @@ dependencies {
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 project(":rosetta")
// Markdown
implementation "io.noties.markwon:core:4.6.2"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 01792e6..0f0ec7d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,7 +32,8 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
- tools:targetApi="s">
+ tools:targetApi="s"
+ tools:replace="android:supportsRtl">
@@ -62,7 +63,9 @@
android:name=".installer.InstallerActivity"
android:parentActivityName=".MainActivity"
android:exported="false"
- android:launchMode="singleTop">
+ android:screenOrientation="portrait"
+ android:launchMode="singleTop"
+ tools:ignore="LockedOrientationActivity">
diff --git a/app/src/main/java/com/fox2code/mmm/Constants.java b/app/src/main/java/com/fox2code/mmm/Constants.java
index 18c21a0..d1d1dff 100644
--- a/app/src/main/java/com/fox2code/mmm/Constants.java
+++ b/app/src/main/java/com/fox2code/mmm/Constants.java
@@ -23,6 +23,11 @@ public class Constants {
public static final String EXTRA_MARKDOWN_URL = "extra_markdown_url";
public static final String EXTRA_MARKDOWN_TITLE = "extra_markdown_title";
public static final String EXTRA_MARKDOWN_CONFIG = "extra_markdown_config";
+ public static final String EXTRA_MARKDOWN_CHANGE_BOOT = "extra_markdown_change_boot";
+ public static final String EXTRA_MARKDOWN_NEEDS_RAMDISK = "extra_markdown_needs_ramdisk";
+ public static final String EXTRA_MARKDOWN_MIN_MAGISK = "extra_markdown_min_magisk";
+ public static final String EXTRA_MARKDOWN_MIN_API= "extra_markdown_min_api";
+ public static final String EXTRA_MARKDOWN_MAX_API = "extra_markdown_max_api";
public static final String EXTRA_FADE_OUT = "extra_fade_out";
public static final String EXTRA_FROM_MANAGER = "extra_from_manager";
}
diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java
index ac98aca..5ef3082 100644
--- a/app/src/main/java/com/fox2code/mmm/MainActivity.java
+++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java
@@ -32,6 +32,8 @@ 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;
@@ -68,10 +70,10 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
super.onCreate(savedInstanceState);
- this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
+ this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
IntentHelper.startActivity(this, SettingsActivity.class);
- return true;
- }, R.string.pref_category_settings);
+ return true;
+ }, R.string.pref_category_settings);
setContentView(R.layout.activity_main);
this.setTitle(R.string.app_name);
this.getWindow().setFlags(
@@ -106,10 +108,10 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
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();
+ .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) {
@@ -255,7 +257,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets();
- this.actionBarBlur.invalidate();
+ //this.actionBarBlur.invalidate();
this.overScrollInsetTop = combinedBarsHeight;
this.overScrollInsetBottom = bottomInset;
Log.d(TAG, "( " + bottomInset + ", " +
diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java
index 059fefe..1e0d745 100644
--- a/app/src/main/java/com/fox2code/mmm/MainApplication.java
+++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java
@@ -23,6 +23,7 @@ import com.fox2code.mmm.compat.CompatThemeWrapper;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.GMSProviderInstaller;
import com.fox2code.mmm.utils.Http;
+import com.google.android.material.color.DynamicColors;
import com.topjohnwu.superuser.Shell;
import java.text.SimpleDateFormat;
@@ -115,11 +116,20 @@ public class MainApplication extends CompatApplication {
return getSharedPreferences().getBoolean("pref_dns_over_https", true);
}
+ public static boolean isMonetEnabled() {
+ return getSharedPreferences().getBoolean("pref_enable_monet", false);
+ }
+
public static boolean isBlurEnabled() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
getSharedPreferences().getBoolean("pref_enable_blur", false);
}
+ public static boolean isChipsDisabled() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ getSharedPreferences().getBoolean("pref_disable_chips", false);
+ }
+
public static boolean isDeveloper() {
return BuildConfig.DEBUG ||
getSharedPreferences().getBoolean("developer", false);
@@ -275,6 +285,11 @@ public class MainApplication extends CompatApplication {
@Override
public void onCreate() {
super.onCreate();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (MainApplication.isMonetEnabled()) {
+ DynamicColors.applyToActivitiesIfAvailable(this);
+ }
+ }
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences();
// We are only one process so it's ok to do this
SharedPreferences bootPrefs = MainApplication.bootSharedPreferences =
diff --git a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java
index 9a944d9..d84eb76 100644
--- a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java
+++ b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java
@@ -5,6 +5,7 @@ import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -36,6 +37,7 @@ import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.R;
@@ -55,12 +57,12 @@ public class CompatActivity extends AppCompatActivity {
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;
- }
- };
+ @Override
+ public boolean onBackPressed(CompatActivity compatActivity) {
+ compatActivity.setOnBackPressedCallback(this);
+ return true;
+ }
+ };
final WeakReference selfReference;
private final CompatConfigHelper compatConfigHelper = new CompatConfigHelper(this);
@@ -68,7 +70,8 @@ public class CompatActivity extends AppCompatActivity {
private CompatActivity.OnBackPressedCallback onBackPressedCallback;
private MenuItem.OnMenuItemClickListener menuClickListener;
private CharSequence menuContentDescription;
- @StyleRes private int setThemeDynamic = 0;
+ @StyleRes
+ private int setThemeDynamic = 0;
private boolean onCreateCalledOnce = false;
private boolean onCreateCalled = false;
private boolean isRefreshUi = false;
@@ -221,7 +224,8 @@ public class CompatActivity extends AppCompatActivity {
}
}
- @Dimension @Px
+ @Dimension
+ @Px
public int getActionBarHeight() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
@@ -247,6 +251,15 @@ public class CompatActivity extends AppCompatActivity {
}
}
+ 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 {
@@ -264,7 +277,8 @@ public class CompatActivity extends AppCompatActivity {
}
}
- @Dimension @Px
+ @Dimension
+ @Px
public int getStatusBarHeight() {
int height = WindowInsetsCompat.CONSUMED.getInsets(
WindowInsetsCompat.Type.statusBars()).top;
@@ -476,7 +490,7 @@ public class CompatActivity extends AppCompatActivity {
this.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
- private void checkResourcesOverrides(boolean forceEnglish,Boolean nightModeOverride) {
+ private void checkResourcesOverrides(boolean forceEnglish, Boolean nightModeOverride) {
if (this.isRefreshUi || !this.onCreateCalled) return; // Wait before reload
this.compatConfigHelper.checkResourcesOverrides(forceEnglish, nightModeOverride);
}
@@ -534,6 +548,14 @@ public class CompatActivity extends AppCompatActivity {
return (CompatActivity) context;
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(newBase);
+ Locale locale = new Locale(mSharedPreferences.getString("user_preferred_country", "en"));
+ Context context = CompatWrapper.setLocale(newBase, locale);
+ super.attachBaseContext(context);
+ }
+
public WeakReference asWeakReference() {
return this.selfReference;
}
diff --git a/app/src/main/java/com/fox2code/mmm/compat/CompatWrapper.java b/app/src/main/java/com/fox2code/mmm/compat/CompatWrapper.java
new file mode 100644
index 0000000..04b1b85
--- /dev/null
+++ b/app/src/main/java/com/fox2code/mmm/compat/CompatWrapper.java
@@ -0,0 +1,40 @@
+package com.fox2code.mmm.compat;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.LocaleList;
+
+import java.util.Locale;
+
+public class CompatWrapper extends android.content.ContextWrapper {
+
+ public CompatWrapper(Context base) {
+ super(base);
+ }
+
+ public static ContextWrapper setLocale(Context context, Locale newLocale) {
+
+ Resources res = context.getResources();
+ Configuration configuration = res.getConfiguration();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ configuration.setLocale(newLocale);
+
+ LocaleList localeList = new LocaleList(newLocale);
+ LocaleList.setDefault(localeList);
+ configuration.setLocales(localeList);
+
+ context = context.createConfigurationContext(configuration);
+
+ } else {
+ configuration.setLocale(newLocale);
+ context = context.createConfigurationContext(configuration);
+
+ }
+
+ return new ContextWrapper(context);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.java b/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.java
index 2adc362..ad289c4 100644
--- a/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.java
+++ b/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.java
@@ -1,30 +1,52 @@
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;
+import android.graphics.drawable.ColorDrawable;
+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.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.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;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
+import eightbitlab.com.blurview.BlurView;
+import eightbitlab.com.blurview.RenderScriptBlur;
+
public class MarkdownActivity extends CompatActivity {
private static final String TAG = "MarkdownActivity";
@@ -32,6 +54,12 @@ public class MarkdownActivity extends CompatActivity {
private static final String[] variants = new String[]{
"readme.md", "README.MD", ".github/README.md"
};
+ private BlurView chipHolder;
+ private TextView actionBarPadding;
+ private BlurView actionBarBlur;
+ private ColorDrawable anyBarBackground;
+ private ScrollView scrollView;
+ private LinearLayout md_layout;
private static byte[] getRawMarkdown(String url) throws IOException {
String newUrl = redirects.get(url);
@@ -51,7 +79,8 @@ public class MarkdownActivity extends CompatActivity {
byte[] rawMarkdown = Http.doHttpGet(prefix + suffix, true);
redirects.put(url, newUrl); // Avoid retries
return rawMarkdown;
- } catch (IOException ignored) {}
+ } catch (IOException ignored) {
+ }
}
}
throw e;
@@ -74,6 +103,16 @@ public class MarkdownActivity extends CompatActivity {
.getString(Constants.EXTRA_MARKDOWN_TITLE);
String config = intent.getExtras()
.getString(Constants.EXTRA_MARKDOWN_CONFIG);
+ boolean change_boot = intent.getExtras()
+ .getBoolean(Constants.EXTRA_MARKDOWN_CHANGE_BOOT);
+ boolean needs_ramdisk = intent.getExtras()
+ .getBoolean(Constants.EXTRA_MARKDOWN_NEEDS_RAMDISK);
+ int min_magisk = intent.getExtras()
+ .getInt(Constants.EXTRA_MARKDOWN_MIN_MAGISK);
+ int min_api = intent.getExtras()
+ .getInt(Constants.EXTRA_MARKDOWN_MIN_API);
+ int max_api = intent.getExtras()
+ .getInt(Constants.EXTRA_MARKDOWN_MAX_API);
if (title != null && !title.isEmpty()) {
this.setTitle(title);
}
@@ -98,9 +137,41 @@ public class MarkdownActivity extends CompatActivity {
setContentView(R.layout.markdown_view);
final ViewGroup markdownBackground = findViewById(R.id.markdownBackground);
final TextView textView = findViewById(R.id.markdownView);
+ this.chipHolder = findViewById(R.id.chip_holder);
+ this.anyBarBackground = new ColorDrawable(Color.TRANSPARENT);
+ this.actionBarPadding = findViewById(R.id.markdown_action_bar_padding);
+ this.actionBarBlur = findViewById(R.id.markdown_action_bar_blur);
+ this.scrollView = findViewById(R.id.scrollView2);
+ this.md_layout = findViewById(R.id.md_layout);
final TextView footer = findViewById(R.id.markdownFooter);
UiThreadHandler.handler.postDelayed(() -> // Fix footer height
footer.setMinHeight(this.getNavigationBarHeight()), 1L);
+ this.actionBarBlur.setBackground(this.anyBarBackground);
+ this.setupBlurView(this.chipHolder, markdownBackground, this.anyBarBackground);
+ this.setupBlurView(this.actionBarBlur, markdownBackground, this.anyBarBackground);
+ this.updateScreenInsets();
+ this.updateUI();
+
+ // Really bad created (MSG by Der_Googler)
+ if (MainApplication.isChipsDisabled()) {
+ this.chipHolder.setVisibility(View.GONE);
+ } else {
+ this.chipHolder.setPadding(0,0,0,this.getNavigationBarHeight());
+ // set "message" to null to disable dialog
+ this.setChip(change_boot,
+ getString(R.string.module_can_change_boot),
+ "This module may change the boot image");
+ this.setChip(needs_ramdisk,
+ getString(R.string.module_needs_ramdisk),
+ "This module need boot ramdisk to be installed");
+ this.setChip(min_magisk, "Min. Magisk \"" + min_magisk + "\"",
+ null);
+ this.setChip(min_api, "Min. Android " + min_api,
+ null);
+ this.setChip(max_api, "Max. Android " + max_api,
+ null);
+ }
+
new Thread(() -> {
try {
Log.d(TAG, "Downloading");
@@ -129,6 +200,122 @@ public class MarkdownActivity extends CompatActivity {
}, "Markdown load thread").start();
}
+ private void setupBlurView(BlurView view, ViewGroup setupWith, ColorDrawable background) {
+ view.setBackground(background);
+ view.setupWith(setupWith).setFrameClearDrawable(
+ this.getWindow().getDecorView().getBackground())
+ .setBlurAlgorithm(new RenderScriptBlur(this))
+ .setBlurRadius(4F).setBlurAutoUpdate(true)
+ .setHasFixedTransformationMatrix(true);
+ }
+
+ private void updateScreenInsets() {
+ this.runOnUiThread(() -> this.updateScreenInsets(
+ this.getResources().getConfiguration()));
+ }
+
+ private void updateScreenInsets(Configuration configuration) {
+ boolean landscape = configuration.orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
+ int bottomInset = (landscape ? 0 : this.getNavigationBarHeight());
+ int statusBarHeight = getStatusBarHeight();
+ int actionBarHeight = getActionBarHeight();
+ int combinedBarsHeight = statusBarHeight + actionBarHeight;
+ this.actionBarPadding.setMinHeight(combinedBarsHeight);
+ //this.actionBarBlur.invalidate();
+ }
+
+ private void updateUI() {
+ boolean isLightMode = this.isLightTheme();
+ int colorBackground;
+ try {
+ colorBackground = this.getColorCompat(
+ android.R.attr.windowBackground);
+ } catch (Resources.NotFoundException e) {
+ colorBackground = this.getColorCompat(isLightMode ?
+ R.color.white : R.color.black);
+ }
+ this.md_layout.setPadding(0,this.getActionBarHeight(this) + this.getStatusBarHeight(),0,this.getNavigationBarHeight() + 56);
+ if (MainApplication.isBlurEnabled()) {
+ this.actionBarBlur.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, this.getActionBarHeight(this) + this.getStatusBarHeight()));
+ this.chipHolder.setBlurEnabled(true);
+ this.anyBarBackground.setColor(ColorUtils
+ .setAlphaComponent(colorBackground, 0x02));
+ this.anyBarBackground.setColor(Color.TRANSPARENT);
+ this.actionBarBlur.setBlurEnabled(true);
+ } else {
+ this.chipHolder.setBlurEnabled(false);
+ this.chipHolder.setOverlayColor(Color.TRANSPARENT);
+ this.anyBarBackground.setColor(colorBackground);
+ this.actionBarBlur.setBlurEnabled(false);
+ this.actionBarBlur.setOverlayColor(Color.TRANSPARENT);
+ }
+ }
+
+ private void setChip(boolean bool, String title, String message) {
+ if (bool) {
+ this.makeChip(title, message);
+ }
+ }
+
+ private void setChip(int i, String title, String message) {
+ if (i != 0) {
+ this.makeChip(title, message);
+ }
+ }
+
+ private void makeChip(String title, String message) {
+ final ChipGroup chip_group_holder = findViewById(R.id.chip_group_holder);
+ Chip chip = new Chip(this);
+ chip.setText(title);
+ chip.setVisibility(View.VISIBLE);
+ if (message != null) {
+ chip.setOnClickListener(_view -> {
+ MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(this);
+
+ builder
+ .setTitle(title)
+ .setMessage(message)
+ .setCancelable(true)
+ .setPositiveButton(R.string.ok, (x, y) -> x.dismiss()).show();
+
+ });
+ }
+ chip_group_holder.addView(chip);
+ }
+
+ private String parseAndroidVersion(int version) {
+ switch (version) {
+ case Build.VERSION_CODES.LOLLIPOP:
+ return "5.0";
+ case Build.VERSION_CODES.LOLLIPOP_MR1:
+ return "5.1";
+ case Build.VERSION_CODES.M:
+ return "6.0";
+ case Build.VERSION_CODES.N:
+ return "7.0";
+ case Build.VERSION_CODES.N_MR1:
+ return "7.1";
+ case Build.VERSION_CODES.O:
+ return "8.0";
+ case Build.VERSION_CODES.O_MR1:
+ return "8.1";
+ case Build.VERSION_CODES.P:
+ return "9.0 (P)";
+ case Build.VERSION_CODES.Q:
+ return "10 (Q)";
+ case Build.VERSION_CODES.R:
+ return "11 (R)";
+ case Build.VERSION_CODES.S:
+ return "12 (S)";
+ case Build.VERSION_CODES.S_V2:
+ return "12L";
+ default:
+ return "false";
+ }
+ }
+
@Override
protected void onResume() {
super.onResume();
diff --git a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java
index 7f7b215..8399883 100644
--- a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java
+++ b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java
@@ -1,10 +1,10 @@
package com.fox2code.mmm.module;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.text.Spanned;
import android.util.Log;
import android.widget.Button;
-import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
@@ -20,16 +20,23 @@ import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
-import com.fox2code.mmm.module.ModuleHolder;
import com.fox2code.mmm.utils.IntentHelper;
+import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import io.noties.markwon.Markwon;
+@SuppressLint("UseCompatLoadingForDrawables")
public enum ActionButtonType {
- INFO(R.drawable.ic_baseline_info_24) {
+ INFO() {
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void update(Chip button, ModuleHolder moduleHolder) {
+ button.setChipIcon(button.getContext().getResources().getDrawable(R.drawable.ic_baseline_info_24));
+ button.setText(R.string.description);
+ }
+
+ @Override
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
String notesUrl = moduleHolder.repoModule.notesUrl;
if (notesUrl.startsWith("https://api.androidacy.com/magisk/readme/?module=") ||
notesUrl.startsWith("https://www.androidacy.com/")) {
@@ -39,36 +46,47 @@ public enum ActionButtonType {
} else {
IntentHelper.openMarkdown(button.getContext(), notesUrl,
moduleHolder.repoModule.moduleInfo.name,
- moduleHolder.getMainModuleConfig());
+ moduleHolder.getMainModuleConfig(),
+ moduleHolder.repoModule.moduleInfo.changeBoot,
+ moduleHolder.repoModule.moduleInfo.needRamdisk,
+ moduleHolder.repoModule.moduleInfo.minMagisk,
+ moduleHolder.repoModule.moduleInfo.minApi,
+ moduleHolder.repoModule.moduleInfo.maxApi
+ );
}
}
@Override
- public boolean doActionLong(ImageButton button, ModuleHolder moduleHolder) {
+ public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
Context context = button.getContext();
Toast.makeText(context, context.getString(R.string.module_id_prefix) +
- moduleHolder.moduleId, Toast.LENGTH_SHORT).show();
+ moduleHolder.moduleId, Toast.LENGTH_SHORT).show();
return true;
}
},
UPDATE_INSTALL() {
@Override
- public void update(ImageButton button, ModuleHolder moduleHolder) {
+ public void update(Chip button, ModuleHolder moduleHolder) {
int icon = moduleHolder.hasUpdate() ?
R.drawable.ic_baseline_update_24 :
R.drawable.ic_baseline_system_update_24;
- button.setImageResource(icon);
+ button.setChipIcon(button.getContext().getResources().getDrawable(icon));
+ if (moduleHolder.hasUpdate()) {
+ button.setText(R.string.update);
+ } else {
+ button.setText(R.string.install);
+ }
}
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
if (moduleInfo == null) return;
String updateZipUrl = moduleHolder.getUpdateZipUrl();
if (updateZipUrl == null) return;
// Androidacy manage the selection between download and install
if (updateZipUrl.startsWith("https://www.androidacy.com/") ||
- updateZipUrl.startsWith("https://api.androidacy.com/magisk/info/?module=")) {
+ updateZipUrl.startsWith("https://api.androidacy.com/magisk/info/?module=")) {
IntentHelper.openUrlAndroidacy(
button.getContext(), updateZipUrl, true,
moduleInfo.name, moduleInfo.config);
@@ -125,18 +143,19 @@ public enum ActionButtonType {
},
UNINSTALL() {
@Override
- public void update(ImageButton button, ModuleHolder moduleHolder) {
+ public void update(Chip button, ModuleHolder moduleHolder) {
int icon = moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING) ?
R.drawable.ic_baseline_delete_outline_24 : (
!moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) ||
- moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) ?
- R.drawable.ic_baseline_delete_24 :
- R.drawable.ic_baseline_delete_forever_24;
- button.setImageResource(icon);
+ moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) ?
+ R.drawable.ic_baseline_delete_24 :
+ R.drawable.ic_baseline_delete_forever_24;
+ button.setChipIcon(button.getContext().getResources().getDrawable(icon));
+ button.setText(R.string.uninstall);
}
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
if (!moduleHolder.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE |
ModuleInfo.FLAG_MODULE_UNINSTALLING) &&
moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING)) {
@@ -151,7 +170,7 @@ public enum ActionButtonType {
}
@Override
- public boolean doActionLong(ImageButton button, ModuleHolder moduleHolder) {
+ public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
// We can't trust active flag on first boot
if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) return false;
new AlertDialog.Builder(button.getContext()).setTitle(R.string.master_delete)
@@ -163,13 +182,20 @@ public enum ActionButtonType {
moduleHolder.moduleInfo = null;
CompatActivity.getCompatActivity(button).refreshUI();
}
- }).setNegativeButton(R.string.master_delete_no, (v, i) -> {}).create().show();
+ }).setNegativeButton(R.string.master_delete_no, (v, i) -> {
+ }).create().show();
return true;
}
},
- CONFIG(R.drawable.ic_baseline_app_settings_alt_24) {
+ CONFIG() {
+ @Override
+ public void update(Chip button, ModuleHolder moduleHolder) {
+ button.setChipIcon(button.getContext().getResources().getDrawable(R.drawable.ic_baseline_app_settings_alt_24));
+ button.setText(R.string.config);
+ }
+
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
String config = moduleHolder.getMainModuleConfig();
if (config == null) return;
if (AndroidacyUtil.isAndroidacyLink(config)) {
@@ -181,19 +207,20 @@ public enum ActionButtonType {
},
SUPPORT() {
@Override
- public void update(ImageButton button, ModuleHolder moduleHolder) {
+ public void update(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
- button.setImageResource(supportIconForUrl(moduleInfo.support));
+ button.setChipIcon(button.getContext().getResources().getDrawable(supportIconForUrl(moduleInfo.support)));
+ button.setText(R.string.support);
}
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
IntentHelper.openUrl(button.getContext(), moduleHolder.getMainModuleInfo().support);
}
},
DONATE() {
@Override
- public void update(ImageButton button, ModuleHolder moduleHolder) {
+ 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/")) {
@@ -201,11 +228,12 @@ public enum ActionButtonType {
} else if (moduleInfo.donate.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
- button.setImageResource(icon);
+ button.setChipIcon(button.getContext().getResources().getDrawable(icon));
+ button.setText(R.string.donate);
}
@Override
- public void doAction(ImageButton button, ModuleHolder moduleHolder) {
+ public void doAction(Chip button, ModuleHolder moduleHolder) {
IntentHelper.openUrl(button.getContext(), moduleHolder.getMainModuleInfo().donate);
}
};
@@ -237,13 +265,13 @@ public enum ActionButtonType {
this.iconId = iconId;
}
- public void update(ImageButton button, ModuleHolder moduleHolder) {
- button.setImageResource(this.iconId);
+ public void update(Chip button, ModuleHolder moduleHolder) {
+ button.setChipIcon(button.getContext().getResources().getDrawable(this.iconId));
}
- public abstract void doAction(ImageButton button, ModuleHolder moduleHolder);
+ public abstract void doAction(Chip button, ModuleHolder moduleHolder);
- public boolean doActionLong(ImageButton button, ModuleHolder moduleHolder) {
+ public boolean doActionLong(Chip button, ModuleHolder moduleHolder) {
return false;
}
}
diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java
index 6331bf2..a5adda9 100644
--- a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java
+++ b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java
@@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -25,12 +26,15 @@ import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoModule;
+import com.google.android.material.chip.Chip;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.util.ArrayList;
import java.util.Objects;
+
public final class ModuleViewAdapter extends RecyclerView.Adapter {
private static final boolean DEBUG = false;
public final ArrayList moduleHolders = new ArrayList<>();
@@ -64,13 +68,15 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter actionButtonsTypes;
private boolean initState;
public ModuleHolder moduleHolder;
@@ -80,13 +86,15 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter {
+ MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(_view.getContext());
+
+ builder
+ .setTitle(R.string.low_quality_module)
+ .setMessage("Actual description for Low-quality module")
+ .setCancelable(true)
+ .setPositiveButton(R.string.ok, (x, y) -> x.dismiss()).show();
+
+ });
+ this.invalidPropsChip.setVisibility(View.VISIBLE);
+ // Backup restore
+ // foregroundAttr = R.attr.colorOnError;
+ // backgroundAttr = R.attr.colorError;
}
Resources.Theme theme = this.cardView.getContext().getTheme();
TypedValue value = new TypedValue();
diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java
index 7f3cf14..2a01a0a 100644
--- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java
+++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java
@@ -1,5 +1,6 @@
package com.fox2code.mmm.settings;
+import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
@@ -19,15 +20,19 @@ 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.compat.CompatThemeWrapper;
import com.fox2code.mmm.installer.InstallerInitializer;
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.ahmedjazzar.rosetta.LanguageSwitcher;
import com.mikepenz.aboutlibraries.LibsBuilder;
import com.topjohnwu.superuser.internal.UiThreadHandler;
+import java.util.HashSet;
+import java.util.Locale;
+
public class SettingsActivity extends CompatActivity {
private static int devModeStep = 0;
@@ -81,25 +86,61 @@ public class SettingsActivity extends CompatActivity {
enableBlur.setSummary(R.string.require_android_6);
enableBlur.setEnabled(false);
}
- Preference forceEnglish = findPreference("pref_force_english");
- forceEnglish.setOnPreferenceChangeListener((preference, newValue) -> {
- CompatThemeWrapper compatThemeWrapper =
- MainApplication.getINSTANCE().getMarkwonThemeContext();
- if (compatThemeWrapper != null) {
- compatThemeWrapper.setForceEnglish(
- Boolean.parseBoolean(String.valueOf(newValue)));
- }
- return true;
- });
- if (!MainApplication.isDeveloper()) {
- forceEnglish.setVisible(false);
+
+ Preference disableMonet = findPreference("pref_enable_monet");
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ disableMonet.setSummary(R.string.require_android_12);
+ disableMonet.setEnabled(false);
}
+
findPreference("pref_dns_over_https").setOnPreferenceChangeListener((p, v) -> {
Http.setDoh((Boolean) v);
return true;
});
- if ("dark".equals(themePreference.getValue())) {
- findPreference("pref_force_dark_terminal").setEnabled(false);
+
+ // This is the locale that you wanna your app to launch with.
+ String firstLaunchLocale = "en";
+
+ // Warning! Locales that are't exist will crash the app
+ HashSet supportedLocales = new HashSet<>();
+ supportedLocales.add("cs");
+ supportedLocales.add("de");
+ supportedLocales.add("es-rMX");
+ supportedLocales.add("et");
+ supportedLocales.add("fr");
+ supportedLocales.add("id");
+ supportedLocales.add("ja");
+ supportedLocales.add("nb-rNO");
+ supportedLocales.add("pl");
+ supportedLocales.add("pt-rBR");
+ supportedLocales.add("ro");
+ supportedLocales.add("ru");
+ supportedLocales.add("sk");
+ supportedLocales.add("tr");
+ supportedLocales.add("vi");
+ supportedLocales.add("zh-rCH");
+ supportedLocales.add("zh-rTW");
+ supportedLocales.add(firstLaunchLocale);
+
+ Preference languageSelector = findPreference("pref_language_selector");
+ languageSelector.setOnPreferenceClickListener(preference -> {
+ LanguageSwitcher ls = new LanguageSwitcher(getActivity(), new Locale(firstLaunchLocale));
+ ls.showChangeLanguageDialog(getActivity());
+ ls.setSupportedStringLocales(supportedLocales);
+ 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;
}
if (!MainApplication.isDeveloper()) {
findPreference("pref_disable_low_quality_module_filter").setVisible(false);
@@ -208,7 +249,7 @@ public class SettingsActivity extends CompatActivity {
preference.setTitle(R.string.repo_disabled);
preference.setEnabled(false);
} else {
- ((TwoStatePreference)preference).setChecked(repoData.isEnabled());
+ ((TwoStatePreference) preference).setChecked(repoData.isEnabled());
preference.setTitle(repoData.isEnabled() ?
R.string.repo_enabled : R.string.repo_disabled);
preference.setOnPreferenceChangeListener((p, newValue) -> {
diff --git a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java
index 0026fd1..2a77007 100644
--- a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java
+++ b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java
@@ -165,12 +165,17 @@ public class IntentHelper {
}
}
- public static void openMarkdown(Context context, String url, String title, String config) {
+ public static void openMarkdown(Context context, String url, String title, String config, Boolean changeBoot, Boolean needsRamdisk,int minMagisk, int minApi, int maxApi) {
try {
Intent intent = new Intent(context, MarkdownActivity.class);
MainApplication.addSecret(intent);
intent.putExtra(Constants.EXTRA_MARKDOWN_URL, url);
intent.putExtra(Constants.EXTRA_MARKDOWN_TITLE, title);
+ intent.putExtra(Constants.EXTRA_MARKDOWN_CHANGE_BOOT, changeBoot);
+ intent.putExtra(Constants.EXTRA_MARKDOWN_NEEDS_RAMDISK, needsRamdisk);
+ intent.putExtra(Constants.EXTRA_MARKDOWN_MIN_MAGISK, minMagisk);
+ intent.putExtra(Constants.EXTRA_MARKDOWN_MIN_API, minApi);
+ intent.putExtra(Constants.EXTRA_MARKDOWN_MAX_API, maxApi);
if (config != null && !config.isEmpty())
intent.putExtra(Constants.EXTRA_MARKDOWN_CONFIG, config);
startActivity(context, intent, true);
diff --git a/app/src/main/res/drawable/ic_baseline_chip_24.xml b/app/src/main/res/drawable/ic_baseline_chip_24.xml
new file mode 100644
index 0000000..f6f82d3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_chip_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_design_services_24.xml b/app/src/main/res/drawable/ic_baseline_design_services_24.xml
new file mode 100644
index 0000000..872fcba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_design_services_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_foreground.xml b/app/src/main/res/drawable/ic_foreground.xml
new file mode 100644
index 0000000..35f0a68
--- /dev/null
+++ b/app/src/main/res/drawable/ic_foreground.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/markdown_view.xml b/app/src/main/res/layout/markdown_view.xml
index 81e1391..a0aef44 100644
--- a/app/src/main/res/layout/markdown_view.xml
+++ b/app/src/main/res/layout/markdown_view.xml
@@ -1,23 +1,36 @@
-
+ app:fitsSystemWindowsInsets="left|right"
+ tools:context=".markdown.MarkdownActivity">
+
+ android:id="@+id/scrollView2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/module_entry.xml b/app/src/main/res/layout/module_entry.xml
index 8881612..e0a8234 100644
--- a/app/src/main/res/layout/module_entry.xml
+++ b/app/src/main/res/layout/module_entry.xml
@@ -8,186 +8,190 @@
android:layout_marginRight="8dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
-
+
+
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
-
+
+ android:layout_height="194dp"
+ android:visibility="gone"
+ android:scaleType="centerCrop"
+ tools:ignore="ContentDescription" />
-
+ android:orientation="vertical"
+ android:padding="8dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+ tools:ignore="UselessParent">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index ca0ddfd..abf424b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,9 +1,5 @@
-
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..f5ff9dd
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index e6ce6f6..4e15f94 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -25,28 +25,24 @@
Sperrmodus
Sperrmodus verhindert, dass der Manager Aktionen an Modulen ausführt
Einstellungen
- Info
Lizenzen anzeigen
Lizenzen
Inkompatible Module anzeigen
Module anzeigen, die aufgrund ihrer Metadaten nicht mit Ihrem Gerät kompatibel sind
Magisk ist veraltet!
- Repos
Das Repository, das Magisk-Module hostet
Eine Alternative zur Magisk-Module-Repo mit weniger Einschränkungen.
Löschen der Moduldateien?
Dateien behalten
Dateien löschen
Fehler beim Löschen der Moduldateien
- Theme
- Module id:
- Modul aus dem Speicher installieren
+ Module aus dem Speicher installieren
Das ausgewählte Modul hat ein ungültiges Format
Lokale Installation
Quellcode
Eingebautes Magisk Modul
Eingebautes Substratum Modul
- Dunkelmodus-Terminal erzwingen
+ Dunkeles Terminal erzwingen
Ihr aktueller Dateimanager konnte keinen Zugriff auf die Datei gewähren.
Remote-Installation
Ihr Dateimanager hat eine nicht standardmäßige Antwort zurückgegeben.
@@ -62,4 +58,49 @@
Einige Module deklarieren ihre Metadaten nicht richtig, was zu visuellen Störungen führt,
und/oder auf eine schlechte Modulqualität hinweisen, deaktivieren auf eigene Gefahr!
+ System WebView konnte nicht geöffnet werden
+ Keine Beschreibung gefunden
+ Module herunterladen
+ Module instalieren
+ Module aktualisieren
+ Anderungsprotokoll
+ Webseite
+ Hilfe
+ Spenden
+ Module einsenden
+ Erfordert Android 6.0+
+ Erfordert Android 12+
+ Neustarten
+ Ja
+ Nein
+ Benötigt Ramdisk
+ Kann Boot ändern
+ Repos verwalten
+ Neustart verhindern
+ Verhindert unerwartete Neustarts
+ Aktivire Monet
+ Sicherheit
+ Ausehen
+ Allgemein
+ Theme Modus
+ DNS über HTTPS
+ Kann in einigen Fällen Verbindungsprobleme beheben
+ Deaktivieren Sie die Mmm-Erweiterungen von Fox, um zu verhindern, dass Module Terminalerweiterungen verwenden. Nützlich, wenn ein Modul die Mmm-Erweiterungen von Fox missbraucht
+ Textumbruch
+ Zeigen Sie Text in mehreren Zeilen an, anstatt den gesamten Text in derselben Zeile zu platzieren, wenn Sie ein Modul installieren.
+ Chips in Beschreibung deaktivieren
+ Repo an
+ Repo aus
+ Repo hinzufügen
+ Repo entfernen
+ Eigene URL
+ Das Androidacy-Repo enthält Anzeigen und Tracker.
+ Backup Modules
+ Module wiederherstellen
+ Dieser Vorgang erfordert eine Internetverbindung
+ Aktualisieren
+ Installieren
+ Beschreibung
+ Deinstallieren
+ Konfig
diff --git a/app/src/main/res/values-v31/colors.xml b/app/src/main/res/values-v31/colors.xml
new file mode 100644
index 0000000..04caa4a
--- /dev/null
+++ b/app/src/main/res/values-v31/colors.xml
@@ -0,0 +1,78 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ @android:color/system_neutral1_0
+ @android:color/system_neutral1_10
+ @android:color/system_neutral1_50
+ @android:color/system_neutral1_100
+ @android:color/system_neutral1_200
+ @android:color/system_neutral1_300
+ @android:color/system_neutral1_400
+ @android:color/system_neutral1_500
+ @android:color/system_neutral1_600
+ @android:color/system_neutral1_700
+ @android:color/system_neutral1_800
+ @android:color/system_neutral1_900
+ @android:color/system_neutral1_1000
+ @android:color/system_neutral2_0
+ @android:color/system_neutral2_10
+ @android:color/system_neutral2_50
+ @android:color/system_neutral2_100
+ @android:color/system_neutral2_200
+ @android:color/system_neutral2_300
+ @android:color/system_neutral2_400
+ @android:color/system_neutral2_500
+ @android:color/system_neutral2_600
+ @android:color/system_neutral2_700
+ @android:color/system_neutral2_800
+ @android:color/system_neutral2_900
+ @android:color/system_neutral2_1000
+ @android:color/system_accent1_0
+ @android:color/system_accent1_10
+ @android:color/system_accent1_50
+ @android:color/system_accent1_100
+ @android:color/system_accent1_200
+ @android:color/system_accent1_300
+ @android:color/system_accent1_400
+ @android:color/system_accent1_500
+ @android:color/system_accent1_600
+ @android:color/system_accent1_700
+ @android:color/system_accent1_800
+ @android:color/system_accent1_900
+ @android:color/system_accent1_1000
+ @android:color/system_accent2_0
+ @android:color/system_accent2_10
+ @android:color/system_accent2_50
+ @android:color/system_accent2_100
+ @android:color/system_accent2_200
+ @android:color/system_accent2_300
+ @android:color/system_accent2_400
+ @android:color/system_accent2_500
+ @android:color/system_accent2_600
+ @android:color/system_accent2_700
+ @android:color/system_accent2_800
+ @android:color/system_accent2_900
+ @android:color/system_accent2_1000
+ @android:color/system_accent3_0
+ @android:color/system_accent3_10
+ @android:color/system_accent3_50
+ @android:color/system_accent3_100
+ @android:color/system_accent3_200
+ @android:color/system_accent3_300
+ @android:color/system_accent3_400
+ @android:color/system_accent3_500
+ @android:color/system_accent3_600
+ @android:color/system_accent3_700
+ @android:color/system_accent3_800
+ @android:color/system_accent3_900
+ @android:color/system_accent3_1000
+
+ @android:color/system_accent1_100
+ @android:color/system_neutral2_700
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml
new file mode 100644
index 0000000..99a50b5
--- /dev/null
+++ b/app/src/main/res/values-v31/themes.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 490833b..2e1c3a1 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -13,4 +13,8 @@
#FF000000
#FFFFFFFF
@color/black_transparent
+ @color/black_transparent
+
+ #FF9B08
+ @android:color/white
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 045038e..6cd9c7a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -22,11 +22,20 @@
Website
Support
Donate
+ Update
+ Install
+ Description
+ Uninstall
+ Config
Submit a module
Requires Android 6.0+
+ Requires Android 12+
Reboot
- Yes
- No
+ Language
+ @android:string/yes
+ @android:string/no
+ @android:string/ok
+ @android:string/cancel
Last version:
@@ -34,6 +43,8 @@
by
Downloads:
Stars:
+ Needs ramdisk
+ Can change boot
@@ -43,6 +54,7 @@
Prevent reboot
Prevents unexpected reboots
Settings
+ Enable Monet
Info
Show licenses
Licences
@@ -64,6 +76,7 @@
Module ID:
Install module from storage
The selected module has an invalid format
+ Low-quality module
Local install
Source code
Magisk built-in module
@@ -100,6 +113,7 @@
all text on the same line when installing a module.
Blur
+ Disable chips in description
Repo on
Repo off
Add Repo
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index c1de3e1..1731fc7 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -28,6 +28,13 @@
- @dimen/card_corner_radius
- #FFFFFFFF
- #FFFFFFFF
+ - @style/Widget.Material.Chip.Choice.Light
+
+
+
+
+
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index f658a6f..0e8c356 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -43,6 +43,11 @@
app:entryValues="@array/theme_values"
app:singleLineTitle="false" />
+
+
-
-
+
+
+
+
+
+
+
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java
new file mode 100644
index 0000000..53d286e
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguageSwitcher.java
@@ -0,0 +1,179 @@
+package com.ahmedjazzar.rosetta;
+
+import android.app.Activity;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import java.util.HashSet;
+import java.util.Locale;
+
+/**
+ * This class is the application door to this Library. It handles the ongoing and outgoing requests,
+ * initializations, preferences, ..
+ * I think that there's no need for logging here because other classes already handle logs for these
+ * actions based on their returned results.
+ *
+ * Created by ahmedjazzar on 1/16/16.
+ */
+public class LanguageSwitcher {
+
+ private Context mContext;
+ private LocalesPreferenceManager mLocalesPreferences;
+ private final String TAG = LanguageSwitcher.class.getName();
+
+ /**
+ * A constructor that accepts context and sets the base and first launch locales to en_US
+ * @param context the context of the dealer
+ */
+ public LanguageSwitcher(@NonNull Context context) {
+ this(context, Locale.US);
+ }
+
+ /**
+ * A constructor that accepts context and sets the base and first launch locales to
+ * firstLaunchLocale.
+ *
+ * NOTE: Please do not use unless:
+ * 1. You wanna set your locales by calling {@link LanguageSwitcher#setSupportedLocales}
+ * 2. You know for sure that the preferred locale is as same as your base locale
+ *
+ * @param context the context of the dealer
+ * @param firstLaunchLocale the locale that owner wanna use at its first launch
+ */
+ public LanguageSwitcher(@NonNull Context context, Locale firstLaunchLocale) {
+ this(context, firstLaunchLocale, firstLaunchLocale);
+ }
+
+ /**
+ * This is supposed to be more specific; It has three parameters cover all owner needs
+ * @param context the context of the dealer
+ * @param firstLaunchLocale the locale that owner wanna use at its first launch
+ * @param baseLocale the locale that used in the main xml strings file (most likely 'en')
+ */
+ public LanguageSwitcher(@NonNull Context context, Locale firstLaunchLocale, Locale baseLocale) {
+ this.mContext = context.getApplicationContext();
+
+ this.mLocalesPreferences =
+ new LocalesPreferenceManager(context, firstLaunchLocale, baseLocale);
+
+ // initializing Locales utils needed objects (detector, preferences)
+ LocalesUtils.setDetector(new LocalesDetector(this.mContext));
+ LocalesUtils.setLocalesPreferenceManager(mLocalesPreferences);
+
+ // Setting app locale to match the user preferred one
+ LocalesUtils.setAppLocale(mContext,
+ mLocalesPreferences
+ .getPreferredLocale(LocalesPreferenceManager.USER_PREFERRED_LOCALE));
+ }
+
+ /**
+ * Responsible for displaying Change dialog fragment
+ */
+ public void showChangeLanguageDialog(FragmentActivity activity) {
+ new LanguagesListDialogFragment()
+ .show(activity.getSupportFragmentManager(), TAG);
+ }
+
+ /**
+ *
+ * @return the application supported locales
+ */
+ public HashSet getLocales() {
+ return LocalesUtils.getLocales();
+ }
+
+ /**
+ * Sets the app locales from a string Set
+ * @param sLocales supported locales in a String form
+ */
+ public void setSupportedStringLocales(HashSet sLocales) {
+
+ HashSet locales = new HashSet<>();
+ for (String sLocale: sLocales) {
+ locales.add(new Locale(sLocale));
+ }
+ this.setSupportedLocales(locales);
+ }
+
+ /**
+ * set supported locales from the given Set
+ * @param locales supported locales
+ */
+ public void setSupportedLocales(HashSet locales) {
+ LocalesUtils.setSupportedLocales(locales);
+ }
+
+ /**
+ * Sets the supported locales after fetching there availability using fetchAvailableLocales
+ * method
+ * @param stringId the string that this library gonna use to detect current app available
+ * locales
+ */
+ public void setSupportedLocales(int stringId) {
+ this.setSupportedLocales(this.fetchAvailableLocales(stringId));
+ }
+
+ /**
+ * Fetching the application available locales inside the resources folder dynamically
+ * @param stringId the string that this library gonna use to detect current app available
+ * locales
+ * @return a set of detected application locales
+ */
+ public HashSet fetchAvailableLocales(int stringId) {
+ return LocalesUtils.fetchAvailableLocales(stringId);
+ }
+
+ /**
+ * Setting the application locale manually
+ * @param newLocale the locale in a string format
+ * @param activity the current activity in order to refresh the app
+ *
+ * @return true if the operation succeed, false otherwise
+ */
+ public boolean setLocale(String newLocale, Activity activity) {
+ return setLocale(new Locale(newLocale), activity);
+ }
+
+ /**
+ * Setting the application locale manually
+ * @param newLocale the desired locale
+ * @param activity the current activity in order to refresh the app
+ *
+ * @return true if the operation succeed, false otherwise
+ */
+ public boolean setLocale(Locale newLocale, Activity activity) {
+
+ return LocalesUtils.setLocale(newLocale, activity);
+ }
+
+ /**
+ *
+ * @return the first launch locale
+ */
+ public Locale getLaunchLocale() {
+
+ return LocalesUtils.getLaunchLocale();
+ }
+
+ /**
+ *
+ * @return the current locale
+ */
+ public Locale getCurrentLocale() {
+
+ return LocalesUtils.getCurrentLocale(this.mContext);
+ }
+
+ /**
+ * Return to the first launch locale
+ * @param activity the current activity in order to refresh the app
+ *
+ * @return true if the operation succeed, false otherwise
+ */
+ public boolean switchToLaunch(Activity activity) {
+
+ return setLocale(getLaunchLocale(), activity);
+ }
+}
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java
new file mode 100644
index 0000000..b141e36
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LanguagesListDialogFragment.java
@@ -0,0 +1,180 @@
+package com.ahmedjazzar.rosetta;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This fragment is responsible for displaying the supported locales and performing any necessary
+ * action that allows user to select, cancel, and commit changes.
+ *
+ * Created by ahmedjazzar on 1/19/16.
+ */
+
+public class LanguagesListDialogFragment extends DialogFragment {
+
+ private final int DIALOG_TITLE_ID = R.string.language;
+ private final int DIALOG_POSITIVE_ID = R.string.ok;
+ private final int DIALOG_NEGATIVE_ID = R.string.cancel;
+
+ private int mSelectedLanguage = -1;
+ private final Logger mLogger;
+
+ public LanguagesListDialogFragment() {
+ String TAG = LanguagesListDialogFragment.class.getName();
+ this.mLogger = new Logger(TAG);
+ }
+
+ /**
+ * @return a Dialog fragment
+ */
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ mLogger.debug("Building DialogFragment.");
+
+ builder.setTitle(getString(DIALOG_TITLE_ID))
+ .setSingleChoiceItems(
+ getLanguages(),
+ getCurrentLocaleIndex(),
+ (dialogInterface, which) -> onLanguageSelectedLocalized(which))
+ .setPositiveButton(
+ getString(DIALOG_POSITIVE_ID).toUpperCase(),
+ (dialogInterface, which) -> onPositiveClick())
+ .setNegativeButton(
+ getString(DIALOG_NEGATIVE_ID).toUpperCase(),
+ (dialogInterface, which) -> onNegativeClick());
+
+ mLogger.verbose("DialogFragment built.");
+ return builder.create();
+ }
+
+ /**
+ * @param which the position of the selected locale
+ */
+ protected void onLanguageSelected(int which) {
+ // just update the selected locale
+ mSelectedLanguage = which;
+ }
+
+ /**
+ * Localizing the dialog buttons and title
+ * @param which the position of the selected locale
+ */
+ protected void onLanguageSelectedLocalized(int which) {
+
+ // update the selected locale
+ mSelectedLanguage = which;
+ AlertDialog dialog = (AlertDialog) getDialog();
+
+ mLogger.debug("Displaying dialog main strings in the selected " +
+ "locale");
+
+ onLanguageSelectedLocalized(
+ which,
+ null,
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+ dialog.getButton(AlertDialog.BUTTON_NEGATIVE));
+ }
+
+ /**
+ * the position of the selected locale given the ids
+ * @param which the position of the selected locale
+ * @param titleView dialog's title text view
+ * @param positiveButton positive button
+ * @param negativeButton negative button
+ */
+ protected void onLanguageSelectedLocalized(int which, TextView titleView, Button positiveButton,
+ Button negativeButton) {
+
+ // update the selected locale
+ mSelectedLanguage = which;
+ Locale locale = LocalesUtils.getLocaleFromIndex(mSelectedLanguage);
+ AlertDialog dialog = (AlertDialog) getDialog();
+ FragmentActivity activity = getActivity();
+
+ mLogger.debug("Displaying dialog main strings in the selected " +
+ "locale");
+
+ assert activity != null;
+ String LocalizedTitle = LocalesUtils.getInSpecificLocale(activity, locale, DIALOG_TITLE_ID);
+ if(titleView == null) {
+ // Display dialog title in the selected locale
+ assert dialog != null;
+ dialog.setTitle(LocalizedTitle);
+ } else {
+ titleView.setText(LocalizedTitle);
+ }
+
+ // Display positive button text in the selected locale
+ positiveButton.setText(LocalesUtils.getInSpecificLocale(
+ activity, locale, DIALOG_POSITIVE_ID));
+
+ // Display negative button text in the selected locale
+ negativeButton.setText(LocalesUtils.getInSpecificLocale(
+ activity, locale, DIALOG_NEGATIVE_ID));
+ }
+
+ /**
+ * called when the user approved changing locale
+ */
+ protected void onPositiveClick() {
+
+ // if the user did not select the same locale go ahead, else ignore
+ if (mSelectedLanguage != -1 &&
+ mSelectedLanguage != LocalesUtils.getCurrentLocaleIndex()) {
+
+ // Try changing the locale
+ if (LocalesUtils.setAppLocale(
+ getActivity(), mSelectedLanguage)) {
+
+ mLogger.info("App locale changed successfully.");
+ LocalesUtils.refreshApplication(requireActivity());
+ } else {
+ mLogger.error("Unsuccessful trial to change the App locale.");
+ // TODO: notify the user that his request not placed
+ }
+ } else {
+ dismiss();
+ }
+ }
+
+ /**
+ * called when the user discarded changing locale
+ */
+ protected void onNegativeClick() {
+ mLogger.verbose("User discarded changing language.");
+ mLogger.debug("Return to the original locale.");
+ this.onLanguageSelectedLocalized(this.getCurrentLocaleIndex());
+ }
+
+ /**
+ *
+ * @return available languages
+ */
+ protected String[] getLanguages() {
+ ArrayList languages = LocalesUtils.getLocalesWithDisplayName();
+ return languages.toArray(new String[languages.size()]);
+ }
+
+ /**
+ *
+ * @return the index of the locale that app is using now
+ */
+ protected int getCurrentLocaleIndex() {
+ return LocalesUtils.getCurrentLocaleIndex();
+ }
+
+}
\ No newline at end of file
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java
new file mode 100644
index 0000000..61e3171
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesDetector.java
@@ -0,0 +1,153 @@
+package com.ahmedjazzar.rosetta;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.DisplayMetrics;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+
+/**
+ * This class detects the application available locales inside the resources based on a string id,
+ * it's not so accurate and expects another methodologies. Next release may hold a better algorithms
+ * for detecting strings' languages and availability inside apps.
+ *
+ * Created by ahmedjazzar on 1/16/16.
+ */
+class LocalesDetector {
+
+ private final Context mContext;
+ private Logger mLogger;
+ private final String TAG = LocalesDetector.class.getName();
+
+ LocalesDetector(Context context) {
+ this.mContext = context;
+ this.mLogger = new Logger(TAG);
+ }
+
+ /**
+ * this method takes an experimental string id to see if it's exists in other available
+ * locales inside the app than default locale.
+ * NOTE: Even if you have a folder named values-ar it doesn't mean you have any resources
+ * there
+ *
+ * @param stringId experimental string id to discover locales
+ * @return the discovered locales
+ */
+ HashSet fetchAvailableLocales(int stringId) {
+
+ DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ Configuration conf = mContext.getResources().getConfiguration();
+ Locale originalLocale = conf.locale;
+ Locale baseLocale = LocalesUtils.getBaseLocale();
+ conf.locale = baseLocale;
+
+ ArrayList references = new ArrayList<>();
+ references.add(new Resources(mContext.getAssets(), dm, conf).getString(stringId));
+
+ HashSet result = new HashSet<>();
+ result.add(baseLocale);
+
+ for(String loc : mContext.getAssets().getLocales()) {
+ if(loc.isEmpty()){
+ continue;
+ }
+
+ Locale l;
+ boolean referencesUpdateLock = false;
+
+ l = Locale.forLanguageTag(loc);
+
+ conf.locale = l;
+
+ //TODO: put it in a method
+ String tmpString = new Resources(mContext.getAssets(), dm, conf).getString(stringId);
+ for (String reference: references) {
+ if(reference.equals(tmpString)){
+ // TODO: check its original locale
+ referencesUpdateLock = true;
+ break;
+ }
+ }
+
+ if(!referencesUpdateLock) {
+ result.add(l);
+ references.add(tmpString);
+ }
+ }
+
+ conf.locale = originalLocale; // to restore our guy initial state
+ return result;
+ }
+
+ /**
+ * TODO: return the selected one instead
+ * @return application current locale
+ */
+ Locale getCurrentLocale() {
+ return mContext.getResources().getConfiguration().locale;
+ }
+
+ /**
+ * TODO: what if a user didn't provide a closer email at all?
+ * TODO: check the closest locale not the first identified
+ *
+ * This method should provide a locale that is close to the given one in the parameter, it's
+ * currently checking the language only if in case the detector detects the string in other
+ * language.
+ *
+ * @param locale mostly the locale that's not detected or provided
+ * @return the index of the most close locale to the given locale. -1 if not detected
+ */
+ int detectMostClosestLocale(Locale locale) {
+
+ mLogger.debug("Start detecting a close locale to: ");
+
+ int index = 0;
+ for (Locale loc: LocalesUtils.getLocales()) {
+ if(loc.getDisplayLanguage().equals(locale.getDisplayLanguage())) {
+ mLogger.info("The locale: '" + loc + "' has been detected as a closer locale to: '"
+ + locale + "'");
+ return index;
+ }
+ index++;
+ }
+
+ mLogger.debug("No closer locales founded.");
+ return -1;
+ }
+
+ /**
+ * This method validate locales by checking if they are available of they contain wrong letter
+ * case and adding the valid ones in a clean set.
+ * @param locales to be checked
+ * @return valid locales
+ */
+ HashSet validateLocales(HashSet locales) {
+
+ mLogger.debug("Validating given locales..");
+
+ for (Locale l:LocalesUtils.getPseudoLocales()) {
+ if(locales.remove(l)) {
+ mLogger.info("Pseudo locale '" + l + "' has been removed.");
+ }
+ }
+
+ HashSet cleanLocales = new HashSet<>();
+ Locale[] androidLocales = Locale.getAvailableLocales();
+ for (Locale locale: locales) {
+ if (Arrays.asList(androidLocales).contains(locale)) {
+ cleanLocales.add(locale);
+ } else {
+ mLogger.error("Invalid passed locale: " + locale);
+ mLogger.warn("Invalid specified locale: '" + locale + "', has been discarded");
+ }
+ }
+ mLogger.debug("passing validated locales.");
+ return cleanLocales;
+ }
+}
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java
new file mode 100644
index 0000000..7228a3b
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesPreferenceManager.java
@@ -0,0 +1,161 @@
+package com.ahmedjazzar.rosetta;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import java.util.Locale;
+
+/**
+ * This class is responsible for setting and getting the preferred locale and manage any related
+ * actions. I think that there's no need for logging here because the utils class already handles
+ * logs for these actions based on their returned results.
+ *
+ * Created by ahmedjazzar on 1/22/16.
+ */
+class LocalesPreferenceManager {
+
+ private SharedPreferences mSharedPreferences;
+ private SharedPreferences.Editor mEditor;
+
+ static final int BASE_LOCALE = 1;
+ private final String BASE_LANGUAGE_KEY = "base_language";
+ private final String BASE_COUNTRY_KEY = "base_country";
+
+ static final int LAUNCH_LOCALE = 2;
+ private final String LAUNCH_LANGUAGE_KEY = "launch_language";
+ private final String LAUNCH_COUNTRY_KEY = "launch_country";
+
+ static final int USER_PREFERRED_LOCALE = 3;
+ private final String USER_PREFERRED_LANGUAGE_KEY = "user_preferred_language";
+ private final String USER_PREFERRED_COUNTRY_KEY = "user_preferred_country";
+
+ LocalesPreferenceManager(Context context, Locale firstLaunchLocale, Locale baseLocale) {
+
+ this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ this.mEditor = this.mSharedPreferences.edit();
+
+ if (!isLocaleExists(BASE_LOCALE)) {
+ this.setPreferredLocale(BASE_LOCALE, baseLocale);
+ }
+
+ if (!isLocaleExists(LAUNCH_LOCALE)) {
+ this.setPreferredLocale(LAUNCH_LOCALE, firstLaunchLocale);
+ }
+
+ if (!isLocaleExists(USER_PREFERRED_LOCALE)) {
+ this.setPreferredLocale(USER_PREFERRED_LOCALE, firstLaunchLocale);
+ }
+ }
+
+ boolean isLocaleExists(int key) {
+
+ switch (key) {
+ case BASE_LOCALE:
+ return mSharedPreferences.contains(this.BASE_LANGUAGE_KEY);
+ case LAUNCH_LOCALE:
+ return mSharedPreferences.contains(this.LAUNCH_LANGUAGE_KEY);
+ case USER_PREFERRED_LOCALE:
+ return mSharedPreferences.contains(this.USER_PREFERRED_LANGUAGE_KEY);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Sets user preferred locale
+ *
+ * @param locale user desired locale
+ * @return true if the preference updated
+ */
+ boolean setPreferredLocale(int key, Locale locale) {
+ return this.setPreferredLocale(key, locale.getLanguage(), locale.getCountry());
+ }
+
+ /**
+ *
+ * @return preferred locale after concatenating language and country
+ */
+ Locale getPreferredLocale(int key) {
+
+ String languageKey;
+ String countryKey;
+
+ switch (key) {
+ case BASE_LOCALE:
+ languageKey = this.BASE_LANGUAGE_KEY;
+ countryKey = this.BASE_COUNTRY_KEY;
+ break;
+ case LAUNCH_LOCALE:
+ languageKey = this.LAUNCH_LANGUAGE_KEY;
+ countryKey = this.LAUNCH_COUNTRY_KEY;
+ break;
+ case USER_PREFERRED_LOCALE:
+ languageKey = this.USER_PREFERRED_LANGUAGE_KEY;
+ countryKey = this.USER_PREFERRED_COUNTRY_KEY;
+ break;
+ default:
+ return null;
+ }
+
+ String language = getPreferredLanguage(languageKey);
+ String country = getPreferredCountry(countryKey);
+
+ if (language == null) {
+ return null;
+ }
+
+ return new Locale(language, country);
+ }
+
+ /**
+ * Sets user preferred locale by setting a language preference and a country preference since
+ * there's no supported preferences for locales
+ * @param language of the locale; ex. en
+ * @param country of the locale; ex. US
+ * @return true if the preferences updated
+ */
+ private boolean setPreferredLocale(int key, String language, String country) {
+
+ String languageKey;
+ String countryKey;
+
+ switch (key) {
+ case BASE_LOCALE:
+ languageKey = this.BASE_LANGUAGE_KEY;
+ countryKey = this.BASE_COUNTRY_KEY;
+ break;
+ case LAUNCH_LOCALE:
+ languageKey = this.LAUNCH_LANGUAGE_KEY;
+ countryKey = this.LAUNCH_COUNTRY_KEY;
+ break;
+ case USER_PREFERRED_LOCALE:
+ languageKey = this.USER_PREFERRED_LANGUAGE_KEY;
+ countryKey = this.USER_PREFERRED_COUNTRY_KEY;
+ break;
+ default:
+ return false;
+ }
+
+ mEditor.putString(languageKey, language);
+ mEditor.putString(countryKey, country);
+
+ return mEditor.commit();
+ }
+
+ /**
+ *
+ * @return preferred language
+ */
+ private String getPreferredLanguage(String key) {
+ return mSharedPreferences.getString(key, null);
+ }
+
+ /**
+ *
+ * @return preferred country
+ */
+ private String getPreferredCountry(String key) {
+ return mSharedPreferences.getString(key, null);
+ }
+}
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java
new file mode 100644
index 0000000..c93c18c
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/LocalesUtils.java
@@ -0,0 +1,302 @@
+package com.ahmedjazzar.rosetta;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is a helper class that connects all library classes activities together and make it
+ * easier for every class in the library to use and look at the shared info without a need to
+ * initialize a new object from the desired class
+ *
+ * Created by ahmedjazzar on 1/19/16.
+ */
+final class LocalesUtils {
+
+ @SuppressLint("StaticFieldLeak")
+ private static LocalesDetector sDetector;
+ private static LocalesPreferenceManager sLocalesPreferenceManager;
+ private static HashSet sLocales;
+ private static final Locale[] PSEUDO_LOCALES = {
+ new Locale("en", "XA"),
+ new Locale("ar", "XB")
+ };
+ private static final String TAG = LocalesDetector.class.getName();
+ private static Logger sLogger = new Logger(TAG);
+
+ /**
+ *
+ * @param detector just a setter because I don't want to declare any constructors in this class
+ */
+ static void setDetector(@NonNull LocalesDetector detector) {
+ LocalesUtils.sDetector = detector;
+ }
+
+ /**
+ *
+ * @param localesPreferenceManager just a setter because I don't want to declare any
+ * constructors in this class
+ */
+ static void setLocalesPreferenceManager(
+ @NonNull LocalesPreferenceManager localesPreferenceManager) {
+
+ LocalesUtils.sLocalesPreferenceManager = localesPreferenceManager;
+ }
+
+ /**
+ *
+ * @param stringId a string to start discovering sLocales in
+ * @return a HashSet of discovered sLocales
+ */
+ static HashSet fetchAvailableLocales(int stringId) {
+ return sDetector.fetchAvailableLocales(stringId);
+ }
+
+ /**
+ *
+ * @param localesSet sLocales user wanna use
+ */
+ static void setSupportedLocales(HashSet localesSet) {
+ LocalesUtils.sLocales = sDetector.validateLocales(localesSet);
+ sLogger.debug("Locales have been changed");
+ }
+
+ /**
+ *
+ * @return a HashSet of the available sLocales discovered in the application
+ */
+ static HashSet getLocales() {
+ return LocalesUtils.sLocales;
+ }
+
+ /**
+ *
+ * @return a list of locales for displaying on the layout purposes
+ */
+ static ArrayList getLocalesWithDisplayName() {
+ ArrayList stringLocales = new ArrayList<>();
+
+ for (Locale loc: LocalesUtils.getLocales()) {
+ String langDisplay = loc.getDisplayName(loc);
+ stringLocales.add(langDisplay.substring(0, 1).toUpperCase() + langDisplay.substring(1).toLowerCase());
+ }
+ return stringLocales;
+ }
+
+ /**
+ *
+ * @return the index of the current app locale
+ */
+ static int getCurrentLocaleIndex() {
+ Locale locale = LocalesUtils.getCurrentLocale();
+ int index = -1;
+ int itr = 0;
+
+ for (Locale l : sLocales) {
+ if(locale.equals(l)) {
+ index = itr;
+ break;
+ }
+ itr++;
+ }
+
+ if (index == -1) {
+ //TODO: change the index to the most closer available locale
+ sLogger.warn("Current device locale '" + locale.toString() +
+ "' does not appear in your given supported locales");
+
+ index = sDetector.detectMostClosestLocale(locale);
+ if(index == -1) {
+ index = 0;
+ sLogger.warn("Current locale index changed to 0 as the current locale '" +
+ locale +
+ "' not supported."
+ );
+ }
+ }
+
+ return index;
+ }
+
+ /**
+ *
+ * @see Pseudolocalization for
+ * more information about pseudo localization
+ * @return pseudo locales list
+ */
+ static List getPseudoLocales() {
+ return Arrays.asList(LocalesUtils.PSEUDO_LOCALES);
+ }
+
+ /**
+ *
+ * @return the locale at the given index
+ */
+ static Locale getLocaleFromIndex(int index) {
+ return LocalesUtils.sLocales.toArray(new Locale[LocalesUtils.sLocales.size()])[index];
+ }
+
+ /**
+ *
+ * @param context
+ * @param index the selected locale position
+ * @return true if the application locale changed
+ */
+ static boolean setAppLocale(Context context, int index) {
+ return setAppLocale(context, getLocaleFromIndex(index));
+ }
+
+ /**
+ *
+ * @return true if the application locale changed
+ */
+ static boolean setAppLocale(Context context, Locale newLocale) {
+
+ Resources resources = context.getResources();
+ DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ Configuration configuration = resources.getConfiguration();
+
+ Locale oldLocale = new Locale(configuration.locale.getLanguage(), configuration.locale.getCountry());
+ configuration.locale = newLocale;
+ // Sets the layout direction from the Locale
+ sLogger.debug("Setting the layout direction");
+ configuration.setLayoutDirection(newLocale);
+ resources.updateConfiguration(configuration, displayMetrics);
+
+ if(oldLocale.equals(newLocale)) {
+ return false;
+ }
+
+ if (LocalesUtils.updatePreferredLocale(newLocale)) {
+ sLogger.info("Locale preferences updated to: " + newLocale);
+ Locale.setDefault(newLocale);
+ } else {
+ sLogger.error("Failed to update locale preferences.");
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * @return application's base locale
+ */
+ static Locale getBaseLocale() {
+ return LocalesUtils.sLocalesPreferenceManager.getPreferredLocale(LocalesPreferenceManager.BASE_LOCALE);
+ }
+
+ /**
+ *
+ * @param stringId the target string
+ * @return a localized string
+ */
+ static String getInSpecificLocale(FragmentActivity activity, Locale locale, int stringId) {
+
+ Configuration conf = activity.getResources().getConfiguration();
+ Locale old = conf.locale;
+
+ conf.locale = locale;
+ DisplayMetrics metrics = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ Resources resources = new Resources(activity.getAssets(), metrics, conf);
+ conf.locale = old;
+
+ return resources.getString(stringId);
+ }
+
+ /**
+ * Refreshing the application so no weired results occurred after changing the locale.
+ */
+ static void refreshApplication(Activity activity) {
+
+ Intent app = activity.getBaseContext().getPackageManager()
+ .getLaunchIntentForPackage(activity.getBaseContext().getPackageName());
+ app.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ Intent current = new Intent(activity, activity.getClass());
+ sLogger.debug("Refreshing the application: " +
+ activity.getBaseContext().getPackageName());
+
+ sLogger.debug("Finishing current activity.");
+ activity.finish();
+
+ sLogger.debug("Start the application");
+ activity.startActivity(app);
+ activity.startActivity(current);
+
+ sLogger.debug("Application refreshed");
+ }
+
+ /**
+ *
+ * @return the first launch locale
+ */
+ static Locale getLaunchLocale() {
+
+ return sLocalesPreferenceManager.getPreferredLocale(LocalesPreferenceManager.LAUNCH_LOCALE);
+ }
+
+ /**
+ * Setting the application locale manually
+ * @param newLocale the desired locale
+ * @param activity the current activity in order to refresh the app
+ *
+ * @return true if the operation succeed, false otherwise
+ */
+ static boolean setLocale(Locale newLocale, Activity activity) {
+ if (newLocale == null || !getLocales().contains(newLocale)) {
+ return false;
+ }
+
+ if (LocalesUtils.setAppLocale(activity.getApplicationContext(), newLocale)) {
+ LocalesUtils.refreshApplication(activity);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param context application base context
+ * @return the current locale
+ */
+ public static Locale getCurrentLocale(Context context) {
+ Resources resources = context.getResources();
+ Configuration configuration = resources.getConfiguration();
+
+ return new Locale(configuration.locale.getLanguage(), configuration.locale.getCountry());
+ }
+
+ /**
+ *
+ * @param locale the new preferred locale
+ * @return true if the preferred locale updated
+ */
+ private static boolean updatePreferredLocale(Locale locale) {
+
+ return LocalesUtils.sLocalesPreferenceManager
+ .setPreferredLocale(LocalesPreferenceManager.USER_PREFERRED_LOCALE, locale);
+ }
+
+ /**
+ *
+ * @return current application locale
+ */
+ private static Locale getCurrentLocale() {
+ return sDetector.getCurrentLocale();
+ }
+}
\ No newline at end of file
diff --git a/rosetta/src/main/java/com/ahmedjazzar/rosetta/Logger.java b/rosetta/src/main/java/com/ahmedjazzar/rosetta/Logger.java
new file mode 100644
index 0000000..ad40a69
--- /dev/null
+++ b/rosetta/src/main/java/com/ahmedjazzar/rosetta/Logger.java
@@ -0,0 +1,38 @@
+package com.ahmedjazzar.rosetta;
+
+import android.util.Log;
+
+/**
+ * This class helps logging app events without a need to rewrite the tag name in every time
+ * Created by ahmedjazzar on 1/16/16.
+ */
+
+class Logger {
+
+ private final String mTag;
+
+ Logger(String tag) {
+ this.mTag = tag;
+ this.verbose("Object from " + this.mTag + " has been created.");
+ }
+
+ void error(String log) {
+ Log.e(this.mTag, log);
+ }
+
+ void warn(String log) {
+ Log.w(this.mTag, log);
+ }
+
+ void debug(String log) {
+ Log.d(this.mTag, log);
+ }
+
+ void info(String log) {
+ Log.i(this.mTag, log);
+ }
+
+ void verbose(String log) {
+ Log.v(this.mTag, log);
+ }
+}
diff --git a/rosetta/src/main/res/values/strings.xml b/rosetta/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e13140f
--- /dev/null
+++ b/rosetta/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+ Language
+ Ok
+ Cancel
+
diff --git a/settings.gradle b/settings.gradle
index 5c996dc..4fd643d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -10,3 +10,4 @@ dependencyResolutionManagement {
}
rootProject.name = "MagiskModuleManager"
include ':app'
+include ':rosetta'