Merge pull request #145 from DerGoogler/master

[WIP] UI rework
pull/149/head
Androidacy Service Account 2 years ago committed by GitHub
commit 1ee0aa31ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,15 @@
# LEGALS
## Fox's Magisk Module Manager
- Maintained: Yes
- Maintainers
- [Fox2Code](https://github.com/Fox2Code)
- [androidacybot](https://github.com/androidacybot)
- License: [<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-law mr-2"><path fill-rule="evenodd" d="M8.75.75a.75.75 0 00-1.5 0V2h-.984c-.305 0-.604.08-.869.23l-1.288.737A.25.25 0 013.984 3H1.75a.75.75 0 000 1.5h.428L.066 9.192a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.514 3.514 0 00.686.45A4.492 4.492 0 003 11c.88 0 1.556-.22 2.023-.454a3.515 3.515 0 00.686-.45l.045-.04.016-.015.006-.006.002-.002.001-.002L5.25 9.5l.53.53a.75.75 0 00.154-.838L3.822 4.5h.162c.305 0 .604-.08.869-.23l1.289-.737a.25.25 0 01.124-.033h.984V13h-2.5a.75.75 0 000 1.5h6.5a.75.75 0 000-1.5h-2.5V3.5h.984a.25.25 0 01.124.033l1.29.736c.264.152.563.231.868.231h.162l-2.112 4.692a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.517 3.517 0 00.686.45A4.492 4.492 0 0013 11c.88 0 1.556-.22 2.023-.454a3.512 3.512 0 00.686-.45l.045-.04.01-.01.006-.005.006-.006.002-.002.001-.002-.529-.531.53.53a.75.75 0 00.154-.838L13.823 4.5h.427a.75.75 0 000-1.5h-2.234a.25.25 0 01-.124-.033l-1.29-.736A1.75 1.75 0 009.735 2H8.75V.75zM1.695 9.227c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327l-1.305 2.9zm10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327l-1.305 2.9z"></path></svg> 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: [<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-law mr-2"><path fill-rule="evenodd" d="M8.75.75a.75.75 0 00-1.5 0V2h-.984c-.305 0-.604.08-.869.23l-1.288.737A.25.25 0 013.984 3H1.75a.75.75 0 000 1.5h.428L.066 9.192a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.514 3.514 0 00.686.45A4.492 4.492 0 003 11c.88 0 1.556-.22 2.023-.454a3.515 3.515 0 00.686-.45l.045-.04.016-.015.006-.006.002-.002.001-.002L5.25 9.5l.53.53a.75.75 0 00.154-.838L3.822 4.5h.162c.305 0 .604-.08.869-.23l1.289-.737a.25.25 0 01.124-.033h.984V13h-2.5a.75.75 0 000 1.5h6.5a.75.75 0 000-1.5h-2.5V3.5h.984a.25.25 0 01.124.033l1.29.736c.264.152.563.231.868.231h.162l-2.112 4.692a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.517 3.517 0 00.686.45A4.492 4.492 0 0013 11c.88 0 1.556-.22 2.023-.454a3.512 3.512 0 00.686-.45l.045-.04.01-.01.006-.005.006-.006.002-.002.001-.002-.529-.531.53.53a.75.75 0 00.154-.838L13.823 4.5h.427a.75.75 0 000-1.5h-2.234a.25.25 0 01-.124-.033l-1.29-.736A1.75 1.75 0 009.735 2H8.75V.75zM1.695 9.227c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327l-1.305 2.9zm10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327l-1.305 2.9z"></path></svg> MIT license](https://github.com/iamjazzar/rosetta/blob/master/LICENSE)

@ -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"

@ -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">
<receiver android:name="com.fox2code.mmm.manager.ModuleBootReceive"
android:exported="true">
<intent-filter>
@ -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">
<intent-filter>
<action android:name="${applicationId}.intent.action.INSTALL_MODULE_INTERNAL" />
</intent-filter>

@ -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";
}

@ -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 + ", " +

@ -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 =

@ -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<CompatActivity> 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<CompatActivity> asWeakReference() {
return this.selfReference;
}

@ -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);
}
}

@ -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();

@ -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;
}
}

@ -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<ModuleViewAdapter.ViewHolder> {
private static final boolean DEBUG = false;
public final ArrayList<ModuleHolder> moduleHolders = new ArrayList<>();
@ -64,13 +68,15 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
public static class ViewHolder extends RecyclerView.ViewHolder {
private final CardView cardView;
private final Chip invalidPropsChip;
private final ImageButton buttonAction;
private final SwitchMaterial switchMaterial;
private final TextView titleText;
private final TextView creditText;
private final TextView descriptionText;
private final LinearLayout moduleOptionsHolder;
private final TextView updateText;
private final ImageButton[] actionsButtons;
private final Chip[] actionsButtons;
private final ArrayList<ActionButtonType> actionButtonsTypes;
private boolean initState;
public ModuleHolder moduleHolder;
@ -80,13 +86,15 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
super(itemView);
this.initState = true;
this.cardView = itemView.findViewById(R.id.card_view);
this.invalidPropsChip = itemView.findViewById(R.id.invalid_module_props);
this.buttonAction = itemView.findViewById(R.id.button_action);
this.switchMaterial = itemView.findViewById(R.id.switch_action);
this.titleText = itemView.findViewById(R.id.title_text);
this.creditText = itemView.findViewById(R.id.credit_text);
this.descriptionText = itemView.findViewById(R.id.description_text);
this.moduleOptionsHolder = itemView.findViewById(R.id.module_options_holder);
this.updateText = itemView.findViewById(R.id.updated_text);
this.actionsButtons = new ImageButton[6];
this.actionsButtons = new Chip[6];
this.actionsButtons[0] = itemView.findViewById(R.id.button_action1);
this.actionsButtons[1] = itemView.findViewById(R.id.button_action2);
this.actionsButtons[2] = itemView.findViewById(R.id.button_action3);
@ -128,7 +136,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
ModuleHolder moduleHolder = this.moduleHolder;
if (index < this.actionButtonsTypes.size() && moduleHolder != null) {
this.actionButtonsTypes.get(index)
.doAction((ImageButton) v, moduleHolder);
.doAction((Chip) v, moduleHolder);
if (moduleHolder.shouldRemove()) {
this.cardView.setVisibility(View.GONE);
}
@ -140,7 +148,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
boolean didSomething = false;
if (index < this.actionButtonsTypes.size() && moduleHolder != null) {
didSomething = this.actionButtonsTypes.get(index)
.doActionLong((ImageButton) v, moduleHolder);
.doActionLong((Chip) v, moduleHolder);
if (moduleHolder.shouldRemove()) {
this.cardView.setVisibility(View.GONE);
}
@ -180,6 +188,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.switchMaterial.setVisibility(View.GONE);
}
this.creditText.setVisibility(View.VISIBLE);
this.moduleOptionsHolder.setVisibility(View.VISIBLE);
this.descriptionText.setVisibility(View.VISIBLE);
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
@ -232,7 +241,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.switchMaterial.setEnabled(!showCaseMode &&
!moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING));
for (int i = 0; i < this.actionsButtons.length; i++) {
ImageButton imageButton = this.actionsButtons[i];
Chip imageButton = this.actionsButtons[i];
if (i < this.actionButtonsTypes.size()) {
imageButton.setVisibility(View.VISIBLE);
imageButton.setImportantForAccessibility(
@ -272,7 +281,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.descriptionText.setText(" ");
this.switchMaterial.setEnabled(false);
this.actionButtonsTypes.clear();
for (ImageButton button : this.actionsButtons) {
for (Chip button : this.actionsButtons) {
button.setVisibility(View.GONE);
button.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@ -313,8 +322,21 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
backgroundAttr = moduleHolder.notificationType.backgroundAttr;
} else if (type == ModuleHolder.Type.INSTALLED &&
moduleHolder.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
foregroundAttr = R.attr.colorOnError;
backgroundAttr = R.attr.colorError;
invalidPropsChip.setOnClickListener(_view -> {
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();

@ -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<String> 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) -> {

@ -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);

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M5,14.5h14v-6L5,8.5v6zM11,0.55L11,3.5h2L13,0.55h-2zM19.04,3.05l-1.79,1.79 1.41,1.41 1.8,-1.79 -1.42,-1.41zM13,22.45L13,19.5h-2v2.95h2zM20.45,18.54l-1.8,-1.79 -1.41,1.41 1.79,1.8 1.42,-1.42zM3.55,4.46l1.79,1.79 1.41,-1.41 -1.79,-1.79 -1.41,1.41zM4.96,19.95l1.79,-1.8 -1.41,-1.41 -1.79,1.79 1.41,1.42z"/>
</vector>

@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16.24,11.51l1.57,-1.57l-3.75,-3.75l-1.57,1.57L8.35,3.63c-0.78,-0.78 -2.05,-0.78 -2.83,0l-1.9,1.9c-0.78,0.78 -0.78,2.05 0,2.83l4.13,4.13L3,17.25V21h3.75l4.76,-4.76l4.13,4.13c0.95,0.95 2.23,0.6 2.83,0l1.9,-1.9c0.78,-0.78 0.78,-2.05 0,-2.83L16.24,11.51zM9.18,11.07L5.04,6.94l1.89,-1.9c0,0 0,0 0,0l1.27,1.27L7.02,7.5l1.41,1.41l1.19,-1.19l1.45,1.45L9.18,11.07zM17.06,18.96l-4.13,-4.13l1.9,-1.9l1.45,1.45l-1.19,1.19l1.41,1.41l1.19,-1.19l1.27,1.27L17.06,18.96z"/>
<path android:fillColor="@android:color/white" android:pathData="M20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.47,-0.47 -1.12,-0.29 -1.41,0l-1.83,1.83l3.75,3.75L20.71,7.04z"/>
</vector>

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="?attr/colorControlNormal">
<group android:scaleX="1.8531"
android:scaleY="1.8531"
android:translateX="31.7628"
android:translateY="31.7628">
<path
android:fillColor="@color/launcher_icon"
android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
</group>
</vector>

@ -1,23 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:id="@+id/markdownBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fitsSystemWindowsInsets="top|left|right">
app:fitsSystemWindowsInsets="left|right"
tools:context=".markdown.MarkdownActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
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">
<LinearLayout
android:id="@+id/md_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/markdownView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/markdown_border_content"
android:text="@string/loading" />
<TextView
android:id="@+id/markdownFooter"
android:layout_width="match_parent"
@ -25,4 +38,44 @@
android:text="" />
</LinearLayout>
</ScrollView>
</LinearLayout>
<eightbitlab.com.blurview.BlurView
android:id="@+id/markdown_action_bar_blur"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/markdown_action_bar_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</eightbitlab.com.blurview.BlurView>
<eightbitlab.com.blurview.BlurView
android:id="@+id/chip_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/scrollView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="8dp"
app:singleLine="true">
<!-- Dynamically added -->
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</eightbitlab.com.blurview.BlurView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -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">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/card_corner_radius"
app:strokeColor="@android:color/transparent"
app:cardPreventCornerOverlap="true"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
style="?attr/materialCardViewFilledStyle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action"
android:textSize="16sp"
android:src="@drawable/ic_baseline_delete_forever_24"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
tools:ignore="RtlHardcoded" />
<!-- Module components -->
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/title_text"
android:textSize="16sp"
android:maxLines="1"
android:text="@string/loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/credit_text"
android:textSize="12sp"
android:text="@string/loading"
<!-- Media -->
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/title_text"
app:layout_constraintTop_toBottomOf="@id/title_text" />
android:layout_height="194dp"
android:visibility="gone"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/description_text"
android:textSize="16sp"
android:text="@string/loading"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_below="@+id/credit_text"
app:layout_constraintTop_toBottomOf="@id/credit_text" />
android:orientation="vertical"
android:padding="8dp">
<!-- Module components -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<com.google.android.material.chip.Chip
android:id="@+id/invalid_module_props"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="@string/low_quality_module"
android:textSize="12sp"
android:layout_marginEnd="4dp"
android:visibility="gone"
app:chipStartPadding="4dp"
app:chipEndPadding="2dp"
app:chipIconSize="15dp"
app:chipIcon="@drawable/ic_baseline_error_24" />
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:text="@string/loading"
android:textSize="16sp"
android:textAppearance="?attr/textAppearanceTitleMedium"
/>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_action"
android:layout_width="wrap_content"
android:layout_height="35dp" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:background="@android:color/transparent"
android:importantForAccessibility="no"
android:src="@drawable/ic_baseline_delete_forever_24"
android:textSize="16sp"
tools:ignore="RtlHardcoded" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:background="@null"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:layout_constraintLeft_toLeftOf="parent">
<TextView
android:id="@+id/updated_text"
android:id="@+id/credit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:textSize="12sp"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?android:attr/textColorSecondary"
/>
<TextView
android:id="@+id/description_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/loading"
android:textSize="16sp"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?android:attr/textColorSecondary"
/>
<TextView
android:id="@+id/updated_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:layout_gravity="center_vertical"
android:layout_marginTop="8dp"
android:text="@string/loading"
android:textSize="12sp" />
</LinearLayout>
<!-- Buttons -->
<LinearLayout
android:id="@+id/module_options_holder"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="horizontal">
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
tools:ignore="UselessParent">
<!-- Module actions -->
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="4dp"
app:singleLine="true">
<com.google.android.material.chip.Chip
android:id="@+id/button_action1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action1"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginRight="3dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action2"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_action1"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action3"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_action2"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action4"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_action3"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action5"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_action4"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_action6"
android:textSize="16sp"
android:visibility="gone"
android:src="@drawable/ic_baseline_error_24"
android:layout_width="@dimen/module_action_icon_size"
android:layout_height="@dimen/module_action_icon_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_action5"
android:importantForAccessibility="no"
android:layout_marginLeft="8dp"
android:layout_marginBottom="1dp"
tools:ignore="RtlHardcoded" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Thanks https://romannurik.github.io/AndroidAssetStudio/ for icons -->
<!--
https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=clipart&foreground.clipart=extension&foreground.space.trim=0&foreground.space.pad=0.25&foreColor=rgb(255%2C%20255%2C%20255)&backColor=rgb(255%2C%20152%2C%200)&crop=0&backgroundShape=circle&effects=elevate&name=ic_launcher
-->
<background android:drawable="@mipmap/ic_launcher_adaptive_back"/>
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
<background android:drawable="@color/launcher_icon_background"/>
<foreground android:drawable="@drawable/ic_foreground"/>
</adaptive-icon>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/launcher_icon_background"/>
<foreground android:drawable="@drawable/ic_foreground"/>
</adaptive-icon>

@ -25,28 +25,24 @@
<string name="showcase_mode_pref">Sperrmodus</string>
<string name="showcase_mode_desc">Sperrmodus verhindert, dass der Manager Aktionen an Modulen ausführt</string>
<string name="pref_category_settings">Einstellungen</string>
<string name="pref_category_info">Info</string>
<string name="show_licenses">Lizenzen anzeigen</string>
<string name="licenses">Lizenzen</string>
<string name="show_incompatible_pref">Inkompatible Module anzeigen</string>
<string name="show_incompatible_desc">Module anzeigen, die aufgrund ihrer Metadaten nicht mit Ihrem Gerät kompatibel sind</string>
<string name="magisk_outdated">Magisk ist veraltet!</string>
<string name="pref_category_repos">Repos</string>
<string name="repo_main_desc">Das Repository, das Magisk-Module hostet</string>
<string name="repo_main_alt">Eine Alternative zur Magisk-Module-Repo mit weniger Einschränkungen.</string>
<string name="master_delete">Löschen der Moduldateien?</string>
<string name="master_delete_no">Dateien behalten</string>
<string name="master_delete_yes">Dateien löschen</string>
<string name="master_delete_fail">Fehler beim Löschen der Moduldateien</string>
<string name="theme_pref">Theme</string>
<string name="module_id_prefix">Module id: </string>
<string name="install_from_storage">Modul aus dem Speicher installieren</string>
<string name="install_from_storage">Module aus dem Speicher installieren</string>
<string name="invalid_format">Das ausgewählte Modul hat ein ungültiges Format</string>
<string name="local_install_title">Lokale Installation</string>
<string name="source_code">Quellcode</string>
<string name="magisk_builtin_module">Eingebautes Magisk Modul</string>
<string name="substratum_builtin_module">Eingebautes Substratum Modul</string>
<string name="force_dark_terminal_title">Dunkelmodus-Terminal erzwingen</string>
<string name="force_dark_terminal_title">Dunkeles Terminal erzwingen</string>
<string name="file_picker_failure">Ihr aktueller Dateimanager konnte keinen Zugriff auf die Datei gewähren.</string>
<string name="remote_install_title">Remote-Installation</string>
<string name="file_picker_wierd">Ihr Dateimanager hat eine nicht standardmäßige Antwort zurückgegeben.</string>
@ -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!
</string>
<string name="no_web_view">System WebView konnte nicht geöffnet werden</string>
<string name="no_desc_found">Keine Beschreibung gefunden</string>
<string name="download_module">Module herunterladen</string>
<string name="install_module">Module instalieren</string>
<string name="update_module">Module aktualisieren</string>
<string name="changelog">Anderungsprotokoll</string>
<string name="website">Webseite</string>
<string name="support">Hilfe</string>
<string name="donate">Spenden</string>
<string name="submit_modules">Module einsenden</string>
<string name="require_android_6">Erfordert Android 6.0+</string>
<string name="require_android_12">Erfordert Android 12+</string>
<string name="install_terminal_reboot_now">Neustarten</string>
<string name="yes">Ja</string>
<string name="no">Nein</string>
<string name="module_needs_ramdisk">Benötigt Ramdisk</string>
<string name="module_can_change_boot">Kann Boot ändern</string>
<string name="manage_repos_pref">Repos verwalten</string>
<string name="prevent_reboot_pref">Neustart verhindern</string>
<string name="prevent_reboot_desc">Verhindert unerwartete Neustarts</string>
<string name="enable_monet">Aktivire Monet</string>
<string name="pref_category_security">Sicherheit</string>
<string name="pref_category_appearance">Ausehen</string>
<string name="pref_category_general">Allgemein</string>
<string name="theme_mode_pref">Theme Modus</string>
<string name="dns_over_https_pref">DNS über HTTPS</string>
<string name="dns_over_https_desc">Kann in einigen Fällen Verbindungsprobleme beheben</string>
<string name="disable_extensions_desc">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</string>
<string name="wrap_text_pref">Textumbruch</string>
<string name="wrap_text_desc">Zeigen Sie Text in mehreren Zeilen an, anstatt den gesamten Text in derselben Zeile zu platzieren, wenn Sie ein Modul installieren.</string>
<string name="disable_chips_in_description">Chips in Beschreibung deaktivieren</string>
<string name="repo_enabled">Repo an</string>
<string name="repo_disabled">Repo aus</string>
<string name="add_repo">Repo hinzufügen</string>
<string name="remove_repo">Repo entfernen</string>
<string name="custom_url">Eigene URL</string>
<string name="androidacy_repo_info">Das Androidacy-Repo enthält Anzeigen und Tracker.</string>
<string name="backup_module_list">Backup Modules</string>
<string name="restore_module_list">Module wiederherstellen</string>
<string name="require_internet">Dieser Vorgang erfordert eine Internetverbindung</string>
<string name="update">Aktualisieren</string>
<string name="install">Installieren</string>
<string name="description">Beschreibung</string>
<string name="uninstall">Deinstallieren</string>
<string name="config">Konfig</string>
</resources>

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="system_neutral1_0">@android:color/system_neutral1_0</color>
<color name="system_neutral1_10">@android:color/system_neutral1_10</color>
<color name="system_neutral1_50">@android:color/system_neutral1_50</color>
<color name="system_neutral1_100">@android:color/system_neutral1_100</color>
<color name="system_neutral1_200">@android:color/system_neutral1_200</color>
<color name="system_neutral1_300">@android:color/system_neutral1_300</color>
<color name="system_neutral1_400">@android:color/system_neutral1_400</color>
<color name="system_neutral1_500">@android:color/system_neutral1_500</color>
<color name="system_neutral1_600">@android:color/system_neutral1_600</color>
<color name="system_neutral1_700">@android:color/system_neutral1_700</color>
<color name="system_neutral1_800">@android:color/system_neutral1_800</color>
<color name="system_neutral1_900">@android:color/system_neutral1_900</color>
<color name="system_neutral1_1000">@android:color/system_neutral1_1000</color>
<color name="system_neutral2_0">@android:color/system_neutral2_0</color>
<color name="system_neutral2_10">@android:color/system_neutral2_10</color>
<color name="system_neutral2_50">@android:color/system_neutral2_50</color>
<color name="system_neutral2_100">@android:color/system_neutral2_100</color>
<color name="system_neutral2_200">@android:color/system_neutral2_200</color>
<color name="system_neutral2_300">@android:color/system_neutral2_300</color>
<color name="system_neutral2_400">@android:color/system_neutral2_400</color>
<color name="system_neutral2_500">@android:color/system_neutral2_500</color>
<color name="system_neutral2_600">@android:color/system_neutral2_600</color>
<color name="system_neutral2_700">@android:color/system_neutral2_700</color>
<color name="system_neutral2_800">@android:color/system_neutral2_800</color>
<color name="system_neutral2_900">@android:color/system_neutral2_900</color>
<color name="system_neutral2_1000">@android:color/system_neutral2_1000</color>
<color name="system_accent1_0">@android:color/system_accent1_0</color>
<color name="system_accent1_10">@android:color/system_accent1_10</color>
<color name="system_accent1_50">@android:color/system_accent1_50</color>
<color name="system_accent1_100">@android:color/system_accent1_100</color>
<color name="system_accent1_200">@android:color/system_accent1_200</color>
<color name="system_accent1_300">@android:color/system_accent1_300</color>
<color name="system_accent1_400">@android:color/system_accent1_400</color>
<color name="system_accent1_500">@android:color/system_accent1_500</color>
<color name="system_accent1_600">@android:color/system_accent1_600</color>
<color name="system_accent1_700">@android:color/system_accent1_700</color>
<color name="system_accent1_800">@android:color/system_accent1_800</color>
<color name="system_accent1_900">@android:color/system_accent1_900</color>
<color name="system_accent1_1000">@android:color/system_accent1_1000</color>
<color name="system_accent2_0">@android:color/system_accent2_0</color>
<color name="system_accent2_10">@android:color/system_accent2_10</color>
<color name="system_accent2_50">@android:color/system_accent2_50</color>
<color name="system_accent2_100">@android:color/system_accent2_100</color>
<color name="system_accent2_200">@android:color/system_accent2_200</color>
<color name="system_accent2_300">@android:color/system_accent2_300</color>
<color name="system_accent2_400">@android:color/system_accent2_400</color>
<color name="system_accent2_500">@android:color/system_accent2_500</color>
<color name="system_accent2_600">@android:color/system_accent2_600</color>
<color name="system_accent2_700">@android:color/system_accent2_700</color>
<color name="system_accent2_800">@android:color/system_accent2_800</color>
<color name="system_accent2_900">@android:color/system_accent2_900</color>
<color name="system_accent2_1000">@android:color/system_accent2_1000</color>
<color name="system_accent3_0">@android:color/system_accent3_0</color>
<color name="system_accent3_10">@android:color/system_accent3_10</color>
<color name="system_accent3_50">@android:color/system_accent3_50</color>
<color name="system_accent3_100">@android:color/system_accent3_100</color>
<color name="system_accent3_200">@android:color/system_accent3_200</color>
<color name="system_accent3_300">@android:color/system_accent3_300</color>
<color name="system_accent3_400">@android:color/system_accent3_400</color>
<color name="system_accent3_500">@android:color/system_accent3_500</color>
<color name="system_accent3_600">@android:color/system_accent3_600</color>
<color name="system_accent3_700">@android:color/system_accent3_700</color>
<color name="system_accent3_800">@android:color/system_accent3_800</color>
<color name="system_accent3_900">@android:color/system_accent3_900</color>
<color name="system_accent3_1000">@android:color/system_accent3_1000</color>
<!-- Icon -->
<color name="launcher_icon_background">@android:color/system_accent1_100</color>
<color name="launcher_icon">@android:color/system_neutral2_700</color>
</resources>

@ -0,0 +1,37 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.MagiskModuleManager.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">
<item name="chipBackgroundColor">@color/system_accent2_300</item>
<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">
<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">
<item name="chipBackgroundColor">@color/system_accent2_700</item>
<item name="chipStrokeWidth">0dp</item>
<item name="chipIconTint">?attr/colorControlNormal</item>
</style>
<!-- Base application theme. -->
<style name="Theme.MagiskModuleManager.Transparent.Dark" parent="Theme.MagiskModuleManager.Dark" />
<style name="Theme.MagiskModuleManager" parent="Theme.MagiskModuleManager.Light" />
</resources>

@ -13,4 +13,8 @@
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="status_bar_color">@color/black_transparent</color>
<color name="module_holder_div">@color/black_transparent</color>
<!-- Icon -->
<color name="launcher_icon_background">#FF9B08</color>
<color name="launcher_icon">@android:color/white</color>
</resources>

@ -22,11 +22,20 @@
<string name="website">Website</string>
<string name="support">Support</string>
<string name="donate">Donate</string>
<string name="update">Update</string>
<string name="install">Install</string>
<string name="description">Description</string>
<string name="uninstall">Uninstall</string>
<string name="config">Config</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>
<string name="install_terminal_reboot_now">Reboot</string>
<string name="yes">Yes</string>
<string name="no">No</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">Last version:</string>
@ -34,6 +43,8 @@
<string name="module_by">by</string>
<string name="module_downloads">Downloads:</string>
<string name="module_stars">Stars:</string>
<string name="module_needs_ramdisk">Needs ramdisk</string>
<string name="module_can_change_boot">Can change boot</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
@ -43,6 +54,7 @@
<string name="prevent_reboot_pref">Prevent reboot</string>
<string name="prevent_reboot_desc">Prevents unexpected reboots</string>
<string name="pref_category_settings">Settings</string>
<string name="enable_monet">Enable Monet</string>
<string name="pref_category_info">Info</string>
<string name="show_licenses">Show licenses</string>
<string name="licenses">Licences</string>
@ -64,6 +76,7 @@
<string name="module_id_prefix">Module ID: </string>
<string name="install_from_storage">Install module from storage</string>
<string name="invalid_format">The selected module has an invalid format</string>
<string name="low_quality_module">Low-quality module</string>
<string name="local_install_title">Local install</string>
<string name="source_code">Source code</string>
<string name="magisk_builtin_module">Magisk built-in module</string>
@ -100,6 +113,7 @@
all text on the same line when installing a module.
</string>
<string name="enable_blur_pref">Blur</string>
<string name="disable_chips_in_description">Disable chips in description</string>
<string name="repo_enabled">Repo on</string>
<string name="repo_disabled">Repo off</string>
<string name="add_repo">Add Repo</string>

@ -28,6 +28,13 @@
<item name="dialogCornerRadius">@dimen/card_corner_radius</item>
<item name="colorBackgroundFloating">#FFFFFFFF</item>
<item name="backgroundColor">#FFFFFFFF</item>
<item name="chipStyle">@style/Widget.Material.Chip.Choice.Light</item>
</style>
<style name="Widget.Material.Chip.Choice.Light" parent="Widget.MaterialComponents.Chip.Action">
<item name="chipBackgroundColor">#DADADA</item>
<item name="chipStrokeWidth">0dp</item>
<item name="chipIconTint">@color/black</item>
</style>
<style name="Theme.MagiskModuleManager.Transparent.Light" parent="Theme.MagiskModuleManager.Light">
@ -73,6 +80,13 @@
<item name="dialogCornerRadius">@dimen/card_corner_radius</item>
<item name="colorBackgroundFloating">#FF1E1E1E</item>
<item name="backgroundColor">#FF111111</item>
<item name="chipStyle">@style/Widget.Material.Chip.Choice.Dark</item>
</style>
<style name="Widget.Material.Chip.Choice.Dark" parent="Widget.MaterialComponents.Chip.Action">
<item name="chipBackgroundColor">#1C1C1C</item>
<item name="chipStrokeWidth">0dp</item>
<item name="chipIconTint">@color/white</item>
</style>
<!-- Base application theme. -->

@ -43,6 +43,11 @@
app:entryValues="@array/theme_values"
app:singleLineTitle="false" />
<Preference
app:key="pref_language_selector"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/language" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_enable_blur"
@ -50,13 +55,6 @@
app:title="@string/enable_blur_pref"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_force_english"
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/force_english_pref"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_force_dark_terminal"
@ -64,6 +62,20 @@
app:title="@string/force_dark_terminal_title"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_disable_chips"
app:icon="@drawable/ic_baseline_chip_24"
app:title="@string/disable_chips_in_description"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_enable_monet"
app:icon="@drawable/ic_baseline_design_services_24"
app:title="@string/enable_monet"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_wrap_text"

@ -5,9 +5,9 @@ buildscript {
mavenCentral()
gradlePluginPortal()
}
project.ext.latestAboutLibsRelease = "10.1.0"
project.ext.latestAboutLibsRelease = "10.2.0"
dependencies {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}"
// NOTE: Do not place your application dependencies here; they belong

@ -1,6 +1,7 @@
#Sun May 15 18:07:03 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=9afb3ca688fc12c761a0e9e4321e4d24e977a4a8916c8a768b1fe05ddb4d6b66
zipStoreBase=GRADLE_USER_HOME
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302

33
rosetta/.gitignore vendored

@ -0,0 +1,33 @@
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# Eclipse project files
.classpath
.project
# Android Studio
*.iml
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/
#NDK
obj/

@ -0,0 +1,66 @@
apply plugin: 'com.android.library'
ext {
bintrayRepo = 'maven'
bintrayName = 'Rosetta'
publishedGroupId = 'com.ahmedjazzar.rosetta'
libraryName = 'Rosetta'
artifact = 'rosetta'
libraryDescription = 'Android library that lets your app supporting multiple languages ' +
'without any concern from you as a developer.'
siteUrl = 'https://github.com/ahmedaljazzar/rosetta'
gitUrl = 'https://github.com/ahmedaljazzar/rosetta.git'
libraryVersion = '1.0.1'
developerId = 'ahmedaljazzar'
developerName = 'Ahmed Jazzar'
developerEmail = 'me@ahmedjazzar.com'
licenseName = 'MIT'
licenseUrl = 'https://opensource.org/licenses/MIT'
allLicenses = ["MIT"]
}
buildscript {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
}
}
android {
compileSdkVersion 32
buildToolsVersion "23.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
versionCode 3
versionName "1.0.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:1.10.19'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
}

@ -0,0 +1,10 @@
/**
* Automatically generated file. DO NOT MODIFY
*/
package com.ahmedjazzar.rosetta;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String LIBRARY_PACKAGE_NAME = "com.ahmedjazzar.rosetta";
public static final String BUILD_TYPE = "debug";
}

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Fox builds props mods
org.gradle.parallel=true
android.enableR8.fullMode=true

@ -0,0 +1,17 @@
# TODO: Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/ahmedjazzar/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ahmedjazzar.rosetta">
<application android:supportsRtl="true" />
</manifest>

@ -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<Locale> getLocales() {
return LocalesUtils.getLocales();
}
/**
* Sets the app locales from a string Set
* @param sLocales supported locales in a String form
*/
public void setSupportedStringLocales(HashSet<String> sLocales) {
HashSet<Locale> 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<Locale> 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<Locale> 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);
}
}

@ -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<String> 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();
}
}

@ -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<Locale> 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<String> references = new ArrayList<>();
references.add(new Resources(mContext.getAssets(), dm, conf).getString(stringId));
HashSet<Locale> 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<Locale> validateLocales(HashSet<Locale> locales) {
mLogger.debug("Validating given locales..");
for (Locale l:LocalesUtils.getPseudoLocales()) {
if(locales.remove(l)) {
mLogger.info("Pseudo locale '" + l + "' has been removed.");
}
}
HashSet<Locale> 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;
}
}

@ -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);
}
}

@ -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<Locale> 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<Locale> fetchAvailableLocales(int stringId) {
return sDetector.fetchAvailableLocales(stringId);
}
/**
*
* @param localesSet sLocales user wanna use
*/
static void setSupportedLocales(HashSet<Locale> 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<Locale> getLocales() {
return LocalesUtils.sLocales;
}
/**
*
* @return a list of locales for displaying on the layout purposes
*/
static ArrayList<String> getLocalesWithDisplayName() {
ArrayList<String> 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 <a href="http://en.wikipedia.org/wiki/Pseudolocalization">Pseudolocalization</a> for
* more information about pseudo localization
* @return pseudo locales list
*/
static List<Locale> 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();
}
}

@ -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);
}
}

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="language">Language</string>
<string name="ok">Ok</string>
<string name="cancel">Cancel</string>
</resources>

@ -10,3 +10,4 @@ dependencyResolutionManagement {
}
rootProject.name = "MagiskModuleManager"
include ':app'
include ':rosetta'

Loading…
Cancel
Save